mirror of https://github.com/usememos/memos
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			309 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			309 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
package v1
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"github.com/usememos/gomark"
 | 
						|
	"github.com/usememos/gomark/ast"
 | 
						|
	"github.com/usememos/gomark/renderer"
 | 
						|
 | 
						|
	"github.com/usememos/memos/plugin/httpgetter"
 | 
						|
	v1pb "github.com/usememos/memos/proto/gen/api/v1"
 | 
						|
)
 | 
						|
 | 
						|
func (*APIV1Service) ParseMarkdown(_ context.Context, request *v1pb.ParseMarkdownRequest) (*v1pb.ParseMarkdownResponse, error) {
 | 
						|
	doc, err := gomark.Parse(request.Markdown)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "failed to parse memo content")
 | 
						|
	}
 | 
						|
 | 
						|
	nodes := convertFromASTDocument(doc)
 | 
						|
	return &v1pb.ParseMarkdownResponse{
 | 
						|
		Nodes: nodes,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.RestoreMarkdownNodesRequest) (*v1pb.RestoreMarkdownNodesResponse, error) {
 | 
						|
	markdown := gomark.Restore(convertToASTDocument(request.Nodes))
 | 
						|
	return &v1pb.RestoreMarkdownNodesResponse{
 | 
						|
		Markdown: markdown,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (*APIV1Service) StringifyMarkdownNodes(_ context.Context, request *v1pb.StringifyMarkdownNodesRequest) (*v1pb.StringifyMarkdownNodesResponse, error) {
 | 
						|
	stringRenderer := renderer.NewStringRenderer()
 | 
						|
	plainText := stringRenderer.RenderDocument(convertToASTDocument(request.Nodes))
 | 
						|
	return &v1pb.StringifyMarkdownNodesResponse{
 | 
						|
		PlainText: plainText,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (*APIV1Service) GetLinkMetadata(_ context.Context, request *v1pb.GetLinkMetadataRequest) (*v1pb.LinkMetadata, error) {
 | 
						|
	htmlMeta, err := httpgetter.GetHTMLMeta(request.Link)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &v1pb.LinkMetadata{
 | 
						|
		Title:       htmlMeta.Title,
 | 
						|
		Description: htmlMeta.Description,
 | 
						|
		Image:       htmlMeta.Image,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func convertFromASTNode(rawNode ast.Node) *v1pb.Node {
 | 
						|
	node := &v1pb.Node{
 | 
						|
		Type: v1pb.NodeType(v1pb.NodeType_value[string(rawNode.Type())]),
 | 
						|
	}
 | 
						|
 | 
						|
	switch n := rawNode.(type) {
 | 
						|
	case *ast.LineBreak:
 | 
						|
		node.Node = &v1pb.Node_LineBreakNode{}
 | 
						|
	case *ast.Paragraph:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_ParagraphNode{ParagraphNode: &v1pb.ParagraphNode{Children: children}}
 | 
						|
	case *ast.CodeBlock:
 | 
						|
		node.Node = &v1pb.Node_CodeBlockNode{CodeBlockNode: &v1pb.CodeBlockNode{Language: n.Language, Content: n.Content}}
 | 
						|
	case *ast.Heading:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_HeadingNode{HeadingNode: &v1pb.HeadingNode{Level: int32(n.Level), Children: children}}
 | 
						|
	case *ast.HorizontalRule:
 | 
						|
		node.Node = &v1pb.Node_HorizontalRuleNode{HorizontalRuleNode: &v1pb.HorizontalRuleNode{Symbol: n.Symbol}}
 | 
						|
	case *ast.Blockquote:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_BlockquoteNode{BlockquoteNode: &v1pb.BlockquoteNode{Children: children}}
 | 
						|
	case *ast.List:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_ListNode{ListNode: &v1pb.ListNode{Kind: convertListKindFromASTNode(n.Kind), Indent: int32(n.Indent), Children: children}}
 | 
						|
	case *ast.OrderedListItem:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_OrderedListItemNode{OrderedListItemNode: &v1pb.OrderedListItemNode{Number: n.Number, Indent: int32(n.Indent), Children: children}}
 | 
						|
	case *ast.UnorderedListItem:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_UnorderedListItemNode{UnorderedListItemNode: &v1pb.UnorderedListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Children: children}}
 | 
						|
	case *ast.TaskListItem:
 | 
						|
		children := convertFromASTNodes(n.Children)
 | 
						|
		node.Node = &v1pb.Node_TaskListItemNode{TaskListItemNode: &v1pb.TaskListItemNode{Symbol: n.Symbol, Indent: int32(n.Indent), Complete: n.Complete, Children: children}}
 | 
						|
	case *ast.MathBlock:
 | 
						|
		node.Node = &v1pb.Node_MathBlockNode{MathBlockNode: &v1pb.MathBlockNode{Content: n.Content}}
 | 
						|
	case *ast.Table:
 | 
						|
		node.Node = &v1pb.Node_TableNode{TableNode: convertTableFromASTNode(n)}
 | 
						|
	case *ast.EmbeddedContent:
 | 
						|
		node.Node = &v1pb.Node_EmbeddedContentNode{EmbeddedContentNode: &v1pb.EmbeddedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
 | 
						|
	case *ast.Text:
 | 
						|
		node.Node = &v1pb.Node_TextNode{TextNode: &v1pb.TextNode{Content: n.Content}}
 | 
						|
	case *ast.Bold:
 | 
						|
		node.Node = &v1pb.Node_BoldNode{BoldNode: &v1pb.BoldNode{Symbol: n.Symbol, Children: convertFromASTNodes(n.Children)}}
 | 
						|
	case *ast.Italic:
 | 
						|
		node.Node = &v1pb.Node_ItalicNode{ItalicNode: &v1pb.ItalicNode{Symbol: n.Symbol, Children: convertFromASTNodes(n.Children)}}
 | 
						|
	case *ast.BoldItalic:
 | 
						|
		childDoc := &ast.Document{Children: n.Children}
 | 
						|
		plain := renderer.NewStringRenderer().RenderDocument(childDoc)
 | 
						|
		node.Node = &v1pb.Node_BoldItalicNode{BoldItalicNode: &v1pb.BoldItalicNode{Symbol: n.Symbol, Content: plain}}
 | 
						|
	case *ast.Code:
 | 
						|
		node.Node = &v1pb.Node_CodeNode{CodeNode: &v1pb.CodeNode{Content: n.Content}}
 | 
						|
	case *ast.Image:
 | 
						|
		node.Node = &v1pb.Node_ImageNode{ImageNode: &v1pb.ImageNode{AltText: n.AltText, Url: n.URL}}
 | 
						|
	case *ast.Link:
 | 
						|
		node.Node = &v1pb.Node_LinkNode{LinkNode: &v1pb.LinkNode{Content: convertFromASTNodes(n.Content), Url: n.URL}}
 | 
						|
	case *ast.AutoLink:
 | 
						|
		node.Node = &v1pb.Node_AutoLinkNode{AutoLinkNode: &v1pb.AutoLinkNode{Url: n.URL, IsRawText: n.IsRawText}}
 | 
						|
	case *ast.Tag:
 | 
						|
		node.Node = &v1pb.Node_TagNode{TagNode: &v1pb.TagNode{Content: n.Content}}
 | 
						|
	case *ast.Strikethrough:
 | 
						|
		node.Node = &v1pb.Node_StrikethroughNode{StrikethroughNode: &v1pb.StrikethroughNode{Content: n.Content}}
 | 
						|
	case *ast.EscapingCharacter:
 | 
						|
		node.Node = &v1pb.Node_EscapingCharacterNode{EscapingCharacterNode: &v1pb.EscapingCharacterNode{Symbol: n.Symbol}}
 | 
						|
	case *ast.Math:
 | 
						|
		node.Node = &v1pb.Node_MathNode{MathNode: &v1pb.MathNode{Content: n.Content}}
 | 
						|
	case *ast.Highlight:
 | 
						|
		node.Node = &v1pb.Node_HighlightNode{HighlightNode: &v1pb.HighlightNode{Content: n.Content}}
 | 
						|
	case *ast.Subscript:
 | 
						|
		node.Node = &v1pb.Node_SubscriptNode{SubscriptNode: &v1pb.SubscriptNode{Content: n.Content}}
 | 
						|
	case *ast.Superscript:
 | 
						|
		node.Node = &v1pb.Node_SuperscriptNode{SuperscriptNode: &v1pb.SuperscriptNode{Content: n.Content}}
 | 
						|
	case *ast.ReferencedContent:
 | 
						|
		node.Node = &v1pb.Node_ReferencedContentNode{ReferencedContentNode: &v1pb.ReferencedContentNode{ResourceName: n.ResourceName, Params: n.Params}}
 | 
						|
	case *ast.Spoiler:
 | 
						|
		node.Node = &v1pb.Node_SpoilerNode{SpoilerNode: &v1pb.SpoilerNode{Content: n.Content}}
 | 
						|
	case *ast.HTMLElement:
 | 
						|
		node.Node = &v1pb.Node_HtmlElementNode{HtmlElementNode: &v1pb.HTMLElementNode{
 | 
						|
			TagName:       n.TagName,
 | 
						|
			Attributes:    n.Attributes,
 | 
						|
			Children:      convertFromASTNodes(n.Children),
 | 
						|
			IsSelfClosing: n.IsSelfClosing,
 | 
						|
		}}
 | 
						|
	default:
 | 
						|
		node.Node = &v1pb.Node_TextNode{TextNode: &v1pb.TextNode{}}
 | 
						|
	}
 | 
						|
	return node
 | 
						|
}
 | 
						|
 | 
						|
func convertFromASTNodes(rawNodes []ast.Node) []*v1pb.Node {
 | 
						|
	nodes := []*v1pb.Node{}
 | 
						|
	for _, rawNode := range rawNodes {
 | 
						|
		node := convertFromASTNode(rawNode)
 | 
						|
		nodes = append(nodes, node)
 | 
						|
	}
 | 
						|
	return nodes
 | 
						|
}
 | 
						|
 | 
						|
func convertFromASTDocument(doc *ast.Document) []*v1pb.Node {
 | 
						|
	if doc == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return convertFromASTNodes(doc.Children)
 | 
						|
}
 | 
						|
 | 
						|
func convertTableFromASTNode(node *ast.Table) *v1pb.TableNode {
 | 
						|
	table := &v1pb.TableNode{
 | 
						|
		Header:    convertFromASTNodes(node.Header),
 | 
						|
		Delimiter: node.Delimiter,
 | 
						|
	}
 | 
						|
	for _, row := range node.Rows {
 | 
						|
		table.Rows = append(table.Rows, &v1pb.TableNode_Row{Cells: convertFromASTNodes(row)})
 | 
						|
	}
 | 
						|
	return table
 | 
						|
}
 | 
						|
 | 
						|
func convertListKindFromASTNode(node ast.ListKind) v1pb.ListNode_Kind {
 | 
						|
	switch node {
 | 
						|
	case ast.OrderedList:
 | 
						|
		return v1pb.ListNode_ORDERED
 | 
						|
	case ast.UnorderedList:
 | 
						|
		return v1pb.ListNode_UNORDERED
 | 
						|
	case ast.DescriptionList:
 | 
						|
		return v1pb.ListNode_DESCRIPTION
 | 
						|
	default:
 | 
						|
		return v1pb.ListNode_KIND_UNSPECIFIED
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func convertToASTNode(node *v1pb.Node) ast.Node {
 | 
						|
	switch n := node.Node.(type) {
 | 
						|
	case *v1pb.Node_LineBreakNode:
 | 
						|
		return &ast.LineBreak{}
 | 
						|
	case *v1pb.Node_ParagraphNode:
 | 
						|
		children := convertToASTNodes(n.ParagraphNode.Children)
 | 
						|
		return &ast.Paragraph{Children: children}
 | 
						|
	case *v1pb.Node_CodeBlockNode:
 | 
						|
		return &ast.CodeBlock{Language: n.CodeBlockNode.Language, Content: n.CodeBlockNode.Content}
 | 
						|
	case *v1pb.Node_HeadingNode:
 | 
						|
		children := convertToASTNodes(n.HeadingNode.Children)
 | 
						|
		return &ast.Heading{Level: int(n.HeadingNode.Level), Children: children}
 | 
						|
	case *v1pb.Node_HorizontalRuleNode:
 | 
						|
		return &ast.HorizontalRule{Symbol: n.HorizontalRuleNode.Symbol}
 | 
						|
	case *v1pb.Node_BlockquoteNode:
 | 
						|
		children := convertToASTNodes(n.BlockquoteNode.Children)
 | 
						|
		return &ast.Blockquote{Children: children}
 | 
						|
	case *v1pb.Node_ListNode:
 | 
						|
		children := convertToASTNodes(n.ListNode.Children)
 | 
						|
		return &ast.List{Kind: convertListKindToASTNode(n.ListNode.Kind), Indent: int(n.ListNode.Indent), Children: children}
 | 
						|
	case *v1pb.Node_OrderedListItemNode:
 | 
						|
		children := convertToASTNodes(n.OrderedListItemNode.Children)
 | 
						|
		return &ast.OrderedListItem{Number: n.OrderedListItemNode.Number, Indent: int(n.OrderedListItemNode.Indent), Children: children}
 | 
						|
	case *v1pb.Node_UnorderedListItemNode:
 | 
						|
		children := convertToASTNodes(n.UnorderedListItemNode.Children)
 | 
						|
		return &ast.UnorderedListItem{Symbol: n.UnorderedListItemNode.Symbol, Indent: int(n.UnorderedListItemNode.Indent), Children: children}
 | 
						|
	case *v1pb.Node_TaskListItemNode:
 | 
						|
		children := convertToASTNodes(n.TaskListItemNode.Children)
 | 
						|
		return &ast.TaskListItem{Symbol: n.TaskListItemNode.Symbol, Indent: int(n.TaskListItemNode.Indent), Complete: n.TaskListItemNode.Complete, Children: children}
 | 
						|
	case *v1pb.Node_MathBlockNode:
 | 
						|
		return &ast.MathBlock{Content: n.MathBlockNode.Content}
 | 
						|
	case *v1pb.Node_TableNode:
 | 
						|
		return convertTableToASTNode(n.TableNode)
 | 
						|
	case *v1pb.Node_EmbeddedContentNode:
 | 
						|
		return &ast.EmbeddedContent{ResourceName: n.EmbeddedContentNode.ResourceName, Params: n.EmbeddedContentNode.Params}
 | 
						|
	case *v1pb.Node_TextNode:
 | 
						|
		return &ast.Text{Content: n.TextNode.Content}
 | 
						|
	case *v1pb.Node_BoldNode:
 | 
						|
		return &ast.Bold{Symbol: n.BoldNode.Symbol, Children: convertToASTNodes(n.BoldNode.Children)}
 | 
						|
	case *v1pb.Node_ItalicNode:
 | 
						|
		return &ast.Italic{Symbol: n.ItalicNode.Symbol, Children: convertToASTNodes(n.ItalicNode.Children)}
 | 
						|
	case *v1pb.Node_BoldItalicNode:
 | 
						|
		children := []ast.Node{}
 | 
						|
		if n.BoldItalicNode.Content != "" {
 | 
						|
			children = append(children, &ast.Text{Content: n.BoldItalicNode.Content})
 | 
						|
		}
 | 
						|
		return &ast.BoldItalic{Symbol: n.BoldItalicNode.Symbol, Children: children}
 | 
						|
	case *v1pb.Node_CodeNode:
 | 
						|
		return &ast.Code{Content: n.CodeNode.Content}
 | 
						|
	case *v1pb.Node_ImageNode:
 | 
						|
		return &ast.Image{AltText: n.ImageNode.AltText, URL: n.ImageNode.Url}
 | 
						|
	case *v1pb.Node_LinkNode:
 | 
						|
		return &ast.Link{Content: convertToASTNodes(n.LinkNode.Content), URL: n.LinkNode.Url}
 | 
						|
	case *v1pb.Node_AutoLinkNode:
 | 
						|
		return &ast.AutoLink{URL: n.AutoLinkNode.Url, IsRawText: n.AutoLinkNode.IsRawText}
 | 
						|
	case *v1pb.Node_TagNode:
 | 
						|
		return &ast.Tag{Content: n.TagNode.Content}
 | 
						|
	case *v1pb.Node_StrikethroughNode:
 | 
						|
		return &ast.Strikethrough{Content: n.StrikethroughNode.Content}
 | 
						|
	case *v1pb.Node_EscapingCharacterNode:
 | 
						|
		return &ast.EscapingCharacter{Symbol: n.EscapingCharacterNode.Symbol}
 | 
						|
	case *v1pb.Node_MathNode:
 | 
						|
		return &ast.Math{Content: n.MathNode.Content}
 | 
						|
	case *v1pb.Node_HighlightNode:
 | 
						|
		return &ast.Highlight{Content: n.HighlightNode.Content}
 | 
						|
	case *v1pb.Node_SubscriptNode:
 | 
						|
		return &ast.Subscript{Content: n.SubscriptNode.Content}
 | 
						|
	case *v1pb.Node_SuperscriptNode:
 | 
						|
		return &ast.Superscript{Content: n.SuperscriptNode.Content}
 | 
						|
	case *v1pb.Node_ReferencedContentNode:
 | 
						|
		return &ast.ReferencedContent{ResourceName: n.ReferencedContentNode.ResourceName, Params: n.ReferencedContentNode.Params}
 | 
						|
	case *v1pb.Node_SpoilerNode:
 | 
						|
		return &ast.Spoiler{Content: n.SpoilerNode.Content}
 | 
						|
	case *v1pb.Node_HtmlElementNode:
 | 
						|
		var children []ast.Node
 | 
						|
		if len(n.HtmlElementNode.Children) > 0 {
 | 
						|
			children = convertToASTNodes(n.HtmlElementNode.Children)
 | 
						|
		}
 | 
						|
		return &ast.HTMLElement{
 | 
						|
			TagName:       n.HtmlElementNode.TagName,
 | 
						|
			Attributes:    n.HtmlElementNode.Attributes,
 | 
						|
			Children:      children,
 | 
						|
			IsSelfClosing: n.HtmlElementNode.IsSelfClosing,
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		return &ast.Text{}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func convertToASTNodes(nodes []*v1pb.Node) []ast.Node {
 | 
						|
	rawNodes := []ast.Node{}
 | 
						|
	for _, node := range nodes {
 | 
						|
		rawNode := convertToASTNode(node)
 | 
						|
		rawNodes = append(rawNodes, rawNode)
 | 
						|
	}
 | 
						|
	return rawNodes
 | 
						|
}
 | 
						|
 | 
						|
func convertToASTDocument(nodes []*v1pb.Node) *ast.Document {
 | 
						|
	return &ast.Document{Children: convertToASTNodes(nodes)}
 | 
						|
}
 | 
						|
 | 
						|
func convertTableToASTNode(node *v1pb.TableNode) *ast.Table {
 | 
						|
	table := &ast.Table{
 | 
						|
		Header:    convertToASTNodes(node.Header),
 | 
						|
		Delimiter: node.Delimiter,
 | 
						|
	}
 | 
						|
	for _, row := range node.Rows {
 | 
						|
		table.Rows = append(table.Rows, convertToASTNodes(row.Cells))
 | 
						|
	}
 | 
						|
	return table
 | 
						|
}
 | 
						|
 | 
						|
func convertListKindToASTNode(kind v1pb.ListNode_Kind) ast.ListKind {
 | 
						|
	switch kind {
 | 
						|
	case v1pb.ListNode_ORDERED:
 | 
						|
		return ast.OrderedList
 | 
						|
	case v1pb.ListNode_UNORDERED:
 | 
						|
		return ast.UnorderedList
 | 
						|
	case v1pb.ListNode_DESCRIPTION:
 | 
						|
		return ast.DescriptionList
 | 
						|
	default:
 | 
						|
		// Default to description list.
 | 
						|
		return ast.DescriptionList
 | 
						|
	}
 | 
						|
}
 |