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
4ad60286
Commit
4ad60286
authored
Aug 07, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: use popover instead of dialog for memo relations
parent
a5978e76
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
194 additions
and
73 deletions
+194
-73
AddMemoRelationButton.tsx
...ponents/MemoEditor/ActionButton/AddMemoRelationButton.tsx
+0
-71
AddMemoRelationPopover.tsx
...onents/MemoEditor/ActionButton/AddMemoRelationPopover.tsx
+185
-0
index.tsx
web/src/components/MemoEditor/index.tsx
+2
-2
index.ts
web/src/theme/index.ts
+7
-0
No files found.
web/src/components/MemoEditor/ActionButton/AddMemoRelationButton.tsx
deleted
100644 → 0
View file @
a5978e76
import
{
IconButton
}
from
"@mui/joy"
;
import
{
uniqBy
}
from
"lodash-es"
;
import
{
useContext
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
showCreateMemoRelationDialog
from
"@/components/CreateMemoRelationDialog"
;
import
Icon
from
"@/components/Icon"
;
import
{
MemoRelation_Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
EditorRefActions
}
from
"../Editor"
;
import
{
MemoEditorContext
}
from
"../types"
;
interface
Props
{
editorRef
:
React
.
RefObject
<
EditorRefActions
>
;
}
const
AddMemoRelationButton
=
(
props
:
Props
)
=>
{
const
{
editorRef
}
=
props
;
const
context
=
useContext
(
MemoEditorContext
);
const
handleAddMemoRelationBtnClick
=
()
=>
{
showCreateMemoRelationDialog
({
onConfirm
:
(
memos
,
embedded
)
=>
{
// If embedded mode is enabled, embed the memo instead of creating a relation.
if
(
embedded
)
{
if
(
!
editorRef
.
current
)
{
toast
.
error
(
"Failed to embed memo"
);
return
;
}
const
cursorPosition
=
editorRef
.
current
.
getCursorPosition
();
const
prevValue
=
editorRef
.
current
.
getContent
().
slice
(
0
,
cursorPosition
);
if
(
prevValue
!==
""
&&
!
prevValue
.
endsWith
(
"
\n
"
))
{
editorRef
.
current
.
insertText
(
"
\n
"
);
}
for
(
const
memo
of
memos
)
{
editorRef
.
current
.
insertText
(
`![[memos/
${
memo
.
uid
}
]]\n`
);
}
setTimeout
(()
=>
{
editorRef
.
current
?.
scrollToCursor
();
editorRef
.
current
?.
focus
();
});
return
;
}
context
.
setRelationList
(
uniqBy
(
[
...
memos
.
map
((
memo
)
=>
({
memo
:
context
.
memoName
||
""
,
relatedMemo
:
memo
.
name
,
type
:
MemoRelation_Type
.
REFERENCE
,
})),
...
context
.
relationList
,
].
filter
((
relation
)
=>
relation
.
relatedMemo
!==
context
.
memoName
),
"relatedMemo"
,
),
);
},
filter
:
(
memo
:
Memo
)
=>
memo
.
name
!==
context
.
memoName
&&
!
context
.
relationList
.
some
((
relation
)
=>
relation
.
relatedMemo
===
memo
.
name
),
});
};
return
(
<
IconButton
size=
"sm"
onClick=
{
handleAddMemoRelationBtnClick
}
>
<
Icon
.
Link
className=
"w-5 h-5 mx-auto"
/>
</
IconButton
>
);
};
export
default
AddMemoRelationButton
;
web/src/components/
CreateMemoRelationDialog
.tsx
→
web/src/components/
MemoEditor/ActionButton/AddMemoRelationPopover
.tsx
View file @
4ad60286
import
{
Autocomplete
,
AutocompleteOption
,
Button
,
Checkbox
,
Chip
,
IconButton
}
from
"@mui/joy"
;
import
React
,
{
useState
}
from
"react"
;
import
{
uniqBy
}
from
"lodash-es"
;
import
React
,
{
useContext
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
useDebounce
from
"react-use/lib/useDebounce"
;
import
Icon
from
"@/components/Icon"
;
import
{
Popover
,
PopoverContent
,
PopoverTrigger
}
from
"@/components/ui/Popover"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
MemoRelation_Type
}
from
"@/types/proto/api/v1/memo_relation_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
generateDialog
}
from
"./Dialog
"
;
import
Icon
from
"./Icon
"
;
import
{
EditorRefActions
}
from
"../Editor
"
;
import
{
MemoEditorContext
}
from
"../types
"
;
interface
Props
extends
DialogProps
{
onConfirm
:
(
memos
:
Memo
[],
embedded
?:
boolean
)
=>
void
;
// Custom filter function for filtering memos.
filter
?:
(
memo
:
Memo
)
=>
boolean
;
interface
Props
{
editorRef
:
React
.
RefObject
<
EditorRefActions
>
;
}
const
CreateMemoRelationDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
,
onConfirm
}
=
props
;
const
AddMemoRelationPopover
=
(
props
:
Props
)
=>
{
const
{
editorRef
}
=
props
;
const
t
=
useTranslate
();
const
context
=
useContext
(
MemoEditorContext
);
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
[
embedded
,
setEmbedded
]
=
useState
<
boolean
>
(
true
);
const
filteredMemos
=
fetchedMemos
.
filter
((
memo
)
=>
!
selectedMemos
.
includes
(
memo
)
&&
(
!
props
.
filter
||
props
.
filter
(
memo
)));
const
[
popoverOpen
,
setPopoverOpen
]
=
useState
<
boolean
>
(
false
);
const
filteredMemos
=
fetchedMemos
.
filter
(
(
memo
)
=>
!
selectedMemos
.
includes
(
memo
)
&&
memo
.
name
!==
context
.
memoName
&&
!
context
.
relationList
.
some
((
relation
)
=>
relation
.
relatedMemo
===
memo
.
name
),
);
useDebounce
(
async
()
=>
{
setIsFetching
(
true
);
try
{
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
,
`include_comments == true`
];
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
];
if
(
searchText
)
{
filters
.
push
(
`content_search == [
${
JSON
.
stringify
(
searchText
)}
]`
);
}
...
...
@@ -74,85 +84,102 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
);
};
const
handleCloseDialog
=
()
=>
{
destroy
();
};
const
addMemoRelations
=
async
()
=>
{
// If embedded mode is enabled, embed the memo instead of creating a relation.
if
(
embedded
)
{
if
(
!
editorRef
.
current
)
{
toast
.
error
(
"Failed to embed memo"
);
return
;
}
const
handleConfirmBtnClick
=
async
()
=>
{
onConfirm
(
selectedMemos
,
embedded
);
destroy
();
const
cursorPosition
=
editorRef
.
current
.
getCursorPosition
();
const
prevValue
=
editorRef
.
current
.
getContent
().
slice
(
0
,
cursorPosition
);
if
(
prevValue
!==
""
&&
!
prevValue
.
endsWith
(
"
\n
"
))
{
editorRef
.
current
.
insertText
(
"
\n
"
);
}
for
(
const
memo
of
selectedMemos
)
{
editorRef
.
current
.
insertText
(
`![[memos/
${
memo
.
uid
}
]]\n`
);
}
setTimeout
(()
=>
{
editorRef
.
current
?.
scrollToCursor
();
editorRef
.
current
?.
focus
();
});
}
else
{
context
.
setRelationList
(
uniqBy
(
[
...
selectedMemos
.
map
((
memo
)
=>
({
memo
:
context
.
memoName
||
""
,
relatedMemo
:
memo
.
name
,
type
:
MemoRelation_Type
.
REFERENCE
,
})),
...
context
.
relationList
,
].
filter
((
relation
)
=>
relation
.
relatedMemo
!==
context
.
memoName
),
"relatedMemo"
,
),
);
}
setSelectedMemos
([]);
setPopoverOpen
(
false
);
};
return
(
<>
<
div
className=
"dialog-header-container !w-96"
>
<
p
className=
"title-text"
>
{
t
(
"reference.add-references"
)
}
</
p
>
<
IconButton
size=
"sm"
onClick=
{
()
=>
destroy
()
}
>
<
Icon
.
X
className=
"w-5 h-auto"
/>
<
Popover
open=
{
popoverOpen
}
onOpenChange=
{
setPopoverOpen
}
>
<
PopoverTrigger
>
<
IconButton
size=
"sm"
>
<
Icon
.
Link
className=
"w-5 h-5 mx-auto"
/>
</
IconButton
>
</
div
>
<
div
className=
"dialog-content-container max-w-[24rem]"
>
<
Autocomplete
className=
"w-full"
size=
"md"
clearOnBlur
disableClearable
placeholder=
{
t
(
"reference.search-placeholder"
)
}
noOptionsText=
{
t
(
"reference.no-memos-found"
)
}
options=
{
filteredMemos
}
loading=
{
isFetching
}
inputValue=
{
searchText
}
value=
{
selectedMemos
}
multiple
onInputChange=
{
(
_
,
value
)
=>
setSearchText
(
value
.
trim
())
}
getOptionKey=
{
(
memo
)
=>
memo
.
name
}
getOptionLabel=
{
(
memo
)
=>
memo
.
content
}
isOptionEqualToValue=
{
(
memo
,
value
)
=>
memo
.
name
===
value
.
name
}
renderOption=
{
(
props
,
memo
)
=>
(
<
AutocompleteOption
{
...
props
}
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
memo
.
displayTime
?.
toLocaleString
()
}
</
p
>
<
p
className=
"mt-0.5 text-sm leading-5 line-clamp-2"
>
{
searchText
?
getHighlightedContent
(
memo
.
content
)
:
memo
.
snippet
}
</
p
>
</
div
>
</
AutocompleteOption
>
)
}
renderTags=
{
(
memos
)
=>
memos
.
map
((
memo
)
=>
(
<
Chip
key=
{
memo
.
name
}
className=
"!max-w-full !rounded"
variant=
"outlined"
color=
"neutral"
>
</
PopoverTrigger
>
<
PopoverContent
align=
"center"
>
<
div
className=
"w-[16rem] flex flex-col justify-start items-start"
>
<
Autocomplete
className=
"w-full"
size=
"md"
clearOnBlur
disableClearable
placeholder=
{
t
(
"reference.search-placeholder"
)
}
noOptionsText=
{
t
(
"reference.no-memos-found"
)
}
options=
{
filteredMemos
}
loading=
{
isFetching
}
inputValue=
{
searchText
}
value=
{
selectedMemos
}
multiple
onInputChange=
{
(
_
,
value
)
=>
setSearchText
(
value
.
trim
())
}
getOptionKey=
{
(
memo
)
=>
memo
.
name
}
getOptionLabel=
{
(
memo
)
=>
memo
.
content
}
isOptionEqualToValue=
{
(
memo
,
value
)
=>
memo
.
name
===
value
.
name
}
renderOption=
{
(
props
,
memo
)
=>
(
<
AutocompleteOption
{
...
props
}
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
p
className=
"text-xs text-gray-400 select-none"
>
{
memo
.
displayTime
?.
toLocaleString
()
}
</
p
>
<
span
className=
"w-full text-sm leading-5 truncate"
>
{
memo
.
content
}
</
span
>
<
p
className=
"mt-0.5 text-sm leading-5 line-clamp-2"
>
{
searchText
?
getHighlightedContent
(
memo
.
content
)
:
memo
.
snippet
}
</
p
>
</
div
>
</
Chip
>
))
}
onChange=
{
(
_
,
value
)
=>
setSelectedMemos
(
value
)
}
/>
<
div
className=
"mt-3"
>
<
Checkbox
label=
{
t
(
"reference.embedded-usage"
)
}
checked=
{
embedded
}
onChange=
{
(
e
)
=>
setEmbedded
(
e
.
target
.
checked
)
}
/>
</
div
>
<
div
className=
"mt-4 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=
{
selectedMemos
.
length
===
0
}
>
{
t
(
"common.confirm"
)
}
</
Button
>
</
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"
>
{
memo
.
displayTime
?.
toLocaleString
()
}
</
p
>
<
span
className=
"w-full text-sm leading-5 truncate"
>
{
memo
.
content
}
</
span
>
</
div
>
</
Chip
>
))
}
onChange=
{
(
_
,
value
)
=>
setSelectedMemos
(
value
)
}
/>
<
div
className=
"mt-2"
>
<
Checkbox
size=
"sm"
label=
{
t
(
"reference.embedded-usage"
)
}
checked=
{
embedded
}
onChange=
{
(
e
)
=>
setEmbedded
(
e
.
target
.
checked
)
}
/>
</
div
>
<
div
className=
"mt-2 w-full flex flex-row justify-start items-center space-x-1"
>
<
Button
size=
"sm"
onClick=
{
addMemoRelations
}
disabled=
{
selectedMemos
.
length
===
0
}
>
{
t
(
"common.confirm"
)
}
</
Button
>
</
div
>
</
div
>
</
div
>
</>
</
PopoverContent
>
</
Popover
>
);
};
function
showCreateMemoRelationDialog
(
props
:
Omit
<
Props
,
"destroy"
>
)
{
generateDialog
(
{
className
:
"create-memo-relation-dialog"
,
dialogName
:
"create-memo-relation-dialog"
,
},
CreateMemoRelationDialog
,
props
,
);
}
export
default
showCreateMemoRelationDialog
;
export
default
AddMemoRelationPopover
;
web/src/components/MemoEditor/index.tsx
View file @
4ad60286
...
...
@@ -20,7 +20,7 @@ import { useTranslate } from "@/utils/i18n";
import
{
convertVisibilityFromString
,
convertVisibilityToString
}
from
"@/utils/memo"
;
import
Icon
from
"../Icon"
;
import
VisibilityIcon
from
"../VisibilityIcon"
;
import
AddMemoRelation
Button
from
"./ActionButton/AddMemoRelationButton
"
;
import
AddMemoRelation
Popover
from
"./ActionButton/AddMemoRelationPopover
"
;
import
MarkdownMenu
from
"./ActionButton/MarkdownMenu"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
UploadResourceButton
from
"./ActionButton/UploadResourceButton"
;
...
...
@@ -432,7 +432,7 @@ const MemoEditor = (props: Props) => {
<
TagSelector
editorRef=
{
editorRef
}
/>
<
MarkdownMenu
editorRef=
{
editorRef
}
/>
<
UploadResourceButton
/>
<
AddMemoRelation
Button
editorRef=
{
editorRef
}
/>
<
AddMemoRelation
Popover
editorRef=
{
editorRef
}
/>
</
div
>
</
div
>
<
Divider
className=
"!mt-2"
/>
...
...
web/src/theme/index.ts
View file @
4ad60286
...
...
@@ -22,6 +22,13 @@ const theme = extendTheme({
},
},
},
JoyAutocomplete
:
{
styleOverrides
:
{
listbox
:
{
zIndex
:
9999
,
},
},
},
},
});
...
...
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