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
d2b42121
Commit
d2b42121
authored
Jan 03, 2026
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: optimize RelationList and unify RelationCard usage
parent
77e9376e
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
40 additions
and
66 deletions
+40
-66
RelationList.tsx
web/src/components/MemoEditor/components/RelationList.tsx
+37
-63
SectionHeader.tsx
...components/MemoView/components/metadata/SectionHeader.tsx
+2
-2
StatisticsView.tsx
web/src/components/StatisticsView/StatisticsView.tsx
+1
-1
No files found.
web/src/components/MemoEditor/components/RelationList.tsx
View file @
d2b42121
...
@@ -2,12 +2,10 @@ import { create } from "@bufbuild/protobuf";
...
@@ -2,12 +2,10 @@ import { create } from "@bufbuild/protobuf";
import
{
LinkIcon
,
XIcon
}
from
"lucide-react"
;
import
{
LinkIcon
,
XIcon
}
from
"lucide-react"
;
import
type
{
FC
}
from
"react"
;
import
type
{
FC
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
Link
}
from
"react-router-dom
"
;
import
RelationCard
from
"@/components/MemoView/components/metadata/RelationCard
"
;
import
{
memoServiceClient
}
from
"@/connect"
;
import
{
memoServiceClient
}
from
"@/connect"
;
import
{
extractMemoIdFromName
}
from
"@/helpers/resource-names"
;
import
type
{
MemoRelation
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
MemoRelation_Memo
,
MemoRelation_MemoSchema
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
type
{
Memo
,
MemoRelation
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
MemoRelation_MemoSchema
}
from
"@/types/proto/api/v1/memo_service_pb"
;
interface
RelationListProps
{
interface
RelationListProps
{
relations
:
MemoRelation
[];
relations
:
MemoRelation
[];
...
@@ -20,65 +18,44 @@ const RelationItemCard: FC<{
...
@@ -20,65 +18,44 @@ const RelationItemCard: FC<{
onRemove
?:
()
=>
void
;
onRemove
?:
()
=>
void
;
parentPage
?:
string
;
parentPage
?:
string
;
}
>
=
({
memo
,
onRemove
,
parentPage
})
=>
{
}
>
=
({
memo
,
onRemove
,
parentPage
})
=>
{
const
memoId
=
extractMemoIdFromName
(
memo
!
.
name
);
if
(
onRemove
)
{
return
(
<
div
className=
{
cn
(
"relative flex items-center gap-1.5 px-1.5 py-1 rounded border border-transparent hover:border-border hover:bg-accent/20 transition-all"
,
)
}
>
<
LinkIcon
className=
"w-3.5 h-3.5 shrink-0 text-muted-foreground"
/>
<
span
className=
"text-xs font-medium truncate flex-1"
title=
{
memo
!
.
snippet
}
>
{
memo
!
.
snippet
}
</
span
>
<
div
className=
"flex-shrink-0 flex items-center gap-0.5"
>
<
button
type=
"button"
onClick=
{
onRemove
}
className=
"p-0.5 rounded hover:bg-destructive/10 active:bg-destructive/10 transition-colors touch-manipulation"
title=
"Remove"
aria
-
label=
"Remove relation"
>
<
XIcon
className=
"w-3 h-3 text-muted-foreground hover:text-destructive"
/>
</
button
>
</
div
>
</
div
>
);
}
return
(
return
(
<
Link
<
div
className=
"group relative flex items-center justify-between w-full rounded hover:bg-accent/20 transition-colors"
>
className=
{
cn
(
<
RelationCard
memo=
{
memo
!
}
parentPage=
{
parentPage
}
className=
"flex-1 hover:bg-transparent"
/>
"relative flex items-center gap-1.5 px-1.5 py-1 rounded border border-transparent hover:border-border hover:bg-accent/20 transition-all"
,
{
onRemove
&&
(
<
button
type=
"button"
onClick=
{
onRemove
}
className=
"p-1 mr-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-destructive/10 active:bg-destructive/10 transition-all touch-manipulation"
title=
"Remove"
aria
-
label=
"Remove relation"
>
<
XIcon
className=
"w-3 h-3 text-muted-foreground hover:text-destructive"
/>
</
button
>
)
}
)
}
to=
{
`/${memo!.name}`
}
</
div
>
viewTransition
state=
{
{
from
:
parentPage
}
}
>
<
span
className=
"text-[10px] font-mono px-1 py-0.5 rounded bg-muted/50 text-muted-foreground shrink-0"
>
{
memoId
.
slice
(
0
,
6
)
}
</
span
>
<
span
className=
"text-xs truncate flex-1"
title=
{
memo
!
.
snippet
}
>
{
memo
!
.
snippet
}
</
span
>
</
Link
>
);
);
};
};
const
RelationList
:
FC
<
RelationListProps
>
=
({
relations
,
onRelationsChange
,
parentPage
})
=>
{
const
RelationList
:
FC
<
RelationListProps
>
=
({
relations
,
onRelationsChange
,
parentPage
})
=>
{
const
[
referencingMemos
,
setReferencingMemos
]
=
useState
<
Memo
[]
>
([]
);
const
[
fetchedMemos
,
setFetchedMemos
]
=
useState
<
Record
<
string
,
MemoRelation_Memo
>>
({}
);
useEffect
(()
=>
{
useEffect
(()
=>
{
(
async
()
=>
{
(
async
()
=>
{
if
(
relations
.
length
>
0
)
{
const
missingSnippetRelations
=
relations
.
filter
((
relation
)
=>
!
relation
.
relatedMemo
?.
snippet
&&
relation
.
relatedMemo
?.
name
);
const
requests
=
relations
.
map
(
async
(
relation
)
=>
{
if
(
missingSnippetRelations
.
length
>
0
)
{
return
await
memoServiceClient
.
getMemo
({
name
:
relation
.
relatedMemo
!
.
name
});
const
requests
=
missingSnippetRelations
.
map
(
async
(
relation
)
=>
{
const
memo
=
await
memoServiceClient
.
getMemo
({
name
:
relation
.
relatedMemo
!
.
name
});
return
create
(
MemoRelation_MemoSchema
,
{
name
:
memo
.
name
,
snippet
:
memo
.
snippet
});
});
});
const
list
=
await
Promise
.
all
(
requests
);
const
list
=
await
Promise
.
all
(
requests
);
setReferencingMemos
(
list
);
setFetchedMemos
((
prev
)
=>
{
}
else
{
const
next
=
{
...
prev
};
setReferencingMemos
([]);
for
(
const
memo
of
list
)
{
next
[
memo
.
name
]
=
memo
;
}
return
next
;
});
}
}
})();
})();
},
[
relations
]);
},
[
relations
]);
...
@@ -89,7 +66,7 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
...
@@ -89,7 +66,7 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
}
}
};
};
if
(
re
ferencingMemo
s
.
length
===
0
)
{
if
(
re
lation
s
.
length
===
0
)
{
return
null
;
return
null
;
}
}
...
@@ -97,18 +74,15 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
...
@@ -97,18 +74,15 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
<
div
className=
"w-full rounded-lg border border-border bg-muted/20 overflow-hidden"
>
<
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.5 border-b border-border bg-muted/30"
>
<
div
className=
"flex items-center gap-1.5 px-2 py-1.5 border-b border-border bg-muted/30"
>
<
LinkIcon
className=
"w-3.5 h-3.5 text-muted-foreground"
/>
<
LinkIcon
className=
"w-3.5 h-3.5 text-muted-foreground"
/>
<
span
className=
"text-xs font-medium text-muted-foreground"
>
Relations (
{
re
ferencingMemo
s
.
length
}
)
</
span
>
<
span
className=
"text-xs font-medium text-muted-foreground"
>
Relations (
{
re
lation
s
.
length
}
)
</
span
>
</
div
>
</
div
>
<
div
className=
"p-1 sm:p-1.5 flex flex-col gap-0.5"
>
<
div
className=
"p-1 sm:p-1.5 flex flex-col gap-0.5"
>
{
referencingMemos
.
map
((
memo
)
=>
(
{
relations
.
map
((
relation
)
=>
{
<
RelationItemCard
const
relatedMemo
=
relation
.
relatedMemo
!
;
key=
{
memo
.
name
}
const
memo
=
relatedMemo
.
snippet
?
relatedMemo
:
fetchedMemos
[
relatedMemo
.
name
]
||
relatedMemo
;
memo=
{
create
(
MemoRelation_MemoSchema
,
{
name
:
memo
.
name
,
snippet
:
memo
.
snippet
})
}
return
<
RelationItemCard
key=
{
memo
.
name
}
memo=
{
memo
}
onRemove=
{
()
=>
handleDeleteRelation
(
memo
.
name
)
}
parentPage=
{
parentPage
}
/>;
onRemove=
{
()
=>
handleDeleteRelation
(
memo
.
name
)
}
})
}
parentPage=
{
parentPage
}
/>
))
}
</
div
>
</
div
>
</
div
>
</
div
>
);
);
...
...
web/src/components/MemoView/components/metadata/SectionHeader.tsx
View file @
d2b42121
...
@@ -30,9 +30,9 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) =
...
@@ -30,9 +30,9 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) =
tab
.
active
?
"text-foreground"
:
"text-muted-foreground hover:text-foreground"
,
tab
.
active
?
"text-foreground"
:
"text-muted-foreground hover:text-foreground"
,
)
}
)
}
>
>
{
tab
.
label
}
(
{
tab
.
count
}
)
{
tab
.
label
}
(
{
tab
.
count
}
)
</
button
>
</
button
>
{
idx
<
tabs
.
length
-
1
&&
<
span
className=
"text-muted-foreground/
50
"
>
/
</
span
>
}
{
idx
<
tabs
.
length
-
1
&&
<
span
className=
"text-muted-foreground/
40 font-mono text-xs
"
>
/
</
span
>
}
</
div
>
</
div
>
))
}
))
}
</
div
>
</
div
>
...
...
web/src/components/StatisticsView/StatisticsView.tsx
View file @
d2b42121
...
@@ -21,7 +21,7 @@ const StatisticsView = (props: Props) => {
...
@@ -21,7 +21,7 @@ const StatisticsView = (props: Props) => {
},
[
activityStats
]);
},
[
activityStats
]);
return
(
return
(
<
div
className=
"group w-full mt-2 flex flex-col
gap-1
text-muted-foreground animate-fade-in"
>
<
div
className=
"group w-full mt-2 flex flex-col text-muted-foreground animate-fade-in"
>
<
MonthNavigator
visibleMonth=
{
visibleMonthString
}
onMonthChange=
{
setVisibleMonthString
}
activityStats=
{
activityStats
}
/>
<
MonthNavigator
visibleMonth=
{
visibleMonthString
}
onMonthChange=
{
setVisibleMonthString
}
activityStats=
{
activityStats
}
/>
<
div
className=
"w-full animate-scale-in"
>
<
div
className=
"w-full animate-scale-in"
>
...
...
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