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
f3f0efba
Unverified
Commit
f3f0efba
authored
Mar 11, 2023
by
boojack
Committed by
GitHub
Mar 11, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update page router (#1330)
parent
ccdcd3d1
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
288 additions
and
266 deletions
+288
-266
Header.tsx
web/src/components/Header.tsx
+42
-22
Memo.tsx
web/src/components/Memo.tsx
+3
-15
MemoList.tsx
web/src/components/MemoList.tsx
+7
-4
MobileHeader.tsx
web/src/components/MobileHeader.tsx
+9
-20
UserBanner.tsx
web/src/components/UserBanner.tsx
+4
-10
daily-review-dialog.less
web/src/less/daily-review-dialog.less
+0
-87
DailyReview.tsx
web/src/pages/DailyReview.tsx
+130
-0
Explore.tsx
web/src/pages/Explore.tsx
+5
-33
Home.tsx
web/src/pages/Home.tsx
+6
-8
index.tsx
web/src/router/index.tsx
+55
-35
memo.ts
web/src/store/module/memo.ts
+3
-7
user.ts
web/src/store/module/user.ts
+8
-14
memo.ts
web/src/store/reducer/memo.ts
+4
-3
user.ts
web/src/store/reducer/user.ts
+12
-8
No files found.
web/src/components/Header.tsx
View file @
f3f0efba
import
{
useEffect
}
from
"react"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
Nav
Link
}
from
"react-router-dom"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useLayoutStore
,
use
LocationStore
,
use
UserStore
}
from
"../store/module"
;
import
{
useLayoutStore
,
useUserStore
}
from
"../store/module"
;
import
{
resolution
}
from
"../utils/layout"
;
import
Icon
from
"./Icon"
;
import
showDailyReviewDialog
from
"./DailyReviewDialog"
;
import
showResourcesDialog
from
"./ResourcesDialog"
;
import
showSettingDialog
from
"./SettingDialog"
;
import
showAskAIDialog
from
"./AskAIDialog"
;
...
...
@@ -14,9 +13,9 @@ import UserBanner from "./UserBanner";
const
Header
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
userStore
=
useUserStore
();
const
locationStore
=
useLocationStore
();
const
layoutStore
=
useLayoutStore
();
const
showHeader
=
layoutStore
.
state
.
showHeader
;
const
isVisitorMode
=
userStore
.
isVisitorMode
()
&&
!
userStore
.
state
.
user
;
useEffect
(()
=>
{
const
handleWindowResize
=
()
=>
{
...
...
@@ -49,26 +48,47 @@ const Header = () => {
>
<
UserBanner
/>
<
div
className=
"w-full px-2 py-2 flex flex-col justify-start items-start shrink-0 space-y-2"
>
<
Link
to=
"/"
className=
"px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
onClick=
{
()
=>
locationStore
.
clearQuery
()
}
>
<
Icon
.
Home
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.home"
)
}
</
Link
>
<
button
className=
"px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
onClick=
{
()
=>
showDailyReviewDialog
()
}
>
<
Icon
.
Calendar
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.daily-review"
)
}
</
button
>
<
Link
{
!
isVisitorMode
&&
(
<>
<
NavLink
to=
"/"
className=
{
({
isActive
})
=>
`${
isActive && "bg-white dark:bg-zinc-700 shadow"
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
}
>
<>
<
Icon
.
Home
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.home"
)
}
</>
</
NavLink
>
<
NavLink
to=
"/review"
className=
{
({
isActive
})
=>
`${
isActive && "bg-white dark:bg-zinc-700 shadow"
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
}
>
<>
<
Icon
.
Calendar
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.daily-review"
)
}
</>
</
NavLink
>
</>
)
}
<
NavLink
to=
"/explore"
className=
"px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
className=
{
({
isActive
})
=>
`${
isActive && "bg-white dark:bg-zinc-700 shadow"
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
}
>
<
Icon
.
Hash
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.explore"
)
}
</
Link
>
{
!
userStore
.
isVisitorMode
()
&&
(
<>
<
Icon
.
Hash
className=
"mr-4 w-6 h-auto opacity-80"
/>
{
t
(
"common.explore"
)
}
</>
</
NavLink
>
{
!
isVisitorMode
&&
(
<>
<
button
className=
"px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
...
...
web/src/components/Memo.tsx
View file @
f3f0efba
...
...
@@ -3,7 +3,7 @@ import dayjs from "dayjs";
import
{
memo
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useNavigate
}
from
"react-router-dom"
;
import
{
Link
,
useNavigate
}
from
"react-router-dom"
;
import
{
useEditorStore
,
useLocationStore
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
Icon
from
"./Icon"
;
import
MemoContent
from
"./MemoContent"
;
...
...
@@ -66,10 +66,6 @@ const Memo: React.FC<Props> = (props: Props) => {
};
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
if
(
isVisitorMode
)
{
return
;
}
try
{
if
(
memo
.
pinned
)
{
await
memoStore
.
unpinMemo
(
memo
.
id
);
...
...
@@ -82,18 +78,10 @@ const Memo: React.FC<Props> = (props: Props) => {
};
const
handleEditMemoClick
=
()
=>
{
if
(
isVisitorMode
)
{
return
;
}
editorStore
.
setEditMemoWithId
(
memo
.
id
);
};
const
handleArchiveMemoClick
=
async
()
=>
{
if
(
isVisitorMode
)
{
return
;
}
try
{
await
memoStore
.
patchMemo
({
id
:
memo
.
id
,
...
...
@@ -205,9 +193,9 @@ const Memo: React.FC<Props> = (props: Props) => {
{
createdTimeStr
}
</
span
>
{
isVisitorMode
&&
(
<
a
className=
"name-text"
href
=
{
`/u/${memo.creatorId}`
}
>
<
Link
className=
"name-text"
to
=
{
`/u/${memo.creatorId}`
}
>
@
{
memo
.
creatorName
}
</
a
>
</
Link
>
)
}
{
memo
.
visibility
!==
"PRIVATE"
&&
!
isVisitorMode
&&
(
<
span
...
...
web/src/components/MemoList.tsx
View file @
f3f0efba
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useLocationStore
,
useMemoStore
,
useShortcutStore
}
from
"../store/module"
;
import
{
useLocationStore
,
useMemoStore
,
useShortcutStore
,
useUserStore
}
from
"../store/module"
;
import
{
TAG_REG
,
LINK_REG
}
from
"../labs/marked/parser"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"../helpers/consts"
;
...
...
@@ -12,17 +12,19 @@ import "../less/memo-list.less";
const
MemoList
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
memoStore
=
useMemoStore
();
const
userStore
=
useUserStore
();
const
shortcutStore
=
useShortcutStore
();
const
locationStore
=
useLocationStore
();
const
query
=
locationStore
.
state
.
query
;
const
{
memos
,
isFetching
}
=
memoStore
.
state
;
const
[
isComplete
,
setIsComplete
]
=
useState
<
boolean
>
(
false
);
const
currentUserId
=
userStore
.
getCurrentUserId
();
const
{
tag
:
tagQuery
,
duration
,
type
:
memoType
,
text
:
textQuery
,
shortcutId
,
visibility
}
=
query
??
{};
const
shortcut
=
shortcutId
?
shortcutStore
.
getShortcutById
(
shortcutId
)
:
null
;
const
showMemoFilter
=
Boolean
(
tagQuery
||
(
duration
&&
duration
.
from
<
duration
.
to
)
||
memoType
||
textQuery
||
shortcut
||
visibility
);
const
shownMemos
=
const
shownMemos
=
(
showMemoFilter
||
shortcut
?
memos
.
filter
((
memo
)
=
>
{
let
shouldShow
=
true
;
...
...
@@ -72,7 +74,8 @@ const MemoList = () => {
return shouldShow;
}
)
: memos;
: memos
).filter((memo) =
>
memo.creatorId === currentUserId);
const pinnedMemos = shownMemos.filter((m) =
>
m.pinned);
const unpinnedMemos = shownMemos.filter((m) =
>
!m.pinned);
...
...
@@ -97,7 +100,7 @@ const MemoList = () => {
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
message
);
});
}
, []);
}
, [
currentUserId
]);
useEffect(() =
>
{
const
pageWrapper
=
document
.
body
.
querySelector
(
".page-wrapper"
);
...
...
web/src/components/MobileHeader.tsx
View file @
f3f0efba
import
{
use
Callback
,
use
Effect
,
useState
}
from
"react"
;
import
{
useLayoutStore
,
useLocationStore
,
use
MemoStore
,
use
ShortcutStore
}
from
"../store/module"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useLayoutStore
,
useLocationStore
,
useShortcutStore
}
from
"../store/module"
;
import
Icon
from
"./Icon"
;
let
prevRequestTimestamp
=
Date
.
now
();
interface
Props
{
showSearch
?:
boolean
;
}
const
MobileHeader
=
()
=>
{
const
MobileHeader
=
(
props
:
Props
)
=>
{
const
{
showSearch
=
true
}
=
props
;
const
locationStore
=
useLocationStore
();
const
memoStore
=
useMemoStore
();
const
shortcutStore
=
useShortcutStore
();
const
layoutStore
=
useLayoutStore
();
const
query
=
locationStore
.
state
.
query
;
...
...
@@ -25,16 +27,6 @@ const MobileHeader = () => {
}
},
[
query
,
shortcuts
]);
const
handleTitleTextClick
=
useCallback
(()
=>
{
const
now
=
Date
.
now
();
if
(
now
-
prevRequestTimestamp
>
1
*
1000
)
{
prevRequestTimestamp
=
now
;
memoStore
.
fetchMemos
().
catch
(()
=>
{
// do nth
});
}
},
[]);
return
(
<
div
className=
"sticky top-0 pt-4 pb-1 mb-1 backdrop-blur-sm flex sm:hidden flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-1"
>
<
div
className=
"flex flex-row justify-start items-center mr-2 shrink-0 overflow-hidden"
>
...
...
@@ -44,14 +36,11 @@ const MobileHeader = () => {
>
<
Icon
.
Menu
className=
"w-5 h-auto dark:text-gray-200"
/>
</
div
>
<
span
className=
"font-bold text-lg leading-10 mr-1 text-ellipsis shrink-0 cursor-pointer overflow-hidden text-gray-700 dark:text-gray-200"
onClick=
{
handleTitleTextClick
}
>
<
span
className=
"font-bold text-lg leading-10 mr-1 text-ellipsis shrink-0 cursor-pointer overflow-hidden text-gray-700 dark:text-gray-200"
>
{
titleText
}
</
span
>
</
div
>
<
div
className=
"flex flex-row justify-end items-center pr-1"
>
<
div
className=
{
`${showSearch ? "flex" : "hidden"} flex-row justify-end items-center pr-1`
}
>
<
Icon
.
Search
className=
"w-5 h-auto dark:text-gray-200"
onClick=
{
()
=>
layoutStore
.
setHomeSidebarStatus
(
true
)
}
/>
</
div
>
</
div
>
...
...
web/src/components/UserBanner.tsx
View file @
f3f0efba
...
...
@@ -9,20 +9,14 @@ import showSettingDialog from "./SettingDialog";
const
UserBanner
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
userStore
=
useUserStore
();
const
{
user
,
owner
}
=
userStore
.
state
;
const
{
user
}
=
userStore
.
state
;
const
[
username
,
setUsername
]
=
useState
(
"Memos"
);
const
isVisitorMode
=
userStore
.
isVisitorMode
();
useEffect
(()
=>
{
if
(
isVisitorMode
)
{
if
(
!
owner
)
{
return
;
}
setUsername
(
owner
.
nickname
||
owner
.
username
);
}
else
if
(
user
)
{
if
(
user
)
{
setUsername
(
user
.
nickname
||
user
.
username
);
}
},
[
isVisitorMode
,
user
,
own
er
]);
},
[
us
er
]);
const
handleMyAccountClick
=
()
=>
{
showSettingDialog
(
"my-account"
);
...
...
@@ -45,7 +39,7 @@ const UserBanner = () => {
<
div
className=
"px-3 py-2 max-w-full flex flex-row justify-start items-center cursor-pointer rounded-lg hover:shadow hover:bg-white dark:hover:bg-zinc-700"
>
<
UserAvatar
avatarUrl=
{
user
?.
avatarUrl
}
/>
<
span
className=
"px-1 text-lg font-medium text-slate-800 dark:text-gray-200 shrink truncate"
>
{
username
}
</
span
>
{
!
isVisitorMode
&&
user
?.
role
===
"HOST"
?
(
{
user
?.
role
===
"HOST"
?
(
<
span
className=
"text-xs px-1 bg-blue-600 dark:bg-blue-800 rounded text-white dark:text-gray-200 shadow"
>
MOD
</
span
>
)
:
null
}
</
div
>
...
...
web/src/less/daily-review-dialog.less
deleted
100644 → 0
View file @
ccdcd3d1
.daily-review-dialog {
@apply p-0 sm:py-16;
> .dialog-container {
@apply w-full sm:w-112 max-w-full grow sm:grow-0 p-0 pb-4 rounded-none sm:rounded-lg;
> .dialog-header-container {
@apply relative flex flex-row justify-between items-center w-full p-6 pb-0 mb-0;
> .title-text {
@apply px-2 py-1 -ml-2 cursor-pointer select-none rounded hover:bg-gray-100 dark:hover:bg-zinc-700;
}
> .btns-container {
@apply flex flex-row justify-start items-center;
> .btn-text {
@apply w-6 h-6 mr-2 rounded cursor-pointer select-none last:mr-0 hover:bg-gray-200 dark:hover:bg-zinc-700 p-0.5;
> .icon-img {
@apply w-full h-auto;
}
}
> .split-line {
@apply font-mono text-gray-300 mr-2;
}
}
> .date-picker {
@apply absolute top-12 left-4 mt-2 bg-white dark:bg-zinc-700 shadow z-20 mx-auto border dark:border-zinc-800 rounded-lg mb-6;
}
}
> .dialog-content-container {
@apply w-full h-auto flex flex-col justify-start items-start p-6 pb-0 bg-white dark:bg-zinc-800;
> .date-card-container {
@apply flex flex-col justify-center items-center m-auto pb-6 select-none;
z-index: 1;
> .year-text {
@apply m-auto font-bold text-gray-600 dark:text-gray-300 text-center leading-6 mb-2;
}
> .date-container {
@apply flex flex-col justify-center items-center m-auto w-24 h-24;
border-radius: 24px;
box-shadow: 0 0 8px 0 rgb(0 0 0 / 20%);
> .month-text,
> .day-text {
@apply flex flex-col justify-center items-center text-center w-full h-6 text-sm;
}
> .month-text {
@apply bg-blue-700 text-white;
border-top-left-radius: 24px;
border-top-right-radius: 24px;
}
> .date-text {
@apply flex flex-col justify-center items-center text-center w-full pt-1 h-12;
font-size: 40px;
font-weight: bold;
}
> .day-text {
@apply text-xs;
}
}
}
> .tip-container {
@apply m-auto py-6 pb-12 px-0;
> .tip-text {
font-style: italic;
}
}
> .dailymemos-wrapper {
@apply flex flex-col justify-start items-start w-full mt-2;
}
}
}
}
web/src/
components/DailyReviewDialog
.tsx
→
web/src/
pages/DailyReview
.tsx
View file @
f3f0efba
import
{
useRef
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
toImage
from
"../labs/html2image"
;
import
useToggle
from
"../hooks/useToggle"
;
import
{
DAILY_TIMESTAMP
}
from
"../helpers/consts"
;
import
*
as
utils
from
"../helpers/utils"
;
import
Icon
from
"./Icon"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
DatePicker
from
"./base/DatePicker"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
DailyMemo
from
"./DailyMemo"
;
import
"../less/daily-review-dialog.less"
;
interface
Props
extends
DialogProps
{
currentDateStamp
:
DateStamp
;
}
import
MobileHeader
from
"../components/MobileHeader"
;
import
useToggle
from
"../hooks/useToggle"
;
import
toImage
from
"../labs/html2image"
;
import
showPreviewImageDialog
from
"../components/PreviewImageDialog"
;
import
Icon
from
"../components/Icon"
;
import
DatePicker
from
"../components/base/DatePicker"
;
import
DailyMemo
from
"../components/DailyMemo"
;
const
monthChineseStrArray
=
[
"Jan"
,
"Feb"
,
"Mar"
,
"Apr"
,
"May"
,
"Jun"
,
"Jul"
,
"Aug"
,
"Sept"
,
"Oct"
,
"Nov"
,
"Dec"
];
const
weekdayChineseStrArray
=
[
"Sun"
,
"Mon"
,
"Tue"
,
"Wed"
,
"Thu"
,
"Fri"
,
"Sat"
];
const
DailyReview
Dialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
DailyReview
=
(
)
=>
{
const
{
t
}
=
useTranslation
();
const
memoStore
=
useMemoStore
();
const
memos
=
memoStore
.
state
.
memos
;
const
userStore
=
useUserStore
();
const
{
localSetting
}
=
userStore
.
state
.
user
as
User
;
const
[
currentDateStamp
,
setCurrentDateStamp
]
=
useState
(
utils
.
getDateStampByDate
(
utils
.
getDateString
(
props
.
currentDateStamp
)));
const
[
currentDateStamp
,
setCurrentDateStamp
]
=
useState
(
utils
.
getDateStampByDate
(
utils
.
getDateString
(
Date
.
now
()
)));
const
[
showDatePicker
,
toggleShowDatePicker
]
=
useToggle
(
false
);
const
memosElRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
currentDate
=
new
Date
(
currentDateStamp
);
...
...
@@ -66,64 +61,70 @@ const DailyReviewDialog: React.FC<Props> = (props: Props) => {
};
return
(
<>
<
div
className=
"dialog-header-container"
>
<
p
className=
"title-text"
onClick=
{
()
=>
toggleShowDatePicker
()
}
>
<
span
className=
"icon-text"
>
📅
</
span
>
{
t
(
"common.daily-review"
)
}
</
p
>
<
div
className=
"btns-container"
>
<
button
className=
"btn-text"
onClick=
{
()
=>
setCurrentDateStamp
(
currentDateStamp
-
DAILY_TIMESTAMP
)
}
>
<
Icon
.
ChevronLeft
className=
"icon-img"
/>
</
button
>
<
button
className=
"btn-text"
onClick=
{
()
=>
setCurrentDateStamp
(
currentDateStamp
+
DAILY_TIMESTAMP
)
}
>
<
Icon
.
ChevronRight
className=
"icon-img"
/>
</
button
>
<
button
className=
"btn-text share"
onClick=
{
handleShareBtnClick
}
>
<
Icon
.
Share2
size=
{
16
}
/>
</
button
>
<
span
className=
"split-line"
>
/
</
span
>
<
button
className=
"btn-text"
onClick=
{
()
=>
props
.
destroy
()
}
>
<
Icon
.
X
className=
"icon-img"
/>
</
button
>
</
div
>
<
DatePicker
className=
{
`date-picker ${showDatePicker ? "" : "!hidden"}`
}
datestamp=
{
currentDateStamp
}
handleDateStampChange=
{
handleDataPickerChange
}
/>
</
div
>
<
div
className=
"dialog-content-container"
ref=
{
memosElRef
}
>
<
div
className=
"date-card-container"
>
<
div
className=
"year-text"
>
{
currentDate
.
getFullYear
()
}
</
div
>
<
div
className=
"date-container"
>
<
div
className=
"month-text"
>
{
monthChineseStrArray
[
currentDate
.
getMonth
()]
}
</
div
>
<
div
className=
"date-text"
>
{
currentDate
.
getDate
()
}
</
div
>
<
div
className=
"day-text"
>
{
weekdayChineseStrArray
[
currentDate
.
getDay
()]
}
</
div
>
<
section
className=
"w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800"
>
<
MobileHeader
showSearch=
{
false
}
/>
<
div
className=
"w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700"
>
<
div
className=
"relative w-full flex flex-row justify-between items-center"
>
<
p
className=
"px-2 py-1 flex flex-row justify-start items-center cursor-pointer select-none rounded hover:bg-gray-100 dark:hover:bg-zinc-700"
onClick=
{
()
=>
toggleShowDatePicker
()
}
>
<
Icon
.
Calendar
className=
"w-5 h-auto mr-1"
/>
{
t
(
"common.daily-review"
)
}
</
p
>
<
div
className=
"flex flex-row justify-end items-center"
>
<
button
className=
"w-7 h-7 mr-2 flex justify-center items-center rounded cursor-pointer select-none last:mr-0 hover:bg-gray-200 dark:hover:bg-zinc-700 p-0.5"
onClick=
{
()
=>
setCurrentDateStamp
(
currentDateStamp
-
DAILY_TIMESTAMP
)
}
>
<
Icon
.
ChevronLeft
className=
"w-full h-auto"
/>
</
button
>
<
button
className=
"w-7 h-7 mr-2 flex justify-center items-center rounded cursor-pointer select-none last:mr-0 hover:bg-gray-200 dark:hover:bg-zinc-700 p-0.5"
onClick=
{
()
=>
setCurrentDateStamp
(
currentDateStamp
+
DAILY_TIMESTAMP
)
}
>
<
Icon
.
ChevronRight
className=
"w-full h-auto"
/>
</
button
>
<
button
className=
"w-7 h-7 mr-2 flex justify-center items-center rounded cursor-pointer select-none last:mr-0 hover:bg-gray-200 dark:hover:bg-zinc-700 p-0.5 share"
onClick=
{
handleShareBtnClick
}
>
<
Icon
.
Share2
size=
{
16
}
/>
</
button
>
</
div
>
<
DatePicker
className=
{
`absolute top-8 bg-white mt-2 z-20 mx-auto border dark:border-zinc-800 rounded-lg mb-6 ${
showDatePicker ? "" : "!hidden"
}`
}
datestamp=
{
currentDateStamp
}
handleDateStampChange=
{
handleDataPickerChange
}
/>
</
div
>
{
dailyMemos
.
length
===
0
?
(
<
div
className=
"tip-container"
>
<
p
className=
"tip-text"
>
{
t
(
"daily-review.oops-nothing"
)
}
</
p
>
</
div
>
)
:
(
<
div
className=
"dailymemos-wrapper"
>
{
dailyMemos
.
map
((
memo
)
=>
(
<
DailyMemo
key=
{
`${memo.id}-${memo.updatedTs}`
}
memo=
{
memo
}
/>
))
}
<
div
className=
"w-full h-auto flex flex-col justify-start items-start p-6 pb-0 bg-white dark:bg-zinc-800"
ref=
{
memosElRef
}
>
<
div
className=
"flex flex-col justify-center items-center mx-auto pb-6 select-none"
>
<
div
className=
"mx-auto font-bold text-gray-600 dark:text-gray-300 text-center leading-6 mb-2"
>
{
currentDate
.
getFullYear
()
}
</
div
>
<
div
className=
"flex flex-col justify-center items-center m-auto w-24 h-24 shadow rounded-3xl"
>
<
div
className=
"text-center w-full leading-6 text-sm text-white bg-blue-700 rounded-t-3xl"
>
{
monthChineseStrArray
[
currentDate
.
getMonth
()]
}
</
div
>
<
div
className=
"text-black text-4xl font-medium leading-12"
>
{
currentDate
.
getDate
()
}
</
div
>
<
div
className=
"text-center w-full leading-6 -mt-2 text-xs"
>
{
weekdayChineseStrArray
[
currentDate
.
getDay
()]
}
</
div
>
</
div
>
</
div
>
)
}
{
dailyMemos
.
length
===
0
?
(
<
div
className=
"mx-auto py-6 pb-12 px-0"
>
<
p
className=
"italic"
>
{
t
(
"daily-review.oops-nothing"
)
}
</
p
>
</
div
>
)
:
(
<
div
className=
"flex flex-col justify-start items-start w-full mt-2"
>
{
dailyMemos
.
map
((
memo
)
=>
(
<
DailyMemo
key=
{
`${memo.id}-${memo.updatedTs}`
}
memo=
{
memo
}
/>
))
}
</
div
>
)
}
</
div
>
</
div
>
</>
</
section
>
);
};
export
default
function
showDailyReviewDialog
(
datestamp
:
DateStamp
=
Date
.
now
()):
void
{
generateDialog
(
{
className
:
"daily-review-dialog"
,
dialogName
:
"daily-review-dialog"
,
},
DailyReviewDialog
,
{
currentDateStamp
:
datestamp
}
);
}
export
default
DailyReview
;
web/src/pages/Explore.tsx
View file @
f3f0efba
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useNavigate
}
from
"react-router-dom"
;
import
{
useGlobalStore
,
useLocationStore
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
{
useLocationStore
,
useMemoStore
}
from
"../store/module"
;
import
{
TAG_REG
}
from
"../labs/marked/parser"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"../helpers/consts"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Icon
from
"../components/Icon"
;
import
MemoFilter
from
"../components/MemoFilter"
;
import
Memo
from
"../components/Memo"
;
import
MobileHeader
from
"../components/MobileHeader"
;
interface
State
{
memos
:
Memo
[];
...
...
@@ -16,10 +15,7 @@ interface State {
const
Explore
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
globalStore
=
useGlobalStore
();
const
locationStore
=
useLocationStore
();
const
userStore
=
useUserStore
();
const
memoStore
=
useMemoStore
();
const
query
=
locationStore
.
state
.
query
;
const
[
state
,
setState
]
=
useState
<
State
>
({
...
...
@@ -27,8 +23,6 @@ const Explore = () => {
});
const
[
isComplete
,
setIsComplete
]
=
useState
<
boolean
>
(
false
);
const
loadingState
=
useLoading
();
const
customizedProfile
=
globalStore
.
state
.
systemStatus
.
customizedProfile
;
const
user
=
userStore
.
state
.
user
;
const
location
=
locationStore
.
state
;
useEffect
(()
=>
{
...
...
@@ -88,33 +82,11 @@ const Explore = () => {
}
};
const
handleTitleClick
=
()
=>
{
if
(
user
)
{
navigate
(
"/"
);
}
else
{
navigate
(
"/auth"
);
}
};
return
(
<
section
className=
"w-full min-h-full flex flex-col justify-start items-center pb-8 bg-zinc-100 dark:bg-zinc-800"
>
<
div
className=
"sticky top-0 z-10 max-w-2xl w-full h-auto flex flex-row justify-between backdrop-blur-sm items-center px-4 sm:pr-6 pt-6 mb-2"
>
<
div
className=
"flex flex-row justify-start items-center cursor-pointer hover:opacity-80"
onClick=
{
handleTitleClick
}
>
<
img
className=
"h-12 w-auto rounded-md mr-2"
src=
{
customizedProfile
.
logoUrl
}
alt=
""
/>
<
span
className=
"text-xl sm:text-4xl text-gray-700 dark:text-gray-200"
>
{
customizedProfile
.
name
}
</
span
>
</
div
>
<
div
className=
"flex flex-row justify-end items-center"
>
<
a
className=
"flex flex-row justify-center items-center h-12 w-12 border rounded-full hover:opacity-80 hover:shadow dark:text-white dark:border-gray-400"
href=
"/explore/rss.xml"
target=
"_blank"
>
<
Icon
.
Rss
className=
"w-7 h-auto opacity-60"
/>
</
a
>
</
div
>
</
div
>
<
section
className=
"w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800"
>
<
MobileHeader
showSearch=
{
false
}
/>
{
!
loadingState
.
isLoading
&&
(
<
main
className=
"relative
flex-grow max-w-2xl w-full h-auto flex flex-col justify-start items-start px-4 sm:pr-6
"
>
<
main
className=
"relative
w-full h-auto flex flex-col justify-start items-start -mt-2
"
>
<
MemoFilter
/>
{
sortedMemos
.
map
((
memo
)
=>
{
return
<
Memo
key=
{
`${memo.id}-${memo.createdTs}`
}
memo=
{
memo
}
readonly=
{
true
}
/>;
...
...
web/src/pages/Home.tsx
View file @
f3f0efba
import
{
useEffect
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useGlobalStore
,
useUserStore
}
from
"../store/module"
;
import
MemoEditor
from
"../components/MemoEditor"
;
...
...
@@ -11,20 +10,19 @@ import HomeSidebar from "../components/HomeSidebar";
function
Home
()
{
const
{
t
}
=
useTranslation
();
const
location
=
useLocation
();
const
globalStore
=
useGlobalStore
();
const
userStore
=
useUserStore
();
const
user
=
userStore
.
state
.
user
;
useEffect
(()
=>
{
const
{
owner
}
=
userStore
.
getState
();
if
(
userStore
.
isVisitorMode
())
{
if
(
!
owner
)
{
const
currentUserId
=
userStore
.
getCurrentUserId
();
userStore
.
getUserById
(
currentUserId
).
then
((
user
)
=>
{
if
(
!
user
)
{
toast
.
error
(
t
(
"message.user-not-found"
));
return
;
}
}
},
[
location
]);
}
);
},
[
userStore
.
getCurrentUserId
()
]);
useEffect
(()
=>
{
if
(
user
?.
setting
.
locale
)
{
...
...
web/src/router/index.tsx
View file @
f3f0efba
...
...
@@ -3,6 +3,7 @@ import { lazy } from "react";
import
{
isNullorUndefined
}
from
"../helpers/utils"
;
import
store
from
"../store"
;
import
{
initialGlobalState
,
initialUserState
}
from
"../store/module"
;
import
DailyReview
from
"../pages/DailyReview"
;
const
Root
=
lazy
(()
=>
import
(
"../layouts/Root"
));
const
Auth
=
lazy
(()
=>
import
(
"../pages/Auth"
));
...
...
@@ -67,45 +68,64 @@ const router = createBrowserRouter([
return
null
;
},
},
],
},
{
path
:
"/u/:userId"
,
element
:
<
Home
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
{
path
:
"/u/:userId"
,
element
:
<
Home
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
const
{
host
}
=
store
.
getState
().
user
;
if
(
isNullorUndefined
(
host
))
{
return
redirect
(
"/auth"
);
}
return
null
;
},
},
{
path
:
"/
explore"
,
element
:
<
Explore
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
const
{
host
}
=
store
.
getState
().
user
;
if
(
isNullorUndefined
(
host
))
{
return
redirect
(
"/auth"
);
}
return
null
;
},
},
{
path
:
"
explore"
,
element
:
<
Explore
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
const
{
host
}
=
store
.
getState
().
user
;
if
(
isNullorUndefined
(
host
))
{
return
redirect
(
"/auth"
);
}
return
null
;
},
const
{
host
}
=
store
.
getState
().
user
;
if
(
isNullorUndefined
(
host
))
{
return
redirect
(
"/auth"
);
}
return
null
;
},
},
{
path
:
"review"
,
element
:
<
DailyReview
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
const
{
host
}
=
store
.
getState
().
user
;
if
(
isNullorUndefined
(
host
))
{
return
redirect
(
"/auth"
);
}
return
null
;
},
},
],
},
{
path
:
"/m/:memoId"
,
...
...
web/src/store/module/memo.ts
View file @
f3f0efba
import
{
omit
,
uniqBy
}
from
"lodash-es"
;
import
{
omit
}
from
"lodash-es"
;
import
*
as
api
from
"../../helpers/api"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"../../helpers/consts"
;
import
{
useUserStore
}
from
"./"
;
import
store
,
{
useAppSelector
}
from
"../"
;
import
{
createMemo
,
deleteMemo
,
patchMemo
,
setIsFetching
,
se
tMemos
}
from
"../reducer/memo"
;
import
{
createMemo
,
deleteMemo
,
patchMemo
,
setIsFetching
,
upser
tMemos
}
from
"../reducer/memo"
;
const
convertResponseModelMemo
=
(
memo
:
Memo
):
Memo
=>
{
return
{
...
...
@@ -41,11 +41,7 @@ export const useMemoStore = () => {
}
const
{
data
}
=
(
await
api
.
getMemoList
(
memoFind
)).
data
;
const
fetchedMemos
=
data
.
map
((
m
)
=>
convertResponseModelMemo
(
m
));
if
(
offset
===
0
)
{
store
.
dispatch
(
setMemos
([]));
}
const
memos
=
state
.
memos
;
store
.
dispatch
(
setMemos
(
uniqBy
(
memos
.
concat
(
fetchedMemos
),
"id"
)));
store
.
dispatch
(
upsertMemos
(
fetchedMemos
));
store
.
dispatch
(
setIsFetching
(
false
));
return
fetchedMemos
;
...
...
web/src/store/module/user.ts
View file @
f3f0efba
...
...
@@ -4,7 +4,7 @@ import * as storage from "../../helpers/storage";
import
{
UNKNOWN_ID
}
from
"../../helpers/consts"
;
import
{
getSystemColorScheme
}
from
"../../helpers/utils"
;
import
{
setAppearance
,
setLocale
}
from
"../reducer/global"
;
import
{
setUser
,
patchUser
,
setHost
,
set
Owner
}
from
"../reducer/user"
;
import
{
setUser
,
patchUser
,
setHost
,
set
UserById
}
from
"../reducer/user"
;
const
defaultSetting
:
Setting
=
{
locale
:
"en"
,
...
...
@@ -50,14 +50,6 @@ export const initialUserState = async () => {
store
.
dispatch
(
setHost
(
convertResponseModelUser
(
systemStatus
.
host
)));
}
const
ownerUserId
=
getUserIdFromPath
();
if
(
ownerUserId
)
{
const
{
data
:
owner
}
=
(
await
api
.
getUserById
(
ownerUserId
)).
data
;
if
(
owner
)
{
store
.
dispatch
(
setOwner
(
convertResponseModelUser
(
owner
)));
}
}
const
{
data
}
=
(
await
api
.
getMyselfUser
()).
data
;
if
(
data
)
{
const
user
=
convertResponseModelUser
(
data
);
...
...
@@ -72,7 +64,7 @@ export const initialUserState = async () => {
};
const
getUserIdFromPath
=
()
=>
{
const
{
pathname
}
=
store
.
getState
().
location
;
const
pathname
=
location
.
pathname
;
const
userIdRegex
=
/^
\/
u
\/(\d
+
)
.*/
;
const
result
=
pathname
.
match
(
userIdRegex
);
if
(
result
&&
result
.
length
===
2
)
{
...
...
@@ -99,7 +91,7 @@ export const useUserStore = () => {
const
state
=
useAppSelector
((
state
)
=>
state
.
user
);
const
isVisitorMode
=
()
=>
{
return
!
(
getUserIdFromPath
()
===
undefined
);
return
state
.
user
===
undefined
||
(
getUserIdFromPath
()
&&
state
.
user
.
id
!==
getUserIdFromPath
()
);
};
return
{
...
...
@@ -119,9 +111,11 @@ export const useUserStore = () => {
}
},
getUserById
:
async
(
userId
:
UserId
)
=>
{
const
{
data
:
user
}
=
(
await
api
.
getUserById
(
userId
)).
data
;
if
(
user
)
{
return
convertResponseModelUser
(
user
);
const
{
data
}
=
(
await
api
.
getUserById
(
userId
)).
data
;
if
(
data
)
{
const
user
=
convertResponseModelUser
(
data
);
store
.
dispatch
(
setUserById
(
user
));
return
user
;
}
else
{
return
undefined
;
}
...
...
web/src/store/reducer/memo.ts
View file @
f3f0efba
import
{
createSlice
,
PayloadAction
}
from
"@reduxjs/toolkit"
;
import
{
uniqBy
}
from
"lodash-es"
;
interface
State
{
memos
:
Memo
[];
...
...
@@ -13,10 +14,10 @@ const memoSlice = createSlice({
isFetching
:
true
,
}
as
State
,
reducers
:
{
se
tMemos
:
(
state
,
action
:
PayloadAction
<
Memo
[]
>
)
=>
{
upser
tMemos
:
(
state
,
action
:
PayloadAction
<
Memo
[]
>
)
=>
{
return
{
...
state
,
memos
:
action
.
payload
,
memos
:
uniqBy
([...
state
.
memos
,
...
action
.
payload
],
"id"
)
,
};
},
createMemo
:
(
state
,
action
:
PayloadAction
<
Memo
>
)
=>
{
...
...
@@ -59,6 +60,6 @@ const memoSlice = createSlice({
},
});
export
const
{
se
tMemos
,
createMemo
,
patchMemo
,
deleteMemo
,
setIsFetching
}
=
memoSlice
.
actions
;
export
const
{
upser
tMemos
,
createMemo
,
patchMemo
,
deleteMemo
,
setIsFetching
}
=
memoSlice
.
actions
;
export
default
memoSlice
.
reducer
;
web/src/store/reducer/user.ts
View file @
f3f0efba
import
{
createSlice
,
PayloadAction
}
from
"@reduxjs/toolkit"
;
import
{
cloneDeep
}
from
"lodash-es"
;
interface
State
{
// host is the user who hist the system
host
?:
User
;
// owner is the user who owns the page. If in `/u/101`, then owner's id is `101`
owner
?:
User
;
// user is the user who is currently logged in
user
?:
User
;
userById
:
{
[
key
:
UserId
]:
User
};
}
const
userSlice
=
createSlice
({
name
:
"user"
,
initialState
:
{}
as
State
,
initialState
:
{
userById
:
{},
}
as
State
,
reducers
:
{
setHost
:
(
state
,
action
:
PayloadAction
<
User
|
undefined
>
)
=>
{
return
{
...
...
@@ -19,16 +21,18 @@ const userSlice = createSlice({
host
:
action
.
payload
,
};
},
set
Own
er
:
(
state
,
action
:
PayloadAction
<
User
|
undefined
>
)
=>
{
set
Us
er
:
(
state
,
action
:
PayloadAction
<
User
|
undefined
>
)
=>
{
return
{
...
state
,
own
er
:
action
.
payload
,
us
er
:
action
.
payload
,
};
},
setUser
:
(
state
,
action
:
PayloadAction
<
User
|
undefined
>
)
=>
{
setUserById
:
(
state
,
action
:
PayloadAction
<
User
>
)
=>
{
const
userById
=
cloneDeep
(
state
.
userById
);
userById
[
action
.
payload
.
id
]
=
action
.
payload
;
return
{
...
state
,
user
:
action
.
payloa
d
,
user
ById
:
userByI
d
,
};
},
patchUser
:
(
state
,
action
:
PayloadAction
<
Partial
<
User
>>
)
=>
{
...
...
@@ -43,6 +47,6 @@ const userSlice = createSlice({
},
});
export
const
{
setHost
,
set
Owner
,
setUser
,
patchUser
}
=
userSlice
.
actions
;
export
const
{
setHost
,
set
User
,
setUserById
,
patchUser
}
=
userSlice
.
actions
;
export
default
userSlice
.
reducer
;
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