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
c8d7f93d
Commit
c8d7f93d
authored
Dec 27, 2023
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement auto link parser
parent
6fac116d
Changes
14
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
418 additions
and
179 deletions
+418
-179
markdown_service.go
api/v2/markdown_service.go
+2
-0
ast.go
plugin/gomark/ast/ast.go
+3
-0
inline.go
plugin/gomark/ast/inline.go
+10
-0
auto_link.go
plugin/gomark/parser/auto_link.go
+50
-0
auto_link_test.go
plugin/gomark/parser/auto_link_test.go
+33
-0
parser.go
plugin/gomark/parser/parser.go
+1
-0
parser_test.go
plugin/gomark/parser/parser_test.go
+2
-0
tokenizer.go
plugin/gomark/parser/tokenizer/tokenizer.go
+3
-0
markdown_service.proto
proto/api/v2/markdown_service.proto
+12
-6
README.md
proto/gen/api/v2/README.md
+21
-3
markdown_service.pb.go
proto/gen/api/v2/markdown_service.pb.go
+255
-169
AutoLink.tsx
web/src/components/MemoContent/AutoLink.tsx
+17
-0
Link.tsx
web/src/components/MemoContent/Link.tsx
+5
-1
Renderer.tsx
web/src/components/MemoContent/Renderer.tsx
+4
-0
No files found.
api/v2/markdown_service.go
View file @
c8d7f93d
...
@@ -76,6 +76,8 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
...
@@ -76,6 +76,8 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
node
.
Node
=
&
apiv2pb
.
Node_ImageNode
{
ImageNode
:
&
apiv2pb
.
ImageNode
{
AltText
:
n
.
AltText
,
Url
:
n
.
URL
}}
node
.
Node
=
&
apiv2pb
.
Node_ImageNode
{
ImageNode
:
&
apiv2pb
.
ImageNode
{
AltText
:
n
.
AltText
,
Url
:
n
.
URL
}}
case
*
ast
.
Link
:
case
*
ast
.
Link
:
node
.
Node
=
&
apiv2pb
.
Node_LinkNode
{
LinkNode
:
&
apiv2pb
.
LinkNode
{
Text
:
n
.
Text
,
Url
:
n
.
URL
}}
node
.
Node
=
&
apiv2pb
.
Node_LinkNode
{
LinkNode
:
&
apiv2pb
.
LinkNode
{
Text
:
n
.
Text
,
Url
:
n
.
URL
}}
case
*
ast
.
AutoLink
:
node
.
Node
=
&
apiv2pb
.
Node_AutoLinkNode
{
AutoLinkNode
:
&
apiv2pb
.
AutoLinkNode
{
Url
:
n
.
URL
}}
case
*
ast
.
Tag
:
case
*
ast
.
Tag
:
node
.
Node
=
&
apiv2pb
.
Node_TagNode
{
TagNode
:
&
apiv2pb
.
TagNode
{
Content
:
n
.
Content
}}
node
.
Node
=
&
apiv2pb
.
Node_TagNode
{
TagNode
:
&
apiv2pb
.
TagNode
{
Content
:
n
.
Content
}}
case
*
ast
.
Strikethrough
:
case
*
ast
.
Strikethrough
:
...
...
plugin/gomark/ast/ast.go
View file @
c8d7f93d
...
@@ -22,6 +22,7 @@ const (
...
@@ -22,6 +22,7 @@ const (
CodeNode
CodeNode
ImageNode
ImageNode
LinkNode
LinkNode
AutoLinkNode
TagNode
TagNode
StrikethroughNode
StrikethroughNode
EscapingCharacterNode
EscapingCharacterNode
...
@@ -61,6 +62,8 @@ func (t NodeType) String() string {
...
@@ -61,6 +62,8 @@ func (t NodeType) String() string {
return
"ImageNode"
return
"ImageNode"
case
LinkNode
:
case
LinkNode
:
return
"LinkNode"
return
"LinkNode"
case
AutoLinkNode
:
return
"AutoLinkNode"
case
TagNode
:
case
TagNode
:
return
"TagNode"
return
"TagNode"
case
StrikethroughNode
:
case
StrikethroughNode
:
...
...
plugin/gomark/ast/inline.go
View file @
c8d7f93d
...
@@ -82,6 +82,16 @@ func (*Link) Type() NodeType {
...
@@ -82,6 +82,16 @@ func (*Link) Type() NodeType {
return
LinkNode
return
LinkNode
}
}
type
AutoLink
struct
{
BaseInline
URL
string
}
func
(
*
AutoLink
)
Type
()
NodeType
{
return
AutoLinkNode
}
type
Tag
struct
{
type
Tag
struct
{
BaseInline
BaseInline
...
...
plugin/gomark/parser/auto_link.go
0 → 100644
View file @
c8d7f93d
package
parser
import
(
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type
AutoLinkParser
struct
{}
func
NewAutoLinkParser
()
*
AutoLinkParser
{
return
&
AutoLinkParser
{}
}
func
(
*
AutoLinkParser
)
Match
(
tokens
[]
*
tokenizer
.
Token
)
(
int
,
bool
)
{
if
len
(
tokens
)
<
3
{
return
0
,
false
}
if
tokens
[
0
]
.
Type
!=
tokenizer
.
LessThan
{
return
0
,
false
}
urlTokens
:=
[]
*
tokenizer
.
Token
{}
for
_
,
token
:=
range
tokens
[
1
:
]
{
if
token
.
Type
==
tokenizer
.
Newline
||
token
.
Type
==
tokenizer
.
Space
{
return
0
,
false
}
if
token
.
Type
==
tokenizer
.
GreaterThan
{
break
}
urlTokens
=
append
(
urlTokens
,
token
)
}
if
2
+
len
(
urlTokens
)
>
len
(
tokens
)
{
return
0
,
false
}
return
2
+
len
(
urlTokens
),
true
}
func
(
p
*
AutoLinkParser
)
Parse
(
tokens
[]
*
tokenizer
.
Token
)
(
ast
.
Node
,
error
)
{
size
,
ok
:=
p
.
Match
(
tokens
)
if
size
==
0
||
!
ok
{
return
nil
,
errors
.
New
(
"not matched"
)
}
urlTokens
:=
tokens
[
1
:
size
-
1
]
return
&
ast
.
AutoLink
{
URL
:
tokenizer
.
Stringify
(
urlTokens
),
},
nil
}
plugin/gomark/parser/auto_link_test.go
0 → 100644
View file @
c8d7f93d
package
parser
import
(
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func
TestAutoLinkParser
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
text
string
link
ast
.
Node
}{
{
text
:
"<https://example.com)"
,
link
:
nil
,
},
{
text
:
"<https://example.com>"
,
link
:
&
ast
.
AutoLink
{
URL
:
"https://example.com"
,
},
},
}
for
_
,
test
:=
range
tests
{
tokens
:=
tokenizer
.
Tokenize
(
test
.
text
)
node
,
_
:=
NewAutoLinkParser
()
.
Parse
(
tokens
)
require
.
Equal
(
t
,
StringifyNodes
([]
ast
.
Node
{
test
.
link
}),
StringifyNodes
([]
ast
.
Node
{
node
}))
}
}
plugin/gomark/parser/parser.go
View file @
c8d7f93d
...
@@ -76,6 +76,7 @@ var defaultInlineParsers = []InlineParser{
...
@@ -76,6 +76,7 @@ var defaultInlineParsers = []InlineParser{
NewBoldItalicParser
(),
NewBoldItalicParser
(),
NewImageParser
(),
NewImageParser
(),
NewLinkParser
(),
NewLinkParser
(),
NewAutoLinkParser
(),
NewBoldParser
(),
NewBoldParser
(),
NewItalicParser
(),
NewItalicParser
(),
NewCodeParser
(),
NewCodeParser
(),
...
...
plugin/gomark/parser/parser_test.go
View file @
c8d7f93d
...
@@ -250,6 +250,8 @@ func StringifyNode(node ast.Node) string {
...
@@ -250,6 +250,8 @@ func StringifyNode(node ast.Node) string {
return
"Image("
+
n
.
URL
+
", "
+
n
.
AltText
+
")"
return
"Image("
+
n
.
URL
+
", "
+
n
.
AltText
+
")"
case
*
ast
.
Link
:
case
*
ast
.
Link
:
return
"Link("
+
n
.
Text
+
", "
+
n
.
URL
+
")"
return
"Link("
+
n
.
Text
+
", "
+
n
.
URL
+
")"
case
*
ast
.
AutoLink
:
return
"AutoLink("
+
n
.
URL
+
")"
case
*
ast
.
Tag
:
case
*
ast
.
Tag
:
return
"Tag("
+
n
.
Content
+
")"
return
"Tag("
+
n
.
Content
+
")"
case
*
ast
.
Strikethrough
:
case
*
ast
.
Strikethrough
:
...
...
plugin/gomark/parser/tokenizer/tokenizer.go
View file @
c8d7f93d
...
@@ -16,6 +16,7 @@ const (
...
@@ -16,6 +16,7 @@ const (
Hyphen
TokenType
=
"-"
Hyphen
TokenType
=
"-"
PlusSign
TokenType
=
"+"
PlusSign
TokenType
=
"+"
Dot
TokenType
=
"."
Dot
TokenType
=
"."
LessThan
TokenType
=
"<"
GreaterThan
TokenType
=
">"
GreaterThan
TokenType
=
">"
Backslash
TokenType
=
"
\\
"
Backslash
TokenType
=
"
\\
"
Newline
TokenType
=
"
\n
"
Newline
TokenType
=
"
\n
"
...
@@ -65,6 +66,8 @@ func Tokenize(text string) []*Token {
...
@@ -65,6 +66,8 @@ func Tokenize(text string) []*Token {
tokens
=
append
(
tokens
,
NewToken
(
Tilde
,
"~"
))
tokens
=
append
(
tokens
,
NewToken
(
Tilde
,
"~"
))
case
'-'
:
case
'-'
:
tokens
=
append
(
tokens
,
NewToken
(
Hyphen
,
"-"
))
tokens
=
append
(
tokens
,
NewToken
(
Hyphen
,
"-"
))
case
'<'
:
tokens
=
append
(
tokens
,
NewToken
(
LessThan
,
"<"
))
case
'>'
:
case
'>'
:
tokens
=
append
(
tokens
,
NewToken
(
GreaterThan
,
">"
))
tokens
=
append
(
tokens
,
NewToken
(
GreaterThan
,
">"
))
case
'+'
:
case
'+'
:
...
...
proto/api/v2/markdown_service.proto
View file @
c8d7f93d
...
@@ -41,9 +41,10 @@ enum NodeType {
...
@@ -41,9 +41,10 @@ enum NodeType {
CODE
=
14
;
CODE
=
14
;
IMAGE
=
15
;
IMAGE
=
15
;
LINK
=
16
;
LINK
=
16
;
TAG
=
17
;
AUTO_LINK
=
17
;
STRIKETHROUGH
=
18
;
TAG
=
18
;
ESCAPING_CHARACTER
=
19
;
STRIKETHROUGH
=
19
;
ESCAPING_CHARACTER
=
20
;
}
}
message
Node
{
message
Node
{
...
@@ -65,9 +66,10 @@ message Node {
...
@@ -65,9 +66,10 @@ message Node {
CodeNode
code_node
=
15
;
CodeNode
code_node
=
15
;
ImageNode
image_node
=
16
;
ImageNode
image_node
=
16
;
LinkNode
link_node
=
17
;
LinkNode
link_node
=
17
;
TagNode
tag_node
=
18
;
AutoLinkNode
auto_link_node
=
18
;
StrikethroughNode
strikethrough_node
=
19
;
TagNode
tag_node
=
19
;
EscapingCharacterNode
escaping_character_node
=
20
;
StrikethroughNode
strikethrough_node
=
20
;
EscapingCharacterNode
escaping_character_node
=
21
;
}
}
}
}
...
@@ -144,6 +146,10 @@ message LinkNode {
...
@@ -144,6 +146,10 @@ message LinkNode {
string
url
=
2
;
string
url
=
2
;
}
}
message
AutoLinkNode
{
string
url
=
1
;
}
message
TagNode
{
message
TagNode
{
string
content
=
1
;
string
content
=
1
;
}
}
...
...
proto/gen/api/v2/README.md
View file @
c8d7f93d
...
@@ -66,6 +66,7 @@
...
@@ -66,6 +66,7 @@
-
[
InboxService
](
#memos-api-v2-InboxService
)
-
[
InboxService
](
#memos-api-v2-InboxService
)
-
[
api/v2/markdown_service.proto
](
#api_v2_markdown_service-proto
)
-
[
api/v2/markdown_service.proto
](
#api_v2_markdown_service-proto
)
-
[
AutoLinkNode
](
#memos-api-v2-AutoLinkNode
)
-
[
BlockquoteNode
](
#memos-api-v2-BlockquoteNode
)
-
[
BlockquoteNode
](
#memos-api-v2-BlockquoteNode
)
-
[
BoldItalicNode
](
#memos-api-v2-BoldItalicNode
)
-
[
BoldItalicNode
](
#memos-api-v2-BoldItalicNode
)
-
[
BoldNode
](
#memos-api-v2-BoldNode
)
-
[
BoldNode
](
#memos-api-v2-BoldNode
)
...
@@ -956,6 +957,21 @@
...
@@ -956,6 +957,21 @@
<a
name=
"memos-api-v2-AutoLinkNode"
></a>
### AutoLinkNode
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| url |
[
string
](
#string
)
| | |
<a
name=
"memos-api-v2-BlockquoteNode"
></a>
<a
name=
"memos-api-v2-BlockquoteNode"
></a>
### BlockquoteNode
### BlockquoteNode
...
@@ -1163,6 +1179,7 @@
...
@@ -1163,6 +1179,7 @@
| code_node |
[
CodeNode
](
#memos-api-v2-CodeNode
)
| | |
| code_node |
[
CodeNode
](
#memos-api-v2-CodeNode
)
| | |
| image_node |
[
ImageNode
](
#memos-api-v2-ImageNode
)
| | |
| image_node |
[
ImageNode
](
#memos-api-v2-ImageNode
)
| | |
| link_node |
[
LinkNode
](
#memos-api-v2-LinkNode
)
| | |
| link_node |
[
LinkNode
](
#memos-api-v2-LinkNode
)
| | |
| auto_link_node |
[
AutoLinkNode
](
#memos-api-v2-AutoLinkNode
)
| | |
| tag_node |
[
TagNode
](
#memos-api-v2-TagNode
)
| | |
| tag_node |
[
TagNode
](
#memos-api-v2-TagNode
)
| | |
| strikethrough_node |
[
StrikethroughNode
](
#memos-api-v2-StrikethroughNode
)
| | |
| strikethrough_node |
[
StrikethroughNode
](
#memos-api-v2-StrikethroughNode
)
| | |
| escaping_character_node |
[
EscapingCharacterNode
](
#memos-api-v2-EscapingCharacterNode
)
| | |
| escaping_character_node |
[
EscapingCharacterNode
](
#memos-api-v2-EscapingCharacterNode
)
| | |
...
@@ -1337,9 +1354,10 @@
...
@@ -1337,9 +1354,10 @@
| CODE | 14 | |
| CODE | 14 | |
| IMAGE | 15 | |
| IMAGE | 15 | |
| LINK | 16 | |
| LINK | 16 | |
| TAG | 17 | |
| AUTO_LINK | 17 | |
| STRIKETHROUGH | 18 | |
| TAG | 18 | |
| ESCAPING_CHARACTER | 19 | |
| STRIKETHROUGH | 19 | |
| ESCAPING_CHARACTER | 20 | |
...
...
proto/gen/api/v2/markdown_service.pb.go
View file @
c8d7f93d
This diff is collapsed.
Click to expand it.
web/src/components/MemoContent/AutoLink.tsx
0 → 100644
View file @
c8d7f93d
interface
Props
{
url
:
string
;
}
const
AutoLink
:
React
.
FC
<
Props
>
=
({
url
}:
Props
)
=>
{
return
(
<
a
className=
"text-blue-600 dark:text-blue-400 cursor-pointer underline break-all hover:opacity-80 decoration-1"
href=
{
url
}
target=
"_blank"
>
{
url
}
</
a
>
);
};
export
default
AutoLink
;
web/src/components/MemoContent/Link.tsx
View file @
c8d7f93d
...
@@ -5,7 +5,11 @@ interface Props {
...
@@ -5,7 +5,11 @@ interface Props {
const
Link
:
React
.
FC
<
Props
>
=
({
text
,
url
}:
Props
)
=>
{
const
Link
:
React
.
FC
<
Props
>
=
({
text
,
url
}:
Props
)
=>
{
return
(
return
(
<
a
className=
"text-blue-600 dark:text-blue-400 cursor-pointer underline break-all hover:opacity-80 decoration-1"
href=
{
url
}
>
<
a
className=
"text-blue-600 dark:text-blue-400 cursor-pointer underline break-all hover:opacity-80 decoration-1"
href=
{
url
}
target=
"_blank"
>
{
text
}
{
text
}
</
a
>
</
a
>
);
);
...
...
web/src/components/MemoContent/Renderer.tsx
View file @
c8d7f93d
import
{
import
{
AutoLinkNode
,
BlockquoteNode
,
BlockquoteNode
,
BoldItalicNode
,
BoldItalicNode
,
BoldNode
,
BoldNode
,
...
@@ -20,6 +21,7 @@ import {
...
@@ -20,6 +21,7 @@ import {
TextNode
,
TextNode
,
UnorderedListNode
,
UnorderedListNode
,
}
from
"@/types/proto/api/v2/markdown_service"
;
}
from
"@/types/proto/api/v2/markdown_service"
;
import
AutoLink
from
"./AutoLink"
;
import
Blockquote
from
"./Blockquote"
;
import
Blockquote
from
"./Blockquote"
;
import
Bold
from
"./Bold"
;
import
Bold
from
"./Bold"
;
import
BoldItalic
from
"./BoldItalic"
;
import
BoldItalic
from
"./BoldItalic"
;
...
@@ -78,6 +80,8 @@ const Renderer: React.FC<Props> = ({ node }: Props) => {
...
@@ -78,6 +80,8 @@ const Renderer: React.FC<Props> = ({ node }: Props) => {
return
<
Image
{
...
(
node
.
imageNode
as
ImageNode
)}
/>;
return
<
Image
{
...
(
node
.
imageNode
as
ImageNode
)}
/>;
case
NodeType
.
LINK
:
case
NodeType
.
LINK
:
return
<
Link
{
...
(
node
.
linkNode
as
LinkNode
)}
/>;
return
<
Link
{
...
(
node
.
linkNode
as
LinkNode
)}
/>;
case
NodeType
.
AUTO_LINK
:
return
<
AutoLink
{
...
(
node
.
autoLinkNode
as
AutoLinkNode
)}
/>;
case
NodeType
.
TAG
:
case
NodeType
.
TAG
:
return
<
Tag
{
...
(
node
.
tagNode
as
TagNode
)}
/>;
return
<
Tag
{
...
(
node
.
tagNode
as
TagNode
)}
/>;
case
NodeType
.
STRIKETHROUGH
:
case
NodeType
.
STRIKETHROUGH
:
...
...
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