diff --git a/api/v2/markdown_service.go b/api/v2/markdown_service.go index 7308228a..12c0229d 100644 --- a/api/v2/markdown_service.go +++ b/api/v2/markdown_service.go @@ -95,6 +95,73 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node { return node } +func convertToASTNodes(nodes []*apiv2pb.Node) []ast.Node { + rawNodes := []ast.Node{} + for _, node := range nodes { + rawNode := convertToASTNode(node) + rawNodes = append(rawNodes, rawNode) + } + return rawNodes +} + +func convertToASTNode(node *apiv2pb.Node) ast.Node { + switch n := node.Node.(type) { + case *apiv2pb.Node_LineBreakNode: + return &ast.LineBreak{} + case *apiv2pb.Node_ParagraphNode: + children := convertToASTNodes(n.ParagraphNode.Children) + return &ast.Paragraph{Children: children} + case *apiv2pb.Node_CodeBlockNode: + return &ast.CodeBlock{Language: n.CodeBlockNode.Language, Content: n.CodeBlockNode.Content} + case *apiv2pb.Node_HeadingNode: + children := convertToASTNodes(n.HeadingNode.Children) + return &ast.Heading{Level: int(n.HeadingNode.Level), Children: children} + case *apiv2pb.Node_HorizontalRuleNode: + return &ast.HorizontalRule{Symbol: n.HorizontalRuleNode.Symbol} + case *apiv2pb.Node_BlockquoteNode: + children := convertToASTNodes(n.BlockquoteNode.Children) + return &ast.Blockquote{Children: children} + case *apiv2pb.Node_OrderedListNode: + children := convertToASTNodes(n.OrderedListNode.Children) + return &ast.OrderedList{Number: n.OrderedListNode.Number, Children: children} + case *apiv2pb.Node_UnorderedListNode: + children := convertToASTNodes(n.UnorderedListNode.Children) + return &ast.UnorderedList{Symbol: n.UnorderedListNode.Symbol, Children: children} + case *apiv2pb.Node_TaskListNode: + children := convertToASTNodes(n.TaskListNode.Children) + return &ast.TaskList{Symbol: n.TaskListNode.Symbol, Complete: n.TaskListNode.Complete, Children: children} + case *apiv2pb.Node_MathBlockNode: + return &ast.MathBlock{Content: n.MathBlockNode.Content} + case *apiv2pb.Node_TextNode: + return &ast.Text{Content: n.TextNode.Content} + case *apiv2pb.Node_BoldNode: + children := convertToASTNodes(n.BoldNode.Children) + return &ast.Bold{Symbol: n.BoldNode.Symbol, Children: children} + case *apiv2pb.Node_ItalicNode: + return &ast.Italic{Symbol: n.ItalicNode.Symbol, Content: n.ItalicNode.Content} + case *apiv2pb.Node_BoldItalicNode: + return &ast.BoldItalic{Symbol: n.BoldItalicNode.Symbol, Content: n.BoldItalicNode.Content} + case *apiv2pb.Node_CodeNode: + return &ast.Code{Content: n.CodeNode.Content} + case *apiv2pb.Node_ImageNode: + return &ast.Image{AltText: n.ImageNode.AltText, URL: n.ImageNode.Url} + case *apiv2pb.Node_LinkNode: + return &ast.Link{Text: n.LinkNode.Text, URL: n.LinkNode.Url} + case *apiv2pb.Node_AutoLinkNode: + return &ast.AutoLink{URL: n.AutoLinkNode.Url} + case *apiv2pb.Node_TagNode: + return &ast.Tag{Content: n.TagNode.Content} + case *apiv2pb.Node_StrikethroughNode: + return &ast.Strikethrough{Content: n.StrikethroughNode.Content} + case *apiv2pb.Node_EscapingCharacterNode: + return &ast.EscapingCharacter{Symbol: n.EscapingCharacterNode.Symbol} + case *apiv2pb.Node_MathNode: + return &ast.Math{Content: n.MathNode.Content} + default: + return &ast.Text{} + } +} + func traverseASTNodes(nodes []ast.Node, fn func(ast.Node)) { for _, node := range nodes { fn(node) diff --git a/api/v2/memo_service.go b/api/v2/memo_service.go index 3655e77c..0148f058 100644 --- a/api/v2/memo_service.go +++ b/api/v2/memo_service.go @@ -19,6 +19,7 @@ import ( "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/gomark/restore" "github.com/usememos/memos/plugin/webhook" apiv2pb "github.com/usememos/memos/proto/gen/api/v2" storepb "github.com/usememos/memos/proto/gen/store" @@ -232,6 +233,10 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe } } }) + } else if path == "nodes" { + nodes := convertToASTNodes(request.Memo.Nodes) + content := restore.Restore(nodes) + update.Content = &content } else if path == "visibility" { visibility := convertVisibilityToStore(request.Memo.Visibility) update.Visibility = &visibility diff --git a/plugin/gomark/ast/utils.go b/plugin/gomark/ast/utils.go new file mode 100644 index 00000000..58e91e19 --- /dev/null +++ b/plugin/gomark/ast/utils.go @@ -0,0 +1,23 @@ +package ast + +func FindPrevSiblingExceptLineBreak(node Node) Node { + if node == nil { + return nil + } + prev := node.PrevSibling() + if prev != nil && prev.Type() == LineBreakNode { + return FindPrevSiblingExceptLineBreak(prev) + } + return prev +} + +func FindNextSiblingExceptLineBreak(node Node) Node { + if node == nil { + return nil + } + next := node.NextSibling() + if next != nil && next.Type() == LineBreakNode { + return FindNextSiblingExceptLineBreak(next) + } + return next +} diff --git a/plugin/gomark/parser/parser.go b/plugin/gomark/parser/parser.go index 8d0f8a4a..bf67a46f 100644 --- a/plugin/gomark/parser/parser.go +++ b/plugin/gomark/parser/parser.go @@ -49,7 +49,6 @@ func ParseBlock(tokens []*tokenizer.Token) ([]ast.Node, error) { func ParseBlockWithParsers(tokens []*tokenizer.Token, blockParsers []BlockParser) ([]ast.Node, error) { nodes := []ast.Node{} var prevNode ast.Node - var skipNextLineBreakFlag bool for len(tokens) > 0 { for _, blockParser := range blockParsers { size, matched := blockParser.Match(tokens) @@ -59,21 +58,12 @@ func ParseBlockWithParsers(tokens []*tokenizer.Token, blockParsers []BlockParser return nil, errors.New("parse error") } - if node.Type() == ast.LineBreakNode && skipNextLineBreakFlag { - if prevNode != nil && ast.IsBlockNode(prevNode) { - tokens = tokens[size:] - skipNextLineBreakFlag = false - break - } - } - tokens = tokens[size:] if prevNode != nil { prevNode.SetNextSibling(node) node.SetPrevSibling(prevNode) } prevNode = node - skipNextLineBreakFlag = true nodes = append(nodes, node) break } diff --git a/plugin/gomark/parser/parser_test.go b/plugin/gomark/parser/parser_test.go index 197da255..817592d2 100644 --- a/plugin/gomark/parser/parser_test.go +++ b/plugin/gomark/parser/parser_test.go @@ -96,6 +96,7 @@ func TestParser(t *testing.T) { }, }, }, + &ast.LineBreak{}, &ast.Paragraph{ Children: []ast.Node{ &ast.Text{ @@ -126,6 +127,7 @@ func TestParser(t *testing.T) { }, }, }, + &ast.LineBreak{}, &ast.CodeBlock{ Language: "javascript", Content: "console.log(\"Hello world!\");", @@ -143,6 +145,7 @@ func TestParser(t *testing.T) { }, }, &ast.LineBreak{}, + &ast.LineBreak{}, &ast.Paragraph{ Children: []ast.Node{ &ast.Text{ @@ -163,6 +166,7 @@ func TestParser(t *testing.T) { }, }, }, + &ast.LineBreak{}, &ast.TaskList{ Symbol: tokenizer.Hyphen, Complete: false, @@ -186,6 +190,7 @@ func TestParser(t *testing.T) { }, }, }, + &ast.LineBreak{}, &ast.TaskList{ Symbol: tokenizer.Hyphen, Complete: true, diff --git a/plugin/gomark/renderer/html/html.go b/plugin/gomark/renderer/html/html.go index 4a4fd8be..538b6f18 100644 --- a/plugin/gomark/renderer/html/html.go +++ b/plugin/gomark/renderer/html/html.go @@ -72,8 +72,19 @@ func (r *HTMLRenderer) RenderNode(node ast.Node) { // RenderNodes renders a slice of AST nodes to HTML. func (r *HTMLRenderer) RenderNodes(nodes []ast.Node) { + var prevNode ast.Node + var skipNextLineBreakFlag bool for _, node := range nodes { + if node.Type() == ast.LineBreakNode && skipNextLineBreakFlag { + if prevNode != nil && ast.IsBlockNode(prevNode) { + skipNextLineBreakFlag = false + continue + } + } + r.RenderNode(node) + prevNode = node + skipNextLineBreakFlag = true } } @@ -111,7 +122,7 @@ func (r *HTMLRenderer) renderHorizontalRule(_ *ast.HorizontalRule) { } func (r *HTMLRenderer) renderBlockquote(node *ast.Blockquote) { - prevSibling, nextSibling := node.PrevSibling(), node.NextSibling() + prevSibling, nextSibling := ast.FindPrevSiblingExceptLineBreak(node), ast.FindNextSiblingExceptLineBreak(node) if prevSibling == nil || prevSibling.Type() != ast.BlockquoteNode { r.output.WriteString("
") } @@ -122,7 +133,7 @@ func (r *HTMLRenderer) renderBlockquote(node *ast.Blockquote) { } func (r *HTMLRenderer) renderTaskList(node *ast.TaskList) { - prevSibling, nextSibling := node.PrevSibling(), node.NextSibling() + prevSibling, nextSibling := ast.FindPrevSiblingExceptLineBreak(node), ast.FindNextSiblingExceptLineBreak(node) if prevSibling == nil || prevSibling.Type() != ast.TaskListNode { r.output.WriteString("