From 5ad2038b1a7a93445b39f99557ec6136b2c55bc5 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 17 Sep 2025 21:09:30 +0800 Subject: [PATCH] feat: update gomark dependency and refactor markdown parsing logic --- go.mod | 2 +- go.sum | 4 +-- internal/util/util.go | 2 +- scripts/build.sh | 29 ++++++++-------- server/router/api/v1/markdown_service.go | 33 ++++++++++++++----- server/router/api/v1/memo_service.go | 14 ++++---- .../router/api/v1/memo_service_converter.go | 9 ++--- server/router/rss/rss.go | 4 +-- server/runner/memopayload/runner.go | 32 ++++++++++-------- 9 files changed, 76 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 30ee57329..25b3e0dbe 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/usememos/gomark v0.0.0-20250328014447-c9fa41c01bc4 + github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218 golang.org/x/crypto v0.41.0 golang.org/x/mod v0.27.0 golang.org/x/net v0.43.0 diff --git a/go.sum b/go.sum index 40b7f9edf..b831664f6 100644 --- a/go.sum +++ b/go.sum @@ -433,8 +433,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/usememos/gomark v0.0.0-20250328014447-c9fa41c01bc4 h1:WUVmhqDHt+5nhHGnsdfZ8no8zdwhKLPQ5AT/IP57egI= -github.com/usememos/gomark v0.0.0-20250328014447-c9fa41c01bc4/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= +github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218 h1:rkH9CKI0AzWOIkwzZOs0PYepWt9fJTZZ9c5W1lL/bMQ= +github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/internal/util/util.go b/internal/util/util.go index 6a2e84c9e..cdb211fb6 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,4 +1,4 @@ -package util +package util //nolint:revive // util namespace is intentional for shared helpers import ( "crypto/rand" diff --git a/scripts/build.sh b/scripts/build.sh index 5340941a7..832813342 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,29 +1,32 @@ #!/bin/sh -# Exit when any command fails set -e -# Get the script directory and change to the project root +# Change to repo root cd "$(dirname "$0")/../" -# Detect the operating system OS=$(uname -s) -# Set output file name based on the OS -if [[ "$OS" == *"CYGWIN"* || "$OS" == *"MINGW"* || "$OS" == *"MSYS"* ]]; then - OUTPUT="./build/memos.exe" -else - OUTPUT="./build/memos" -fi +# Determine output binary name +case "$OS" in + *CYGWIN*|*MINGW*|*MSYS*) + OUTPUT="./build/memos.exe" + ;; + *) + OUTPUT="./build/memos" + ;; +esac echo "Building for $OS..." +# Ensure build directories exist and configure a writable Go build cache +mkdir -p ./build/.gocache ./build/.gomodcache +export GOCACHE="$(pwd)/build/.gocache" +export GOMODCACHE="$(pwd)/build/.gomodcache" + # Build the executable -go build -o "$OUTPUT" ./bin/memos/main.go +go build -o "$OUTPUT" ./bin/memos -# Output the success message echo "Build successful!" - -# Output the command to run echo "To run the application, execute the following command:" echo "$OUTPUT --mode dev" diff --git a/server/router/api/v1/markdown_service.go b/server/router/api/v1/markdown_service.go index 96eb7930d..85585b6fe 100644 --- a/server/router/api/v1/markdown_service.go +++ b/server/router/api/v1/markdown_service.go @@ -4,30 +4,28 @@ import ( "context" "github.com/pkg/errors" + "github.com/usememos/gomark" "github.com/usememos/gomark/ast" - "github.com/usememos/gomark/parser" - "github.com/usememos/gomark/parser/tokenizer" "github.com/usememos/gomark/renderer" - "github.com/usememos/gomark/restore" "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) { - rawNodes, err := parser.Parse(tokenizer.Tokenize(request.Markdown)) + doc, err := gomark.Parse(request.Markdown) if err != nil { return nil, errors.Wrap(err, "failed to parse memo content") } - nodes := convertFromASTNodes(rawNodes) + nodes := convertFromASTDocument(doc) return &v1pb.ParseMarkdownResponse{ Nodes: nodes, }, nil } func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.RestoreMarkdownNodesRequest) (*v1pb.RestoreMarkdownNodesResponse, error) { - markdown := restore.Restore(convertToASTNodes(request.Nodes)) + markdown := gomark.Restore(convertToASTDocument(request.Nodes)) return &v1pb.RestoreMarkdownNodesResponse{ Markdown: markdown, }, nil @@ -35,7 +33,7 @@ func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.Resto func (*APIV1Service) StringifyMarkdownNodes(_ context.Context, request *v1pb.StringifyMarkdownNodesRequest) (*v1pb.StringifyMarkdownNodesResponse, error) { stringRenderer := renderer.NewStringRenderer() - plainText := stringRenderer.Render(convertToASTNodes(request.Nodes)) + plainText := stringRenderer.RenderDocument(convertToASTDocument(request.Nodes)) return &v1pb.StringifyMarkdownNodesResponse{ PlainText: plainText, }, nil @@ -100,7 +98,9 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node { case *ast.Italic: node.Node = &v1pb.Node_ItalicNode{ItalicNode: &v1pb.ItalicNode{Symbol: n.Symbol, Children: convertFromASTNodes(n.Children)}} case *ast.BoldItalic: - node.Node = &v1pb.Node_BoldItalicNode{BoldItalicNode: &v1pb.BoldItalicNode{Symbol: n.Symbol, Content: n.Content}} + 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: @@ -144,6 +144,13 @@ func convertFromASTNodes(rawNodes []ast.Node) []*v1pb.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), @@ -210,7 +217,11 @@ func convertToASTNode(node *v1pb.Node) ast.Node { case *v1pb.Node_ItalicNode: return &ast.Italic{Symbol: n.ItalicNode.Symbol, Children: convertToASTNodes(n.ItalicNode.Children)} case *v1pb.Node_BoldItalicNode: - return &ast.BoldItalic{Symbol: n.BoldItalicNode.Symbol, Content: n.BoldItalicNode.Content} + 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: @@ -253,6 +264,10 @@ func convertToASTNodes(nodes []*v1pb.Node) []ast.Node { 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), diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index 57f6bfab8..244059e60 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -10,11 +10,9 @@ import ( "github.com/lithammer/shortuuid/v4" "github.com/pkg/errors" + "github.com/usememos/gomark" "github.com/usememos/gomark/ast" - "github.com/usememos/gomark/parser" - "github.com/usememos/gomark/parser/tokenizer" "github.com/usememos/gomark/renderer" - "github.com/usememos/gomark/restore" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" @@ -711,16 +709,16 @@ func (s *APIV1Service) RenameMemoTag(ctx context.Context, request *v1pb.RenameMe } for _, memo := range memos { - nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content)) + doc, err := gomark.Parse(memo.Content) if err != nil { return nil, status.Errorf(codes.Internal, "failed to parse memo: %v", err) } - memopayload.TraverseASTNodes(nodes, func(node ast.Node) { + memopayload.TraverseASTDocument(doc, func(node ast.Node) { if tag, ok := node.(*ast.Tag); ok && tag.Content == request.OldTag { tag.Content = request.NewTag } }) - memo.Content = restore.Restore(nodes) + memo.Content = gomark.Restore(doc) if err := memopayload.RebuildMemoPayload(memo); err != nil { return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err) } @@ -840,12 +838,12 @@ func convertMemoToWebhookPayload(memo *v1pb.Memo) (*webhook.WebhookRequestPayloa } func getMemoContentSnippet(content string) (string, error) { - nodes, err := parser.Parse(tokenizer.Tokenize(content)) + doc, err := gomark.Parse(content) if err != nil { return "", errors.Wrap(err, "failed to parse content") } - plainText := renderer.NewStringRenderer().Render(nodes) + plainText := renderer.NewStringRenderer().RenderDocument(doc) if len(plainText) > 64 { return substring(plainText, 64) + "...", nil } diff --git a/server/router/api/v1/memo_service_converter.go b/server/router/api/v1/memo_service_converter.go index cca1eef92..cc423cc81 100644 --- a/server/router/api/v1/memo_service_converter.go +++ b/server/router/api/v1/memo_service_converter.go @@ -8,8 +8,7 @@ import ( "github.com/pkg/errors" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/usememos/gomark/parser" - "github.com/usememos/gomark/parser/tokenizer" + "github.com/usememos/gomark" v1pb "github.com/usememos/memos/proto/gen/api/v1" storepb "github.com/usememos/memos/proto/gen/store" @@ -69,11 +68,13 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem memoMessage.Attachments = append(memoMessage.Attachments, attachmentResponse) } - nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content)) + doc, err := gomark.Parse(memo.Content) if err != nil { return nil, errors.Wrap(err, "failed to parse content") } - memoMessage.Nodes = convertFromASTNodes(nodes) + if doc != nil { + memoMessage.Nodes = convertFromASTNodes(doc.Children) + } snippet, err := getMemoContentSnippet(memo.Content) if err != nil { diff --git a/server/router/rss/rss.go b/server/router/rss/rss.go index 42b42aa41..a6d30a5a8 100644 --- a/server/router/rss/rss.go +++ b/server/router/rss/rss.go @@ -152,11 +152,11 @@ func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*st } func getRSSItemDescription(content string) (string, error) { - nodes, err := gomark.Parse(content) + doc, err := gomark.Parse(content) if err != nil { return "", err } - result := renderer.NewHTMLRenderer().Render(nodes) + result := renderer.NewHTMLRenderer().RenderDocument(doc) return result, nil } diff --git a/server/runner/memopayload/runner.go b/server/runner/memopayload/runner.go index 141110d1d..59d935cc2 100644 --- a/server/runner/memopayload/runner.go +++ b/server/runner/memopayload/runner.go @@ -6,9 +6,8 @@ import ( "slices" "github.com/pkg/errors" + "github.com/usememos/gomark" "github.com/usememos/gomark/ast" - "github.com/usememos/gomark/parser" - "github.com/usememos/gomark/parser/tokenizer" storepb "github.com/usememos/memos/proto/gen/store" "github.com/usememos/memos/store" @@ -73,7 +72,7 @@ func (r *Runner) RunOnce(ctx context.Context) { } func RebuildMemoPayload(memo *store.Memo) error { - nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content)) + doc, err := gomark.Parse(memo.Content) if err != nil { return errors.Wrap(err, "failed to parse content") } @@ -83,7 +82,7 @@ func RebuildMemoPayload(memo *store.Memo) error { } tags := []string{} property := &storepb.MemoPayload_Property{} - TraverseASTNodes(nodes, func(node ast.Node) { + TraverseASTDocument(doc, func(node ast.Node) { switch n := node.(type) { case *ast.Tag: tag := n.Content @@ -109,26 +108,33 @@ func RebuildMemoPayload(memo *store.Memo) error { return nil } -func TraverseASTNodes(nodes []ast.Node, fn func(ast.Node)) { +func TraverseASTDocument(doc *ast.Document, fn func(ast.Node)) { + if doc == nil { + return + } + traverseASTNodes(doc.Children, fn) +} + +func traverseASTNodes(nodes []ast.Node, fn func(ast.Node)) { for _, node := range nodes { fn(node) switch n := node.(type) { case *ast.Paragraph: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.Heading: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.Blockquote: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.List: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.OrderedListItem: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.UnorderedListItem: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.TaskListItem: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) case *ast.Bold: - TraverseASTNodes(n.Children, fn) + traverseASTNodes(n.Children, fn) } } }