mirror of
https://github.com/usememos/memos.git
synced 2025-03-27 16:10:18 +01:00
chore: implement html render
This commit is contained in:
parent
3edce174d6
commit
242f64fa8e
@ -69,6 +69,10 @@ linters-settings:
|
|||||||
disabled: true
|
disabled: true
|
||||||
- name: use-any
|
- name: use-any
|
||||||
disabled: true
|
disabled: true
|
||||||
|
- name: exported
|
||||||
|
disabled: true
|
||||||
|
- name: unhandled-error
|
||||||
|
disabled: true
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type BaseBlock struct {
|
type BaseBlock struct {
|
||||||
BaseNode
|
BaseNode
|
||||||
}
|
}
|
||||||
@ -10,14 +8,8 @@ type LineBreak struct {
|
|||||||
BaseBlock
|
BaseBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeLineBreak = NewNodeType("LineBreak")
|
|
||||||
|
|
||||||
func (*LineBreak) Type() NodeType {
|
func (*LineBreak) Type() NodeType {
|
||||||
return NodeTypeLineBreak
|
return LineBreakNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *LineBreak) String() string {
|
|
||||||
return n.Type().String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Paragraph struct {
|
type Paragraph struct {
|
||||||
@ -26,18 +18,8 @@ type Paragraph struct {
|
|||||||
Children []Node
|
Children []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeParagraph = NewNodeType("Paragraph")
|
|
||||||
|
|
||||||
func (*Paragraph) Type() NodeType {
|
func (*Paragraph) Type() NodeType {
|
||||||
return NodeTypeParagraph
|
return ParagraphNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Paragraph) String() string {
|
|
||||||
str := n.Type().String()
|
|
||||||
for _, child := range n.Children {
|
|
||||||
str += " " + child.String()
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeBlock struct {
|
type CodeBlock struct {
|
||||||
@ -47,14 +29,8 @@ type CodeBlock struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeCodeBlock = NewNodeType("CodeBlock")
|
|
||||||
|
|
||||||
func (*CodeBlock) Type() NodeType {
|
func (*CodeBlock) Type() NodeType {
|
||||||
return NodeTypeCodeBlock
|
return CodeBlockNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *CodeBlock) String() string {
|
|
||||||
return n.Type().String() + " " + n.Language + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Heading struct {
|
type Heading struct {
|
||||||
@ -64,18 +40,8 @@ type Heading struct {
|
|||||||
Children []Node
|
Children []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeHeading = NewNodeType("Heading")
|
|
||||||
|
|
||||||
func (*Heading) Type() NodeType {
|
func (*Heading) Type() NodeType {
|
||||||
return NodeTypeHeading
|
return HeadingNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Heading) String() string {
|
|
||||||
str := n.Type().String() + " " + fmt.Sprintf("%d", n.Level)
|
|
||||||
for _, child := range n.Children {
|
|
||||||
str += " " + child.String()
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HorizontalRule struct {
|
type HorizontalRule struct {
|
||||||
@ -85,14 +51,8 @@ type HorizontalRule struct {
|
|||||||
Symbol string
|
Symbol string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeHorizontalRule = NewNodeType("HorizontalRule")
|
|
||||||
|
|
||||||
func (*HorizontalRule) Type() NodeType {
|
func (*HorizontalRule) Type() NodeType {
|
||||||
return NodeTypeHorizontalRule
|
return HorizontalRuleNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *HorizontalRule) String() string {
|
|
||||||
return n.Type().String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Blockquote struct {
|
type Blockquote struct {
|
||||||
@ -101,16 +61,6 @@ type Blockquote struct {
|
|||||||
Children []Node
|
Children []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeBlockquote = NewNodeType("Blockquote")
|
|
||||||
|
|
||||||
func (*Blockquote) Type() NodeType {
|
func (*Blockquote) Type() NodeType {
|
||||||
return NodeTypeBlockquote
|
return BlockquoteNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Blockquote) String() string {
|
|
||||||
str := n.Type().String()
|
|
||||||
for _, child := range n.Children {
|
|
||||||
str += " " + child.String()
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,8 @@ type Text struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeText = NewNodeType("Text")
|
|
||||||
|
|
||||||
func (*Text) Type() NodeType {
|
func (*Text) Type() NodeType {
|
||||||
return NodeTypeText
|
return TextNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Text) String() string {
|
|
||||||
return n.Type().String() + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bold struct {
|
type Bold struct {
|
||||||
@ -28,14 +22,8 @@ type Bold struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeBold = NewNodeType("Bold")
|
|
||||||
|
|
||||||
func (*Bold) Type() NodeType {
|
func (*Bold) Type() NodeType {
|
||||||
return NodeTypeBold
|
return BoldNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Bold) String() string {
|
|
||||||
return n.Type().String() + " " + n.Symbol + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Italic struct {
|
type Italic struct {
|
||||||
@ -46,14 +34,8 @@ type Italic struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeItalic = NewNodeType("Italic")
|
|
||||||
|
|
||||||
func (*Italic) Type() NodeType {
|
func (*Italic) Type() NodeType {
|
||||||
return NodeTypeItalic
|
return ItalicNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Italic) String() string {
|
|
||||||
return n.Type().String() + " " + n.Symbol + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoldItalic struct {
|
type BoldItalic struct {
|
||||||
@ -64,14 +46,8 @@ type BoldItalic struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeBoldItalic = NewNodeType("BoldItalic")
|
|
||||||
|
|
||||||
func (*BoldItalic) Type() NodeType {
|
func (*BoldItalic) Type() NodeType {
|
||||||
return NodeTypeBoldItalic
|
return BoldItalicNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *BoldItalic) String() string {
|
|
||||||
return n.Type().String() + " " + n.Symbol + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
@ -80,14 +56,8 @@ type Code struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeCode = NewNodeType("Code")
|
|
||||||
|
|
||||||
func (*Code) Type() NodeType {
|
func (*Code) Type() NodeType {
|
||||||
return NodeTypeCode
|
return CodeNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Code) String() string {
|
|
||||||
return n.Type().String() + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
@ -97,14 +67,8 @@ type Image struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeImage = NewNodeType("Image")
|
|
||||||
|
|
||||||
func (*Image) Type() NodeType {
|
func (*Image) Type() NodeType {
|
||||||
return NodeTypeImage
|
return ImageNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Image) String() string {
|
|
||||||
return n.Type().String() + " " + n.AltText + " " + n.URL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
@ -114,14 +78,8 @@ type Link struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeLink = NewNodeType("Link")
|
|
||||||
|
|
||||||
func (*Link) Type() NodeType {
|
func (*Link) Type() NodeType {
|
||||||
return NodeTypeLink
|
return LinkNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Link) String() string {
|
|
||||||
return n.Type().String() + " " + n.Text + " " + n.URL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
@ -130,14 +88,8 @@ type Tag struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeTag = NewNodeType("Tag")
|
|
||||||
|
|
||||||
func (*Tag) Type() NodeType {
|
func (*Tag) Type() NodeType {
|
||||||
return NodeTypeTag
|
return TagNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Tag) String() string {
|
|
||||||
return n.Type().String() + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strikethrough struct {
|
type Strikethrough struct {
|
||||||
@ -146,12 +98,6 @@ type Strikethrough struct {
|
|||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
var NodeTypeStrikethrough = NewNodeType("Strikethrough")
|
|
||||||
|
|
||||||
func (*Strikethrough) Type() NodeType {
|
func (*Strikethrough) Type() NodeType {
|
||||||
return NodeTypeStrikethrough
|
return StrikethroughNode
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Strikethrough) String() string {
|
|
||||||
return n.Type().String() + " " + n.Content
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,37 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
|
type NodeType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownNode NodeType = iota
|
||||||
|
// Block nodes.
|
||||||
|
LineBreakNode
|
||||||
|
ParagraphNode
|
||||||
|
CodeBlockNode
|
||||||
|
HeadingNode
|
||||||
|
HorizontalRuleNode
|
||||||
|
BlockquoteNode
|
||||||
|
// Inline nodes.
|
||||||
|
TextNode
|
||||||
|
BoldNode
|
||||||
|
ItalicNode
|
||||||
|
BoldItalicNode
|
||||||
|
CodeNode
|
||||||
|
ImageNode
|
||||||
|
LinkNode
|
||||||
|
TagNode
|
||||||
|
StrikethroughNode
|
||||||
|
)
|
||||||
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
// Type returns a node type.
|
// Type returns a node type.
|
||||||
Type() NodeType
|
Type() NodeType
|
||||||
|
|
||||||
// String returns a string representation of this node.
|
// PrevSibling returns a previous sibling node of this node.
|
||||||
// This method is used for debugging.
|
PrevSibling() Node
|
||||||
String() string
|
|
||||||
|
|
||||||
// GetPrevSibling returns a previous sibling node of this node.
|
// NextSibling returns a next sibling node of this node.
|
||||||
GetPrevSibling() Node
|
NextSibling() Node
|
||||||
|
|
||||||
// GetNextSibling returns a next sibling node of this node.
|
|
||||||
GetNextSibling() Node
|
|
||||||
|
|
||||||
// SetPrevSibling sets a previous sibling node to this node.
|
// SetPrevSibling sets a previous sibling node to this node.
|
||||||
SetPrevSibling(Node)
|
SetPrevSibling(Node)
|
||||||
@ -21,32 +40,17 @@ type Node interface {
|
|||||||
SetNextSibling(Node)
|
SetNextSibling(Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeType int
|
|
||||||
|
|
||||||
func (t NodeType) String() string {
|
|
||||||
return nodeTypeNames[t]
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeTypeIndex NodeType
|
|
||||||
var nodeTypeNames = []string{""}
|
|
||||||
|
|
||||||
func NewNodeType(name string) NodeType {
|
|
||||||
nodeTypeNames = append(nodeTypeNames, name)
|
|
||||||
nodeTypeIndex++
|
|
||||||
return nodeTypeIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseNode struct {
|
type BaseNode struct {
|
||||||
prevSibling Node
|
prevSibling Node
|
||||||
|
|
||||||
nextSibling Node
|
nextSibling Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *BaseNode) GetPrevSibling() Node {
|
func (n *BaseNode) PrevSibling() Node {
|
||||||
return n.prevSibling
|
return n.prevSibling
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *BaseNode) GetNextSibling() Node {
|
func (n *BaseNode) NextSibling() Node {
|
||||||
return n.nextSibling
|
return n.nextSibling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ func ParseInline(tokens []*tokenizer.Token) ([]ast.Node, error) {
|
|||||||
tokens = tokens[size:]
|
tokens = tokens[size:]
|
||||||
if prevNode != nil {
|
if prevNode != nil {
|
||||||
// Merge text nodes if possible.
|
// Merge text nodes if possible.
|
||||||
if prevNode.Type() == ast.NodeTypeText && node.Type() == ast.NodeTypeText {
|
if prevNode.Type() == ast.TextNode && node.Type() == ast.TextNode {
|
||||||
prevNode.(*ast.Text).Content += node.(*ast.Text).Content
|
prevNode.(*ast.Text).Content += node.(*ast.Text).Content
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,44 @@ func StringifyNodes(nodes []ast.Node) string {
|
|||||||
var result string
|
var result string
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if node != nil {
|
if node != nil {
|
||||||
result += node.String()
|
result += StringifyNode(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringifyNode(node ast.Node) string {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LineBreak:
|
||||||
|
return "LineBreak()"
|
||||||
|
case *ast.CodeBlock:
|
||||||
|
return "CodeBlock(" + n.Language + ", " + n.Content + ")"
|
||||||
|
case *ast.Paragraph:
|
||||||
|
return "Paragraph(" + StringifyNodes(n.Children) + ")"
|
||||||
|
case *ast.Heading:
|
||||||
|
return "Heading(" + StringifyNodes(n.Children) + ")"
|
||||||
|
case *ast.HorizontalRule:
|
||||||
|
return "HorizontalRule(" + n.Symbol + ")"
|
||||||
|
case *ast.Blockquote:
|
||||||
|
return "Blockquote(" + StringifyNodes(n.Children) + ")"
|
||||||
|
case *ast.Text:
|
||||||
|
return "Text(" + n.Content + ")"
|
||||||
|
case *ast.Bold:
|
||||||
|
return "Bold(" + n.Symbol + n.Content + n.Symbol + ")"
|
||||||
|
case *ast.Italic:
|
||||||
|
return "Italic(" + n.Symbol + n.Content + n.Symbol + ")"
|
||||||
|
case *ast.BoldItalic:
|
||||||
|
return "BoldItalic(" + n.Symbol + n.Content + n.Symbol + ")"
|
||||||
|
case *ast.Code:
|
||||||
|
return "Code(" + n.Content + ")"
|
||||||
|
case *ast.Image:
|
||||||
|
return "Image(" + n.URL + ", " + n.AltText + ")"
|
||||||
|
case *ast.Link:
|
||||||
|
return "Link(" + n.Text + ", " + n.URL + ")"
|
||||||
|
case *ast.Tag:
|
||||||
|
return "Tag(" + n.Content + ")"
|
||||||
|
case *ast.Strikethrough:
|
||||||
|
return "Strikethrough(" + n.Content + ")"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -68,14 +68,14 @@ func Tokenize(text string) []*Token {
|
|||||||
case ' ':
|
case ' ':
|
||||||
tokens = append(tokens, NewToken(Space, " "))
|
tokens = append(tokens, NewToken(Space, " "))
|
||||||
default:
|
default:
|
||||||
var lastToken *Token
|
var prevToken *Token
|
||||||
if len(tokens) > 0 {
|
if len(tokens) > 0 {
|
||||||
lastToken = tokens[len(tokens)-1]
|
prevToken = tokens[len(tokens)-1]
|
||||||
}
|
}
|
||||||
if lastToken == nil || lastToken.Type != Text {
|
if prevToken == nil || prevToken.Type != Text {
|
||||||
tokens = append(tokens, NewToken(Text, string(c)))
|
tokens = append(tokens, NewToken(Text, string(c)))
|
||||||
} else {
|
} else {
|
||||||
lastToken.Value += string(c)
|
prevToken.Value += string(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
plugin/gomark/render/html/html.go
Normal file
171
plugin/gomark/render/html/html.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTMLRender is a simple renderer that converts AST to HTML.
|
||||||
|
type HTMLRender struct {
|
||||||
|
output *bytes.Buffer
|
||||||
|
context *RendererContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type RendererContext struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTMLRender creates a new HTMLRender.
|
||||||
|
func NewHTMLRender() *HTMLRender {
|
||||||
|
return &HTMLRender{
|
||||||
|
output: new(bytes.Buffer),
|
||||||
|
context: &RendererContext{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderNode renders a single AST node to HTML.
|
||||||
|
func (r *HTMLRender) RenderNode(node ast.Node) {
|
||||||
|
switch n := node.(type) {
|
||||||
|
case *ast.LineBreak:
|
||||||
|
r.renderLineBreak(n)
|
||||||
|
case *ast.Paragraph:
|
||||||
|
r.renderParagraph(n)
|
||||||
|
case *ast.CodeBlock:
|
||||||
|
r.renderCodeBlock(n)
|
||||||
|
case *ast.Heading:
|
||||||
|
r.renderHeading(n)
|
||||||
|
case *ast.HorizontalRule:
|
||||||
|
r.renderHorizontalRule(n)
|
||||||
|
case *ast.Blockquote:
|
||||||
|
r.renderBlockquote(n)
|
||||||
|
case *ast.Bold:
|
||||||
|
r.renderBold(n)
|
||||||
|
case *ast.Italic:
|
||||||
|
r.renderItalic(n)
|
||||||
|
case *ast.BoldItalic:
|
||||||
|
r.renderBoldItalic(n)
|
||||||
|
case *ast.Code:
|
||||||
|
r.renderCode(n)
|
||||||
|
case *ast.Image:
|
||||||
|
r.renderImage(n)
|
||||||
|
case *ast.Link:
|
||||||
|
r.renderLink(n)
|
||||||
|
case *ast.Tag:
|
||||||
|
r.renderTag(n)
|
||||||
|
case *ast.Strikethrough:
|
||||||
|
r.renderStrikethrough(n)
|
||||||
|
case *ast.Text:
|
||||||
|
r.renderText(n)
|
||||||
|
default:
|
||||||
|
// Handle other block types if needed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderNodes renders a slice of AST nodes to HTML.
|
||||||
|
func (r *HTMLRender) RenderNodes(nodes []ast.Node) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
r.RenderNode(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the AST to HTML.
|
||||||
|
func (r *HTMLRender) Render(astRoot []ast.Node) string {
|
||||||
|
r.RenderNodes(astRoot)
|
||||||
|
return r.output.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderLineBreak(_ *ast.LineBreak) {
|
||||||
|
r.output.WriteString("<br>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderParagraph(node *ast.Paragraph) {
|
||||||
|
r.output.WriteString("<p>")
|
||||||
|
r.RenderNodes(node.Children)
|
||||||
|
r.output.WriteString("</p>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderCodeBlock(node *ast.CodeBlock) {
|
||||||
|
r.output.WriteString("<pre><code>")
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString("</code></pre>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderHeading(node *ast.Heading) {
|
||||||
|
element := fmt.Sprintf("<h%d>", node.Level)
|
||||||
|
r.output.WriteString(fmt.Sprintf("<%s>", element))
|
||||||
|
r.RenderNodes(node.Children)
|
||||||
|
r.output.WriteString(fmt.Sprintf("</%s>", element))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderHorizontalRule(_ *ast.HorizontalRule) {
|
||||||
|
r.output.WriteString("<hr>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderBlockquote(node *ast.Blockquote) {
|
||||||
|
prevSibling, nextSibling := node.PrevSibling(), node.NextSibling()
|
||||||
|
if prevSibling == nil || prevSibling.Type() != ast.BlockquoteNode {
|
||||||
|
r.output.WriteString("<blockquote>")
|
||||||
|
}
|
||||||
|
r.RenderNodes(node.Children)
|
||||||
|
if nextSibling == nil || nextSibling.Type() != ast.BlockquoteNode {
|
||||||
|
r.output.WriteString("</blockquote>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderText(node *ast.Text) {
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderBold(node *ast.Bold) {
|
||||||
|
r.output.WriteString("<strong>")
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString("</strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderItalic(node *ast.Italic) {
|
||||||
|
r.output.WriteString("<em>")
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString("</em>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderBoldItalic(node *ast.BoldItalic) {
|
||||||
|
r.output.WriteString("<strong><em>")
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString("</em></strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderCode(node *ast.Code) {
|
||||||
|
r.output.WriteString("<code>")
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString("</code>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderImage(node *ast.Image) {
|
||||||
|
r.output.WriteString(`<img src="`)
|
||||||
|
r.output.WriteString(node.URL)
|
||||||
|
r.output.WriteString(`" alt="`)
|
||||||
|
r.output.WriteString(node.AltText)
|
||||||
|
r.output.WriteString(`" />`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderLink(node *ast.Link) {
|
||||||
|
r.output.WriteString(`<a href="`)
|
||||||
|
r.output.WriteString(node.URL)
|
||||||
|
r.output.WriteString(`">`)
|
||||||
|
r.output.WriteString(node.Text)
|
||||||
|
r.output.WriteString("</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderTag(node *ast.Tag) {
|
||||||
|
r.output.WriteString(`<span>`)
|
||||||
|
r.output.WriteString(`# `)
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString(`</span>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HTMLRender) renderStrikethrough(node *ast.Strikethrough) {
|
||||||
|
r.output.WriteString(`<del>`)
|
||||||
|
r.output.WriteString(node.Content)
|
||||||
|
r.output.WriteString(`</del>`)
|
||||||
|
}
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTMLRenderer(t *testing.T) {
|
func TestHTMLRender(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
text string
|
text string
|
||||||
expected string
|
expected string
|
||||||
@ -36,7 +36,7 @@ func TestHTMLRenderer(t *testing.T) {
|
|||||||
tokens := tokenizer.Tokenize(test.text)
|
tokens := tokenizer.Tokenize(test.text)
|
||||||
nodes, err := parser.Parse(tokens)
|
nodes, err := parser.Parse(tokens)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
actual := NewHTMLRenderer().Render(nodes)
|
actual := NewHTMLRender().Render(nodes)
|
||||||
require.Equal(t, test.expected, actual)
|
require.Equal(t, test.expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
1
plugin/gomark/render/renderer.go
Normal file
1
plugin/gomark/render/renderer.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package render
|
@ -1,124 +0,0 @@
|
|||||||
package html
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/usememos/memos/plugin/gomark/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTMLRenderer is a simple renderer that converts AST to HTML.
|
|
||||||
// nolint
|
|
||||||
type HTMLRenderer struct {
|
|
||||||
output *bytes.Buffer
|
|
||||||
context *RendererContext
|
|
||||||
}
|
|
||||||
|
|
||||||
type RendererContext struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTMLRenderer creates a new HTMLRenderer.
|
|
||||||
func NewHTMLRenderer() *HTMLRenderer {
|
|
||||||
return &HTMLRenderer{
|
|
||||||
output: new(bytes.Buffer),
|
|
||||||
context: &RendererContext{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderNode renders a single AST node to HTML.
|
|
||||||
func (r *HTMLRenderer) RenderNode(node ast.Node) {
|
|
||||||
prevSibling, nextSibling := node.GetPrevSibling(), node.GetNextSibling()
|
|
||||||
|
|
||||||
switch n := node.(type) {
|
|
||||||
case *ast.LineBreak:
|
|
||||||
r.output.WriteString("<br>")
|
|
||||||
case *ast.Paragraph:
|
|
||||||
r.output.WriteString("<p>")
|
|
||||||
r.RenderNodes(n.Children)
|
|
||||||
r.output.WriteString("</p>")
|
|
||||||
case *ast.CodeBlock:
|
|
||||||
r.output.WriteString("<pre><code>")
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString("</code></pre>")
|
|
||||||
case *ast.Heading:
|
|
||||||
r.output.WriteString(fmt.Sprintf("<h%d>", n.Level))
|
|
||||||
r.RenderNodes(n.Children)
|
|
||||||
r.output.WriteString(fmt.Sprintf("</h%d>", n.Level))
|
|
||||||
case *ast.HorizontalRule:
|
|
||||||
r.output.WriteString("<hr>")
|
|
||||||
case *ast.Blockquote:
|
|
||||||
if prevSibling == nil || prevSibling.Type() != ast.NodeTypeBlockquote {
|
|
||||||
r.output.WriteString("<blockquote>")
|
|
||||||
}
|
|
||||||
r.RenderNodes(n.Children)
|
|
||||||
if nextSibling != nil && nextSibling.Type() == ast.NodeTypeBlockquote {
|
|
||||||
r.RenderNode(nextSibling)
|
|
||||||
}
|
|
||||||
if prevSibling == nil || prevSibling.Type() != ast.NodeTypeBlockquote {
|
|
||||||
r.output.WriteString("</blockquote>")
|
|
||||||
}
|
|
||||||
case *ast.BoldItalic:
|
|
||||||
r.output.WriteString("<strong><em>")
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString("</em></strong>")
|
|
||||||
case *ast.Bold:
|
|
||||||
r.output.WriteString("<strong>")
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString("</strong>")
|
|
||||||
case *ast.Italic:
|
|
||||||
r.output.WriteString("<em>")
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString("</em>")
|
|
||||||
case *ast.Code:
|
|
||||||
r.output.WriteString("<code>")
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString("</code>")
|
|
||||||
case *ast.Link:
|
|
||||||
r.output.WriteString(`<a href="`)
|
|
||||||
r.output.WriteString(n.URL)
|
|
||||||
r.output.WriteString(`">`)
|
|
||||||
r.output.WriteString(n.Text)
|
|
||||||
r.output.WriteString("</a>")
|
|
||||||
case *ast.Image:
|
|
||||||
r.output.WriteString(`<img src="`)
|
|
||||||
r.output.WriteString(n.URL)
|
|
||||||
r.output.WriteString(`" alt="`)
|
|
||||||
r.output.WriteString(n.AltText)
|
|
||||||
r.output.WriteString(`" />`)
|
|
||||||
case *ast.Tag:
|
|
||||||
r.output.WriteString(`<span>`)
|
|
||||||
r.output.WriteString(`# `)
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString(`</span>`)
|
|
||||||
case *ast.Strikethrough:
|
|
||||||
r.output.WriteString(`<del>`)
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
r.output.WriteString(`</del>`)
|
|
||||||
case *ast.Text:
|
|
||||||
r.output.WriteString(n.Content)
|
|
||||||
default:
|
|
||||||
// Handle other block types if needed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderNodes renders a slice of AST nodes to HTML.
|
|
||||||
func (r *HTMLRenderer) RenderNodes(nodes []ast.Node) {
|
|
||||||
for _, node := range nodes {
|
|
||||||
prevSibling := node.GetPrevSibling()
|
|
||||||
if prevSibling != nil {
|
|
||||||
if prevSibling.Type() == node.Type() {
|
|
||||||
if node.Type() == ast.NodeTypeBlockquote {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.RenderNode(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders the AST to HTML.
|
|
||||||
func (r *HTMLRenderer) Render(astRoot []ast.Node) string {
|
|
||||||
r.RenderNodes(astRoot)
|
|
||||||
return r.output.String()
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package renderer
|
|
Loading…
x
Reference in New Issue
Block a user