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
3d4f793f
Unverified
Commit
3d4f793f
authored
Mar 05, 2026
by
memoclaw
Committed by
GitHub
Mar 05, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: include plain URLs and tags in memo snippet generation (#5688)
Co-authored-by:
Claude Opus 4.6
<
noreply@anthropic.com
>
parent
92d937b1
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
99 additions
and
35 deletions
+99
-35
markdown.go
plugin/markdown/markdown.go
+11
-6
markdown_test.go
plugin/markdown/markdown_test.go
+41
-0
AttachmentList.tsx
web/src/components/MemoEditor/components/AttachmentList.tsx
+1
-1
RelationList.tsx
web/src/components/MemoEditor/components/RelationList.tsx
+1
-1
MemoCommentListView.tsx
...rc/components/MemoView/components/MemoCommentListView.tsx
+7
-6
MemoSnippetLink.tsx
web/src/components/MemoView/components/MemoSnippetLink.tsx
+34
-0
RelationCard.tsx
.../components/MemoView/components/metadata/RelationCard.tsx
+2
-19
SectionHeader.tsx
...components/MemoView/components/metadata/SectionHeader.tsx
+2
-2
No files found.
plugin/markdown/markdown.go
View file @
3d4f793f
...
...
@@ -244,15 +244,20 @@ func (s *service) GenerateSnippet(content []byte, maxLength int) (string, error)
lastNodeWasBlock
=
false
// Only extract plain text nodes
if
textNode
,
ok
:=
n
.
(
*
gast
.
Text
);
ok
{
segment
:=
textNode
.
Segment
// Extract text from various node types
switch
node
:=
n
.
(
type
)
{
case
*
gast
.
Text
:
segment
:=
node
.
Segment
buf
.
Write
(
segment
.
Value
(
content
))
// Add space if this is a soft line break
if
textNode
.
SoftLineBreak
()
{
if
node
.
SoftLineBreak
()
{
buf
.
WriteByte
(
' '
)
}
case
*
gast
.
AutoLink
:
buf
.
Write
(
node
.
URL
(
content
))
return
gast
.
WalkSkipChildren
,
nil
case
*
mast
.
TagNode
:
buf
.
WriteByte
(
'#'
)
buf
.
Write
(
node
.
Tag
)
}
// Stop walking if we've exceeded double the max length
...
...
plugin/markdown/markdown_test.go
View file @
3d4f793f
...
...
@@ -94,6 +94,18 @@ func TestGenerateSnippet(t *testing.T) {
maxLength
:
100
,
expected
:
"Item 1 Item 2 Item 3"
,
},
{
name
:
"plain URL autolink"
,
content
:
"https://usememos.com"
,
maxLength
:
100
,
expected
:
"https://usememos.com"
,
},
{
name
:
"text with plain URL"
,
content
:
"Check out https://usememos.com for more info."
,
maxLength
:
100
,
expected
:
"Check out https://usememos.com for more info."
,
},
}
for
_
,
tt
:=
range
tests
{
...
...
@@ -103,6 +115,35 @@ func TestGenerateSnippet(t *testing.T) {
assert
.
Equal
(
t
,
tt
.
expected
,
snippet
)
})
}
// Test with tag extension enabled (matches production config).
svcWithTags
:=
NewService
(
WithTagExtension
())
tagTests
:=
[]
struct
{
name
string
content
string
maxLength
int
expected
string
}{
{
name
:
"tag only"
,
content
:
"#todo"
,
maxLength
:
100
,
expected
:
"#todo"
,
},
{
name
:
"text with tags"
,
content
:
"Remember to #review the #code"
,
maxLength
:
100
,
expected
:
"Remember to #review the #code"
,
},
}
for
_
,
tt
:=
range
tagTests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
snippet
,
err
:=
svcWithTags
.
GenerateSnippet
([]
byte
(
tt
.
content
),
tt
.
maxLength
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
tt
.
expected
,
snippet
)
})
}
}
func
TestExtractProperties
(
t
*
testing
.
T
)
{
...
...
web/src/components/MemoEditor/components/AttachmentList.tsx
View file @
3d4f793f
...
...
@@ -141,7 +141,7 @@ const AttachmentList: FC<AttachmentListProps> = ({ attachments, localFiles = [],
<
div
className=
"w-full rounded-lg border border-border bg-muted/20 overflow-hidden"
>
<
div
className=
"flex items-center gap-1.5 px-2 py-1 border-b border-border bg-muted/30"
>
<
PaperclipIcon
className=
"w-3.5 h-3.5 text-muted-foreground"
/>
<
span
className=
"text-xs text-foreground"
>
Attachments (
{
items
.
length
}
)
</
span
>
<
span
className=
"text-xs text-
muted-
foreground"
>
Attachments (
{
items
.
length
}
)
</
span
>
</
div
>
<
div
className=
"p-1 sm:p-1.5 flex flex-col gap-0.5"
>
...
...
web/src/components/MemoEditor/components/RelationList.tsx
View file @
3d4f793f
...
...
@@ -78,7 +78,7 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
<
div
className=
"w-full rounded-lg border border-border bg-muted/20 overflow-hidden"
>
<
div
className=
"flex items-center gap-1.5 px-2 py-1 border-b border-border bg-muted/30"
>
<
LinkIcon
className=
"w-3.5 h-3.5 text-muted-foreground"
/>
<
span
className=
"text-xs text-foreground"
>
Relations (
{
referenceRelations
.
length
}
)
</
span
>
<
span
className=
"text-xs text-
muted-
foreground"
>
Relations (
{
referenceRelations
.
length
}
)
</
span
>
</
div
>
<
div
className=
"p-1 sm:p-1.5 flex flex-col gap-0.5"
>
...
...
web/src/components/MemoView/components/MemoCommentListView.tsx
View file @
3d4f793f
...
...
@@ -3,6 +3,7 @@ import { Link } from "react-router-dom";
import
{
extractMemoIdFromName
}
from
"@/helpers/resource-names"
;
import
{
useMemoComments
}
from
"@/hooks/useMemoQueries"
;
import
{
useMemoViewContext
,
useMemoViewDerived
}
from
"../MemoViewContext"
;
import
MemoSnippetLink
from
"./MemoSnippetLink"
;
const
MemoCommentListView
:
React
.
FC
=
()
=>
{
const
{
memo
}
=
useMemoViewContext
();
...
...
@@ -23,7 +24,7 @@ const MemoCommentListView: React.FC = () => {
<
span
className=
"text-xs text-muted-foreground"
>
Comments
{
commentAmount
>
1
?
` (${commentAmount})`
:
""
}
</
span
>
<
Link
to=
{
`/${memo.name}#comments`
}
className=
"flex items-center gap-0.5 text-xs text-muted-foreground
hover:text-foreground
hover:underline underline-offset-2 transition-colors"
className=
"flex items-center gap-0.5 text-xs text-muted-foreground
/80
hover:underline underline-offset-2 transition-colors"
>
View all
<
ArrowUpRightIcon
className=
"w-3 h-3"
/>
...
...
@@ -32,13 +33,13 @@ const MemoCommentListView: React.FC = () => {
{
displayedComments
.
map
((
comment
)
=>
{
const
uid
=
extractMemoIdFromName
(
comment
.
name
);
return
(
<
Link
<
MemoSnippet
Link
key=
{
comment
.
name
}
name=
{
comment
.
name
}
snippet=
{
comment
.
snippet
||
comment
.
content
}
to=
{
`/${memo.name}#${uid}`
}
className=
"bg-muted/60 rounded-md px-2 py-1 text-xs text-muted-foreground truncate leading-relaxed hover:bg-muted transition-colors block"
>
{
comment
.
content
}
</
Link
>
className=
"bg-muted/40 rounded-md"
/>
);
})
}
</
div
>
...
...
web/src/components/MemoView/components/MemoSnippetLink.tsx
0 → 100644
View file @
3d4f793f
import
{
Link
}
from
"react-router-dom"
;
import
{
extractMemoIdFromName
}
from
"@/helpers/resource-names"
;
import
{
cn
}
from
"@/lib/utils"
;
interface
MemoSnippetLinkProps
{
name
:
string
;
snippet
:
string
;
to
:
string
;
state
?:
object
;
className
?:
string
;
}
const
MemoSnippetLink
=
({
name
,
snippet
,
to
,
state
,
className
}:
MemoSnippetLinkProps
)
=>
{
const
memoId
=
extractMemoIdFromName
(
name
);
return
(
<
Link
className=
{
cn
(
"flex items-center gap-1 px-1 py-1 rounded text-xs text-muted-foreground hover:text-foreground hover:bg-accent/20 transition-colors group"
,
className
,
)
}
to=
{
to
}
viewTransition
state=
{
state
}
>
<
span
className=
"text-[8px] font-mono px-1 py-0.5 rounded border border-border bg-muted/40 group-hover:bg-accent/30 transition-colors shrink-0"
>
{
memoId
.
slice
(
0
,
6
)
}
</
span
>
<
span
className=
"truncate"
>
{
snippet
}
</
span
>
</
Link
>
);
};
export
default
MemoSnippetLink
;
web/src/components/MemoView/components/metadata/RelationCard.tsx
View file @
3d4f793f
import
{
Link
}
from
"react-router-dom"
;
import
{
extractMemoIdFromName
}
from
"@/helpers/resource-names"
;
import
{
cn
}
from
"@/lib/utils"
;
import
type
{
MemoRelation_Memo
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
MemoSnippetLink
from
"../MemoSnippetLink"
;
interface
RelationCardProps
{
memo
:
MemoRelation_Memo
;
...
...
@@ -10,23 +8,8 @@ interface RelationCardProps {
}
const
RelationCard
=
({
memo
,
parentPage
,
className
}:
RelationCardProps
)
=>
{
const
memoId
=
extractMemoIdFromName
(
memo
.
name
);
return
(
<
Link
className=
{
cn
(
"flex items-center gap-1 px-1 py-1 rounded text-xs text-muted-foreground hover:text-foreground hover:bg-accent/20 transition-colors group"
,
className
,
)
}
to=
{
`/${memo.name}`
}
viewTransition
state=
{
{
from
:
parentPage
}
}
>
<
span
className=
"text-[8px] font-mono px-1 py-0.5 rounded border border-border bg-muted/40 group-hover:bg-accent/30 transition-colors shrink-0"
>
{
memoId
.
slice
(
0
,
6
)
}
</
span
>
<
span
className=
"truncate"
>
{
memo
.
snippet
}
</
span
>
</
Link
>
<
MemoSnippetLink
name=
{
memo
.
name
}
snippet=
{
memo
.
snippet
}
to=
{
`/${memo.name}`
}
state=
{
{
from
:
parentPage
}
}
className=
{
className
}
/>
);
};
...
...
web/src/components/MemoView/components/metadata/SectionHeader.tsx
View file @
3d4f793f
...
...
@@ -27,7 +27,7 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) =
onClick=
{
tab
.
onClick
}
className=
{
cn
(
"text-xs px-0 py-0 transition-colors"
,
tab
.
active
?
"text-
foreground"
:
"text-muted-foreground hover:text
-foreground"
,
tab
.
active
?
"text-
muted-foreground"
:
"text-muted-foreground/60 hover:text-muted
-foreground"
,
)
}
>
{
tab
.
label
}
(
{
tab
.
count
}
)
...
...
@@ -37,7 +37,7 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) =
))
}
</
div
>
)
:
(
<
span
className=
"text-xs text-foreground"
>
<
span
className=
"text-xs text-
muted-
foreground"
>
{
title
}
(
{
count
}
)
</
span
>
)
}
...
...
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