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
08ac60cc
Commit
08ac60cc
authored
Jan 25, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update memo relation dialog
parent
f654d3c9
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
107 additions
and
81 deletions
+107
-81
CreateMemoRelationDialog.tsx
web/src/components/CreateMemoRelationDialog.tsx
+89
-63
RelationListView.tsx
web/src/components/MemoEditor/RelationListView.tsx
+1
-1
Archived.tsx
web/src/pages/Archived.tsx
+2
-2
Explore.tsx
web/src/pages/Explore.tsx
+3
-3
Home.tsx
web/src/pages/Home.tsx
+3
-3
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+1
-1
Timeline.tsx
web/src/pages/Timeline.tsx
+5
-5
UserProfile.tsx
web/src/pages/UserProfile.tsx
+3
-3
No files found.
web/src/components/CreateMemoRelationDialog.tsx
View file @
08ac60cc
import
{
Button
,
IconButton
,
Input
}
from
"@mui/joy"
;
import
{
isNaN
,
unionBy
}
from
"lodash-es"
;
import
{
Autocomplete
,
AutocompleteOption
,
Button
,
Chip
,
IconButton
}
from
"@mui/joy"
;
import
React
,
{
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
useDebounce
from
"react-use/lib/useDebounce"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
getDateTimeString
}
from
"@/helpers/datetime"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
Memo
}
from
"@/types/proto/api/v2/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog"
;
...
...
@@ -16,46 +18,58 @@ interface Props extends DialogProps {
const
CreateMemoRelationDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
,
onCancel
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
[
memoId
,
setMemoId
]
=
useState
<
string
>
(
""
);
const
[
memoList
,
setMemoList
]
=
useState
<
Memo
[]
>
([]);
const
user
=
useCurrentUser
();
const
[
searchText
,
setSearchText
]
=
useState
<
string
>
(
""
);
const
[
isFetching
,
setIsFetching
]
=
useState
<
boolean
>
(
true
);
const
[
fetchedMemos
,
setFetchedMemos
]
=
useState
<
Memo
[]
>
([]);
const
[
selectedMemos
,
setSelectedMemos
]
=
useState
<
Memo
[]
>
([]);
const
filteredMemos
=
fetchedMemos
.
filter
((
memo
)
=>
!
selectedMemos
.
includes
(
memo
));
const
handleMemoIdInputKeyDown
=
(
event
:
React
.
KeyboardEvent
)
=>
{
if
(
event
.
key
===
"Enter"
)
{
handleSaveBtnClick
();
}
};
const
handleMemoIdChanged
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
memoId
=
event
.
target
.
value
;
setMemoId
(
memoId
.
trim
());
};
const
handleSaveBtnClick
=
async
()
=>
{
const
id
=
Number
(
memoId
);
if
(
isNaN
(
id
))
{
toast
.
error
(
"Invalid memo id"
);
return
;
}
try
{
const
{
memo
}
=
await
memoServiceClient
.
getMemo
({
id
,
});
if
(
!
memo
)
{
toast
.
error
(
"Not found memo"
);
return
;
useDebounce
(
async
()
=>
{
setIsFetching
(
true
);
try
{
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
];
if
(
searchText
)
{
filters
.
push
(
`content_search == [
${
JSON
.
stringify
(
searchText
)}
]`
);
}
const
{
memos
}
=
await
memoServiceClient
.
listMemos
({
limit
:
10
,
filter
:
filters
.
length
>
0
?
filters
.
join
(
" && "
)
:
undefined
,
});
setFetchedMemos
(
memos
);
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
}
setIsFetching
(
false
);
},
300
,
[
searchText
]
);
setMemoId
(
""
);
setMemoList
(
unionBy
([
memo
,
...
memoList
],
"id"
));
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
const
getHighlightedContent
=
(
content
:
string
)
=>
{
const
index
=
content
.
toLowerCase
().
indexOf
(
searchText
.
toLowerCase
());
if
(
index
===
-
1
)
{
return
content
;
}
let
before
=
content
.
slice
(
0
,
index
);
if
(
before
.
length
>
20
)
{
before
=
"..."
+
before
.
slice
(
before
.
length
-
20
);
}
const
highlighted
=
content
.
slice
(
index
,
index
+
searchText
.
length
);
let
after
=
content
.
slice
(
index
+
searchText
.
length
);
if
(
after
.
length
>
20
)
{
after
=
after
.
slice
(
0
,
20
)
+
"..."
;
}
};
const
handleDeleteMemoRelation
=
async
(
memo
:
Memo
)
=>
{
setMemoList
(
memoList
.
filter
((
m
)
=>
m
!==
memo
));
return
(
<>
{
before
}
<
mark
className=
"font-medium"
>
{
highlighted
}
</
mark
>
{
after
}
</>
);
};
const
handleCloseDialog
=
()
=>
{
...
...
@@ -67,7 +81,7 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
const
handleConfirmBtnClick
=
async
()
=>
{
if
(
onConfirm
)
{
onConfirm
(
memoList
.
map
((
memo
)
=>
memo
.
id
));
onConfirm
(
selectedMemos
.
map
((
memo
)
=>
memo
.
id
));
}
destroy
();
};
...
...
@@ -81,37 +95,49 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
</
IconButton
>
</
div
>
<
div
className=
"dialog-content-container !w-80"
>
<
Input
className=
"
mb-2
"
<
Autocomplete
className=
"
w-full
"
size=
"md"
placeholder=
{
"Input memo ID. e.g. 26"
}
value=
{
memoId
}
onChange=
{
handleMemoIdChanged
}
onKeyDown=
{
handleMemoIdInputKeyDown
}
fullWidth
endDecorator=
{
<
Icon
.
Check
onClick=
{
handleSaveBtnClick
}
className=
"w-4 h-auto cursor-pointer hover:opacity-80"
/>
}
/>
{
memoList
.
length
>
0
&&
(
<>
<
div
className=
"w-full flex flex-row justify-start items-start flex-wrap gap-2 mt-1"
>
{
memoList
.
map
((
memo
)
=>
(
<
div
className=
"max-w-[50%] text-sm px-2 py-1 flex flex-row justify-start items-center border rounded-md cursor-pointer truncate opacity-80 text-gray-600 dark:text-gray-400 dark:border-zinc-700 dark:bg-zinc-900 hover:opacity-60 hover:line-through"
key=
{
memo
.
name
}
onClick=
{
()
=>
handleDeleteMemoRelation
(
memo
)
}
>
<
span
className=
"max-w-full text-ellipsis whitespace-nowrap overflow-hidden"
>
{
memo
.
content
}
</
span
>
<
Icon
.
X
className=
"opacity-60 w-4 h-auto shrink-0 ml-1"
/>
clearOnBlur
disableClearable
placeholder=
{
"Search content"
}
noOptionsText=
{
"No memos found"
}
options=
{
filteredMemos
}
loading=
{
isFetching
}
inputValue=
{
searchText
}
value=
{
selectedMemos
}
multiple
onInputChange=
{
(
_
,
value
)
=>
setSearchText
(
value
.
trim
())
}
getOptionKey=
{
(
option
)
=>
option
.
name
}
getOptionLabel=
{
(
option
)
=>
option
.
content
}
isOptionEqualToValue=
{
(
option
,
value
)
=>
option
.
id
===
value
.
id
}
renderOption=
{
(
props
,
option
)
=>
(
<
AutocompleteOption
{
...
props
}
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
getDateTimeString
(
option
.
displayTime
)
}
</
p
>
<
p
className=
"mt-0.5 text-sm leading-5 line-clamp-2"
>
{
searchText
?
getHighlightedContent
(
option
.
content
)
:
option
.
content
}
</
p
>
</
div
>
</
AutocompleteOption
>
)
}
renderTags=
{
(
memos
)
=>
memos
.
map
((
memo
)
=>
(
<
Chip
key=
{
memo
.
name
}
className=
"!max-w-full !rounded"
variant=
"outlined"
color=
"neutral"
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
getDateTimeString
(
memo
.
displayTime
)
}
</
p
>
<
span
className=
"w-full text-sm leading-5 truncate"
>
{
memo
.
content
}
</
span
>
</
div
>
))
}
</
div
>
</>
)
}
</
Chip
>
))
}
onChange=
{
(
_
,
value
)
=>
setSelectedMemos
(
value
)
}
/>
<
div
className=
"mt-2 w-full flex flex-row justify-end items-center space-x-1"
>
<
Button
variant=
"plain"
color=
"neutral"
onClick=
{
handleCloseDialog
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
onClick=
{
handleConfirmBtnClick
}
disabled=
{
memoList
.
length
===
0
}
>
<
Button
onClick=
{
handleConfirmBtnClick
}
disabled=
{
selectedMemos
.
length
===
0
}
>
{
t
(
"common.confirm"
)
}
</
Button
>
</
div
>
...
...
web/src/components/MemoEditor/RelationListView.tsx
View file @
08ac60cc
...
...
@@ -43,7 +43,7 @@ const RelationListView = (props: Props) => {
>
<
Icon
.
Link
className=
"w-4 h-auto shrink-0 opacity-80"
/>
<
span
className=
"mx-1 max-w-full text-ellipsis whitespace-nowrap overflow-hidden"
>
{
memo
.
content
}
</
span
>
<
Icon
.
X
className=
"w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"
/>
<
Icon
.
X
className=
"w-4 h-auto cursor-pointer
shrink-0
opacity-60 hover:opacity-100"
/>
</
div
>
);
})
}
...
...
web/src/pages/Archived.tsx
View file @
08ac60cc
...
...
@@ -34,10 +34,10 @@ const Archived = () => {
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
"row_status == 'ARCHIVED'"
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
web/src/pages/Explore.tsx
View file @
08ac60cc
...
...
@@ -32,10 +32,10 @@ const Explore = () => {
const
filters
=
[
`row_status == "NORMAL"`
,
`visibilities == [
${
user
?
"'PUBLIC', 'PROTECTED'"
:
"'PUBLIC'"
}
]`
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
@@ -56,7 +56,7 @@ const Explore = () => {
<
div
className=
"relative w-full h-auto flex flex-col justify-start items-start px-4 sm:px-6"
>
<
MemoFilter
className=
"px-2 pb-2"
/>
{
sortedMemos
.
map
((
memo
)
=>
(
<
MemoView
key=
{
memo
.
id
}
memo=
{
memo
}
showCreator
/>
<
MemoView
key=
{
`${memo.id}-${memo.displayTime}`
}
memo=
{
memo
}
showCreator
/>
))
}
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-4"
>
...
...
web/src/pages/Home.tsx
View file @
08ac60cc
...
...
@@ -42,10 +42,10 @@ const Home = () => {
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
,
`order_by_pinned == true`
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
@@ -73,7 +73,7 @@ const Home = () => {
<
div
className=
"flex flex-col justify-start items-start w-full max-w-full pb-28"
>
<
MemoFilter
className=
"px-2 pb-2"
/>
{
sortedMemos
.
map
((
memo
)
=>
(
<
MemoView
key=
{
`${memo.id}-${memo.
update
Time}`
}
memo=
{
memo
}
showVisibility
showPinned
/>
<
MemoView
key=
{
`${memo.id}-${memo.
display
Time}`
}
memo=
{
memo
}
showVisibility
showPinned
/>
))
}
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-4"
>
...
...
web/src/pages/MemoDetail.tsx
View file @
08ac60cc
...
...
@@ -198,7 +198,7 @@ const MemoDetail = () => {
<
span
className=
"text-gray-400 text-sm ml-0.5"
>
(
{
comments
.
length
}
)
</
span
>
</
div
>
{
comments
.
map
((
comment
)
=>
(
<
MemoView
key=
{
comment
.
id
}
memo=
{
comment
}
showCreator
/>
<
MemoView
key=
{
`${memo.id}-${memo.displayTime}`
}
memo=
{
comment
}
showCreator
/>
))
}
</>
)
}
...
...
web/src/pages/Timeline.tsx
View file @
08ac60cc
...
...
@@ -69,10 +69,10 @@ const Timeline = () => {
const
filters
=
[
`row_status == "NORMAL"`
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
@@ -90,10 +90,10 @@ const Timeline = () => {
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
@@ -159,7 +159,7 @@ const Timeline = () => {
<
div
className=
{
classNames
(
"flex flex-col justify-start items-start"
,
md
?
"w-[calc(100%-8rem)]"
:
"w-full"
)
}
>
{
group
.
memos
.
map
((
memo
,
index
)
=>
(
<
div
key=
{
`${memo.id}-${memo.
create
Time}`
}
key=
{
`${memo.id}-${memo.
display
Time}`
}
className=
{
classNames
(
"relative w-full flex flex-col justify-start items-start pl-4 sm:pl-10 pt-0"
)
}
>
<
MemoView
className=
"!border !border-gray-100 dark:!border-zinc-700"
memo=
{
memo
}
/>
...
...
web/src/pages/UserProfile.tsx
View file @
08ac60cc
...
...
@@ -67,10 +67,10 @@ const UserProfile = () => {
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
,
`order_by_pinned == true`
];
const
contentSearch
:
string
[]
=
[];
if
(
tagQuery
)
{
contentSearch
.
push
(
`"#
${
tagQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
`#
${
tagQuery
}
`
)
);
}
if
(
textQuery
)
{
contentSearch
.
push
(
`"
${
textQuery
}
"`
);
contentSearch
.
push
(
JSON
.
stringify
(
textQuery
)
);
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
...
...
@@ -107,7 +107,7 @@ const UserProfile = () => {
</
div
>
<
MemoFilter
className=
"px-2 pb-3"
/>
{
sortedMemos
.
map
((
memo
)
=>
(
<
MemoView
key=
{
memo
.
id
}
memo=
{
memo
}
showVisibility
showPinned
/>
<
MemoView
key=
{
`${memo.id}-${memo.displayTime}`
}
memo=
{
memo
}
showVisibility
showPinned
/>
))
}
{
isRequesting
?
(
<
div
className=
"flex flex-col justify-start items-center w-full my-4"
>
...
...
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