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
e40621eb
Commit
e40621eb
authored
Oct 01, 2023
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: implement memo content views
parent
fd395e56
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
140 additions
and
71 deletions
+140
-71
FloatingNavButton.tsx
web/src/components/FloatingNavButton.tsx
+5
-0
Memo.tsx
web/src/components/Memo.tsx
+14
-26
index.tsx
web/src/components/MemoEditor/Editor/index.tsx
+0
-1
RelationListView.tsx
web/src/components/MemoEditor/RelationListView.tsx
+11
-10
index.tsx
web/src/components/MemoEditor/index.tsx
+0
-1
MemoList.tsx
web/src/components/MemoList.tsx
+1
-1
MemoRelationListView.tsx
web/src/components/MemoRelationListView.tsx
+5
-6
VisibilityIcon.tsx
web/src/components/VisibilityIcon.tsx
+26
-0
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+76
-25
memo.d.ts
web/src/types/modules/memo.d.ts
+1
-0
memoRelation.d.ts
web/src/types/modules/memoRelation.d.ts
+1
-1
No files found.
web/src/components/FloatingNavButton.tsx
View file @
e40621eb
import
{
Dropdown
,
IconButton
,
Menu
,
MenuButton
}
from
"@mui/joy"
;
import
{
useEffect
}
from
"react"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
Icon
from
"./Icon"
;
...
...
@@ -7,6 +8,10 @@ const FloatingNavButton = () => {
const
t
=
useTranslate
();
const
navigateTo
=
useNavigateTo
();
useEffect
(()
=>
{
handleScrollToTop
();
},
[]);
const
handleScrollToTop
=
()
=>
{
document
.
body
.
querySelector
(
"#root"
)?.
scrollTo
({
top
:
0
,
behavior
:
"smooth"
});
};
...
...
web/src/components/Memo.tsx
View file @
e40621eb
import
{
Divider
,
Select
,
Tooltip
,
Option
}
from
"@mui/joy"
;
import
{
Divider
,
Tooltip
}
from
"@mui/joy"
;
import
{
memo
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
UNKNOWN_ID
,
VISIBILITY_SELECTOR_ITEMS
}
from
"@/helpers/consts"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
getRelativeTimeString
}
from
"@/helpers/datetime"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
...
...
@@ -19,11 +19,13 @@ import MemoRelationListView from "./MemoRelationListView";
import
MemoResourceListView
from
"./MemoResourceListView"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
UserAvatar
from
"./UserAvatar"
;
import
VisibilityIcon
from
"./VisibilityIcon"
;
import
"@/less/memo.less"
;
interface
Props
{
memo
:
Memo
;
showVisibility
?:
boolean
;
showCommentEntry
?:
boolean
;
lazyRendering
?:
boolean
;
}
...
...
@@ -90,14 +92,6 @@ const Memo: React.FC<Props> = (props: Props) => {
return
<
div
className=
{
`memo-wrapper min-h-[128px] ${"memos-" + memo.id}`
}
ref=
{
memoContainerRef
}
></
div
>;
}
const
handleMemoVisibilityOptionChanged
=
async
(
value
:
string
)
=>
{
const
visibilityValue
=
value
as
Visibility
;
await
memoStore
.
patchMemo
({
id
:
memo
.
id
,
visibility
:
visibilityValue
,
});
};
const
handleGotoMemoDetailPage
=
(
event
:
React
.
MouseEvent
<
HTMLDivElement
>
)
=>
{
if
(
event
.
altKey
)
{
showChangeMemoCreatedTsDialog
(
memo
.
id
);
...
...
@@ -274,6 +268,13 @@ const Memo: React.FC<Props> = (props: Props) => {
)
}
</
div
>
</
div
>
{
memo
.
parent
&&
props
.
showCommentEntry
&&
(
<
div
>
<
Link
to=
{
`/m/${memo.parent.id}`
}
>
<
span
className=
"text-xs text-gray-400 opacity-80 dark:text-gray-500"
>
This is a comment of #
{
memo
.
parent
.
id
}
</
span
>
</
Link
>
</
div
>
)
}
<
MemoContent
content=
{
memo
.
content
}
onMemoContentClick=
{
handleMemoContentClick
}
...
...
@@ -299,22 +300,9 @@ const Memo: React.FC<Props> = (props: Props) => {
<>
<
Icon
.
Dot
className=
"w-4 h-auto text-gray-400 dark:text-zinc-400"
/>
<
Tooltip
title=
{
"The visibility of memo"
}
placement=
"top"
>
<
Select
className=
"w-auto text-sm"
variant=
"plain"
value=
{
memo
.
visibility
}
onChange=
{
(
_
,
visibility
)
=>
{
if
(
visibility
)
{
handleMemoVisibilityOptionChanged
(
visibility
);
}
}
}
>
{
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
(
<
Option
key=
{
item
.
value
}
value=
{
item
.
value
}
className=
"whitespace-nowrap"
>
{
item
.
text
}
</
Option
>
))
}
</
Select
>
<
span
>
<
VisibilityIcon
visibility=
{
memo
.
visibility
}
/>
</
span
>
</
Tooltip
>
</>
)
}
...
...
web/src/components/MemoEditor/Editor/index.tsx
View file @
e40621eb
...
...
@@ -98,7 +98,6 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
setContent
:
(
text
:
string
)
=>
{
if
(
editorRef
.
current
)
{
editorRef
.
current
.
value
=
text
;
editorRef
.
current
.
focus
();
handleContentChangeCallback
(
editorRef
.
current
.
value
);
updateEditorHeight
();
}
...
...
web/src/components/MemoEditor/RelationListView.tsx
View file @
e40621eb
...
...
@@ -17,18 +17,19 @@ const RelationListView = (props: Props) => {
const
[
formatedMemoRelationList
,
setFormatedMemoRelationList
]
=
useState
<
FormatedMemoRelation
[]
>
([]);
useEffect
(()
=>
{
const
fetchRelatedMemoList
=
async
()
=>
{
const
requests
=
relationList
.
map
(
async
(
relation
)
=>
{
const
relatedMemo
=
await
memoCacheStore
.
getOrFetchMemoById
(
relation
.
relatedMemoId
);
return
{
...
relation
,
relatedMemo
,
};
});
(
async
()
=>
{
const
requests
=
relationList
.
filter
((
relation
)
=>
relation
.
type
===
"REFERENCE"
)
.
map
(
async
(
relation
)
=>
{
const
relatedMemo
=
await
memoCacheStore
.
getOrFetchMemoById
(
relation
.
relatedMemoId
);
return
{
...
relation
,
relatedMemo
,
};
});
const
list
=
await
Promise
.
all
(
requests
);
setFormatedMemoRelationList
(
list
);
};
fetchRelatedMemoList
();
})();
},
[
relationList
]);
const
handleDeleteRelation
=
async
(
memoRelation
:
FormatedMemoRelation
)
=>
{
...
...
web/src/components/MemoEditor/index.tsx
View file @
e40621eb
...
...
@@ -49,7 +49,6 @@ const MemoEditor = (props: Props) => {
const
memoStore
=
useMemoStore
();
const
tagStore
=
useTagStore
();
const
resourceStore
=
useResourceStore
();
const
[
state
,
setState
]
=
useState
<
State
>
({
memoVisibility
:
"PRIVATE"
,
resourceList
:
[],
...
...
web/src/components/MemoList.tsx
View file @
e40621eb
...
...
@@ -133,7 +133,7 @@ const MemoList: React.FC = () => {
return (
<
div
className=
"memo-list-container"
>
{
sortedMemos
.
map
((
memo
)
=>
(
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
lazyRendering
showVisibility
/>
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
lazyRendering
showVisibility
showCommentEntry
/>
))
}
{
isFetching
?
(
<
div
className=
"status-text-container fetching-tip"
>
...
...
web/src/components/MemoRelationListView.tsx
View file @
e40621eb
...
...
@@ -9,16 +9,15 @@ interface Props {
const
MemoRelationListView
=
(
props
:
Props
)
=>
{
const
memoCacheStore
=
useMemoCacheStore
();
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
relationList
=
props
.
relationList
;
useEffect
(()
=>
{
const
fetchRelatedMemoList
=
async
()
=>
{
(
async
()
=>
{
// Only show reference relations.
const
relationList
=
props
.
relationList
.
filter
((
relation
)
=>
relation
.
type
===
"REFERENCE"
);
const
memoList
=
await
Promise
.
all
(
relationList
.
map
((
relation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
relation
.
relatedMemoId
)));
setRelatedMemoList
(
memoList
);
};
fetchRelatedMemoList
();
},
[
relationList
]);
})();
},
[
props
.
relationList
]);
const
handleGotoMemoDetail
=
(
memo
:
Memo
)
=>
{
window
.
open
(
`/m/
${
memo
.
id
}
`
,
"_blank"
);
...
...
web/src/components/VisibilityIcon.tsx
0 → 100644
View file @
e40621eb
import
classNames
from
"classnames"
;
import
Icon
from
"./Icon"
;
interface
Props
{
visibility
:
Visibility
;
}
const
VisibilityIcon
=
(
props
:
Props
)
=>
{
const
{
visibility
}
=
props
;
let
VIcon
=
null
;
if
(
visibility
===
"PRIVATE"
)
{
VIcon
=
Icon
.
Lock
;
}
else
if
(
visibility
===
"PROTECTED"
)
{
VIcon
=
Icon
.
Users
;
}
else
if
(
visibility
===
"PUBLIC"
)
{
VIcon
=
Icon
.
Globe2
;
}
if
(
!
VIcon
)
{
return
null
;
}
return
<
VIcon
className=
{
classNames
(
"w-4 h-auto text-gray-400"
)
}
/>;
};
export
default
VisibilityIcon
;
web/src/pages/MemoDetail.tsx
View file @
e40621eb
import
{
Divider
,
Select
,
Tooltip
,
Option
,
IconButton
}
from
"@mui/joy"
;
import
{
Select
,
Tooltip
,
Option
,
IconButton
,
Divider
}
from
"@mui/joy"
;
import
copy
from
"copy-to-clipboard"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
Link
,
useParams
}
from
"react-router-dom"
;
import
FloatingNavButton
from
"@/components/FloatingNavButton"
;
import
Icon
from
"@/components/Icon"
;
import
Memo
from
"@/components/Memo"
;
import
MemoContent
from
"@/components/MemoContent"
;
import
MemoEditor
from
"@/components/MemoEditor"
;
import
showMemoEditorDialog
from
"@/components/MemoEditor/MemoEditorDialog"
;
import
MemoRelationListView
from
"@/components/MemoRelationListView"
;
import
MemoResourceListView
from
"@/components/MemoResourceListView"
;
import
showShareMemoDialog
from
"@/components/ShareMemoDialog"
;
import
UserAvatar
from
"@/components/UserAvatar"
;
import
{
VISIBILITY_SELECTOR_ITEMS
}
from
"@/helpers/consts"
;
import
VisibilityIcon
from
"@/components/VisibilityIcon"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
UNKNOWN_ID
,
VISIBILITY_SELECTOR_ITEMS
}
from
"@/helpers/consts"
;
import
{
getDateTimeString
}
from
"@/helpers/datetime"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
...
...
@@ -29,11 +33,13 @@ const MemoDetail = () => {
const
userV1Store
=
useUserV1Store
();
const
currentUser
=
useCurrentUser
();
const
[
user
,
setUser
]
=
useState
<
User
>
();
const
[
comments
,
setComments
]
=
useState
<
Memo
[]
>
([]);
const
{
systemStatus
}
=
globalStore
.
state
;
const
memoId
=
Number
(
params
.
memoId
);
const
memo
=
memoStore
.
state
.
memos
.
find
((
memo
)
=>
memo
.
id
===
memoId
);
const
allowEdit
=
memo
?.
creatorUsername
===
currentUser
?.
username
;
// Prepare memo.
useEffect
(()
=>
{
if
(
memoId
&&
!
isNaN
(
memoId
))
{
memoStore
...
...
@@ -51,6 +57,28 @@ const MemoDetail = () => {
}
},
[
memoId
]);
// Prepare memo comments.
useEffect
(()
=>
{
if
(
!
memo
)
{
return
;
}
fetchMemoComments
();
},
[
memo
]);
const
fetchMemoComments
=
async
()
=>
{
if
(
!
memo
)
{
return
;
}
const
{
memos
}
=
await
memoServiceClient
.
listMemoComments
({
id
:
memo
.
id
,
});
const
requests
=
memos
.
map
((
memo
)
=>
memoStore
.
fetchMemoById
(
memo
.
id
));
const
composedMemos
=
await
Promise
.
all
(
requests
);
setComments
(
composedMemos
);
};
if
(
!
memo
)
{
return
null
;
}
...
...
@@ -76,21 +104,20 @@ const MemoDetail = () => {
return
(
<>
<
section
className=
"relative top-0 w-full min-h-full overflow-x-hidden bg-
white
dark:bg-zinc-800"
>
<
div
className=
"relative w-full
min-h-full mx-auto flex flex-col justify-start items-center pb-6
"
>
<
section
className=
"relative top-0 w-full min-h-full overflow-x-hidden bg-
zinc-100
dark:bg-zinc-800"
>
<
div
className=
"relative w-full
h-auto mx-auto flex flex-col justify-start items-center bg-white dark:bg-zinc-900
"
>
<
div
className=
"w-full flex flex-col justify-start items-center py-8"
>
<
UserAvatar
className=
"!w-20 h-auto mb-2 drop-shadow"
avatarUrl=
{
systemStatus
.
customizedProfile
.
logoUrl
}
/>
<
p
className=
"text-3xl text-black opacity-80 dark:text-gray-200"
>
{
systemStatus
.
customizedProfile
.
name
}
</
p
>
</
div
>
<
div
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"
>
<
div
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4
pb-6
"
>
<
div
className=
"w-full mb-4 flex flex-row justify-start items-center mr-1"
>
<
span
className=
"text-gray-400 select-none"
>
{
getDateTimeString
(
memo
.
displayTs
)
}
</
span
>
</
div
>
<
MemoContent
content=
{
memo
.
content
}
/>
<
MemoResourceListView
resourceList=
{
memo
.
resourceList
}
/>
<
MemoRelationListView
relationList=
{
memo
.
relationList
}
/>
<
Divider
className=
"!my-6"
/>
<
div
className=
"w-full flex flex-col sm:flex-row justify-start sm:justify-between sm:items-center gap-2"
>
<
div
className=
"w-full mt-4 flex flex-col sm:flex-row justify-start sm:justify-between sm:items-center gap-2"
>
<
div
className=
"flex flex-row justify-start items-center"
>
<
Tooltip
title=
{
"The identifier of memo"
}
placement=
"top"
>
<
span
className=
"text-sm text-gray-500 dark:text-gray-400"
>
#
{
memo
.
id
}
</
span
>
...
...
@@ -103,24 +130,23 @@ const MemoDetail = () => {
{
allowEdit
&&
(
<>
<
Icon
.
Dot
className=
"w-4 h-auto text-gray-400 dark:text-zinc-400"
/>
<
Tooltip
title=
{
"The visibility of memo"
}
placement=
"top"
>
<
Select
className=
"w-auto text-sm"
variant=
"plain"
value=
{
memo
.
visibility
}
onChange=
{
(
_
,
visibility
)
=>
{
if
(
visibility
)
{
handleMemoVisibilityOptionChanged
(
visibility
);
}
}
}
>
{
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
(
<
Option
key=
{
item
.
value
}
value=
{
item
.
value
}
className=
"whitespace-nowrap"
>
{
item
.
text
}
</
Option
>
))
}
</
Select
>
</
Tooltip
>
<
Select
className=
"w-auto text-sm"
variant=
"plain"
value=
{
memo
.
visibility
}
startDecorator=
{
<
VisibilityIcon
visibility=
{
memo
.
visibility
}
/>
}
onChange=
{
(
_
,
visibility
)
=>
{
if
(
visibility
)
{
handleMemoVisibilityOptionChanged
(
visibility
);
}
}
}
>
{
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
(
<
Option
key=
{
item
.
value
}
value=
{
item
.
value
}
className=
"whitespace-nowrap"
>
{
item
.
text
}
</
Option
>
))
}
</
Select
>
</>
)
}
</
div
>
...
...
@@ -146,6 +172,31 @@ const MemoDetail = () => {
</
div
>
</
div
>
</
div
>
<
div
className=
"py-6 w-full border-t dark:border-t-zinc-700"
>
<
div
className=
"relative mx-auto flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 gap-y-1"
>
{
comments
.
map
((
comment
)
=>
(
<
Memo
key=
{
comment
.
id
}
memo=
{
comment
}
/>
))
}
{
comments
.
length
===
0
&&
(
<
div
className=
"w-full flex flex-col justify-center items-center py-6"
>
<
Icon
.
MessageCircle
strokeWidth=
{
1
}
className=
"w-8 h-auto text-gray-400"
/>
<
p
className=
"text-gray-400 italic text-sm"
>
No comments
</
p
>
</
div
>
)
}
{
/* Only show comment editor when user login */
}
{
currentUser
&&
(
<>
{
comments
.
length
===
0
&&
<
Divider
className=
"!my-4"
/>
}
<
MemoEditor
key=
{
memo
.
id
}
className=
"border-none"
relationList=
{
[{
memoId
:
UNKNOWN_ID
,
relatedMemoId
:
memo
.
id
,
type
:
"COMMENT"
}]
}
onConfirm=
{
()
=>
fetchMemoComments
()
}
/>
</>
)
}
</
div
>
</
div
>
</
section
>
<
FloatingNavButton
/>
...
...
web/src/types/modules/memo.d.ts
View file @
e40621eb
...
...
@@ -18,6 +18,7 @@ interface Memo {
creatorName
:
string
;
resourceList
:
any
[];
relationList
:
MemoRelation
[];
parent
?:
Memo
;
}
interface
MemoCreate
{
...
...
web/src/types/modules/memoRelation.d.ts
View file @
e40621eb
type
MemoRelationType
=
"REFERENCE"
|
"
ADDITIONAL
"
;
type
MemoRelationType
=
"REFERENCE"
|
"
COMMENT
"
;
interface
MemoRelation
{
memoId
:
MemoId
;
...
...
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