Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
f74fa97b
Commit
f74fa97b
authored
Jan 02, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: traverse nodes to upsert tags
parent
c7970999
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
71 additions
and
65 deletions
+71
-65
markdown_service.go
api/v2/markdown_service.go
+22
-0
memo_service.go
api/v2/memo_service.go
+34
-0
index.tsx
web/src/components/MemoEditor/index.tsx
+6
-58
TagList.tsx
web/src/components/TagList.tsx
+3
-1
Explore.tsx
web/src/pages/Explore.tsx
+2
-2
Home.tsx
web/src/pages/Home.tsx
+2
-2
UserProfile.tsx
web/src/pages/UserProfile.tsx
+2
-2
No files found.
api/v2/markdown_service.go
View file @
f74fa97b
...
@@ -90,3 +90,25 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
...
@@ -90,3 +90,25 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
return
node
return
node
}
}
func
traverseASTNodes
(
nodes
[]
ast
.
Node
,
fn
func
(
ast
.
Node
))
{
for
_
,
node
:=
range
nodes
{
fn
(
node
)
switch
n
:=
node
.
(
type
)
{
case
*
ast
.
Paragraph
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Heading
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Blockquote
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
OrderedList
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
UnorderedList
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
TaskList
:
traverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Bold
:
traverseASTNodes
(
n
.
Children
,
fn
)
}
}
}
api/v2/memo_service.go
View file @
f74fa97b
...
@@ -16,6 +16,7 @@ import (
...
@@ -16,6 +16,7 @@ import (
apiv1
"github.com/usememos/memos/api/v1"
apiv1
"github.com/usememos/memos/api/v1"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser"
"github.com/usememos/memos/plugin/gomark/parser"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
"github.com/usememos/memos/plugin/webhook"
"github.com/usememos/memos/plugin/webhook"
...
@@ -34,6 +35,11 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
...
@@ -34,6 +35,11 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"permission denied"
)
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"permission denied"
)
}
}
nodes
,
err
:=
parser
.
Parse
(
tokenizer
.
Tokenize
(
request
.
Content
))
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to parse memo content"
)
}
create
:=
&
store
.
Memo
{
create
:=
&
store
.
Memo
{
CreatorID
:
user
.
ID
,
CreatorID
:
user
.
ID
,
Content
:
request
.
Content
,
Content
:
request
.
Content
,
...
@@ -45,6 +51,18 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
...
@@ -45,6 +51,18 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
}
}
metric
.
Enqueue
(
"memo create"
)
metric
.
Enqueue
(
"memo create"
)
// Dynamically upsert tags from memo content.
traverseASTNodes
(
nodes
,
func
(
node
ast
.
Node
)
{
if
tag
,
ok
:=
node
.
(
*
ast
.
Tag
);
ok
{
if
_
,
err
:=
s
.
Store
.
UpsertTag
(
ctx
,
&
store
.
Tag
{
Name
:
tag
.
Content
,
CreatorID
:
user
.
ID
,
});
err
!=
nil
{
log
.
Warn
(
"Failed to create tag"
,
zap
.
Error
(
err
))
}
}
})
memoMessage
,
err
:=
s
.
convertMemoFromStore
(
ctx
,
memo
)
memoMessage
,
err
:=
s
.
convertMemoFromStore
(
ctx
,
memo
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to convert memo"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to convert memo"
)
...
@@ -198,6 +216,22 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe
...
@@ -198,6 +216,22 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe
for
_
,
path
:=
range
request
.
UpdateMask
.
Paths
{
for
_
,
path
:=
range
request
.
UpdateMask
.
Paths
{
if
path
==
"content"
{
if
path
==
"content"
{
update
.
Content
=
&
request
.
Memo
.
Content
update
.
Content
=
&
request
.
Memo
.
Content
nodes
,
err
:=
parser
.
Parse
(
tokenizer
.
Tokenize
(
*
update
.
Content
))
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to parse memo content"
)
}
// Dynamically upsert tags from memo content.
traverseASTNodes
(
nodes
,
func
(
node
ast
.
Node
)
{
if
tag
,
ok
:=
node
.
(
*
ast
.
Tag
);
ok
{
if
_
,
err
:=
s
.
Store
.
UpsertTag
(
ctx
,
&
store
.
Tag
{
Name
:
tag
.
Content
,
CreatorID
:
user
.
ID
,
});
err
!=
nil
{
log
.
Warn
(
"Failed to create tag"
,
zap
.
Error
(
err
))
}
}
})
}
else
if
path
==
"visibility"
{
}
else
if
path
==
"visibility"
{
visibility
:=
convertVisibilityToStore
(
request
.
Memo
.
Visibility
)
visibility
:=
convertVisibilityToStore
(
request
.
Memo
.
Visibility
)
update
.
Visibility
=
&
visibility
update
.
Visibility
=
&
visibility
...
...
web/src/components/MemoEditor/index.tsx
View file @
f74fa97b
...
@@ -281,6 +281,7 @@ const MemoEditor = (props: Props) => {
...
@@ -281,6 +281,7 @@ const MemoEditor = (props: Props) => {
id
:
memo
.
id
,
id
:
memo
.
id
,
relations
:
state
.
relationList
,
relations
:
state
.
relationList
,
});
});
await
memoStore
.
getOrFetchMemoById
(
memo
.
id
,
{
skipCache
:
true
});
if
(
onConfirm
)
{
if
(
onConfirm
)
{
onConfirm
(
memo
.
id
);
onConfirm
(
memo
.
id
);
}
}
...
@@ -310,6 +311,7 @@ const MemoEditor = (props: Props) => {
...
@@ -310,6 +311,7 @@ const MemoEditor = (props: Props) => {
id
:
memo
.
id
,
id
:
memo
.
id
,
relations
:
state
.
relationList
,
relations
:
state
.
relationList
,
});
});
await
memoStore
.
getOrFetchMemoById
(
memo
.
id
,
{
skipCache
:
true
});
if
(
onConfirm
)
{
if
(
onConfirm
)
{
onConfirm
(
memo
.
id
);
onConfirm
(
memo
.
id
);
}
}
...
@@ -319,57 +321,15 @@ const MemoEditor = (props: Props) => {
...
@@ -319,57 +321,15 @@ const MemoEditor = (props: Props) => {
console
.
error
(
error
);
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
toast
.
error
(
error
.
response
.
data
.
message
);
}
}
setState
((
state
)
=>
{
setState
((
state
)
=>
{
return
{
return
{
...
state
,
...
state
,
isRequesting
:
false
,
isRequesting
:
false
,
resourceList
:
[],
relationList
:
[],
};
};
});
});
setState
((
prevState
)
=>
({
...
prevState
,
resourceList
:
[],
}));
};
const
handleCheckBoxBtnClick
=
()
=>
{
if
(
!
editorRef
.
current
)
{
return
;
}
const
currentPosition
=
editorRef
.
current
?.
getCursorPosition
();
const
currentLineNumber
=
editorRef
.
current
?.
getCursorLineNumber
();
const
currentLine
=
editorRef
.
current
?.
getLine
(
currentLineNumber
);
let
newLine
=
""
;
let
cursorChange
=
0
;
if
(
/^-
\[(
|x|X
)\]
/
.
test
(
currentLine
))
{
newLine
=
currentLine
.
replace
(
/^-
\[(
|x|X
)\]
/
,
""
);
cursorChange
=
-
6
;
}
else
if
(
/^
\d
+
\.
|- /
.
test
(
currentLine
))
{
const
match
=
currentLine
.
match
(
/^
\d
+
\.
|- /
)
??
[
""
];
newLine
=
currentLine
.
replace
(
/^
\d
+
\.
|- /
,
"- [ ] "
);
cursorChange
=
-
match
[
0
].
length
+
6
;
}
else
{
newLine
=
"- [ ] "
+
currentLine
;
cursorChange
=
6
;
}
editorRef
.
current
?.
setLine
(
currentLineNumber
,
newLine
);
editorRef
.
current
.
setCursorPosition
(
currentPosition
+
cursorChange
);
editorRef
.
current
?.
scrollToCursor
();
};
const
handleCodeBlockBtnClick
=
()
=>
{
if
(
!
editorRef
.
current
)
{
return
;
}
const
cursorPosition
=
editorRef
.
current
.
getCursorPosition
();
const
prevValue
=
editorRef
.
current
.
getContent
().
slice
(
0
,
cursorPosition
);
if
(
prevValue
===
""
||
prevValue
.
endsWith
(
"
\n
"
))
{
editorRef
.
current
?.
insertText
(
""
,
"```
\n
"
,
"
\n
```"
);
}
else
{
editorRef
.
current
?.
insertText
(
""
,
"
\n
```
\n
"
,
"
\n
```"
);
}
editorRef
.
current
?.
scrollToCursor
();
};
};
const
handleTagSelectorClick
=
useCallback
((
tag
:
string
)
=>
{
const
handleTagSelectorClick
=
useCallback
((
tag
:
string
)
=>
{
...
@@ -419,18 +379,6 @@ const MemoEditor = (props: Props) => {
...
@@ -419,18 +379,6 @@ const MemoEditor = (props: Props) => {
>
>
<
Icon
.
Link
className=
"w-5 h-5 mx-auto"
/>
<
Icon
.
Link
className=
"w-5 h-5 mx-auto"
/>
</
IconButton
>
</
IconButton
>
<
IconButton
className=
"flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick=
{
handleCheckBoxBtnClick
}
>
<
Icon
.
CheckSquare
className=
"w-5 h-5 mx-auto"
/>
</
IconButton
>
<
IconButton
className=
"flex flex-row justify-center items-center p-1 w-auto h-auto mr-1 select-none rounded cursor-pointer text-gray-600 dark:text-gray-400 hover:bg-gray-300 dark:hover:bg-zinc-800 hover:shadow"
onClick=
{
handleCodeBlockBtnClick
}
>
<
Icon
.
Code
className=
"w-5 h-5 mx-auto"
/>
</
IconButton
>
</
div
>
</
div
>
</
div
>
</
div
>
<
ResourceListView
resourceList=
{
state
.
resourceList
}
setResourceList=
{
handleSetResourceList
}
/>
<
ResourceListView
resourceList=
{
state
.
resourceList
}
setResourceList=
{
handleSetResourceList
}
/>
...
@@ -456,7 +404,7 @@ const MemoEditor = (props: Props) => {
...
@@ -456,7 +404,7 @@ const MemoEditor = (props: Props) => {
</
Select
>
</
Select
>
</
div
>
</
div
>
<
div
className=
"shrink-0 flex flex-row justify-end items-center"
>
<
div
className=
"shrink-0 flex flex-row justify-end items-center"
>
<
Button
color=
"success"
disabled=
{
!
allowSave
}
onClick=
{
handleSaveBtnClick
}
>
<
Button
color=
"success"
disabled=
{
!
allowSave
}
loading=
{
state
.
isRequesting
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"editor.save"
)
}
{
t
(
"editor.save"
)
}
</
Button
>
</
Button
>
</
div
>
</
div
>
...
...
web/src/components/TagList.tsx
View file @
f74fa97b
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
useToggle
from
"react-use/lib/useToggle"
;
import
useToggle
from
"react-use/lib/useToggle"
;
import
{
useFilterStore
,
useTagStore
}
from
"@/store/module"
;
import
{
useFilterStore
,
useTagStore
}
from
"@/store/module"
;
import
{
useMemoList
}
from
"@/store/v1"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showCreateTagDialog
from
"./CreateTagDialog"
;
import
showCreateTagDialog
from
"./CreateTagDialog"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
...
@@ -15,13 +16,14 @@ const TagList = () => {
...
@@ -15,13 +16,14 @@ const TagList = () => {
const
t
=
useTranslate
();
const
t
=
useTranslate
();
const
filterStore
=
useFilterStore
();
const
filterStore
=
useFilterStore
();
const
tagStore
=
useTagStore
();
const
tagStore
=
useTagStore
();
const
memoList
=
useMemoList
();
const
tagsText
=
tagStore
.
state
.
tags
;
const
tagsText
=
tagStore
.
state
.
tags
;
const
filter
=
filterStore
.
state
;
const
filter
=
filterStore
.
state
;
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
useEffect
(()
=>
{
useEffect
(()
=>
{
tagStore
.
fetchTags
();
tagStore
.
fetchTags
();
},
[]);
},
[
memoList
.
size
()
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
const
sortedTags
=
Array
.
from
(
tagsText
).
sort
();
const
sortedTags
=
Array
.
from
(
tagsText
).
sort
();
...
...
web/src/pages/Explore.tsx
View file @
f74fa97b
...
@@ -58,7 +58,7 @@ const Explore = () => {
...
@@ -58,7 +58,7 @@ const Explore = () => {
))
}
))
}
{
isRequesting
?
(
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-
8
"
>
<
div
className=
"flex flex-col justify-start items-center w-full my-
4
"
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
isComplete
?
(
)
:
isComplete
?
(
...
@@ -69,7 +69,7 @@ const Explore = () => {
...
@@ -69,7 +69,7 @@ const Explore = () => {
</
div
>
</
div
>
)
)
)
:
(
)
:
(
<
div
className=
"w-full flex flex-row justify-center items-center my-
2
"
>
<
div
className=
"w-full flex flex-row justify-center items-center my-
4
"
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
{
t
(
"memo.fetch-more"
)
}
{
t
(
"memo.fetch-more"
)
}
</
span
>
</
span
>
...
...
web/src/pages/Home.tsx
View file @
f74fa97b
...
@@ -68,7 +68,7 @@ const Home = () => {
...
@@ -68,7 +68,7 @@ const Home = () => {
<
MemoView
key=
{
`${memo.id}-${memo.updateTime}`
}
memo=
{
memo
}
showVisibility
showPinnedStyle
showParent
/>
<
MemoView
key=
{
`${memo.id}-${memo.updateTime}`
}
memo=
{
memo
}
showVisibility
showPinnedStyle
showParent
/>
))
}
))
}
{
isRequesting
?
(
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-
8
"
>
<
div
className=
"flex flex-col justify-start items-center w-full my-
4
"
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
isComplete
?
(
)
:
isComplete
?
(
...
@@ -79,7 +79,7 @@ const Home = () => {
...
@@ -79,7 +79,7 @@ const Home = () => {
</
div
>
</
div
>
)
)
)
:
(
)
:
(
<
div
className=
"w-full flex flex-row justify-center items-center my-
2
"
>
<
div
className=
"w-full flex flex-row justify-center items-center my-
4
"
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
{
t
(
"memo.fetch-more"
)
}
{
t
(
"memo.fetch-more"
)
}
</
span
>
</
span
>
...
...
web/src/pages/UserProfile.tsx
View file @
f74fa97b
...
@@ -97,7 +97,7 @@ const UserProfile = () => {
...
@@ -97,7 +97,7 @@ const UserProfile = () => {
<
MemoView
key=
{
memo
.
id
}
memo=
{
memo
}
showVisibility
showPinnedStyle
showParent
/>
<
MemoView
key=
{
memo
.
id
}
memo=
{
memo
}
showVisibility
showPinnedStyle
showParent
/>
))
}
))
}
{
isRequesting
?
(
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-
8
"
>
<
div
className=
"flex flex-col justify-start items-center w-full my-
4
"
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
<
p
className=
"text-sm text-gray-400 italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
isComplete
?
(
)
:
isComplete
?
(
...
@@ -108,7 +108,7 @@ const UserProfile = () => {
...
@@ -108,7 +108,7 @@ const UserProfile = () => {
</
div
>
</
div
>
)
)
)
:
(
)
:
(
<
div
className=
"w-full flex flex-row justify-center items-center my-
2
"
>
<
div
className=
"w-full flex flex-row justify-center items-center my-
4
"
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
<
span
className=
"cursor-pointer text-sm italic text-gray-500 hover:text-green-600"
onClick=
{
fetchMemos
}
>
{
t
(
"memo.fetch-more"
)
}
{
t
(
"memo.fetch-more"
)
}
</
span
>
</
span
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment