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
8a340135
Commit
8a340135
authored
Jan 19, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement embedded memo renderer
parent
67f5ac36
Changes
18
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
915 additions
and
441 deletions
+915
-441
markdown_service.go
api/v2/markdown_service.go
+4
-0
ast.go
plugin/gomark/ast/ast.go
+1
-0
block.go
plugin/gomark/ast/block.go
+14
-0
embedded_content.go
plugin/gomark/parser/embedded_content.go
+51
-0
embedded_content_test.go
plugin/gomark/parser/embedded_content_test.go
+51
-0
parser.go
plugin/gomark/parser/parser.go
+1
-0
parser_test.go
plugin/gomark/parser/parser_test.go
+16
-0
table.go
plugin/gomark/parser/table.go
+26
-9
tokenizer.go
plugin/gomark/parser/tokenizer/tokenizer.go
+7
-7
tokenizer_test.go
plugin/gomark/parser/tokenizer/tokenizer_test.go
+141
-0
markdown_service.proto
proto/api/v2/markdown_service.proto
+36
-30
README.md
proto/gen/api/v2/README.md
+33
-15
markdown_service.pb.go
proto/gen/api/v2/markdown_service.pb.go
+469
-379
EmbeddedMemo.tsx
...c/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
+32
-0
index.tsx
web/src/components/MemoContent/EmbeddedContent/index.tsx
+20
-0
Renderer.tsx
web/src/components/MemoContent/Renderer.tsx
+4
-0
index.tsx
web/src/components/MemoContent/index.tsx
+5
-1
context.ts
web/src/components/MemoContent/types/context.ts
+4
-0
No files found.
api/v2/markdown_service.go
View file @
8a340135
...
...
@@ -65,6 +65,8 @@ func convertFromASTNode(rawNode ast.Node) *apiv2pb.Node {
node
.
Node
=
&
apiv2pb
.
Node_MathBlockNode
{
MathBlockNode
:
&
apiv2pb
.
MathBlockNode
{
Content
:
n
.
Content
}}
case
*
ast
.
Table
:
node
.
Node
=
&
apiv2pb
.
Node_TableNode
{
TableNode
:
convertTableFromASTNode
(
n
)}
case
*
ast
.
EmbeddedContent
:
node
.
Node
=
&
apiv2pb
.
Node_EmbeddedContentNode
{
EmbeddedContentNode
:
&
apiv2pb
.
EmbeddedContentNode
{
ResourceName
:
n
.
ResourceName
}}
case
*
ast
.
Text
:
node
.
Node
=
&
apiv2pb
.
Node_TextNode
{
TextNode
:
&
apiv2pb
.
TextNode
{
Content
:
n
.
Content
}}
case
*
ast
.
Bold
:
...
...
@@ -142,6 +144,8 @@ func convertToASTNode(node *apiv2pb.Node) ast.Node {
return
&
ast
.
MathBlock
{
Content
:
n
.
MathBlockNode
.
Content
}
case
*
apiv2pb
.
Node_TableNode
:
return
convertTableToASTNode
(
node
)
case
*
apiv2pb
.
Node_EmbeddedContentNode
:
return
&
ast
.
EmbeddedContent
{
ResourceName
:
n
.
EmbeddedContentNode
.
ResourceName
}
case
*
apiv2pb
.
Node_TextNode
:
return
&
ast
.
Text
{
Content
:
n
.
TextNode
.
Content
}
case
*
apiv2pb
.
Node_BoldNode
:
...
...
plugin/gomark/ast/ast.go
View file @
8a340135
...
...
@@ -16,6 +16,7 @@ const (
TaskListNode
MathBlockNode
TableNode
EmbeddedContentNode
// Inline nodes.
TextNode
BoldNode
...
...
plugin/gomark/ast/block.go
View file @
8a340135
...
...
@@ -228,3 +228,17 @@ func (n *Table) Restore() string {
}
return
result
}
type
EmbeddedContent
struct
{
BaseBlock
ResourceName
string
}
func
(
*
EmbeddedContent
)
Type
()
NodeType
{
return
EmbeddedContentNode
}
func
(
n
*
EmbeddedContent
)
Restore
()
string
{
return
fmt
.
Sprintf
(
"![[%s]]"
,
n
.
ResourceName
)
}
plugin/gomark/parser/embedded_content.go
0 → 100644
View file @
8a340135
package
parser
import
(
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type
EmbeddedContentParser
struct
{}
func
NewEmbeddedContentParser
()
*
EmbeddedContentParser
{
return
&
EmbeddedContentParser
{}
}
func
(
*
EmbeddedContentParser
)
Match
(
tokens
[]
*
tokenizer
.
Token
)
(
int
,
bool
)
{
lines
:=
tokenizer
.
Split
(
tokens
,
tokenizer
.
Newline
)
if
len
(
lines
)
<
1
{
return
0
,
false
}
firstLine
:=
lines
[
0
]
if
len
(
firstLine
)
<
5
{
return
0
,
false
}
if
firstLine
[
0
]
.
Type
!=
tokenizer
.
ExclamationMark
||
firstLine
[
1
]
.
Type
!=
tokenizer
.
LeftSquareBracket
||
firstLine
[
2
]
.
Type
!=
tokenizer
.
LeftSquareBracket
{
return
0
,
false
}
matched
:=
false
for
index
,
token
:=
range
firstLine
[
:
len
(
firstLine
)
-
1
]
{
if
token
.
Type
==
tokenizer
.
RightSquareBracket
&&
firstLine
[
index
+
1
]
.
Type
==
tokenizer
.
RightSquareBracket
&&
index
+
1
==
len
(
firstLine
)
-
1
{
matched
=
true
break
}
}
if
!
matched
{
return
0
,
false
}
return
len
(
firstLine
),
true
}
func
(
p
*
EmbeddedContentParser
)
Parse
(
tokens
[]
*
tokenizer
.
Token
)
(
ast
.
Node
,
error
)
{
size
,
ok
:=
p
.
Match
(
tokens
)
if
size
==
0
||
!
ok
{
return
nil
,
errors
.
New
(
"not matched"
)
}
return
&
ast
.
EmbeddedContent
{
ResourceName
:
tokenizer
.
Stringify
(
tokens
[
3
:
size
-
2
]),
},
nil
}
plugin/gomark/parser/embedded_content_test.go
0 → 100644
View file @
8a340135
package
parser
import
(
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
"github.com/usememos/memos/plugin/gomark/restore"
)
func
TestEmbeddedContentParser
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
text
string
embeddedContent
ast
.
Node
}{
{
text
:
"![[Hello world]"
,
embeddedContent
:
nil
,
},
{
text
:
"![[Hello world]]"
,
embeddedContent
:
&
ast
.
EmbeddedContent
{
ResourceName
:
"Hello world"
,
},
},
{
text
:
"![[memos/1]]"
,
embeddedContent
:
&
ast
.
EmbeddedContent
{
ResourceName
:
"memos/1"
,
},
},
{
text
:
"![[resources/101]]
\n
123"
,
embeddedContent
:
nil
,
},
{
text
:
"![[resources/101]]
\n
123"
,
embeddedContent
:
&
ast
.
EmbeddedContent
{
ResourceName
:
"resources/101"
,
},
},
}
for
_
,
test
:=
range
tests
{
tokens
:=
tokenizer
.
Tokenize
(
test
.
text
)
node
,
_
:=
NewEmbeddedContentParser
()
.
Parse
(
tokens
)
require
.
Equal
(
t
,
restore
.
Restore
([]
ast
.
Node
{
test
.
embeddedContent
}),
restore
.
Restore
([]
ast
.
Node
{
node
}))
}
}
plugin/gomark/parser/parser.go
View file @
8a340135
...
...
@@ -39,6 +39,7 @@ var defaultBlockParsers = []BlockParser{
NewUnorderedListParser
(),
NewOrderedListParser
(),
NewMathBlockParser
(),
NewEmbeddedContentParser
(),
NewParagraphParser
(),
NewLineBreakParser
(),
}
...
...
plugin/gomark/parser/parser_test.go
View file @
8a340135
...
...
@@ -218,6 +218,22 @@ func TestParser(t *testing.T) {
},
},
},
{
text
:
"Hello
\n
![[memos/101]]"
,
nodes
:
[]
ast
.
Node
{
&
ast
.
Paragraph
{
Children
:
[]
ast
.
Node
{
&
ast
.
Text
{
Content
:
"Hello"
,
},
},
},
&
ast
.
LineBreak
{},
&
ast
.
EmbeddedContent
{
ResourceName
:
"memos/101"
,
},
},
},
}
for
_
,
test
:=
range
tests
{
...
...
plugin/gomark/parser/table.go
View file @
8a340135
...
...
@@ -39,8 +39,10 @@ func (*TableParser) Match(tokens []*tokenizer.Token) (int, bool) {
rowTokens
:=
[]
*
tokenizer
.
Token
{}
for
index
,
token
:=
range
tokens
[
len
(
headerTokens
)
+
len
(
delimiterTokens
)
+
2
:
]
{
temp
:=
len
(
headerTokens
)
+
len
(
delimiterTokens
)
+
2
+
index
if
token
.
Type
==
tokenizer
.
Newline
&&
temp
!=
len
(
tokens
)
-
1
&&
tokens
[
temp
+
1
]
.
Type
!=
tokenizer
.
Pipe
{
break
if
token
.
Type
==
tokenizer
.
Newline
{
if
(
temp
==
len
(
tokens
)
-
1
)
||
(
temp
+
1
==
len
(
tokens
)
-
1
&&
tokens
[
temp
+
1
]
.
Type
==
tokenizer
.
Newline
)
{
break
}
}
rowTokens
=
append
(
rowTokens
,
token
)
}
...
...
@@ -65,7 +67,18 @@ func (*TableParser) Match(tokens []*tokenizer.Token) (int, bool) {
if
delimiterCells
!=
headerCells
||
!
ok
{
return
0
,
false
}
for
_
,
t
:=
range
tokenizer
.
Split
(
delimiterTokens
,
tokenizer
.
Pipe
)
{
for
index
,
t
:=
range
tokenizer
.
Split
(
delimiterTokens
,
tokenizer
.
Pipe
)
{
if
index
==
0
||
index
==
headerCells
{
if
len
(
t
)
!=
0
{
return
0
,
false
}
continue
}
if
len
(
t
)
<
5
{
return
0
,
false
}
delimiterTokens
:=
t
[
1
:
len
(
t
)
-
1
]
if
len
(
delimiterTokens
)
<
3
{
return
0
,
false
...
...
@@ -112,15 +125,16 @@ func (p *TableParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
delimiter
:=
make
([]
string
,
0
)
rows
:=
make
([][]
string
,
0
)
for
_
,
t
:=
range
tokenizer
.
Split
(
headerTokens
,
tokenizer
.
Pipe
)
{
cols
:=
len
(
tokenizer
.
Split
(
headerTokens
,
tokenizer
.
Pipe
))
-
2
for
_
,
t
:=
range
tokenizer
.
Split
(
headerTokens
,
tokenizer
.
Pipe
)[
1
:
cols
+
1
]
{
header
=
append
(
header
,
tokenizer
.
Stringify
(
t
[
1
:
len
(
t
)
-
1
]))
}
for
_
,
t
:=
range
tokenizer
.
Split
(
dilimiterTokens
,
tokenizer
.
Pipe
)
{
for
_
,
t
:=
range
tokenizer
.
Split
(
dilimiterTokens
,
tokenizer
.
Pipe
)
[
1
:
cols
+
1
]
{
delimiter
=
append
(
delimiter
,
tokenizer
.
Stringify
(
t
[
1
:
len
(
t
)
-
1
]))
}
for
_
,
row
:=
range
rowTokens
{
cells
:=
make
([]
string
,
0
)
for
_
,
t
:=
range
tokenizer
.
Split
(
row
,
tokenizer
.
Pipe
)
{
for
_
,
t
:=
range
tokenizer
.
Split
(
row
,
tokenizer
.
Pipe
)
[
1
:
cols
+
1
]
{
cells
=
append
(
cells
,
tokenizer
.
Stringify
(
t
[
1
:
len
(
t
)
-
1
]))
}
rows
=
append
(
rows
,
cells
)
...
...
@@ -145,10 +159,13 @@ func matchTableCellTokens(tokens []*tokenizer.Token) (int, bool) {
}
}
cells
:=
tokenizer
.
Split
(
tokens
,
tokenizer
.
Pipe
)
if
len
(
cells
)
!=
pipes
-
1
{
if
len
(
cells
)
!=
pipes
+
1
{
return
0
,
false
}
if
len
(
cells
[
0
])
!=
0
||
len
(
cells
[
len
(
cells
)
-
1
])
!=
0
{
return
0
,
false
}
for
_
,
cellTokens
:=
range
cells
{
for
_
,
cellTokens
:=
range
cells
[
1
:
len
(
cells
)
-
1
]
{
if
len
(
cellTokens
)
==
0
{
return
0
,
false
}
...
...
@@ -160,5 +177,5 @@ func matchTableCellTokens(tokens []*tokenizer.Token) (int, bool) {
}
}
return
len
(
cells
),
true
return
len
(
cells
)
-
1
,
true
}
plugin/gomark/parser/tokenizer/tokenizer.go
View file @
8a340135
...
...
@@ -132,20 +132,20 @@ func Stringify(tokens []*Token) string {
}
func
Split
(
tokens
[]
*
Token
,
delimiter
TokenType
)
[][]
*
Token
{
if
len
(
tokens
)
==
0
{
return
[][]
*
Token
{}
}
result
:=
make
([][]
*
Token
,
0
)
current
:=
make
([]
*
Token
,
0
)
for
_
,
token
:=
range
tokens
{
if
token
.
Type
==
delimiter
{
if
len
(
current
)
>
0
{
result
=
append
(
result
,
current
)
current
=
make
([]
*
Token
,
0
)
}
result
=
append
(
result
,
current
)
current
=
make
([]
*
Token
,
0
)
}
else
{
current
=
append
(
current
,
token
)
}
}
if
len
(
current
)
>
0
{
result
=
append
(
result
,
current
)
}
result
=
append
(
result
,
current
)
return
result
}
plugin/gomark/parser/tokenizer/tokenizer_test.go
View file @
8a340135
...
...
@@ -77,3 +77,144 @@ func TestTokenize(t *testing.T) {
require
.
Equal
(
t
,
test
.
tokens
,
result
)
}
}
func
TestSplit
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
tokens
[]
*
Token
sep
TokenType
result
[][]
*
Token
}{
{
tokens
:
[]
*
Token
{
{
Type
:
Asterisk
,
Value
:
"*"
,
},
{
Type
:
Text
,
Value
:
"Hello"
,
},
{
Type
:
Space
,
Value
:
" "
,
},
{
Type
:
Text
,
Value
:
"world"
,
},
{
Type
:
ExclamationMark
,
Value
:
"!"
,
},
},
sep
:
Asterisk
,
result
:
[][]
*
Token
{
{
{
Type
:
Text
,
Value
:
"Hello"
,
},
{
Type
:
Space
,
Value
:
" "
,
},
{
Type
:
Text
,
Value
:
"world"
,
},
{
Type
:
ExclamationMark
,
Value
:
"!"
,
},
},
},
},
{
tokens
:
[]
*
Token
{
{
Type
:
Asterisk
,
Value
:
"*"
,
},
{
Type
:
Text
,
Value
:
"Hello"
,
},
{
Type
:
Space
,
Value
:
" "
,
},
{
Type
:
Text
,
Value
:
"world"
,
},
{
Type
:
ExclamationMark
,
Value
:
"!"
,
},
},
sep
:
Text
,
result
:
[][]
*
Token
{
{
{
Type
:
Asterisk
,
Value
:
"*"
,
},
},
{
{
Type
:
Space
,
Value
:
" "
,
},
},
{
{
Type
:
ExclamationMark
,
Value
:
"!"
,
},
},
},
},
{
tokens
:
[]
*
Token
{
{
Type
:
Text
,
Value
:
"Hello"
,
},
{
Type
:
Space
,
Value
:
" "
,
},
{
Type
:
Text
,
Value
:
"world"
,
},
{
Type
:
Newline
,
Value
:
"
\n
"
,
},
},
sep
:
Newline
,
result
:
[][]
*
Token
{
{
{
Type
:
Text
,
Value
:
"Hello"
,
},
{
Type
:
Space
,
Value
:
" "
,
},
{
Type
:
Text
,
Value
:
"world"
,
},
},
},
},
}
for
_
,
test
:=
range
tests
{
result
:=
Split
(
test
.
tokens
,
test
.
sep
)
require
.
Equal
(
t
,
test
.
result
,
result
)
}
}
proto/api/v2/markdown_service.proto
View file @
8a340135
...
...
@@ -36,21 +36,22 @@ enum NodeType {
TASK_LIST
=
9
;
MATH_BLOCK
=
10
;
TABLE
=
11
;
TEXT
=
12
;
BOLD
=
13
;
ITALIC
=
14
;
BOLD_ITALIC
=
15
;
CODE
=
16
;
IMAGE
=
17
;
LINK
=
18
;
AUTO_LINK
=
19
;
TAG
=
20
;
STRIKETHROUGH
=
21
;
ESCAPING_CHARACTER
=
22
;
MATH
=
23
;
HIGHLIGHT
=
24
;
SUBSCRIPT
=
25
;
SUPERSCRIPT
=
26
;
EMBEDDED_CONTENT
=
12
;
TEXT
=
13
;
BOLD
=
14
;
ITALIC
=
15
;
BOLD_ITALIC
=
16
;
CODE
=
17
;
IMAGE
=
18
;
LINK
=
19
;
AUTO_LINK
=
20
;
TAG
=
21
;
STRIKETHROUGH
=
22
;
ESCAPING_CHARACTER
=
23
;
MATH
=
24
;
HIGHLIGHT
=
25
;
SUBSCRIPT
=
26
;
SUPERSCRIPT
=
27
;
}
message
Node
{
...
...
@@ -67,21 +68,22 @@ message Node {
TaskListNode
task_list_node
=
10
;
MathBlockNode
math_block_node
=
11
;
TableNode
table_node
=
12
;
TextNode
text_node
=
13
;
BoldNode
bold_node
=
14
;
ItalicNode
italic_node
=
15
;
BoldItalicNode
bold_italic_node
=
16
;
CodeNode
code_node
=
17
;
ImageNode
image_node
=
18
;
LinkNode
link_node
=
19
;
AutoLinkNode
auto_link_node
=
20
;
TagNode
tag_node
=
21
;
StrikethroughNode
strikethrough_node
=
22
;
EscapingCharacterNode
escaping_character_node
=
23
;
MathNode
math_node
=
24
;
HighlightNode
highlight_node
=
25
;
SubscriptNode
subscript_node
=
26
;
SuperscriptNode
superscript_node
=
27
;
EmbeddedContentNode
embedded_content_node
=
13
;
TextNode
text_node
=
14
;
BoldNode
bold_node
=
15
;
ItalicNode
italic_node
=
16
;
BoldItalicNode
bold_italic_node
=
17
;
CodeNode
code_node
=
18
;
ImageNode
image_node
=
19
;
LinkNode
link_node
=
20
;
AutoLinkNode
auto_link_node
=
21
;
TagNode
tag_node
=
22
;
StrikethroughNode
strikethrough_node
=
23
;
EscapingCharacterNode
escaping_character_node
=
24
;
MathNode
math_node
=
25
;
HighlightNode
highlight_node
=
26
;
SubscriptNode
subscript_node
=
27
;
SuperscriptNode
superscript_node
=
28
;
}
}
...
...
@@ -142,6 +144,10 @@ message TableNode {
repeated
Row
rows
=
3
;
}
message
EmbeddedContentNode
{
string
resource_name
=
1
;
}
message
TextNode
{
string
content
=
1
;
}
...
...
proto/gen/api/v2/README.md
View file @
8a340135
...
...
@@ -72,6 +72,7 @@
-
[
BoldNode
](
#memos-api-v2-BoldNode
)
-
[
CodeBlockNode
](
#memos-api-v2-CodeBlockNode
)
-
[
CodeNode
](
#memos-api-v2-CodeNode
)
-
[
EmbeddedContentNode
](
#memos-api-v2-EmbeddedContentNode
)
-
[
EscapingCharacterNode
](
#memos-api-v2-EscapingCharacterNode
)
-
[
HeadingNode
](
#memos-api-v2-HeadingNode
)
-
[
HighlightNode
](
#memos-api-v2-HighlightNode
)
...
...
@@ -1058,6 +1059,21 @@
<a
name=
"memos-api-v2-EmbeddedContentNode"
></a>
### EmbeddedContentNode
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| resource_name |
[
string
](
#string
)
| | |
<a
name=
"memos-api-v2-EscapingCharacterNode"
></a>
### EscapingCharacterNode
...
...
@@ -1227,6 +1243,7 @@
| task_list_node |
[
TaskListNode
](
#memos-api-v2-TaskListNode
)
| | |
| math_block_node |
[
MathBlockNode
](
#memos-api-v2-MathBlockNode
)
| | |
| table_node |
[
TableNode
](
#memos-api-v2-TableNode
)
| | |
| embedded_content_node |
[
EmbeddedContentNode
](
#memos-api-v2-EmbeddedContentNode
)
| | |
| text_node |
[
TextNode
](
#memos-api-v2-TextNode
)
| | |
| bold_node |
[
BoldNode
](
#memos-api-v2-BoldNode
)
| | |
| italic_node |
[
ItalicNode
](
#memos-api-v2-ItalicNode
)
| | |
...
...
@@ -1473,21 +1490,22 @@
| TASK_LIST | 9 | |
| MATH_BLOCK | 10 | |
| TABLE | 11 | |
| TEXT | 12 | |
| BOLD | 13 | |
| ITALIC | 14 | |
| BOLD_ITALIC | 15 | |
| CODE | 16 | |
| IMAGE | 17 | |
| LINK | 18 | |
| AUTO_LINK | 19 | |
| TAG | 20 | |
| STRIKETHROUGH | 21 | |
| ESCAPING_CHARACTER | 22 | |
| MATH | 23 | |
| HIGHLIGHT | 24 | |
| SUBSCRIPT | 25 | |
| SUPERSCRIPT | 26 | |
| EMBEDDED_CONTENT | 12 | |
| TEXT | 13 | |
| BOLD | 14 | |
| ITALIC | 15 | |
| BOLD_ITALIC | 16 | |
| CODE | 17 | |
| IMAGE | 18 | |
| LINK | 19 | |
| AUTO_LINK | 20 | |
| TAG | 21 | |
| STRIKETHROUGH | 22 | |
| ESCAPING_CHARACTER | 23 | |
| MATH | 24 | |
| HIGHLIGHT | 25 | |
| SUBSCRIPT | 26 | |
| SUPERSCRIPT | 27 | |
...
...
proto/gen/api/v2/markdown_service.pb.go
View file @
8a340135
This diff is collapsed.
Click to expand it.
web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
0 → 100644
View file @
8a340135
import
{
useContext
,
useEffect
}
from
"react"
;
import
{
useMemoStore
}
from
"@/store/v1"
;
import
MemoContent
from
".."
;
import
{
RendererContext
}
from
"../types"
;
interface
Props
{
memoId
:
number
;
}
const
EmbeddedMemo
=
({
memoId
}:
Props
)
=>
{
const
context
=
useContext
(
RendererContext
);
const
memoStore
=
useMemoStore
();
const
memo
=
memoStore
.
getMemoById
(
memoId
);
const
resourceName
=
`memos/
${
memoId
}
`
;
useEffect
(()
=>
{
memoStore
.
getOrFetchMemoById
(
memoId
);
},
[
memoId
]);
if
(
memoId
===
context
.
memoId
||
context
.
embeddedMemos
.
has
(
resourceName
))
{
return
<
p
>
Nested Rendering Error:
{
`![[${resourceName}]]`
}
</
p
>;
}
context
.
embeddedMemos
.
add
(
resourceName
);
return
(
<
div
className=
"embedded-memo"
>
<
MemoContent
nodes=
{
memo
.
nodes
}
memoId=
{
memoId
}
embeddedMemos=
{
context
.
embeddedMemos
}
/>
</
div
>
);
};
export
default
EmbeddedMemo
;
web/src/components/MemoContent/EmbeddedContent/index.tsx
0 → 100644
View file @
8a340135
import
EmbeddedMemo
from
"./EmbeddedMemo"
;
interface
Props
{
resourceName
:
string
;
}
const
extractResourceTypeAndId
=
(
resourceName
:
string
)
=>
{
const
[
resourceType
,
resourceId
]
=
resourceName
.
split
(
"/"
);
return
{
resourceType
,
resourceId
};
};
const
EmbeddedContent
=
({
resourceName
}:
Props
)
=>
{
const
{
resourceType
,
resourceId
}
=
extractResourceTypeAndId
(
resourceName
);
if
(
resourceType
===
"memos"
)
{
return
<
EmbeddedMemo
memoId=
{
Number
(
resourceId
)
}
/>;
}
return
<
p
>
Unknown resource:
{
resourceName
}
</
p
>;
};
export
default
EmbeddedContent
;
web/src/components/MemoContent/Renderer.tsx
View file @
8a340135
...
...
@@ -5,6 +5,7 @@ import {
BoldNode
,
CodeBlockNode
,
CodeNode
,
EmbeddedContentNode
,
EscapingCharacterNode
,
HeadingNode
,
HighlightNode
,
...
...
@@ -31,6 +32,7 @@ import Bold from "./Bold";
import
BoldItalic
from
"./BoldItalic"
;
import
Code
from
"./Code"
;
import
CodeBlock
from
"./CodeBlock"
;
import
EmbeddedContent
from
"./EmbeddedContent"
;
import
EscapingCharacter
from
"./EscapingCharacter"
;
import
Heading
from
"./Heading"
;
import
Highlight
from
"./Highlight"
;
...
...
@@ -80,6 +82,8 @@ const Renderer: React.FC<Props> = ({ index, node }: Props) => {
return
<
Math
{
...
(
node
.
mathBlockNode
as
MathNode
)}
block=
{
true
}
/>;
case
NodeType
.
TABLE
:
return
<
Table
{
...
(
node
.
tableNode
as
TableNode
)}
/>;
case
NodeType
.
EMBEDDED_CONTENT
:
return
<
EmbeddedContent
{
...
(
node
.
embeddedContentNode
as
EmbeddedContentNode
)}
/>;
case
NodeType
.
TEXT
:
return
<
Text
{
...
(
node
.
textNode
as
TextNode
)}
/>;
case
NodeType
.
BOLD
:
...
...
web/src/components/MemoContent/index.tsx
View file @
8a340135
...
...
@@ -10,12 +10,15 @@ interface Props {
memoId
?:
number
;
readonly
?:
boolean
;
disableFilter
?:
boolean
;
// embeddedMemos is a set of memo resource names that are embedded in the current memo.
// This is used to prevent infinite loops when a memo embeds itself.
embeddedMemos
?:
Set
<
string
>
;
className
?:
string
;
onClick
?:
(
e
:
React
.
MouseEvent
)
=>
void
;
}
const
MemoContent
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
className
,
memoId
,
nodes
,
onClick
}
=
props
;
const
{
className
,
memoId
,
nodes
,
embeddedMemos
,
onClick
}
=
props
;
const
currentUser
=
useCurrentUser
();
const
memoStore
=
useMemoStore
();
const
memoContentContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
...
...
@@ -37,6 +40,7 @@ const MemoContent: React.FC<Props> = (props: Props) => {
memoId
,
readonly
:
!
allowEdit
,
disableFilter
:
props
.
disableFilter
,
embeddedMemos
:
embeddedMemos
||
new
Set
(),
}
}
>
<
div
className=
{
`w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-300 ${className || ""}`
}
>
...
...
web/src/components/MemoContent/types/context.ts
View file @
8a340135
...
...
@@ -3,6 +3,9 @@ import { Node } from "@/types/proto/api/v2/markdown_service";
interface
Context
{
nodes
:
Node
[];
// embeddedMemos is a set of memo resource names that are embedded in the current memo.
// This is used to prevent infinite loops when a memo embeds itself.
embeddedMemos
:
Set
<
string
>
;
memoId
?:
number
;
readonly
?:
boolean
;
disableFilter
?:
boolean
;
...
...
@@ -10,4 +13,5 @@ interface Context {
export
const
RendererContext
=
createContext
<
Context
>
({
nodes
:
[],
embeddedMemos
:
new
Set
(),
});
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