markdown_service.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package v1
  2. import (
  3. "context"
  4. "github.com/pkg/errors"
  5. "github.com/usememos/gomark/ast"
  6. "github.com/usememos/gomark/parser"
  7. "github.com/usememos/gomark/parser/tokenizer"
  8. "github.com/usememos/gomark/renderer"
  9. "github.com/usememos/gomark/restore"
  10. "github.com/usememos/memos/plugin/httpgetter"
  11. v1pb "github.com/usememos/memos/proto/gen/api/v1"
  12. )
  13. func (*APIV1Service) ParseMarkdown(_ context.Context, request *v1pb.ParseMarkdownRequest) (*v1pb.ParseMarkdownResponse, error) {
  14. rawNodes, err := parser.Parse(tokenizer.Tokenize(request.Markdown))
  15. if err != nil {
  16. return nil, errors.Wrap(err, "failed to parse memo content")
  17. }
  18. nodes := convertFromASTNodes(rawNodes)
  19. return &v1pb.ParseMarkdownResponse{
  20. Nodes: nodes,
  21. }, nil
  22. }
  23. func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.RestoreMarkdownNodesRequest) (*v1pb.RestoreMarkdownNodesResponse, error) {
  24. markdown := restore.Restore(convertToASTNodes(request.Nodes))
  25. return &v1pb.RestoreMarkdownNodesResponse{
  26. Markdown: markdown,
  27. }, nil
  28. }
  29. func (*APIV1Service) StringifyMarkdownNodes(_ context.Context, request *v1pb.StringifyMarkdownNodesRequest) (*v1pb.StringifyMarkdownNodesResponse, error) {
  30. stringRenderer := renderer.NewStringRenderer()
  31. plainText := stringRenderer.Render(convertToASTNodes(request.Nodes))
  32. return &v1pb.StringifyMarkdownNodesResponse{
  33. PlainText: plainText,
  34. }, nil
  35. }
  36. func (*APIV1Service) GetLinkMetadata(_ context.Context, request *v1pb.GetLinkMetadataRequest) (*v1pb.LinkMetadata, error) {
  37. htmlMeta, err := httpgetter.GetHTMLMeta(request.Link)
  38. if err != nil {
  39. return nil, err
  40. }
  41. return &v1pb.LinkMetadata{
  42. Title: htmlMeta.Title,
  43. Description: htmlMeta.Description,
  44. Image: htmlMeta.Image,
  45. }, nil
  46. }
  47. func convertFromASTNode(rawNode ast.Node) *v1pb.Node {
  48. node := &v1pb.Node{
  49. Type: v1pb.NodeType(v1pb.NodeType_value[string(rawNode.Type())]),
  50. }
  51. switch n := rawNode.(type) {
  52. case *ast.LineBreak:
  53. node.Node = &v1pb.Node_LineBreakNode{}
  54. case *ast.Paragraph:
  55. children := convertFromASTNodes(n.Children)
  56. node.Node = &v1pb.Node_ParagraphNode{ParagraphNode: &v1pb.ParagraphNode{Children: children}}
  57. case *ast.CodeBlock:
  58. node.Node = &v1pb.Node_CodeBlockNode{CodeBlockNode: &v1pb.CodeBlockNode{Language: n.Language, Content: n.Content}}
  59. case *ast.Heading:
  60. children := convertFromASTNodes(n.Children)
  61. node.Node = &v1pb.Node_HeadingNode{HeadingNode: &v1pb.HeadingNode{Level: int32(n.Level), Children: children}}
  62. case *ast.HorizontalRule:
  63. node.Node = &v1pb.Node_HorizontalRuleNode{HorizontalRuleNode: &v1pb.HorizontalRuleNode{Symbol: n.Symbol}}
  64. case *ast.Blockquote:
  65. children := convertFromASTNodes(n.Children)
  66. node.Node = &v1pb.Node_BlockquoteNode{BlockquoteNode: &v1pb.BlockquoteNode{Children: children}}
  67. case *ast.List:
  68. children := convertFromASTNodes(n.Children)
  69. node.Node = &v1pb.Node_ListNode{ListNode: &v1pb.ListNode{Kind: convertListKindFromASTNode(n.Kind), Indent: int32(n.Indent), Children: children}}
  70. case *ast.OrderedListItem:
  71. children := convertFromASTNodes(n.Children)
  72. node.Node = &v1pb.Node_OrderedListItemNode{OrderedListItemNode: &v1pb.OrderedListItemNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
  73. case *ast.UnorderedListItem:
  74. children := convertFromASTNodes(n.Children)
  75. node.Node = &v1pb.Node_UnorderedListItemNode{UnorderedListItemNode: &v1pb.UnorderedListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Children: children}}
  76. case *ast.TaskListItem:
  77. children := convertFromASTNodes(n.Children)
  78. node.Node = &v1pb.Node_TaskListItemNode{TaskListItemNode: &v1pb.TaskListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Complete: n.Complete, Children: children}}
  79. case *ast.MathBlock:
  80. node.Node = &v1pb.Node_MathBlockNode{MathBlockNode: &v1pb.MathBlockNode{Content: n.Content}}
  81. case *ast.Table:
  82. node.Node = &v1pb.Node_TableNode{TableNode: convertTableFromASTNode(n)}
  83. case *ast.EmbeddedContent:
  84. node.Node = &v1pb.Node_EmbeddedContentNode{EmbeddedContentNode: &v1pb.EmbeddedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
  85. case *ast.Text:
  86. node.Node = &v1pb.Node_TextNode{TextNode: &v1pb.TextNode{Content: n.Content}}
  87. case *ast.Bold:
  88. children := convertFromASTNodes(n.Children)
  89. node.Node = &v1pb.Node_BoldNode{BoldNode: &v1pb.BoldNode{Symbol: n.Symbol, Children: children}}
  90. case *ast.Italic:
  91. node.Node = &v1pb.Node_ItalicNode{ItalicNode: &v1pb.ItalicNode{Symbol: n.Symbol, Content: n.Content}}
  92. case *ast.BoldItalic:
  93. node.Node = &v1pb.Node_BoldItalicNode{BoldItalicNode: &v1pb.BoldItalicNode{Symbol: n.Symbol, Content: n.Content}}
  94. case *ast.Code:
  95. node.Node = &v1pb.Node_CodeNode{CodeNode: &v1pb.CodeNode{Content: n.Content}}
  96. case *ast.Image:
  97. node.Node = &v1pb.Node_ImageNode{ImageNode: &v1pb.ImageNode{AltText: n.AltText, Url: n.URL}}
  98. case *ast.Link:
  99. node.Node = &v1pb.Node_LinkNode{LinkNode: &v1pb.LinkNode{Text: n.Text, Url: n.URL}}
  100. case *ast.AutoLink:
  101. node.Node = &v1pb.Node_AutoLinkNode{AutoLinkNode: &v1pb.AutoLinkNode{Url: n.URL, IsRawText: n.IsRawText}}
  102. case *ast.Tag:
  103. node.Node = &v1pb.Node_TagNode{TagNode: &v1pb.TagNode{Content: n.Content}}
  104. case *ast.Strikethrough:
  105. node.Node = &v1pb.Node_StrikethroughNode{StrikethroughNode: &v1pb.StrikethroughNode{Content: n.Content}}
  106. case *ast.EscapingCharacter:
  107. node.Node = &v1pb.Node_EscapingCharacterNode{EscapingCharacterNode: &v1pb.EscapingCharacterNode{Symbol: n.Symbol}}
  108. case *ast.Math:
  109. node.Node = &v1pb.Node_MathNode{MathNode: &v1pb.MathNode{Content: n.Content}}
  110. case *ast.Highlight:
  111. node.Node = &v1pb.Node_HighlightNode{HighlightNode: &v1pb.HighlightNode{Content: n.Content}}
  112. case *ast.Subscript:
  113. node.Node = &v1pb.Node_SubscriptNode{SubscriptNode: &v1pb.SubscriptNode{Content: n.Content}}
  114. case *ast.Superscript:
  115. node.Node = &v1pb.Node_SuperscriptNode{SuperscriptNode: &v1pb.SuperscriptNode{Content: n.Content}}
  116. case *ast.ReferencedContent:
  117. node.Node = &v1pb.Node_ReferencedContentNode{ReferencedContentNode: &v1pb.ReferencedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
  118. case *ast.Spoiler:
  119. node.Node = &v1pb.Node_SpoilerNode{SpoilerNode: &v1pb.SpoilerNode{Content: n.Content}}
  120. case *ast.HTMLElement:
  121. node.Node = &v1pb.Node_HtmlElementNode{HtmlElementNode: &v1pb.HTMLElementNode{TagName: n.TagName, Attributes: n.Attributes}}
  122. default:
  123. node.Node = &v1pb.Node_TextNode{TextNode: &v1pb.TextNode{}}
  124. }
  125. return node
  126. }
  127. func convertFromASTNodes(rawNodes []ast.Node) []*v1pb.Node {
  128. nodes := []*v1pb.Node{}
  129. for _, rawNode := range rawNodes {
  130. node := convertFromASTNode(rawNode)
  131. nodes = append(nodes, node)
  132. }
  133. return nodes
  134. }
  135. func convertTableFromASTNode(node *ast.Table) *v1pb.TableNode {
  136. table := &v1pb.TableNode{
  137. Header: convertFromASTNodes(node.Header),
  138. Delimiter: node.Delimiter,
  139. }
  140. for _, row := range node.Rows {
  141. table.Rows = append(table.Rows, &v1pb.TableNode_Row{Cells: convertFromASTNodes(row)})
  142. }
  143. return table
  144. }
  145. func convertListKindFromASTNode(node ast.ListKind) v1pb.ListNode_Kind {
  146. switch node {
  147. case ast.OrderedList:
  148. return v1pb.ListNode_ORDERED
  149. case ast.UnorderedList:
  150. return v1pb.ListNode_UNORDERED
  151. case ast.DescrpitionList:
  152. return v1pb.ListNode_DESCRIPTION
  153. default:
  154. return v1pb.ListNode_KIND_UNSPECIFIED
  155. }
  156. }
  157. func convertToASTNode(node *v1pb.Node) ast.Node {
  158. switch n := node.Node.(type) {
  159. case *v1pb.Node_LineBreakNode:
  160. return &ast.LineBreak{}
  161. case *v1pb.Node_ParagraphNode:
  162. children := convertToASTNodes(n.ParagraphNode.Children)
  163. return &ast.Paragraph{Children: children}
  164. case *v1pb.Node_CodeBlockNode:
  165. return &ast.CodeBlock{Language: n.CodeBlockNode.Language, Content: n.CodeBlockNode.Content}
  166. case *v1pb.Node_HeadingNode:
  167. children := convertToASTNodes(n.HeadingNode.Children)
  168. return &ast.Heading{Level: int(n.HeadingNode.Level), Children: children}
  169. case *v1pb.Node_HorizontalRuleNode:
  170. return &ast.HorizontalRule{Symbol: n.HorizontalRuleNode.Symbol}
  171. case *v1pb.Node_BlockquoteNode:
  172. children := convertToASTNodes(n.BlockquoteNode.Children)
  173. return &ast.Blockquote{Children: children}
  174. case *v1pb.Node_ListNode:
  175. children := convertToASTNodes(n.ListNode.Children)
  176. return &ast.List{Kind: convertListKindToASTNode(n.ListNode.Kind), Indent: int(n.ListNode.Indent), Children: children}
  177. case *v1pb.Node_OrderedListItemNode:
  178. children := convertToASTNodes(n.OrderedListItemNode.Children)
  179. return &ast.OrderedListItem{Number: n.OrderedListItemNode.Number, Indent: int(n.OrderedListItemNode.Indent), Children: children}
  180. case *v1pb.Node_UnorderedListItemNode:
  181. children := convertToASTNodes(n.UnorderedListItemNode.Children)
  182. return &ast.UnorderedListItem{Symbol: n.UnorderedListItemNode.Symbol, Indent: int(n.UnorderedListItemNode.Indent), Children: children}
  183. case *v1pb.Node_TaskListItemNode:
  184. children := convertToASTNodes(n.TaskListItemNode.Children)
  185. return &ast.TaskListItem{Symbol: n.TaskListItemNode.Symbol, Indent: int(n.TaskListItemNode.Indent), Complete: n.TaskListItemNode.Complete, Children: children}
  186. case *v1pb.Node_MathBlockNode:
  187. return &ast.MathBlock{Content: n.MathBlockNode.Content}
  188. case *v1pb.Node_TableNode:
  189. return convertTableToASTNode(n.TableNode)
  190. case *v1pb.Node_EmbeddedContentNode:
  191. return &ast.EmbeddedContent{ResourceName: n.EmbeddedContentNode.ResourceName, Params: n.EmbeddedContentNode.Params}
  192. case *v1pb.Node_TextNode:
  193. return &ast.Text{Content: n.TextNode.Content}
  194. case *v1pb.Node_BoldNode:
  195. children := convertToASTNodes(n.BoldNode.Children)
  196. return &ast.Bold{Symbol: n.BoldNode.Symbol, Children: children}
  197. case *v1pb.Node_ItalicNode:
  198. return &ast.Italic{Symbol: n.ItalicNode.Symbol, Content: n.ItalicNode.Content}
  199. case *v1pb.Node_BoldItalicNode:
  200. return &ast.BoldItalic{Symbol: n.BoldItalicNode.Symbol, Content: n.BoldItalicNode.Content}
  201. case *v1pb.Node_CodeNode:
  202. return &ast.Code{Content: n.CodeNode.Content}
  203. case *v1pb.Node_ImageNode:
  204. return &ast.Image{AltText: n.ImageNode.AltText, URL: n.ImageNode.Url}
  205. case *v1pb.Node_LinkNode:
  206. return &ast.Link{Text: n.LinkNode.Text, URL: n.LinkNode.Url}
  207. case *v1pb.Node_AutoLinkNode:
  208. return &ast.AutoLink{URL: n.AutoLinkNode.Url, IsRawText: n.AutoLinkNode.IsRawText}
  209. case *v1pb.Node_TagNode:
  210. return &ast.Tag{Content: n.TagNode.Content}
  211. case *v1pb.Node_StrikethroughNode:
  212. return &ast.Strikethrough{Content: n.StrikethroughNode.Content}
  213. case *v1pb.Node_EscapingCharacterNode:
  214. return &ast.EscapingCharacter{Symbol: n.EscapingCharacterNode.Symbol}
  215. case *v1pb.Node_MathNode:
  216. return &ast.Math{Content: n.MathNode.Content}
  217. case *v1pb.Node_HighlightNode:
  218. return &ast.Highlight{Content: n.HighlightNode.Content}
  219. case *v1pb.Node_SubscriptNode:
  220. return &ast.Subscript{Content: n.SubscriptNode.Content}
  221. case *v1pb.Node_SuperscriptNode:
  222. return &ast.Superscript{Content: n.SuperscriptNode.Content}
  223. case *v1pb.Node_ReferencedContentNode:
  224. return &ast.ReferencedContent{ResourceName: n.ReferencedContentNode.ResourceName, Params: n.ReferencedContentNode.Params}
  225. case *v1pb.Node_SpoilerNode:
  226. return &ast.Spoiler{Content: n.SpoilerNode.Content}
  227. case *v1pb.Node_HtmlElementNode:
  228. return &ast.HTMLElement{TagName: n.HtmlElementNode.TagName, Attributes: n.HtmlElementNode.Attributes}
  229. default:
  230. return &ast.Text{}
  231. }
  232. }
  233. func convertToASTNodes(nodes []*v1pb.Node) []ast.Node {
  234. rawNodes := []ast.Node{}
  235. for _, node := range nodes {
  236. rawNode := convertToASTNode(node)
  237. rawNodes = append(rawNodes, rawNode)
  238. }
  239. return rawNodes
  240. }
  241. func convertTableToASTNode(node *v1pb.TableNode) *ast.Table {
  242. table := &ast.Table{
  243. Header: convertToASTNodes(node.Header),
  244. Delimiter: node.Delimiter,
  245. }
  246. for _, row := range node.Rows {
  247. table.Rows = append(table.Rows, convertToASTNodes(row.Cells))
  248. }
  249. return table
  250. }
  251. func convertListKindToASTNode(kind v1pb.ListNode_Kind) ast.ListKind {
  252. switch kind {
  253. case v1pb.ListNode_ORDERED:
  254. return ast.OrderedList
  255. case v1pb.ListNode_UNORDERED:
  256. return ast.UnorderedList
  257. case v1pb.ListNode_DESCRIPTION:
  258. return ast.DescrpitionList
  259. default:
  260. // Default to description list.
  261. return ast.DescrpitionList
  262. }
  263. }