mirror of
https://github.com/usememos/memos.git
synced 2025-10-21 19:58:06 +08:00
feat: update gomark dependency and refactor markdown parsing logic
This commit is contained in:
parent
b7f792cbf7
commit
5ad2038b1a
9 changed files with 76 additions and 53 deletions
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package util
|
||||
package util //nolint:revive // util namespace is intentional for shared helpers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue