markdown_service.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package v2
  2. import (
  3. "context"
  4. "github.com/pkg/errors"
  5. "github.com/usememos/memos/plugin/gomark/ast"
  6. "github.com/usememos/memos/plugin/gomark/parser"
  7. "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
  8. apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
  9. )
  10. func (*APIV2Service) ParseMarkdown(_ context.Context, request *apiv2pb.ParseMarkdownRequest) (*apiv2pb.ParseMarkdownResponse, error) {
  11. rawNodes, err := parser.Parse(tokenizer.Tokenize(request.Markdown))
  12. if err != nil {
  13. return nil, errors.Wrap(err, "failed to parse memo content")
  14. }
  15. nodes := convertFromASTNodes(rawNodes)
  16. return &apiv2pb.ParseMarkdownResponse{
  17. Nodes: nodes,
  18. }, nil
  19. }
  20. func convertFromASTNodes(rawNodes []ast.Node) []*apiv2pb.Node {
  21. nodes := []*apiv2pb.Node{}
  22. for _, rawNode := range rawNodes {
  23. node := convertFromASTNode(rawNode)
  24. nodes = append(nodes, node)
  25. }
  26. return nodes
  27. }
  28. func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
  29. node := &apiv2pb.Node{
  30. Type: apiv2pb.NodeType(rawNode.Type()),
  31. }
  32. switch n := rawNode.(type) {
  33. case *ast.LineBreak:
  34. node.Node = &apiv2pb.Node_LineBreakNode{}
  35. case *ast.Paragraph:
  36. children := convertFromASTNodes(n.Children)
  37. node.Node = &apiv2pb.Node_ParagraphNode{ParagraphNode: &apiv2pb.ParagraphNode{Children: children}}
  38. case *ast.CodeBlock:
  39. node.Node = &apiv2pb.Node_CodeBlockNode{CodeBlockNode: &apiv2pb.CodeBlockNode{Language: n.Language, Content: n.Content}}
  40. case *ast.Heading:
  41. children := convertFromASTNodes(n.Children)
  42. node.Node = &apiv2pb.Node_HeadingNode{HeadingNode: &apiv2pb.HeadingNode{Level: int32(n.Level), Children: children}}
  43. case *ast.HorizontalRule:
  44. node.Node = &apiv2pb.Node_HorizontalRuleNode{HorizontalRuleNode: &apiv2pb.HorizontalRuleNode{Symbol: n.Symbol}}
  45. case *ast.Blockquote:
  46. children := convertFromASTNodes(n.Children)
  47. node.Node = &apiv2pb.Node_BlockquoteNode{BlockquoteNode: &apiv2pb.BlockquoteNode{Children: children}}
  48. case *ast.OrderedList:
  49. children := convertFromASTNodes(n.Children)
  50. node.Node = &apiv2pb.Node_OrderedListNode{OrderedListNode: &apiv2pb.OrderedListNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
  51. case *ast.UnorderedList:
  52. children := convertFromASTNodes(n.Children)
  53. node.Node = &apiv2pb.Node_UnorderedListNode{UnorderedListNode: &apiv2pb.UnorderedListNode{Symbol: n.Symbol, Indent: int32(n.Indent), Children: children}}
  54. case *ast.TaskList:
  55. children := convertFromASTNodes(n.Children)
  56. node.Node = &apiv2pb.Node_TaskListNode{TaskListNode: &apiv2pb.TaskListNode{Symbol: n.Symbol, Indent: int32(n.Indent), Complete: n.Complete, Children: children}}
  57. case *ast.MathBlock:
  58. node.Node = &apiv2pb.Node_MathBlockNode{MathBlockNode: &apiv2pb.MathBlockNode{Content: n.Content}}
  59. case *ast.Table:
  60. node.Node = &apiv2pb.Node_TableNode{TableNode: convertTableFromASTNode(n)}
  61. case *ast.EmbeddedContent:
  62. node.Node = &apiv2pb.Node_EmbeddedContentNode{EmbeddedContentNode: &apiv2pb.EmbeddedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
  63. case *ast.Text:
  64. node.Node = &apiv2pb.Node_TextNode{TextNode: &apiv2pb.TextNode{Content: n.Content}}
  65. case *ast.Bold:
  66. children := convertFromASTNodes(n.Children)
  67. node.Node = &apiv2pb.Node_BoldNode{BoldNode: &apiv2pb.BoldNode{Symbol: n.Symbol, Children: children}}
  68. case *ast.Italic:
  69. node.Node = &apiv2pb.Node_ItalicNode{ItalicNode: &apiv2pb.ItalicNode{Symbol: n.Symbol, Content: n.Content}}
  70. case *ast.BoldItalic:
  71. node.Node = &apiv2pb.Node_BoldItalicNode{BoldItalicNode: &apiv2pb.BoldItalicNode{Symbol: n.Symbol, Content: n.Content}}
  72. case *ast.Code:
  73. node.Node = &apiv2pb.Node_CodeNode{CodeNode: &apiv2pb.CodeNode{Content: n.Content}}
  74. case *ast.Image:
  75. node.Node = &apiv2pb.Node_ImageNode{ImageNode: &apiv2pb.ImageNode{AltText: n.AltText, Url: n.URL}}
  76. case *ast.Link:
  77. node.Node = &apiv2pb.Node_LinkNode{LinkNode: &apiv2pb.LinkNode{Text: n.Text, Url: n.URL}}
  78. case *ast.AutoLink:
  79. node.Node = &apiv2pb.Node_AutoLinkNode{AutoLinkNode: &apiv2pb.AutoLinkNode{Url: n.URL, IsRawText: n.IsRawText}}
  80. case *ast.Tag:
  81. node.Node = &apiv2pb.Node_TagNode{TagNode: &apiv2pb.TagNode{Content: n.Content}}
  82. case *ast.Strikethrough:
  83. node.Node = &apiv2pb.Node_StrikethroughNode{StrikethroughNode: &apiv2pb.StrikethroughNode{Content: n.Content}}
  84. case *ast.EscapingCharacter:
  85. node.Node = &apiv2pb.Node_EscapingCharacterNode{EscapingCharacterNode: &apiv2pb.EscapingCharacterNode{Symbol: n.Symbol}}
  86. case *ast.Math:
  87. node.Node = &apiv2pb.Node_MathNode{MathNode: &apiv2pb.MathNode{Content: n.Content}}
  88. case *ast.Highlight:
  89. node.Node = &apiv2pb.Node_HighlightNode{HighlightNode: &apiv2pb.HighlightNode{Content: n.Content}}
  90. case *ast.Subscript:
  91. node.Node = &apiv2pb.Node_SubscriptNode{SubscriptNode: &apiv2pb.SubscriptNode{Content: n.Content}}
  92. case *ast.Superscript:
  93. node.Node = &apiv2pb.Node_SuperscriptNode{SuperscriptNode: &apiv2pb.SuperscriptNode{Content: n.Content}}
  94. case *ast.ReferencedContent:
  95. node.Node = &apiv2pb.Node_ReferencedContentNode{ReferencedContentNode: &apiv2pb.ReferencedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
  96. default:
  97. node.Node = &apiv2pb.Node_TextNode{TextNode: &apiv2pb.TextNode{}}
  98. }
  99. return node
  100. }
  101. func convertToASTNodes(nodes []*apiv2pb.Node) []ast.Node {
  102. rawNodes := []ast.Node{}
  103. for _, node := range nodes {
  104. rawNode := convertToASTNode(node)
  105. rawNodes = append(rawNodes, rawNode)
  106. }
  107. return rawNodes
  108. }
  109. func convertToASTNode(node *apiv2pb.Node) ast.Node {
  110. switch n := node.Node.(type) {
  111. case *apiv2pb.Node_LineBreakNode:
  112. return &ast.LineBreak{}
  113. case *apiv2pb.Node_ParagraphNode:
  114. children := convertToASTNodes(n.ParagraphNode.Children)
  115. return &ast.Paragraph{Children: children}
  116. case *apiv2pb.Node_CodeBlockNode:
  117. return &ast.CodeBlock{Language: n.CodeBlockNode.Language, Content: n.CodeBlockNode.Content}
  118. case *apiv2pb.Node_HeadingNode:
  119. children := convertToASTNodes(n.HeadingNode.Children)
  120. return &ast.Heading{Level: int(n.HeadingNode.Level), Children: children}
  121. case *apiv2pb.Node_HorizontalRuleNode:
  122. return &ast.HorizontalRule{Symbol: n.HorizontalRuleNode.Symbol}
  123. case *apiv2pb.Node_BlockquoteNode:
  124. children := convertToASTNodes(n.BlockquoteNode.Children)
  125. return &ast.Blockquote{Children: children}
  126. case *apiv2pb.Node_OrderedListNode:
  127. children := convertToASTNodes(n.OrderedListNode.Children)
  128. return &ast.OrderedList{Number: n.OrderedListNode.Number, Indent: int(n.OrderedListNode.Indent), Children: children}
  129. case *apiv2pb.Node_UnorderedListNode:
  130. children := convertToASTNodes(n.UnorderedListNode.Children)
  131. return &ast.UnorderedList{Symbol: n.UnorderedListNode.Symbol, Indent: int(n.UnorderedListNode.Indent), Children: children}
  132. case *apiv2pb.Node_TaskListNode:
  133. children := convertToASTNodes(n.TaskListNode.Children)
  134. return &ast.TaskList{Symbol: n.TaskListNode.Symbol, Indent: int(n.TaskListNode.Indent), Complete: n.TaskListNode.Complete, Children: children}
  135. case *apiv2pb.Node_MathBlockNode:
  136. return &ast.MathBlock{Content: n.MathBlockNode.Content}
  137. case *apiv2pb.Node_TableNode:
  138. return convertTableToASTNode(node)
  139. case *apiv2pb.Node_EmbeddedContentNode:
  140. return &ast.EmbeddedContent{ResourceName: n.EmbeddedContentNode.ResourceName, Params: n.EmbeddedContentNode.Params}
  141. case *apiv2pb.Node_TextNode:
  142. return &ast.Text{Content: n.TextNode.Content}
  143. case *apiv2pb.Node_BoldNode:
  144. children := convertToASTNodes(n.BoldNode.Children)
  145. return &ast.Bold{Symbol: n.BoldNode.Symbol, Children: children}
  146. case *apiv2pb.Node_ItalicNode:
  147. return &ast.Italic{Symbol: n.ItalicNode.Symbol, Content: n.ItalicNode.Content}
  148. case *apiv2pb.Node_BoldItalicNode:
  149. return &ast.BoldItalic{Symbol: n.BoldItalicNode.Symbol, Content: n.BoldItalicNode.Content}
  150. case *apiv2pb.Node_CodeNode:
  151. return &ast.Code{Content: n.CodeNode.Content}
  152. case *apiv2pb.Node_ImageNode:
  153. return &ast.Image{AltText: n.ImageNode.AltText, URL: n.ImageNode.Url}
  154. case *apiv2pb.Node_LinkNode:
  155. return &ast.Link{Text: n.LinkNode.Text, URL: n.LinkNode.Url}
  156. case *apiv2pb.Node_AutoLinkNode:
  157. return &ast.AutoLink{URL: n.AutoLinkNode.Url, IsRawText: n.AutoLinkNode.IsRawText}
  158. case *apiv2pb.Node_TagNode:
  159. return &ast.Tag{Content: n.TagNode.Content}
  160. case *apiv2pb.Node_StrikethroughNode:
  161. return &ast.Strikethrough{Content: n.StrikethroughNode.Content}
  162. case *apiv2pb.Node_EscapingCharacterNode:
  163. return &ast.EscapingCharacter{Symbol: n.EscapingCharacterNode.Symbol}
  164. case *apiv2pb.Node_MathNode:
  165. return &ast.Math{Content: n.MathNode.Content}
  166. case *apiv2pb.Node_HighlightNode:
  167. return &ast.Highlight{Content: n.HighlightNode.Content}
  168. case *apiv2pb.Node_SubscriptNode:
  169. return &ast.Subscript{Content: n.SubscriptNode.Content}
  170. case *apiv2pb.Node_SuperscriptNode:
  171. return &ast.Superscript{Content: n.SuperscriptNode.Content}
  172. case *apiv2pb.Node_ReferencedContentNode:
  173. return &ast.ReferencedContent{ResourceName: n.ReferencedContentNode.ResourceName, Params: n.ReferencedContentNode.Params}
  174. default:
  175. return &ast.Text{}
  176. }
  177. }
  178. func convertTableToASTNode(node *apiv2pb.Node) *ast.Table {
  179. table := &ast.Table{
  180. Header: node.GetTableNode().Header,
  181. Delimiter: node.GetTableNode().Delimiter,
  182. }
  183. for _, row := range node.GetTableNode().Rows {
  184. table.Rows = append(table.Rows, row.Cells)
  185. }
  186. return table
  187. }
  188. func convertTableFromASTNode(node *ast.Table) *apiv2pb.TableNode {
  189. table := &apiv2pb.TableNode{
  190. Header: node.Header,
  191. Delimiter: node.Delimiter,
  192. }
  193. for _, row := range node.Rows {
  194. table.Rows = append(table.Rows, &apiv2pb.TableNode_Row{Cells: row})
  195. }
  196. return table
  197. }
  198. func traverseASTNodes(nodes []ast.Node, fn func(ast.Node)) {
  199. for _, node := range nodes {
  200. fn(node)
  201. switch n := node.(type) {
  202. case *ast.Paragraph:
  203. traverseASTNodes(n.Children, fn)
  204. case *ast.Heading:
  205. traverseASTNodes(n.Children, fn)
  206. case *ast.Blockquote:
  207. traverseASTNodes(n.Children, fn)
  208. case *ast.OrderedList:
  209. traverseASTNodes(n.Children, fn)
  210. case *ast.UnorderedList:
  211. traverseASTNodes(n.Children, fn)
  212. case *ast.TaskList:
  213. traverseASTNodes(n.Children, fn)
  214. case *ast.Bold:
  215. traverseASTNodes(n.Children, fn)
  216. }
  217. }
  218. }