diff --git a/api/v2/markdown_service.go b/api/v2/markdown_service.go
index d61ea15e..0b720cbf 100644
--- a/api/v2/markdown_service.go
+++ b/api/v2/markdown_service.go
@@ -90,3 +90,25 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.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)
+ }
+ }
+}
diff --git a/api/v2/memo_service.go b/api/v2/memo_service.go
index f515cf78..2045ebd1 100644
--- a/api/v2/memo_service.go
+++ b/api/v2/memo_service.go
@@ -16,6 +16,7 @@ import (
apiv1 "github.com/usememos/memos/api/v1"
"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/tokenizer"
"github.com/usememos/memos/plugin/webhook"
@@ -34,6 +35,11 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
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{
CreatorID: user.ID,
Content: request.Content,
@@ -45,6 +51,18 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
}
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)
if err != nil {
return nil, errors.Wrap(err, "failed to convert memo")
@@ -198,6 +216,22 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe
for _, path := range request.UpdateMask.Paths {
if path == "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" {
visibility := convertVisibilityToStore(request.Memo.Visibility)
update.Visibility = &visibility
diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx
index b26c61b3..65457cb1 100644
--- a/web/src/components/MemoEditor/index.tsx
+++ b/web/src/components/MemoEditor/index.tsx
@@ -281,6 +281,7 @@ const MemoEditor = (props: Props) => {
id: memo.id,
relations: state.relationList,
});
+ await memoStore.getOrFetchMemoById(memo.id, { skipCache: true });
if (onConfirm) {
onConfirm(memo.id);
}
@@ -310,6 +311,7 @@ const MemoEditor = (props: Props) => {
id: memo.id,
relations: state.relationList,
});
+ await memoStore.getOrFetchMemoById(memo.id, { skipCache: true });
if (onConfirm) {
onConfirm(memo.id);
}
@@ -319,57 +321,15 @@ const MemoEditor = (props: Props) => {
console.error(error);
toast.error(error.response.data.message);
}
+
setState((state) => {
return {
...state,
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) => {
@@ -419,18 +379,6 @@ const MemoEditor = (props: Props) => {
>
{t("memo.fetching-data")}
{t("memo.fetching-data")}
{t("memo.fetching-data")}