diff --git a/api/v1/rss.go b/api/v1/rss.go
index 33c225c2..ed6c8270 100644
--- a/api/v1/rss.go
+++ b/api/v1/rss.go
@@ -15,7 +15,7 @@ import (
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/gomark/parser"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
- "github.com/usememos/memos/plugin/gomark/render/html"
+ "github.com/usememos/memos/plugin/gomark/render"
"github.com/usememos/memos/store"
)
@@ -198,7 +198,7 @@ func getRSSItemDescription(content string) (string, error) {
if err != nil {
return "", err
}
- result := html.NewHTMLRender().Render(nodes)
+ result := render.NewHTMLRender().Render(nodes)
return result, nil
}
diff --git a/plugin/gomark/render/renderer.go b/plugin/gomark/render/renderer.go
index da4cad77..065236e1 100644
--- a/plugin/gomark/render/renderer.go
+++ b/plugin/gomark/render/renderer.go
@@ -1 +1,14 @@
package render
+
+import (
+ htmlrender "github.com/usememos/memos/plugin/gomark/render/html"
+ stringrender "github.com/usememos/memos/plugin/gomark/render/string"
+)
+
+func NewHTMLRender() *htmlrender.HTMLRender {
+ return htmlrender.NewHTMLRender()
+}
+
+func NewStringRender() *stringrender.StringRender {
+ return stringrender.NewStringRender()
+}
diff --git a/plugin/gomark/render/string/string.go b/plugin/gomark/render/string/string.go
new file mode 100644
index 00000000..72f817d2
--- /dev/null
+++ b/plugin/gomark/render/string/string.go
@@ -0,0 +1,169 @@
+package string
+
+import (
+ "bytes"
+
+ "github.com/usememos/memos/plugin/gomark/ast"
+)
+
+// StringRender renders AST to raw string.
+type StringRender struct {
+ output *bytes.Buffer
+ context *RendererContext
+}
+
+type RendererContext struct {
+}
+
+// NewStringRender creates a new StringRender.
+func NewStringRender() *StringRender {
+ return &StringRender{
+ output: new(bytes.Buffer),
+ context: &RendererContext{},
+ }
+}
+
+// RenderNode renders a single AST node to raw string.
+func (r *StringRender) 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.UnorderedList:
+ r.renderUnorderedList(n)
+ case *ast.OrderedList:
+ r.renderOrderedList(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 raw string.
+func (r *StringRender) RenderNodes(nodes []ast.Node) {
+ for _, node := range nodes {
+ r.RenderNode(node)
+ }
+}
+
+// Render renders the AST to raw string.
+func (r *StringRender) Render(astRoot []ast.Node) string {
+ r.RenderNodes(astRoot)
+ return r.output.String()
+}
+
+func (r *StringRender) renderLineBreak(_ *ast.LineBreak) {
+ r.output.WriteString("\n")
+}
+
+func (r *StringRender) renderParagraph(node *ast.Paragraph) {
+ r.RenderNodes(node.Children)
+}
+
+func (r *StringRender) renderCodeBlock(node *ast.CodeBlock) {
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderHeading(node *ast.Heading) {
+ r.RenderNodes(node.Children)
+ r.output.WriteString("\n")
+}
+
+func (r *StringRender) renderHorizontalRule(_ *ast.HorizontalRule) {
+ r.output.WriteString("\n---\n")
+}
+
+func (r *StringRender) renderBlockquote(node *ast.Blockquote) {
+ r.output.WriteString("\n")
+ r.RenderNodes(node.Children)
+ r.output.WriteString("\n")
+}
+
+func (r *StringRender) renderUnorderedList(node *ast.UnorderedList) {
+ prevSibling, nextSibling := node.PrevSibling(), node.NextSibling()
+ if prevSibling == nil || prevSibling.Type() != ast.UnorderedListNode {
+ r.output.WriteString("\n")
+ }
+ r.output.WriteString("* ")
+ r.RenderNodes(node.Children)
+ if nextSibling == nil || nextSibling.Type() != ast.UnorderedListNode {
+ r.output.WriteString("\n")
+ }
+}
+
+func (r *StringRender) renderOrderedList(node *ast.OrderedList) {
+ prevSibling, nextSibling := node.PrevSibling(), node.NextSibling()
+ if prevSibling == nil || prevSibling.Type() != ast.OrderedListNode {
+ r.output.WriteString("\n")
+ }
+ r.output.WriteString("1. ")
+ r.RenderNodes(node.Children)
+ if nextSibling == nil || nextSibling.Type() != ast.OrderedListNode {
+ r.output.WriteString("\n")
+ }
+}
+
+func (r *StringRender) renderText(node *ast.Text) {
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderBold(node *ast.Bold) {
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderItalic(node *ast.Italic) {
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderBoldItalic(node *ast.BoldItalic) {
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderCode(node *ast.Code) {
+ r.output.WriteString("`")
+ r.output.WriteString(node.Content)
+ r.output.WriteString("`")
+}
+
+func (*StringRender) renderImage(*ast.Image) {
+ // Do nothing.
+}
+
+func (*StringRender) renderLink(*ast.Link) {
+ // Do nothing.
+}
+
+func (r *StringRender) renderTag(node *ast.Tag) {
+ r.output.WriteString(`#`)
+ r.output.WriteString(node.Content)
+}
+
+func (r *StringRender) renderStrikethrough(node *ast.Strikethrough) {
+ r.output.WriteString(node.Content)
+}
diff --git a/plugin/gomark/render/string/string_test.go b/plugin/gomark/render/string/string_test.go
new file mode 100644
index 00000000..e80f8c63
--- /dev/null
+++ b/plugin/gomark/render/string/string_test.go
@@ -0,0 +1,34 @@
+package string
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/usememos/memos/plugin/gomark/parser"
+ "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
+)
+
+func TestStringRender(t *testing.T) {
+ tests := []struct {
+ text string
+ expected string
+ }{
+ {
+ text: "Hello world!",
+ expected: `Hello world!`,
+ },
+ {
+ text: "**Hello** world!",
+ expected: `Hello world!`,
+ },
+ }
+
+ for _, test := range tests {
+ tokens := tokenizer.Tokenize(test.text)
+ nodes, err := parser.Parse(tokens)
+ require.NoError(t, err)
+ actual := NewStringRender().Render(nodes)
+ require.Equal(t, test.expected, actual)
+ }
+}
diff --git a/server/frontend/frontend.go b/server/frontend/frontend.go
index 039125f4..d1165dc5 100644
--- a/server/frontend/frontend.go
+++ b/server/frontend/frontend.go
@@ -12,6 +12,9 @@ import (
v1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/internal/util"
+ "github.com/usememos/memos/plugin/gomark/parser"
+ "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
+ "github.com/usememos/memos/plugin/gomark/render"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
)
@@ -151,12 +154,23 @@ Sitemap: %s/sitemap.xml`, instanceURL, instanceURL)
}
func generateMemoMetadata(memo *store.Memo, creator *store.User) string {
- description := memo.Content
+ description := ""
if memo.Visibility == store.Private {
description = "This memo is private."
} else if memo.Visibility == store.Protected {
description = "This memo is protected."
+ } else {
+ tokens := tokenizer.Tokenize(memo.Content)
+ nodes, _ := parser.Parse(tokens)
+ description = render.NewStringRender().Render(nodes)
+ if len(description) == 0 {
+ description = memo.Content
+ }
+ if len(description) > 100 {
+ description = description[:100] + "..."
+ }
}
+
metadataList := []string{
fmt.Sprintf(``, description),
fmt.Sprintf(``, fmt.Sprintf("%s(@%s) on Memos", creator.Nickname, creator.Username)),