Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
5ad2038b
Commit
5ad2038b
authored
Sep 17, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update gomark dependency and refactor markdown parsing logic
parent
b7f792cb
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
76 additions
and
53 deletions
+76
-53
go.mod
go.mod
+1
-1
go.sum
go.sum
+2
-2
util.go
internal/util/util.go
+1
-1
build.sh
scripts/build.sh
+16
-13
markdown_service.go
server/router/api/v1/markdown_service.go
+24
-9
memo_service.go
server/router/api/v1/memo_service.go
+6
-8
memo_service_converter.go
server/router/api/v1/memo_service_converter.go
+5
-4
rss.go
server/router/rss/rss.go
+2
-2
runner.go
server/runner/memopayload/runner.go
+19
-13
No files found.
go.mod
View file @
5ad2038b
...
...
@@ -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-20250
328014447-c9fa41c01bc4
github.com/usememos/gomark v0.0.0-20250
917125604-82623ecaf218
golang.org/x/crypto v0.41.0
golang.org/x/mod v0.27.0
golang.org/x/net v0.43.0
...
...
go.sum
View file @
5ad2038b
...
...
@@ -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-20250
328014447-c9fa41c01bc4 h1:WUVmhqDHt+5nhHGnsdfZ8no8zdwhKLPQ5AT/IP57egI
=
github.com/usememos/gomark v0.0.0-20250
328014447-c9fa41c01bc4
/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
github.com/usememos/gomark v0.0.0-20250
917125604-82623ecaf218 h1:rkH9CKI0AzWOIkwzZOs0PYepWt9fJTZZ9c5W1lL/bMQ
=
github.com/usememos/gomark v0.0.0-20250
917125604-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=
...
...
internal/util/util.go
View file @
5ad2038b
package
util
package
util
//nolint:revive // util namespace is intentional for shared helpers
import
(
"crypto/rand"
...
...
scripts/build.sh
View file @
5ad2038b
#!/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"
server/router/api/v1/markdown_service.go
View file @
5ad2038b
...
...
@@ -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
:=
convertFromAST
Nodes
(
rawNodes
)
nodes
:=
convertFromAST
Document
(
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
.
Render
Document
(
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
),
...
...
server/router/api/v1/memo_service.go
View file @
5ad2038b
...
...
@@ -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
.
TraverseAST
Nodes
(
nodes
,
func
(
node
ast
.
Node
)
{
memopayload
.
TraverseAST
Document
(
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
()
.
Render
Document
(
doc
)
if
len
(
plainText
)
>
64
{
return
substring
(
plainText
,
64
)
+
"..."
,
nil
}
...
...
server/router/api/v1/memo_service_converter.go
View file @
5ad2038b
...
...
@@ -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
{
...
...
server/router/rss/rss.go
View file @
5ad2038b
...
...
@@ -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
()
.
Render
Document
(
doc
)
return
result
,
nil
}
...
...
server/runner/memopayload/runner.go
View file @
5ad2038b
...
...
@@ -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
{}
TraverseAST
Nodes
(
nodes
,
func
(
node
ast
.
Node
)
{
TraverseAST
Document
(
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
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Heading
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Blockquote
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
List
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
OrderedListItem
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
UnorderedListItem
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
TaskListItem
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
case
*
ast
.
Bold
:
T
raverseASTNodes
(
n
.
Children
,
fn
)
t
raverseASTNodes
(
n
.
Children
,
fn
)
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment