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
60efd3ac
Commit
60efd3ac
authored
Feb 08, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: tweak memo view
parent
4081a6f5
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
153 additions
and
211 deletions
+153
-211
MemoActionMenu.tsx
web/src/components/MemoActionMenu.tsx
+128
-0
MemoReactionListView.tsx
web/src/components/MemoReactionListView.tsx
+2
-2
MemoView.tsx
web/src/components/MemoView.tsx
+18
-136
ReactionSelector.tsx
web/src/components/ReactionSelector.tsx
+3
-3
ReactionView.tsx
web/src/components/ReactionView.tsx
+1
-1
VisibilityIcon.tsx
web/src/components/VisibilityIcon.tsx
+1
-1
memo.less
web/src/less/memo.less
+0
-68
No files found.
web/src/components/MemoActionMenu.tsx
0 → 100644
View file @
60efd3ac
import
{
Divider
,
Dropdown
,
Menu
,
MenuButton
,
MenuItem
}
from
"@mui/joy"
;
import
copy
from
"copy-to-clipboard"
;
import
toast
from
"react-hot-toast"
;
import
Icon
from
"@/components/Icon"
;
import
{
useMemoStore
}
from
"@/store/v1"
;
import
{
RowStatus
}
from
"@/types/proto/api/v2/common"
;
import
{
Memo
}
from
"@/types/proto/api/v2/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
showMemoEditorDialog
from
"./MemoEditor/MemoEditorDialog"
;
import
showShareMemoDialog
from
"./ShareMemoDialog"
;
interface
Props
{
memo
:
Memo
;
showPinned
?:
boolean
;
}
const
MemoActionMenu
=
(
props
:
Props
)
=>
{
const
{
memo
,
showPinned
}
=
props
;
const
t
=
useTranslate
();
const
memoStore
=
useMemoStore
();
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
try
{
if
(
memo
.
pinned
)
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
pinned
:
false
,
},
[
"pinned"
],
);
}
else
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
pinned
:
true
,
},
[
"pinned"
],
);
}
}
catch
(
error
)
{
// do nth
}
};
const
handleEditMemoClick
=
()
=>
{
showMemoEditorDialog
({
memoId
:
memo
.
id
,
cacheKey
:
`
${
memo
.
id
}
-
${
memo
.
updateTime
}
`
,
});
};
const
handleArchiveMemoClick
=
async
()
=>
{
try
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
rowStatus
:
RowStatus
.
ARCHIVED
,
},
[
"row_status"
],
);
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
}
};
const
handleDeleteMemoClick
=
async
()
=>
{
showCommonDialog
({
title
:
t
(
"memo.delete-memo"
),
content
:
t
(
"memo.delete-confirm"
),
style
:
"danger"
,
dialogName
:
"delete-memo-dialog"
,
onConfirm
:
async
()
=>
{
await
memoStore
.
deleteMemo
(
memo
.
id
);
},
});
};
const
handleCopyMemoId
=
()
=>
{
copy
(
memo
.
name
);
toast
.
success
(
"Copied to clipboard!"
);
};
return
(
<
Dropdown
>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
>
<
span
className=
"h-7 w-7 flex justify-center items-center rounded-full hover:opacity-70"
>
<
Icon
.
MoreHorizontal
className=
"w-4 h-4 mx-auto text-gray-500 dark:text-gray-400"
/>
</
span
>
</
MenuButton
>
<
Menu
className=
"text-sm"
size=
"sm"
placement=
"bottom-end"
>
{
showPinned
&&
(
<
MenuItem
onClick=
{
handleTogglePinMemoBtnClick
}
>
{
memo
.
pinned
?
<
Icon
.
BookmarkMinus
className=
"w-4 h-auto"
/>
:
<
Icon
.
BookmarkPlus
className=
"w-4 h-auto"
/>
}
{
memo
.
pinned
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
MenuItem
>
)
}
<
MenuItem
onClick=
{
handleEditMemoClick
}
>
<
Icon
.
Edit3
className=
"w-4 h-auto"
/>
{
t
(
"common.edit"
)
}
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
showShareMemoDialog
(
memo
)
}
>
<
Icon
.
Share
className=
"w-4 h-auto"
/>
{
t
(
"common.share"
)
}
</
MenuItem
>
<
Divider
className=
"!my-1"
/>
<
MenuItem
color=
"warning"
onClick=
{
handleArchiveMemoClick
}
>
<
Icon
.
Archive
className=
"w-4 h-auto"
/>
{
t
(
"common.archive"
)
}
</
MenuItem
>
<
MenuItem
color=
"danger"
onClick=
{
handleDeleteMemoClick
}
>
<
Icon
.
Trash
className=
"w-4 h-auto"
/>
{
t
(
"common.delete"
)
}
</
MenuItem
>
<
Divider
className=
"!my-1"
/>
<
div
className=
"-mt-0.5 pl-2 pr-2 text-xs text-gray-400"
>
<
div
className=
"font-mono max-w-20 cursor-pointer truncate"
onClick=
{
handleCopyMemoId
}
>
ID:
{
memo
.
name
}
</
div
>
</
div
>
</
Menu
>
</
Dropdown
>
);
};
export
default
MemoActionMenu
;
web/src/components/MemoReactionListView.tsx
View file @
60efd3ac
...
...
@@ -33,12 +33,12 @@ const MemoReactionListView = (props: Props) => {
},
[
reactions
]);
return
(
(
currentUser
||
reactionGroup
.
size
>
0
)
&&
(
reactions
.
length
>
0
&&
(
<
div
className=
"w-full mt-2 flex flex-row justify-start items-start flex-wrap gap-1 select-none"
>
{
currentUser
&&
<
ReactionSelector
memo=
{
memo
}
/>
}
{
Array
.
from
(
reactionGroup
).
map
(([
reactionType
,
users
])
=>
{
return
<
ReactionView
key=
{
`${reactionType.toString()} ${users.length}`
}
memo=
{
memo
}
reactionType=
{
reactionType
}
users=
{
users
}
/>;
})
}
{
currentUser
&&
<
ReactionSelector
memo=
{
memo
}
/>
}
</
div
>
)
);
...
...
web/src/components/MemoView.tsx
View file @
60efd3ac
import
{
Divider
,
Tooltip
}
from
"@mui/joy"
;
import
{
Tooltip
}
from
"@mui/joy"
;
import
classNames
from
"classnames"
;
import
copy
from
"copy-to-clipboard"
;
import
{
memo
,
useCallback
,
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
}
from
"@/helpers/consts"
;
import
{
getRelativeTimeString
,
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useNavigateTo
from
"@/hooks/useNavigateTo"
;
import
{
useUserStore
,
extractUsernameFromName
,
useMemoStore
}
from
"@/store/v1"
;
import
{
RowStatus
}
from
"@/types/proto/api/v2/common"
;
import
{
useUserStore
,
extractUsernameFromName
}
from
"@/store/v1"
;
import
{
MemoRelation_Type
}
from
"@/types/proto/api/v2/memo_relation_service"
;
import
{
Memo
,
Visibility
}
from
"@/types/proto/api/v2/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
convertVisibilityToString
}
from
"@/utils/memo"
;
import
showChangeMemoCreatedTsDialog
from
"./ChangeMemoCreatedTsDialog"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
Icon
from
"./Icon"
;
import
MemoActionMenu
from
"./MemoActionMenu"
;
import
MemoContent
from
"./MemoContent"
;
import
showMemoEditorDialog
from
"./MemoEditor/MemoEditorDialog"
;
import
MemoReactionistView
from
"./MemoReactionListView"
;
import
MemoRelationListView
from
"./MemoRelationListView"
;
import
MemoResourceListView
from
"./MemoResourceListView"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
showShareMemoDialog
from
"./ShareMemoDialog
"
;
import
ReactionSelector
from
"./ReactionSelector
"
;
import
UserAvatar
from
"./UserAvatar"
;
import
VisibilityIcon
from
"./VisibilityIcon"
;
import
"@/less/memo.less"
;
interface
Props
{
memo
:
Memo
;
...
...
@@ -42,7 +36,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
const
t
=
useTranslate
();
const
navigateTo
=
useNavigateTo
();
const
{
i18n
}
=
useTranslation
();
const
memoStore
=
useMemoStore
();
const
currentUser
=
useCurrentUser
();
const
userStore
=
useUserStore
();
const
user
=
useCurrentUser
();
const
[
displayTime
,
setDisplayTime
]
=
useState
<
string
>
(
getRelativeTimeString
(
getTimeStampByDate
(
memo
.
displayTime
)));
...
...
@@ -80,81 +74,6 @@ const MemoView: React.FC<Props> = (props: Props) => {
}
};
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
try
{
if
(
memo
.
pinned
)
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
pinned
:
false
,
},
[
"pinned"
],
);
}
else
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
pinned
:
true
,
},
[
"pinned"
],
);
}
}
catch
(
error
)
{
// do nth
}
};
const
handleEditMemoClick
=
()
=>
{
showMemoEditorDialog
({
memoId
:
memo
.
id
,
cacheKey
:
`
${
memo
.
id
}
-
${
memo
.
updateTime
}
`
,
});
};
const
handleMarkMemoClick
=
()
=>
{
showMemoEditorDialog
({
relationList
:
[
{
memoId
:
UNKNOWN_ID
,
relatedMemoId
:
memo
.
id
,
type
:
MemoRelation_Type
.
REFERENCE
,
},
],
});
};
const
handleArchiveMemoClick
=
async
()
=>
{
try
{
await
memoStore
.
updateMemo
(
{
id
:
memo
.
id
,
rowStatus
:
RowStatus
.
ARCHIVED
,
},
[
"row_status"
],
);
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
}
};
const
handleDeleteMemoClick
=
async
()
=>
{
showCommonDialog
({
title
:
t
(
"memo.delete-memo"
),
content
:
t
(
"memo.delete-confirm"
),
style
:
"danger"
,
dialogName
:
"delete-memo-dialog"
,
onConfirm
:
async
()
=>
{
await
memoStore
.
deleteMemo
(
memo
.
id
);
},
});
};
const
handleCopyMemoId
=
()
=>
{
copy
(
memo
.
name
);
toast
.
success
(
"Copied to clipboard!"
);
};
const
handleMemoContentClick
=
useCallback
(
async
(
e
:
React
.
MouseEvent
)
=>
{
const
targetEl
=
e
.
target
as
HTMLElement
;
...
...
@@ -168,11 +87,16 @@ const MemoView: React.FC<Props> = (props: Props) => {
return
(
<
div
className=
{
classNames
(
"group memo-wrapper"
,
"memos-"
+
memo
.
id
,
memo
.
pinned
&&
props
.
showPinned
?
"pinned"
:
""
,
className
)
}
className=
{
classNames
(
"group relative flex flex-col justify-start items-start w-full px-4 pt-2 pb-3 mb-2 bg-white dark:bg-zinc-800 rounded-lg border border-white dark:border-zinc-800 hover:border-gray-200 dark:hover:border-zinc-700"
,
"memos-"
+
memo
.
id
,
memo
.
pinned
&&
props
.
showPinned
&&
"border-gray-200 border dark:border-zinc-700"
,
className
,
)
}
ref=
{
memoContainerRef
}
>
<
div
className=
"
memo-top-wrapp
er mb-1"
>
<
div
className=
"w-
full max-w-[calc(100%-20px)]
flex flex-row justify-start items-center mr-1"
>
<
div
className=
"
w-full flex flex-row justify-between items-cent
er mb-1"
>
<
div
className=
"w-
auto
flex flex-row justify-start items-center mr-1"
>
{
props
.
showCreator
&&
creator
&&
(
<>
<
Link
to=
{
`/u/${encodeURIComponent(extractUsernameFromName(memo.creator))}`
}
unstable_viewTransition
>
...
...
@@ -200,62 +124,20 @@ const MemoView: React.FC<Props> = (props: Props) => {
</>
)
}
</
div
>
<
div
className=
"
btns-container space-x-2
"
>
<
div
className=
"w-auto hidden group-hover:flex flex-row justify-between items-center"
>
<
div
className=
"
flex flex-row justify-end items-center
"
>
<
div
className=
"w-auto hidden group-hover:flex flex-row justify-between items-center
gap-1
"
>
{
props
.
showVisibility
&&
memo
.
visibility
!==
Visibility
.
PRIVATE
&&
(
<>
<
Tooltip
title=
{
t
(
`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}`
as
any
)
}
placement=
"top"
>
<
span
>
<
span
className=
"h-7 w-7 flex justify-center items-center rounded-full border dark:border-zinc-700 hover:opacity-70"
>
<
VisibilityIcon
visibility=
{
memo
.
visibility
}
/>
</
span
>
</
Tooltip
>
</>
)
}
{
currentUser
&&
memo
.
reactions
.
length
===
0
&&
<
ReactionSelector
memo=
{
memo
}
/>
}
</
div
>
{
!
readonly
&&
(
<>
<
span
className=
"btn more-action-btn"
>
<
Icon
.
MoreVertical
className=
"icon-img"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container min-w-[6em]"
>
{
props
.
showPinned
&&
(
<
span
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
{
memo
.
pinned
?
<
Icon
.
BookmarkMinus
className=
"w-4 h-auto mr-2"
/>
:
<
Icon
.
BookmarkPlus
className=
"w-4 h-auto mr-2"
/>
}
{
memo
.
pinned
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
span
>
)
}
<
span
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
Icon
.
Edit3
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.edit"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
<
Icon
.
Link
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.mark"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
()
=>
showShareMemoDialog
(
memo
)
}
>
<
Icon
.
Share
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.share"
)
}
</
span
>
<
Divider
className=
"!my-1"
/>
<
span
className=
"btn text-orange-500"
onClick=
{
handleArchiveMemoClick
}
>
<
Icon
.
Archive
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.archive"
)
}
</
span
>
<
span
className=
"btn text-red-600"
onClick=
{
handleDeleteMemoClick
}
>
<
Icon
.
Trash
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.delete"
)
}
</
span
>
<
Divider
className=
"!my-1"
/>
<
div
className=
"w-full pl-3 pr-2 text-xs text-gray-400"
>
<
div
className=
"font-mono max-w-20 cursor-pointer truncate"
onClick=
{
handleCopyMemoId
}
>
ID:
{
memo
.
name
}
</
div
>
</
div
>
</
div
>
</
div
>
</>
)
}
{
!
readonly
&&
<
MemoActionMenu
memo=
{
memo
}
showPinned=
{
props
.
showPinned
}
/>
}
</
div
>
</
div
>
<
MemoContent
...
...
web/src/components/ReactionSelector.tsx
View file @
60efd3ac
...
...
@@ -56,9 +56,9 @@ const ReactionSelector = (props: Props) => {
return
(
<
Dropdown
open=
{
open
}
onOpenChange=
{
(
_
,
isOpen
)
=>
setOpen
(
isOpen
)
}
>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
slotProps=
{
{}
}
>
<
span
className=
"h-7 w-7 flex justify-center items-center rounded-full border dark:border-zinc-700 hover:opacity-
8
0"
>
<
Icon
.
Smile
className=
"w-4 h-4 mx-auto
dark:text-gray-400"
/>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
>
<
span
className=
"h-7 w-7 flex justify-center items-center rounded-full border dark:border-zinc-700 hover:opacity-
7
0"
>
<
Icon
.
Smile
Plus
className=
"w-4 h-4 mx-auto text-gray-500
dark:text-gray-400"
/>
</
span
>
</
MenuButton
>
<
Menu
className=
"relative text-sm"
component=
"div"
size=
"sm"
placement=
"bottom-start"
>
...
...
web/src/components/ReactionView.tsx
View file @
60efd3ac
...
...
@@ -96,7 +96,7 @@ const ReactionView = (props: Props) => {
className=
{
classNames
(
"h-7 border px-2 py-0.5 rounded-full font-memo flex flex-row justify-center items-center gap-1 dark:border-zinc-700"
,
currenUser
&&
"cursor-pointer"
,
hasReaction
&&
"bg-blue-
50 border-blue-1
00 dark:bg-zinc-900"
,
hasReaction
&&
"bg-blue-
100 border-blue-2
00 dark:bg-zinc-900"
,
)
}
onClick=
{
handleReactionClick
}
>
...
...
web/src/components/VisibilityIcon.tsx
View file @
60efd3ac
...
...
@@ -21,7 +21,7 @@ const VisibilityIcon = (props: Props) => {
return
null
;
}
return
<
VIcon
className=
{
classNames
(
"w-4 h-auto text-gray-400"
)
}
/>;
return
<
VIcon
className=
{
classNames
(
"w-4 h-auto text-gray-
500 dark:text-gray-
400"
)
}
/>;
};
export
default
VisibilityIcon
;
web/src/less/memo.less
deleted
100644 → 0
View file @
4081a6f5
.memo-wrapper {
@apply relative flex flex-col justify-start items-start w-full p-4 pt-3 mb-2 bg-white dark:bg-zinc-800 rounded-lg border border-white dark:border-zinc-800 hover:border-gray-200 dark:hover:border-zinc-700;
&.pinned {
@apply border-gray-200 border dark:border-zinc-700;
}
> .memo-top-wrapper {
@apply flex flex-row justify-between items-center w-full h-6;
> .btns-container {
@apply flex flex-row justify-end items-center relative shrink-0;
> .more-action-btns-wrapper {
@apply hidden flex-col justify-start items-center absolute top-2 -right-4 flex-nowrap hover:flex p-3;
> .more-action-btns-container {
@apply w-auto h-auto p-1 z-1 whitespace-nowrap rounded-lg bg-white dark:bg-zinc-700;
box-shadow: 0 0 8px 0 rgb(0 0 0 / 20%);
> .btn {
@apply w-full text-sm leading-6 py-1 px-3 rounded justify-start cursor-pointer select-none dark:text-gray-300;
&.archive-btn {
@apply text-orange-600;
}
}
}
}
.btn {
@apply flex flex-row justify-center items-center leading-6 text-sm rounded hover:bg-gray-200 dark:hover:bg-zinc-600;
&.more-action-btn {
@apply w-auto opacity-50 cursor-default hover:bg-transparent;
> .icon-img {
@apply w-4 h-auto dark:text-gray-300;
}
&:hover {
& + .more-action-btns-wrapper {
display: flex;
}
}
}
}
.final-confirm {
color: red;
}
}
}
> .memo-content-wrapper {
@apply mt-1;
> .memo-content-text {
.tag-span {
@apply cursor-pointer hover:opacity-80;
}
.img {
@apply max-w-xs;
}
}
}
}
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