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
77a3513a
Commit
77a3513a
authored
Sep 24, 2022
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update memo detail page
parent
0dd23376
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
183 additions
and
37 deletions
+183
-37
Memo.tsx
web/src/components/Memo.tsx
+44
-30
MemoCardDialog.tsx
web/src/components/MemoCardDialog.tsx
+7
-2
memo-detail.less
web/src/less/memo-detail.less
+83
-0
en.json
web/src/locales/en.json
+1
-1
vi.json
web/src/locales/vi.json
+1
-1
zh.json
web/src/locales/zh.json
+1
-1
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+46
-2
No files found.
web/src/components/Memo.tsx
View file @
77a3513a
...
...
@@ -3,6 +3,7 @@ import relativeTime from "dayjs/plugin/relativeTime";
import
{
indexOf
}
from
"lodash-es"
;
import
{
memo
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useNavigate
}
from
"react-router-dom"
;
import
"dayjs/locale/zh"
;
import
{
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
DONE_BLOCK_REG
,
TODO_BLOCK_REG
}
from
"../helpers/marked"
;
...
...
@@ -32,6 +33,7 @@ export const getFormatedMemoCreatedAtStr = (createdTs: number, locale = "en"): s
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
memo
=
props
.
memo
;
const
{
t
,
i18n
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
[
createdAtStr
,
setCreatedAtStr
]
=
useState
<
string
>
(
getFormatedMemoCreatedAtStr
(
memo
.
createdTs
,
i18n
.
language
));
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
isVisitorMode
=
userService
.
isVisitorMode
();
...
...
@@ -50,9 +52,17 @@ const Memo: React.FC<Props> = (props: Props) => {
},
[
i18n
.
language
]);
const
handleShowMemoStoryDialog
=
()
=>
{
if
(
isVisitorMode
)
{
return
;
}
showMemoCardDialog
(
memo
);
};
const
handleViewMemoDetailPage
=
()
=>
{
navigate
(
`/m/
${
memo
.
id
}
`
);
};
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
try
{
if
(
memo
.
pinned
)
{
...
...
@@ -165,44 +175,48 @@ const Memo: React.FC<Props> = (props: Props) => {
return
(
<
div
className=
{
`memo-wrapper ${"memos-" + memo.id} ${memo.pinned ? "pinned" : ""}`
}
ref=
{
memoContainerRef
}
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"status-text-container"
onClick=
{
handleShowMemoStoryDialog
}
>
<
span
className=
"time-text"
>
{
createdAtStr
}
</
span
>
<
div
className=
"status-text-container"
>
<
span
className=
"time-text"
onClick=
{
handleShowMemoStoryDialog
}
>
{
createdAtStr
}
</
span
>
{
memo
.
visibility
!==
"PRIVATE"
&&
!
isVisitorMode
&&
(
<
span
className=
{
`status-text ${memo.visibility.toLocaleLowerCase()}`
}
>
{
memo
.
visibility
}
</
span
>
)
}
</
div
>
<
div
className=
{
`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`
}
>
<
span
className=
"btn more-action-btn"
>
<
Icon
.
MoreHorizontal
className=
"icon-img"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container"
>
<
div
className=
"btns-container"
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
Icon
.
Flag
className=
{
`icon-img ${memo.pinned ? "" : "opacity-20"}`
}
/>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
Icon
.
Edit3
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
{
t
(
"common.edit"
)
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
Icon
.
Share
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
{
t
(
"common.share"
)
}
</
span
>
{
!
isVisitorMode
&&
(
<
div
className=
"btns-container"
>
<
span
className=
"btn more-action-btn"
>
<
Icon
.
MoreHorizontal
className=
"icon-img"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container"
>
<
div
className=
"btns-container"
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
Icon
.
Flag
className=
{
`icon-img ${memo.pinned ? "" : "opacity-20"}`
}
/>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
Icon
.
Edit3
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
{
t
(
"common.edit"
)
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
Icon
.
Share
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
{
t
(
"common.share"
)
}
</
span
>
</
div
>
</
div
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
{
t
(
"common.mark"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleViewMemoDetailPage
}
>
{
t
(
"memo.view-detail"
)
}
</
span
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
{
t
(
"common.archive"
)
}
</
span
>
</
div
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
{
t
(
"common.mark"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
{
t
(
"memo.view-story"
)
}
</
span
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
{
t
(
"common.archive"
)
}
</
span
>
</
div
>
</
div
>
</
div
>
)
}
</
div
>
<
MemoContent
content=
{
memo
.
content
}
...
...
web/src/components/MemoCardDialog.tsx
View file @
77a3513a
...
...
@@ -31,6 +31,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
});
const
[
linkMemos
,
setLinkMemos
]
=
useState
<
LinkedMemo
[]
>
([]);
const
[
linkedMemos
,
setLinkedMemos
]
=
useState
<
LinkedMemo
[]
>
([]);
const
isVisitorMode
=
userService
.
isVisitorMode
();
const
visibilitySelectorItems
=
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
{
return
{
value
:
item
.
value
,
...
...
@@ -83,6 +84,10 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
},
[
memos
,
memo
.
id
]);
const
handleMemoCreatedAtClick
=
()
=>
{
if
(
isVisitorMode
)
{
return
;
}
showChangeMemoCreatedTsDialog
(
memo
.
id
);
};
...
...
@@ -140,7 +145,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
return
(
<>
{
!
userService
.
isVisitorMode
()
&&
(
{
!
isVisitorMode
&&
(
<
div
className=
"card-header-container"
>
<
div
className=
"visibility-selector-container"
>
<
Icon
.
Eye
className=
"icon-img"
/>
...
...
@@ -159,7 +164,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
{
utils
.
getDateTimeString
(
memo
.
createdTs
)
}
</
p
>
<
div
className=
"btns-container"
>
{
!
userService
.
isVisitorMode
()
&&
(
{
!
isVisitorMode
&&
(
<>
<
button
className=
"btn edit-btn"
onClick=
{
handleGotoMemoLinkBtnClick
}
>
<
Icon
.
ExternalLink
className=
"icon-img"
/>
...
...
web/src/less/memo-detail.less
0 → 100644
View file @
77a3513a
@import "./mixin.less";
.page-wrapper.memo-detail {
@apply relative top-0 w-full h-screen overflow-y-auto overflow-x-hidden;
background-color: #f6f5f4;
> .page-container {
@apply relative w-full min-h-screen mx-auto flex flex-col justify-start items-center pb-8;
> .page-header {
@apply sticky top-0 z-10 max-w-2xl w-full min-h-full flex flex-row justify-between items-center px-4 sm:pr-6 pt-6 mb-2;
background-color: #f6f5f4;
> .title-container {
@apply flex flex-row justify-start items-center;
> .logo-img {
@apply h-12 sm:h-14 w-auto mr-1;
}
> .title-text {
@apply text-xl sm:text-3xl font-mono text-gray-700;
}
}
> .action-button-container {
> .btn {
@apply block text-gray-600 font-mono text-base py-1 border px-3 leading-8 rounded-xl hover:opacity-80 hover:underline;
> .icon {
@apply text-lg;
}
}
}
}
> .memos-wrapper {
@apply relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 sm:pr-6;
> .memo-container {
@apply flex flex-col justify-start items-start w-full p-4 mt-2 bg-white rounded-lg border border-white hover:border-gray-200;
> .memo-header {
@apply mb-2 w-full flex flex-row justify-start items-center text-sm font-mono text-gray-400;
> .split-text {
@apply mx-2;
}
> .name-text {
@apply hover:text-green-600 hover:underline;
}
> .visibility-selector {
> .status-text {
@apply flex flex-row justify-start items-center leading-5 text-xs cursor-pointer ml-2 rounded border px-1;
&.public {
@apply border-green-600 text-green-600;
}
&.protected {
@apply border-gray-400 text-gray-400;
}
}
.action-button {
@apply px-2 leading-7 w-full rounded text-gray-600 hover:bg-gray-100;
}
}
}
> .memo-content {
@apply cursor-default;
> * {
@apply cursor-default;
}
}
}
}
}
}
web/src/locales/en.json
View file @
77a3513a
...
...
@@ -73,7 +73,7 @@
"cant-empty"
:
"Content can't be empty"
},
"memo"
:
{
"view-
story"
:
"View Story
"
,
"view-
detail"
:
"View Detail
"
,
"visibility"
:
{
"private"
:
"Private"
,
"protected"
:
"Protected"
,
...
...
web/src/locales/vi.json
View file @
77a3513a
...
...
@@ -73,7 +73,7 @@
"cant-empty"
:
"Nội dung không thể trống"
},
"memo"
:
{
"view-
story"
:
"Xem nội dung
"
,
"view-
detail"
:
"View Detail
"
,
"visibility"
:
{
"private"
:
"Private"
,
"protected"
:
"Protected"
,
...
...
web/src/locales/zh.json
View file @
77a3513a
...
...
@@ -73,7 +73,7 @@
"cant-empty"
:
"Content can't be empty"
},
"memo"
:
{
"view-
story
"
:
"查看详情"
,
"view-
detail
"
:
"查看详情"
,
"visibility"
:
{
"private"
:
"仅自己可见"
,
"protected"
:
"对所有用户公开"
,
...
...
web/src/pages/MemoDetail.tsx
View file @
77a3513a
...
...
@@ -6,10 +6,12 @@ import { memoService } from "../services";
import
{
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
useAppSelector
}
from
"../store"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Icon
from
"../components/Icon"
;
import
toastHelper
from
"../components/Toast"
;
import
Dropdown
from
"../components/common/Dropdown"
;
import
MemoContent
from
"../components/MemoContent"
;
import
MemoResources
from
"../components/MemoResources"
;
import
"../less/
explore
.less"
;
import
"../less/
memo-detail
.less"
;
interface
State
{
memo
:
Memo
;
...
...
@@ -45,8 +47,26 @@ const MemoDetail = () => {
}
},
[
location
]);
const
handleVisibilitySelectorChange
=
async
(
visibility
:
Visibility
)
=>
{
if
(
state
.
memo
.
visibility
===
visibility
)
{
return
;
}
await
memoService
.
patchMemo
({
id
:
state
.
memo
.
id
,
visibility
:
visibility
,
});
setState
({
...
state
,
memo
:
{
...
state
.
memo
,
visibility
:
visibility
,
},
});
};
return
(
<
section
className=
"page-wrapper
explore
"
>
<
section
className=
"page-wrapper
memo-detail
"
>
<
div
className=
"page-container"
>
<
div
className=
"page-header"
>
<
div
className=
"title-container"
>
...
...
@@ -77,6 +97,30 @@ const MemoDetail = () => {
<
a
className=
"name-text"
href=
{
`/u/${state.memo.creator.id}`
}
>
{
state
.
memo
.
creator
.
name
}
</
a
>
{
user
?.
id
===
state
.
memo
.
creatorId
&&
(
<
Dropdown
className=
"visibility-selector"
trigger=
{
<
span
className=
{
`status-text ${state.memo.visibility.toLowerCase()}`
}
>
{
state
.
memo
.
visibility
}
<
Icon
.
ChevronDown
className=
"w-4 h-auto ml-px"
/>
</
span
>
}
actions=
{
<>
<
span
className=
"action-button"
onClick=
{
()
=>
handleVisibilitySelectorChange
(
"PRIVATE"
)
}
>
Private
</
span
>
<
span
className=
"action-button"
onClick=
{
()
=>
handleVisibilitySelectorChange
(
"PROTECTED"
)
}
>
Protected
</
span
>
<
span
className=
"action-button"
onClick=
{
()
=>
handleVisibilitySelectorChange
(
"PUBLIC"
)
}
>
Public
</
span
>
</>
}
actionsClassName=
"!w-28 !left-0 !p-1"
/>
)
}
</
div
>
<
MemoContent
className=
"memo-content"
content=
{
state
.
memo
.
content
}
onMemoContentClick=
{
()
=>
undefined
}
/>
<
MemoResources
memo=
{
state
.
memo
}
/>
...
...
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