mirror of
https://github.com/usememos/memos.git
synced 2025-10-26 06:16:02 +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/cobra v1.10.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/stretchr/testify v1.10.0
|
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/crypto v0.41.0
|
||||||
golang.org/x/mod v0.27.0
|
golang.org/x/mod v0.27.0
|
||||||
golang.org/x/net v0.43.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/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.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
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-20250917125604-82623ecaf218 h1:rkH9CKI0AzWOIkwzZOs0PYepWt9fJTZZ9c5W1lL/bMQ=
|
||||||
github.com/usememos/gomark v0.0.0-20250328014447-c9fa41c01bc4/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
|
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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
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 (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Exit when any command fails
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Get the script directory and change to the project root
|
# Change to repo root
|
||||||
cd "$(dirname "$0")/../"
|
cd "$(dirname "$0")/../"
|
||||||
|
|
||||||
# Detect the operating system
|
|
||||||
OS=$(uname -s)
|
OS=$(uname -s)
|
||||||
|
|
||||||
# Set output file name based on the OS
|
# Determine output binary name
|
||||||
if [[ "$OS" == *"CYGWIN"* || "$OS" == *"MINGW"* || "$OS" == *"MSYS"* ]]; then
|
case "$OS" in
|
||||||
OUTPUT="./build/memos.exe"
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
else
|
OUTPUT="./build/memos.exe"
|
||||||
OUTPUT="./build/memos"
|
;;
|
||||||
fi
|
*)
|
||||||
|
OUTPUT="./build/memos"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo "Building for $OS..."
|
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
|
# Build the executable
|
||||||
go build -o "$OUTPUT" ./bin/memos/main.go
|
go build -o "$OUTPUT" ./bin/memos
|
||||||
|
|
||||||
# Output the success message
|
|
||||||
echo "Build successful!"
|
echo "Build successful!"
|
||||||
|
|
||||||
# Output the command to run
|
|
||||||
echo "To run the application, execute the following command:"
|
echo "To run the application, execute the following command:"
|
||||||
echo "$OUTPUT --mode dev"
|
echo "$OUTPUT --mode dev"
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,28 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/usememos/gomark"
|
||||||
"github.com/usememos/gomark/ast"
|
"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/renderer"
|
||||||
"github.com/usememos/gomark/restore"
|
|
||||||
|
|
||||||
"github.com/usememos/memos/plugin/httpgetter"
|
"github.com/usememos/memos/plugin/httpgetter"
|
||||||
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (*APIV1Service) ParseMarkdown(_ context.Context, request *v1pb.ParseMarkdownRequest) (*v1pb.ParseMarkdownResponse, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse memo content")
|
return nil, errors.Wrap(err, "failed to parse memo content")
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes := convertFromASTNodes(rawNodes)
|
nodes := convertFromASTDocument(doc)
|
||||||
return &v1pb.ParseMarkdownResponse{
|
return &v1pb.ParseMarkdownResponse{
|
||||||
Nodes: nodes,
|
Nodes: nodes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.RestoreMarkdownNodesRequest) (*v1pb.RestoreMarkdownNodesResponse, error) {
|
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{
|
return &v1pb.RestoreMarkdownNodesResponse{
|
||||||
Markdown: markdown,
|
Markdown: markdown,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -35,7 +33,7 @@ func (*APIV1Service) RestoreMarkdownNodes(_ context.Context, request *v1pb.Resto
|
||||||
|
|
||||||
func (*APIV1Service) StringifyMarkdownNodes(_ context.Context, request *v1pb.StringifyMarkdownNodesRequest) (*v1pb.StringifyMarkdownNodesResponse, error) {
|
func (*APIV1Service) StringifyMarkdownNodes(_ context.Context, request *v1pb.StringifyMarkdownNodesRequest) (*v1pb.StringifyMarkdownNodesResponse, error) {
|
||||||
stringRenderer := renderer.NewStringRenderer()
|
stringRenderer := renderer.NewStringRenderer()
|
||||||
plainText := stringRenderer.Render(convertToASTNodes(request.Nodes))
|
plainText := stringRenderer.RenderDocument(convertToASTDocument(request.Nodes))
|
||||||
return &v1pb.StringifyMarkdownNodesResponse{
|
return &v1pb.StringifyMarkdownNodesResponse{
|
||||||
PlainText: plainText,
|
PlainText: plainText,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -100,7 +98,9 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node {
|
||||||
case *ast.Italic:
|
case *ast.Italic:
|
||||||
node.Node = &v1pb.Node_ItalicNode{ItalicNode: &v1pb.ItalicNode{Symbol: n.Symbol, Children: convertFromASTNodes(n.Children)}}
|
node.Node = &v1pb.Node_ItalicNode{ItalicNode: &v1pb.ItalicNode{Symbol: n.Symbol, Children: convertFromASTNodes(n.Children)}}
|
||||||
case *ast.BoldItalic:
|
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:
|
case *ast.Code:
|
||||||
node.Node = &v1pb.Node_CodeNode{CodeNode: &v1pb.CodeNode{Content: n.Content}}
|
node.Node = &v1pb.Node_CodeNode{CodeNode: &v1pb.CodeNode{Content: n.Content}}
|
||||||
case *ast.Image:
|
case *ast.Image:
|
||||||
|
|
@ -144,6 +144,13 @@ func convertFromASTNodes(rawNodes []ast.Node) []*v1pb.Node {
|
||||||
return nodes
|
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 {
|
func convertTableFromASTNode(node *ast.Table) *v1pb.TableNode {
|
||||||
table := &v1pb.TableNode{
|
table := &v1pb.TableNode{
|
||||||
Header: convertFromASTNodes(node.Header),
|
Header: convertFromASTNodes(node.Header),
|
||||||
|
|
@ -210,7 +217,11 @@ func convertToASTNode(node *v1pb.Node) ast.Node {
|
||||||
case *v1pb.Node_ItalicNode:
|
case *v1pb.Node_ItalicNode:
|
||||||
return &ast.Italic{Symbol: n.ItalicNode.Symbol, Children: convertToASTNodes(n.ItalicNode.Children)}
|
return &ast.Italic{Symbol: n.ItalicNode.Symbol, Children: convertToASTNodes(n.ItalicNode.Children)}
|
||||||
case *v1pb.Node_BoldItalicNode:
|
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:
|
case *v1pb.Node_CodeNode:
|
||||||
return &ast.Code{Content: n.CodeNode.Content}
|
return &ast.Code{Content: n.CodeNode.Content}
|
||||||
case *v1pb.Node_ImageNode:
|
case *v1pb.Node_ImageNode:
|
||||||
|
|
@ -253,6 +264,10 @@ func convertToASTNodes(nodes []*v1pb.Node) []ast.Node {
|
||||||
return rawNodes
|
return rawNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToASTDocument(nodes []*v1pb.Node) *ast.Document {
|
||||||
|
return &ast.Document{Children: convertToASTNodes(nodes)}
|
||||||
|
}
|
||||||
|
|
||||||
func convertTableToASTNode(node *v1pb.TableNode) *ast.Table {
|
func convertTableToASTNode(node *v1pb.TableNode) *ast.Table {
|
||||||
table := &ast.Table{
|
table := &ast.Table{
|
||||||
Header: convertToASTNodes(node.Header),
|
Header: convertToASTNodes(node.Header),
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@ import (
|
||||||
|
|
||||||
"github.com/lithammer/shortuuid/v4"
|
"github.com/lithammer/shortuuid/v4"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/usememos/gomark"
|
||||||
"github.com/usememos/gomark/ast"
|
"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/renderer"
|
||||||
"github.com/usememos/gomark/restore"
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"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 {
|
for _, memo := range memos {
|
||||||
nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content))
|
doc, err := gomark.Parse(memo.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to parse memo: %v", err)
|
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 {
|
if tag, ok := node.(*ast.Tag); ok && tag.Content == request.OldTag {
|
||||||
tag.Content = request.NewTag
|
tag.Content = request.NewTag
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
memo.Content = restore.Restore(nodes)
|
memo.Content = gomark.Restore(doc)
|
||||||
if err := memopayload.RebuildMemoPayload(memo); err != nil {
|
if err := memopayload.RebuildMemoPayload(memo); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err)
|
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) {
|
func getMemoContentSnippet(content string) (string, error) {
|
||||||
nodes, err := parser.Parse(tokenizer.Tokenize(content))
|
doc, err := gomark.Parse(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to parse content")
|
return "", errors.Wrap(err, "failed to parse content")
|
||||||
}
|
}
|
||||||
|
|
||||||
plainText := renderer.NewStringRenderer().Render(nodes)
|
plainText := renderer.NewStringRenderer().RenderDocument(doc)
|
||||||
if len(plainText) > 64 {
|
if len(plainText) > 64 {
|
||||||
return substring(plainText, 64) + "...", nil
|
return substring(plainText, 64) + "...", nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/usememos/gomark/parser"
|
"github.com/usememos/gomark"
|
||||||
"github.com/usememos/gomark/parser/tokenizer"
|
|
||||||
|
|
||||||
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
||||||
storepb "github.com/usememos/memos/proto/gen/store"
|
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)
|
memoMessage.Attachments = append(memoMessage.Attachments, attachmentResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content))
|
doc, err := gomark.Parse(memo.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse content")
|
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)
|
snippet, err := getMemoContentSnippet(memo.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,11 @@ func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*st
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRSSItemDescription(content string) (string, error) {
|
func getRSSItemDescription(content string) (string, error) {
|
||||||
nodes, err := gomark.Parse(content)
|
doc, err := gomark.Parse(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
result := renderer.NewHTMLRenderer().Render(nodes)
|
result := renderer.NewHTMLRenderer().RenderDocument(doc)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/usememos/gomark"
|
||||||
"github.com/usememos/gomark/ast"
|
"github.com/usememos/gomark/ast"
|
||||||
"github.com/usememos/gomark/parser"
|
|
||||||
"github.com/usememos/gomark/parser/tokenizer"
|
|
||||||
|
|
||||||
storepb "github.com/usememos/memos/proto/gen/store"
|
storepb "github.com/usememos/memos/proto/gen/store"
|
||||||
"github.com/usememos/memos/store"
|
"github.com/usememos/memos/store"
|
||||||
|
|
@ -73,7 +72,7 @@ func (r *Runner) RunOnce(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RebuildMemoPayload(memo *store.Memo) error {
|
func RebuildMemoPayload(memo *store.Memo) error {
|
||||||
nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content))
|
doc, err := gomark.Parse(memo.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to parse content")
|
return errors.Wrap(err, "failed to parse content")
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +82,7 @@ func RebuildMemoPayload(memo *store.Memo) error {
|
||||||
}
|
}
|
||||||
tags := []string{}
|
tags := []string{}
|
||||||
property := &storepb.MemoPayload_Property{}
|
property := &storepb.MemoPayload_Property{}
|
||||||
TraverseASTNodes(nodes, func(node ast.Node) {
|
TraverseASTDocument(doc, func(node ast.Node) {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.Tag:
|
case *ast.Tag:
|
||||||
tag := n.Content
|
tag := n.Content
|
||||||
|
|
@ -109,26 +108,33 @@ func RebuildMemoPayload(memo *store.Memo) error {
|
||||||
return nil
|
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 {
|
for _, node := range nodes {
|
||||||
fn(node)
|
fn(node)
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case *ast.Paragraph:
|
case *ast.Paragraph:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.Heading:
|
case *ast.Heading:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.Blockquote:
|
case *ast.Blockquote:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.List:
|
case *ast.List:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.OrderedListItem:
|
case *ast.OrderedListItem:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.UnorderedListItem:
|
case *ast.UnorderedListItem:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.TaskListItem:
|
case *ast.TaskListItem:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
case *ast.Bold:
|
case *ast.Bold:
|
||||||
TraverseASTNodes(n.Children, fn)
|
traverseASTNodes(n.Children, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue