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
0292f472
Unverified
Commit
0292f472
authored
Jul 08, 2023
by
boojack
Committed by
GitHub
Jul 08, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: add data empty placeholder (#1913)
parent
7e391bd5
Changes
30
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
168 additions
and
441 deletions
+168
-441
empty.png
web/public/assets/empty.png
+0
-0
ArchivedMemo.tsx
web/src/components/ArchivedMemo.tsx
+11
-22
CreateIdentityProviderDialog.tsx
web/src/components/CreateIdentityProviderDialog.tsx
+8
-3
CreateStorageServiceDialog.tsx
web/src/components/CreateStorageServiceDialog.tsx
+4
-6
Empty.tsx
web/src/components/Empty.tsx
+9
-0
Header.tsx
web/src/components/Header.tsx
+3
-3
Memo.tsx
web/src/components/Memo.tsx
+57
-54
MemoContent.tsx
web/src/components/MemoContent.tsx
+1
-4
MemoList.tsx
web/src/components/MemoList.tsx
+8
-7
MyAccountSection.tsx
web/src/components/Settings/MyAccountSection.tsx
+11
-16
PreferencesSection.tsx
web/src/components/Settings/PreferencesSection.tsx
+2
-2
SSOSection.tsx
web/src/components/Settings/SSOSection.tsx
+4
-4
StorageSection.tsx
web/src/components/Settings/StorageSection.tsx
+2
-2
SystemSection.tsx
web/src/components/Settings/SystemSection.tsx
+8
-5
UserBanner.tsx
web/src/components/UserBanner.tsx
+1
-1
HelpButton.tsx
web/src/components/kit/HelpButton.tsx
+0
-283
memo-filter.less
web/src/less/memo-filter.less
+1
-1
memo.less
web/src/less/memo.less
+1
-1
setting.less
web/src/less/setting.less
+1
-1
en.json
web/src/locales/en.json
+1
-2
zh-Hans.json
web/src/locales/zh-Hans.json
+2
-3
zh-Hant.json
web/src/locales/zh-Hant.json
+2
-3
Archived.tsx
web/src/pages/Archived.tsx
+5
-3
DailyReview.tsx
web/src/pages/DailyReview.tsx
+5
-3
Explore.tsx
web/src/pages/Explore.tsx
+10
-5
Home.tsx
web/src/pages/Home.tsx
+1
-1
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+1
-1
ResourcesDashboard.tsx
web/src/pages/ResourcesDashboard.tsx
+6
-2
user.ts
web/src/store/module/user.ts
+2
-2
setting.d.ts
web/src/types/modules/setting.d.ts
+1
-1
No files found.
web/public/assets/empty.png
0 → 100644
View file @
0292f472
127 KB
web/src/components/ArchivedMemo.tsx
View file @
0292f472
...
@@ -3,10 +3,10 @@ import { toast } from "react-hot-toast";
...
@@ -3,10 +3,10 @@ import { toast } from "react-hot-toast";
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useMemoStore
}
from
"@/store/module"
;
import
{
useMemoStore
}
from
"@/store/module"
;
import
{
getDateTimeString
}
from
"@/helpers/datetime"
;
import
{
getDateTimeString
}
from
"@/helpers/datetime"
;
import
useToggle
from
"@/hooks/useToggle"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
MemoContent
from
"./MemoContent"
;
import
MemoContent
from
"./MemoContent"
;
import
MemoResourceListView
from
"./MemoResourceListView"
;
import
MemoResourceListView
from
"./MemoResourceListView"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
"@/less/memo.less"
;
import
"@/less/memo.less"
;
interface
Props
{
interface
Props
{
...
@@ -17,19 +17,17 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
...
@@ -17,19 +17,17 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
const
{
memo
}
=
props
;
const
{
memo
}
=
props
;
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
memoStore
=
useMemoStore
();
const
memoStore
=
useMemoStore
();
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
handleDeleteMemoClick
=
async
()
=>
{
const
handleDeleteMemoClick
=
async
()
=>
{
if
(
showConfirmDeleteBtn
)
{
showCommonDialog
({
try
{
title
:
t
(
"memo.delete-memo"
),
content
:
t
(
"memo.delete-confirm"
),
style
:
"warning"
,
dialogName
:
"delete-memo-dialog"
,
onConfirm
:
async
()
=>
{
await
memoStore
.
deleteMemoById
(
memo
.
id
);
await
memoStore
.
deleteMemoById
(
memo
.
id
);
}
catch
(
error
:
any
)
{
},
console
.
error
(
error
);
});
toast
.
error
(
error
.
response
.
data
.
message
);
}
}
else
{
toggleConfirmDeleteBtn
();
}
};
};
const
handleRestoreMemoClick
=
async
()
=>
{
const
handleRestoreMemoClick
=
async
()
=>
{
...
@@ -46,14 +44,8 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
...
@@ -46,14 +44,8 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
}
}
};
};
const
handleMouseLeaveMemoWrapper
=
()
=>
{
if
(
showConfirmDeleteBtn
)
{
toggleConfirmDeleteBtn
(
false
);
}
};
return
(
return
(
<
div
className=
{
`memo-wrapper archived ${"memos-" + memo.id}`
}
onMouseLeave=
{
handleMouseLeaveMemoWrapper
}
>
<
div
className=
{
`memo-wrapper archived ${"memos-" + memo.id}`
}
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"status-text-container"
>
<
div
className=
"status-text-container"
>
<
span
className=
"time-text"
>
{
getDateTimeString
(
memo
.
updatedTs
)
}
</
span
>
<
span
className=
"time-text"
>
{
getDateTimeString
(
memo
.
updatedTs
)
}
</
span
>
...
@@ -65,10 +57,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
...
@@ -65,10 +57,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
</
button
>
</
button
>
</
Tooltip
>
</
Tooltip
>
<
Tooltip
title=
{
t
(
"common.delete"
)
}
placement=
"top"
>
<
Tooltip
title=
{
t
(
"common.delete"
)
}
placement=
"top"
>
<
button
<
button
onClick=
{
handleDeleteMemoClick
}
className=
"text-gray-500 dark:text-gray-400"
>
onClick=
{
handleDeleteMemoClick
}
className=
{
`text-gray-500 dark:text-gray-400 ${showConfirmDeleteBtn ? "text-red-600" : ""}`
}
>
<
Icon
.
Trash
className=
"w-4 h-auto cursor-pointer"
/>
<
Icon
.
Trash
className=
"w-4 h-auto cursor-pointer"
/>
</
button
>
</
button
>
</
Tooltip
>
</
Tooltip
>
...
...
web/src/components/CreateIdentityProviderDialog.tsx
View file @
0292f472
import
{
Button
,
Divider
,
Input
,
Option
,
Select
,
Typography
}
from
"@mui/joy"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
Button
,
Divider
,
Input
,
Option
,
Select
,
Typography
}
from
"@mui/joy"
;
import
*
as
api
from
"@/helpers/api"
;
import
*
as
api
from
"@/helpers/api"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
absolutifyLink
}
from
"@/helpers/utils"
;
import
{
absolutifyLink
}
from
"@/helpers/utils"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
{
useTranslation
}
from
"react-i18next"
;
const
templateList
:
IdentityProvider
[]
=
[
const
templateList
:
IdentityProvider
[]
=
[
{
{
...
@@ -170,7 +170,6 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
...
@@ -170,7 +170,6 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
if
(
type
===
"OAUTH2"
)
{
if
(
type
===
"OAUTH2"
)
{
if
(
if
(
oauth2Config
.
clientId
===
""
||
oauth2Config
.
clientId
===
""
||
oauth2Config
.
clientSecret
===
""
||
oauth2Config
.
authUrl
===
""
||
oauth2Config
.
authUrl
===
""
||
oauth2Config
.
tokenUrl
===
""
||
oauth2Config
.
tokenUrl
===
""
||
oauth2Config
.
userInfoUrl
===
""
||
oauth2Config
.
userInfoUrl
===
""
||
...
@@ -179,7 +178,13 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
...
@@ -179,7 +178,13 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
)
{
)
{
return
false
;
return
false
;
}
}
if
(
isCreating
)
{
if
(
oauth2Config
.
clientSecret
===
""
)
{
return
false
;
}
}
}
}
return
true
;
return
true
;
};
};
...
...
web/src/components/CreateStorageServiceDialog.tsx
View file @
0292f472
...
@@ -6,7 +6,7 @@ import * as api from "@/helpers/api";
...
@@ -6,7 +6,7 @@ import * as api from "@/helpers/api";
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
RequiredBadge
from
"./RequiredBadge"
;
import
RequiredBadge
from
"./RequiredBadge"
;
import
HelpButton
from
"./kit/HelpButton
"
;
import
LearnMore
from
"./LearnMore
"
;
interface
Props
extends
DialogProps
{
interface
Props
extends
DialogProps
{
storage
?:
ObjectStorage
;
storage
?:
ObjectStorage
;
...
@@ -183,11 +183,9 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
...
@@ -183,11 +183,9 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
onChange=
{
(
e
)
=>
setPartialS3Config
({
bucket
:
e
.
target
.
value
})
}
onChange=
{
(
e
)
=>
setPartialS3Config
({
bucket
:
e
.
target
.
value
})
}
fullWidth
fullWidth
/>
/>
<
div
className=
"flex flex-row"
>
<
div
className=
"flex flex-row items-center mb-1"
>
<
Typography
className=
"!mb-1"
level=
"body2"
>
<
Typography
level=
"body2"
>
{
t
(
"setting.storage-section.path"
)
}
</
Typography
>
{
t
(
"setting.storage-section.path"
)
}
<
LearnMore
className=
"ml-1"
title=
{
t
(
"setting.storage-section.path-description"
)
}
url=
"https://usememos.com/docs/local-storage"
/>
</
Typography
>
<
HelpButton
text=
{
t
(
"setting.storage-section.path-description"
)
}
url=
"https://usememos.com/docs/local-storage"
/>
</
div
>
</
div
>
<
Input
<
Input
className=
"mb-2"
className=
"mb-2"
...
...
web/src/components/Empty.tsx
0 → 100644
View file @
0292f472
const
Empty
=
()
=>
{
return
(
<
div
className=
"mx-auto"
>
<
img
className=
"w-24 h-auto dark:opacity-40"
src=
"/assets/empty.png"
alt=
""
/>
</
div
>
);
};
export
default
Empty
;
web/src/components/Header.tsx
View file @
0292f472
...
@@ -132,12 +132,12 @@ const Header = () => {
...
@@ -132,12 +132,12 @@ const Header = () => {
<
Icon
.
Settings
className=
"mr-3 w-6 h-auto opacity-70"
/>
{
t
(
"common.settings"
)
}
<
Icon
.
Settings
className=
"mr-3 w-6 h-auto opacity-70"
/>
{
t
(
"common.settings"
)
}
</>
</>
</
NavLink
>
</
NavLink
>
<
div
className=
"pr-3 pl-1 w-full
"
>
<
div
id=
"header-memo
"
>
<
button
<
button
className=
"
mt-2 w-full py-3 rounded-full flex flex-row justify-center items-center bg-green-600 text-white hover:shadow hover:opacity-8
0"
className=
"
px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 bg-gray-50/100 dark:bg-zinc-700/20 hover:bg-white hover:shadow dark:hover:bg-zinc-70
0"
onClick=
{
()
=>
showMemoEditorDialog
()
}
onClick=
{
()
=>
showMemoEditorDialog
()
}
>
>
<
Icon
.
Edit3
className=
"w-4 h-auto mr-1
"
/>
<
Icon
.
Feather
className=
"mr-3 w-6 h-auto opacity-70
"
/>
{
t
(
"common.new"
)
}
{
t
(
"common.new"
)
}
</
button
>
</
button
>
</
div
>
</
div
>
...
...
web/src/components/Memo.tsx
View file @
0292f472
...
@@ -21,12 +21,13 @@ import "@/less/memo.less";
...
@@ -21,12 +21,13 @@ import "@/less/memo.less";
interface
Props
{
interface
Props
{
memo
:
Memo
;
memo
:
Memo
;
readonly
?:
boolean
;
showCreator
?:
boolean
;
showVisibility
?:
boolean
;
showRelatedMemos
?:
boolean
;
showRelatedMemos
?:
boolean
;
}
}
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
memo
,
readonl
y
,
showRelatedMemos
}
=
props
;
const
{
memo
,
showCreator
,
showVisibilit
y
,
showRelatedMemos
}
=
props
;
const
{
t
,
i18n
}
=
useTranslation
();
const
{
t
,
i18n
}
=
useTranslation
();
const
filterStore
=
useFilterStore
();
const
filterStore
=
useFilterStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
...
@@ -35,7 +36,7 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -35,7 +36,7 @@ const Memo: React.FC<Props> = (props: Props) => {
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
displayTs
));
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
displayTs
));
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
isVisitorMode
=
userStore
.
isVisitorMode
()
||
readonly
;
const
readonly
=
userStore
.
isVisitorMode
()
||
userStore
.
getCurrentUserId
()
!==
memo
.
creatorId
;
useEffect
(()
=>
{
useEffect
(()
=>
{
Promise
.
allSettled
(
memo
.
relationList
.
map
((
memoRelation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
memoRelation
.
relatedMemoId
))).
then
(
Promise
.
allSettled
(
memo
.
relationList
.
map
((
memoRelation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
memoRelation
.
relatedMemoId
))).
then
(
...
@@ -134,7 +135,7 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -134,7 +135,7 @@ const Memo: React.FC<Props> = (props: Props) => {
filterStore
.
setTagFilter
(
tagName
);
filterStore
.
setTagFilter
(
tagName
);
}
}
}
else
if
(
targetEl
.
classList
.
contains
(
"todo-block"
))
{
}
else
if
(
targetEl
.
classList
.
contains
(
"todo-block"
))
{
if
(
isVisitorMode
)
{
if
(
readonly
)
{
return
;
return
;
}
}
...
@@ -173,7 +174,7 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -173,7 +174,7 @@ const Memo: React.FC<Props> = (props: Props) => {
};
};
const
handleMemoContentDoubleClick
=
(
e
:
React
.
MouseEvent
)
=>
{
const
handleMemoContentDoubleClick
=
(
e
:
React
.
MouseEvent
)
=>
{
if
(
isVisitorMode
)
{
if
(
readonly
)
{
return
;
return
;
}
}
...
@@ -216,60 +217,62 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -216,60 +217,62 @@ const Memo: React.FC<Props> = (props: Props) => {
<
Link
className=
"time-text"
to=
{
`/m/${memo.id}`
}
onClick=
{
handleMemoCreatedTimeClick
}
>
<
Link
className=
"time-text"
to=
{
`/m/${memo.id}`
}
onClick=
{
handleMemoCreatedTimeClick
}
>
{
createdTimeStr
}
{
createdTimeStr
}
</
Link
>
</
Link
>
{
isVisitorMode
&&
(
{
showCreator
&&
(
<
Link
className=
"name-text"
to=
{
`/u/${memo.creatorId}`
}
>
<
Link
className=
"name-text"
to=
{
`/u/${memo.creatorId}`
}
>
@
{
memo
.
creatorName
}
@
{
memo
.
creatorName
}
</
Link
>
</
Link
>
)
}
)
}
</
div
>
</
div
>
{
!
isVisitorMode
&&
(
<
div
className=
"btns-container space-x-2"
>
<
div
className=
"btns-container space-x-2"
>
{
showVisibility
&&
memo
.
visibility
!==
"PRIVATE"
&&
(
{
memo
.
visibility
!==
"PRIVATE"
&&
(
<
Tooltip
title=
{
t
(
`memo.visibility.${memo.visibility.toLowerCase()}`
)
}
placement=
"top"
>
<
Tooltip
title=
{
t
(
`memo.visibility.${memo.visibility.toLowerCase()}`
)
}
placement=
"top"
>
<
div
onClick=
{
()
=>
handleMemoVisibilityClick
(
memo
.
visibility
)
}
>
<
div
onClick=
{
()
=>
handleMemoVisibilityClick
(
memo
.
visibility
)
}
>
{
memo
.
visibility
===
"PUBLIC"
?
(
{
memo
.
visibility
===
"PUBLIC"
?
(
<
Icon
.
Globe2
className=
"w-4 h-auto cursor-pointer rounded text-green-600"
/>
<
Icon
.
Globe2
className=
"w-4 h-auto cursor-pointer rounded text-green-600"
/>
)
:
(
)
:
(
<
Icon
.
Users
className=
"w-4 h-auto cursor-pointer rounded text-gray-500 dark:text-gray-400"
/>
<
Icon
.
Users
className=
"w-4 h-auto cursor-pointer rounded text-gray-500 dark:text-gray-400"
/>
)
}
)
}
</
div
>
</
Tooltip
>
)
}
{
memo
.
pinned
&&
<
Icon
.
Bookmark
className=
"w-4 h-auto rounded text-green-600"
/>
}
{
!
readonly
&&
(
<>
<
span
className=
"btn more-action-btn"
>
<
Icon
.
MoreHorizontal
className=
"icon-img"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container min-w-[6em]"
>
<
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=
{
handleGenerateMemoImageBtnClick
}
>
<
Icon
.
Share
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.share"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
<
Icon
.
Link
className=
"w-4 h-auto mr-2"
/>
Mark
</
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
>
</
div
>
</
div
>
</
Tooltip
>
)
}
{
memo
.
pinned
&&
<
Icon
.
Bookmark
className=
"w-4 h-auto rounded text-green-600"
/>
}
<
span
className=
"btn more-action-btn"
>
<
Icon
.
MoreHorizontal
className=
"icon-img"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container min-w-[6em]"
>
<
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=
{
handleGenerateMemoImageBtnClick
}
>
<
Icon
.
Share
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.share"
)
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
<
Icon
.
Link
className=
"w-4 h-auto mr-2"
/>
Mark
</
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
>
</
div
>
</
div
>
</
div
>
</>
</
div
>
)
}
)
}
</
div
>
</
div
>
</
div
>
<
MemoContent
<
MemoContent
content=
{
memo
.
content
}
content=
{
memo
.
content
}
...
@@ -282,14 +285,14 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -282,14 +285,14 @@ const Memo: React.FC<Props> = (props: Props) => {
{
showRelatedMemos
&&
relatedMemoList
.
length
>
0
&&
(
{
showRelatedMemos
&&
relatedMemoList
.
length
>
0
&&
(
<>
<>
<
p
className=
"text-sm dark:text-gray-300 m
t-4 mb-1
pl-4 opacity-50 flex flex-row items-center"
>
<
p
className=
"text-sm dark:text-gray-300 m
y-2
pl-4 opacity-50 flex flex-row items-center"
>
<
Icon
.
Link
className=
"w-4 h-auto mr-1"
/>
<
Icon
.
Link
className=
"w-4 h-auto mr-1"
/>
<
span
>
Related memos
</
span
>
<
span
>
Related memos
</
span
>
</
p
>
</
p
>
{
relatedMemoList
.
map
((
relatedMemo
)
=>
{
{
relatedMemoList
.
map
((
relatedMemo
)
=>
{
return
(
return
(
<
div
key=
{
relatedMemo
.
id
}
className=
"w-full"
>
<
div
key=
{
relatedMemo
.
id
}
className=
"w-full"
>
<
Memo
memo=
{
relatedMemo
}
readonly=
{
readonly
}
/>
<
Memo
memo=
{
relatedMemo
}
showCreator
/>
</
div
>
</
div
>
);
);
})
}
})
}
...
...
web/src/components/MemoContent.tsx
View file @
0292f472
...
@@ -24,14 +24,11 @@ interface State {
...
@@ -24,14 +24,11 @@ interface State {
const
MemoContent
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
MemoContent
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
className
,
content
,
showFull
,
onMemoContentClick
,
onMemoContentDoubleClick
}
=
props
;
const
{
className
,
content
,
showFull
,
onMemoContentClick
,
onMemoContentDoubleClick
}
=
props
;
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
userStore
=
useUserStore
();
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
expandButtonStatus
:
-
1
,
expandButtonStatus
:
-
1
,
});
});
const
memoContentContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContentContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
//variable for auto-collapse
const
userStore
=
useUserStore
();
const
isVisitorMode
=
userStore
.
isVisitorMode
();
const
isVisitorMode
=
userStore
.
isVisitorMode
();
const
autoCollapse
:
boolean
=
isVisitorMode
?
true
:
(
userStore
.
state
.
user
as
User
).
localSetting
.
enableAutoCollapse
;
const
autoCollapse
:
boolean
=
isVisitorMode
?
true
:
(
userStore
.
state
.
user
as
User
).
localSetting
.
enableAutoCollapse
;
...
...
web/src/components/MemoList.tsx
View file @
0292f472
...
@@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from "react";
...
@@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from "react";
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useFilterStore
,
useMemoStore
,
useShortcutStore
,
useUserStore
}
from
"@/store/module"
;
import
{
useFilterStore
,
useMemoStore
,
useShortcutStore
,
useUserStore
}
from
"@/store/module"
;
import
{
TAG_REG
,
LINK_REG
}
from
"@/labs/marked/parser"
;
import
{
TAG_REG
,
LINK_REG
,
PLAIN_LINK_REG
}
from
"@/labs/marked/parser"
;
import
{
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
{
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"@/helpers/consts"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"@/helpers/consts"
;
import
{
checkShouldShowMemoWithFilters
}
from
"@/helpers/filter"
;
import
{
checkShouldShowMemoWithFilters
}
from
"@/helpers/filter"
;
import
Empty
from
"./Empty"
;
import
Memo
from
"./Memo"
;
import
Memo
from
"./Memo"
;
import
"@/less/memo-list.less"
;
import
"@/less/memo-list.less"
;
import
{
PLAIN_LINK_REG
}
from
"@/labs/marked/parser"
;
const
MemoList
=
()
=>
{
const
MemoList
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -153,7 +153,7 @@ const MemoList = () => {
...
@@ -153,7 +153,7 @@ const MemoList = () => {
return (
return (
<
div
className=
"memo-list-container"
>
<
div
className=
"memo-list-container"
>
{
sortedMemos
.
map
((
memo
)
=>
(
{
sortedMemos
.
map
((
memo
)
=>
(
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
/>
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
showVisibility
/>
))
}
))
}
{
isFetching
?
(
{
isFetching
?
(
<
div
className=
"status-text-container fetching-tip"
>
<
div
className=
"status-text-container fetching-tip"
>
...
@@ -163,10 +163,11 @@ const MemoList = () => {
...
@@ -163,10 +163,11 @@ const MemoList = () => {
<
div
ref=
{
statusRef
}
className=
"status-text-container"
>
<
div
ref=
{
statusRef
}
className=
"status-text-container"
>
<
p
className=
"status-text"
>
<
p
className=
"status-text"
>
{
isComplete
?
(
{
isComplete
?
(
sortedMemos
.
length
===
0
?
(
sortedMemos
.
length
===
0
&&
(
t
(
"message.no-memos"
)
<
div
className=
"w-full mt-4 mb-8 flex flex-col justify-center items-center italic"
>
)
:
(
<
Empty
/>
t
(
"message.memos-ready"
)
<
p
className=
"mt-4 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
)
)
:
(
)
:
(
<>
<>
...
...
web/src/components/Settings/MyAccountSection.tsx
View file @
0292f472
...
@@ -28,6 +28,8 @@ const MyAccountSection = () => {
...
@@ -28,6 +28,8 @@ const MyAccountSection = () => {
});
});
};
};
const
exampleWithCurl
=
`curl '
${
openAPIRoute
}
' -H 'Content-Type: application/json' --data-raw '{"content":"Hello world!"}'`
;
return
(
return
(
<>
<>
<
div
className=
"section-container account-section-container"
>
<
div
className=
"section-container account-section-container"
>
...
@@ -48,22 +50,15 @@ const MyAccountSection = () => {
...
@@ -48,22 +50,15 @@ const MyAccountSection = () => {
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"section-container openapi-section-container mt-6"
>
<
div
className=
"section-container openapi-section-container mt-6"
>
<
p
className=
"title-text"
>
{
t
(
"setting.account-section.openapi-title"
)
}
</
p
>
<
p
className=
"title-text"
>
Open ID
</
p
>
<
Input
className=
"w-full mb-3"
value=
{
openAPIRoute
}
readOnly
/>
<
div
className=
"w-full flex flex-row justify-start items-center"
>
<
Button
color=
"warning"
className=
"mb-4"
onClick=
{
handleResetOpenIdBtnClick
}
>
<
Input
className=
"grow mr-2"
value=
{
user
.
openId
}
readOnly
/>
{
t
(
"setting.account-section.reset-api"
)
}
<
Icon
.
RefreshCw
className=
"ml-2 h-4 w-4"
/>
<
Button
className=
"shrink-0"
color=
"warning"
onClick=
{
handleResetOpenIdBtnClick
}
>
</
Button
>
<
Icon
.
RefreshCw
className=
"h-4 w-4"
/>
<
Textarea
</
Button
>
className=
"w-full !font-mono !text-sm mt-4"
</
div
>
value=
{
`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "${t(
<
p
className=
"title-text"
>
Open API Example with cURL
</
p
>
"setting.account-section.openapi-sample-post",
<
Textarea
className=
"w-full !font-mono !text-sm whitespace-pre"
value=
{
exampleWithCurl
}
readOnly
/>
{
url: window.location.origin,
interpolation: { escapeValue: false },
}
)}"\n}`
}
readOnly
/>
</
div
>
</
div
>
</>
</>
);
);
...
...
web/src/components/Settings/PreferencesSection.tsx
View file @
0292f472
...
@@ -7,7 +7,7 @@ import { useGlobalStore, useUserStore } from "@/store/module";
...
@@ -7,7 +7,7 @@ import { useGlobalStore, useUserStore } from "@/store/module";
import
{
VISIBILITY_SELECTOR_ITEMS
}
from
"@/helpers/consts"
;
import
{
VISIBILITY_SELECTOR_ITEMS
}
from
"@/helpers/consts"
;
import
AppearanceSelect
from
"../AppearanceSelect"
;
import
AppearanceSelect
from
"../AppearanceSelect"
;
import
LocaleSelect
from
"../LocaleSelect"
;
import
LocaleSelect
from
"../LocaleSelect"
;
import
HelpButton
from
"../kit/HelpButton
"
;
import
LearnMore
from
"../LearnMore
"
;
import
"@/less/settings/preferences-section.less"
;
import
"@/less/settings/preferences-section.less"
;
const
PreferencesSection
=
()
=>
{
const
PreferencesSection
=
()
=>
{
...
@@ -141,7 +141,7 @@ const PreferencesSection = () => {
...
@@ -141,7 +141,7 @@ const PreferencesSection = () => {
<
div
className=
"mb-2 w-full flex flex-row justify-between items-center"
>
<
div
className=
"mb-2 w-full flex flex-row justify-between items-center"
>
<
div
className=
"w-auto flex items-center"
>
<
div
className=
"w-auto flex items-center"
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.preference-section.telegram-user-id"
)
}
</
span
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.preference-section.telegram-user-id"
)
}
</
span
>
<
HelpButton
icon=
"help"
url=
"https://usememos.com/docs/integration/telegram-bot"
/>
<
LearnMore
url=
"https://usememos.com/docs/integration/telegram-bot"
/>
</
div
>
</
div
>
<
Button
onClick=
{
handleSaveTelegramUserId
}
>
{
t
(
"common.save"
)
}
</
Button
>
<
Button
onClick=
{
handleSaveTelegramUserId
}
>
{
t
(
"common.save"
)
}
</
Button
>
</
div
>
</
div
>
...
...
web/src/components/Settings/SSOSection.tsx
View file @
0292f472
import
{
Divider
}
from
"@mui/joy"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
*
as
api
from
"@/helpers/api"
;
import
*
as
api
from
"@/helpers/api"
;
import
{
Divider
}
from
"@mui/joy"
;
import
showCreateIdentityProviderDialog
from
"../CreateIdentityProviderDialog"
;
import
showCreateIdentityProviderDialog
from
"../CreateIdentityProviderDialog"
;
import
Dropdown
from
"../kit/Dropdown"
;
import
Dropdown
from
"../kit/Dropdown"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
HelpButton
from
"../kit/HelpButton
"
;
import
LearnMore
from
"../LearnMore
"
;
const
SSOSection
=
()
=>
{
const
SSOSection
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -43,7 +43,7 @@ const SSOSection = () => {
...
@@ -43,7 +43,7 @@ const SSOSection = () => {
<
div
className=
"section-container"
>
<
div
className=
"section-container"
>
<
div
className=
"mb-2 w-full flex flex-row justify-start items-center gap-1"
>
<
div
className=
"mb-2 w-full flex flex-row justify-start items-center gap-1"
>
<
span
className=
"font-mono text-sm text-gray-400"
>
{
t
(
"setting.sso-section.sso-list"
)
}
</
span
>
<
span
className=
"font-mono text-sm text-gray-400"
>
{
t
(
"setting.sso-section.sso-list"
)
}
</
span
>
<
HelpButton
icon=
"help"
url=
"https://usememos.com/docs/keycloak"
/>
<
LearnMore
url=
"https://usememos.com/docs/keycloak"
/>
<
button
<
button
className=
"btn-normal px-2 py-0 ml-1"
className=
"btn-normal px-2 py-0 ml-1"
onClick=
{
()
=>
showCreateIdentityProviderDialog
(
undefined
,
fetchIdentityProviderList
)
}
onClick=
{
()
=>
showCreateIdentityProviderDialog
(
undefined
,
fetchIdentityProviderList
)
}
...
@@ -57,7 +57,7 @@ const SSOSection = () => {
...
@@ -57,7 +57,7 @@ const SSOSection = () => {
{
identityProviderList
.
map
((
identityProvider
)
=>
(
{
identityProviderList
.
map
((
identityProvider
)
=>
(
<
div
<
div
key=
{
identityProvider
.
id
}
key=
{
identityProvider
.
id
}
className=
"py-2 w-full border-
t
last:border-b dark:border-zinc-700 flex flex-row items-center justify-between"
className=
"py-2 w-full border-
b
last:border-b dark:border-zinc-700 flex flex-row items-center justify-between"
>
>
<
div
className=
"flex flex-row items-center"
>
<
div
className=
"flex flex-row items-center"
>
<
p
className=
"ml-2"
>
<
p
className=
"ml-2"
>
...
...
web/src/components/Settings/StorageSection.tsx
View file @
0292f472
...
@@ -8,7 +8,7 @@ import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
...
@@ -8,7 +8,7 @@ import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
import
showUpdateLocalStorageDialog
from
"../UpdateLocalStorageDialog"
;
import
showUpdateLocalStorageDialog
from
"../UpdateLocalStorageDialog"
;
import
Dropdown
from
"../kit/Dropdown"
;
import
Dropdown
from
"../kit/Dropdown"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
HelpButton
from
"../kit/HelpButton
"
;
import
LearnMore
from
"../LearnMore
"
;
const
StorageSection
=
()
=>
{
const
StorageSection
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -76,7 +76,7 @@ const StorageSection = () => {
...
@@ -76,7 +76,7 @@ const StorageSection = () => {
<
Divider
/>
<
Divider
/>
<
div
className=
"mt-4 mb-2 w-full flex flex-row justify-start items-center gap-1"
>
<
div
className=
"mt-4 mb-2 w-full flex flex-row justify-start items-center gap-1"
>
<
span
className=
"font-mono text-sm text-gray-400"
>
{
t
(
"setting.storage-section.storage-services-list"
)
}
</
span
>
<
span
className=
"font-mono text-sm text-gray-400"
>
{
t
(
"setting.storage-section.storage-services-list"
)
}
</
span
>
<
HelpButton
className=
"btn"
icon=
"info"
url=
"https://usememos.com/docs/storage"
/>
<
LearnMore
url=
"https://usememos.com/docs/storage"
/>
<
button
className=
"btn-normal px-2 py-0 ml-1"
onClick=
{
()
=>
showCreateStorageServiceDialog
(
undefined
,
fetchStorageList
)
}
>
<
button
className=
"btn-normal px-2 py-0 ml-1"
onClick=
{
()
=>
showCreateStorageServiceDialog
(
undefined
,
fetchStorageList
)
}
>
{
t
(
"common.create"
)
}
{
t
(
"common.create"
)
}
</
button
>
</
button
>
...
...
web/src/components/Settings/SystemSection.tsx
View file @
0292f472
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
Button
,
Divider
,
Input
,
Switch
,
Textarea
}
from
"@mui/joy"
;
import
{
Button
,
Divider
,
Input
,
Switch
,
Textarea
,
Tooltip
}
from
"@mui/joy"
;
import
{
formatBytes
}
from
"@/helpers/utils"
;
import
{
formatBytes
}
from
"@/helpers/utils"
;
import
{
useGlobalStore
}
from
"@/store/module"
;
import
{
useGlobalStore
}
from
"@/store/module"
;
import
*
as
api
from
"@/helpers/api"
;
import
*
as
api
from
"@/helpers/api"
;
import
HelpButton
from
"../kit/HelpButton"
;
import
showUpdateCustomizedProfileDialog
from
"../UpdateCustomizedProfileDialog"
;
import
showUpdateCustomizedProfileDialog
from
"../UpdateCustomizedProfileDialog"
;
import
Icon
from
"../Icon"
;
import
LearnMore
from
"../LearnMore"
;
import
"@/less/settings/system-section.less"
;
import
"@/less/settings/system-section.less"
;
interface
State
{
interface
State
{
...
@@ -224,7 +225,9 @@ const SystemSection = () => {
...
@@ -224,7 +225,9 @@ const SystemSection = () => {
<
div
className=
"form-label"
>
<
div
className=
"form-label"
>
<
div
className=
"flex flex-row items-center"
>
<
div
className=
"flex flex-row items-center"
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.system-section.max-upload-size"
)
}
</
span
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.system-section.max-upload-size"
)
}
</
span
>
<
HelpButton
icon=
"info"
hint=
{
t
(
"setting.system-section.max-upload-size-hint"
)
}
/>
<
Tooltip
title=
{
t
(
"setting.system-section.max-upload-size-hint"
)
}
placement=
"top"
>
<
Icon
.
HelpCircle
className=
"w-4 h-auto"
/>
</
Tooltip
>
</
div
>
</
div
>
<
Input
<
Input
className=
"w-16"
className=
"w-16"
...
@@ -241,9 +244,9 @@ const SystemSection = () => {
...
@@ -241,9 +244,9 @@ const SystemSection = () => {
<
div
className=
"flex flex-row items-center"
>
<
div
className=
"flex flex-row items-center"
>
<
div
className=
"w-auto flex items-center"
>
<
div
className=
"w-auto flex items-center"
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.system-section.telegram-bot-token"
)
}
</
span
>
<
span
className=
"text-sm mr-1"
>
{
t
(
"setting.system-section.telegram-bot-token"
)
}
</
span
>
<
HelpButton
<
LearnMore
hint=
{
t
(
"setting.system-section.telegram-bot-token-description"
)
}
url=
"https://usememos.com/docs/integration/telegram-bot"
url=
"https://usememos.com/docs/integration/telegram-bot"
title=
{
t
(
"setting.system-section.telegram-bot-token-description"
)
}
/>
/>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/UserBanner.tsx
View file @
0292f472
...
@@ -40,7 +40,7 @@ const UserBanner = () => {
...
@@ -40,7 +40,7 @@ const UserBanner = () => {
<
Dropdown
<
Dropdown
className=
"w-full"
className=
"w-full"
trigger=
{
trigger=
{
<
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"
>
<
div
className=
"px-
4 py-2 max-w-full flex flex-row justify-start items-center cursor-pointer rounded-full
hover:shadow hover:bg-white dark:hover:bg-zinc-700"
>
<
UserAvatar
avatarUrl=
{
user
?.
avatarUrl
}
/>
<
UserAvatar
avatarUrl=
{
user
?.
avatarUrl
}
/>
<
span
className=
"px-1 text-lg font-medium text-slate-800 dark:text-gray-200 shrink truncate"
>
<
span
className=
"px-1 text-lg font-medium text-slate-800 dark:text-gray-200 shrink truncate"
>
{
userStore
.
isVisitorMode
()
?
systemStatus
.
customizedProfile
.
name
:
username
}
{
userStore
.
isVisitorMode
()
?
systemStatus
.
customizedProfile
.
name
:
username
}
...
...
web/src/components/kit/HelpButton.tsx
deleted
100644 → 0
View file @
7e391bd5
import
{
ReactNode
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
Button
,
IconButton
,
Tooltip
}
from
"@mui/joy"
;
import
{
generateDialog
}
from
"../Dialog"
;
import
Icon
from
"../Icon"
;
const
openUrl
=
(
url
?:
string
)
=>
{
window
.
open
(
url
,
"_blank"
);
};
/** Options for {@link HelpButton} */
interface
HelpProps
{
/**
* Plain text to show in the dialog.
*
* If the text contains "\n", it will be split to multiple paragraphs.
*/
text
?:
string
;
/**
* The title of the dialog.
*
* If not provided, the title will be set according to the `icon` prop.
*/
title
?:
string
;
/**
* External documentation URL.
*
* If provided, this will be shown as a link button in the bottom of the dialog.
*
* If provided alone, the button will just open the URL in a new tab.
*
* @param {string} url - External URL to the documentation.
*/
url
?:
string
;
/**
* The tooltip of the button.
*/
hint
?:
string
|
"none"
;
/**
* The placement of the hovering hint.
* @defaultValue "top"
*/
hintPlacement
?:
"top"
|
"bottom"
|
"left"
|
"right"
;
/**
* The icon to show in the button.
*
* Also used to infer `title` and `hint`, if they are not provided.
*
* @defaultValue Icon.HelpCircle
* @see {@link Icon.LucideIcon}
*/
icon
?:
Icon
.
LucideIcon
|
"link"
|
"info"
|
"help"
|
"alert"
|
"warn"
;
/**
* The className for the button.
* @defaultValue `!-mt-2` (aligns the button vertically with nearby text)
*/
className
?:
string
;
/**
* The color of the button.
* @defaultValue "neutral"
*/
color
?:
"primary"
|
"neutral"
|
"danger"
|
"info"
|
"success"
|
"warning"
;
/**
* The variant of the button.
* @defaultValue "plain"
*/
variant
?:
"plain"
|
"outlined"
|
"soft"
|
"solid"
;
/**
* The size of the button.
* @defaultValue "md"
*/
size
?:
"sm"
|
"md"
|
"lg"
;
/**
* `ReactNode` HTML content to show in the dialog.
*
* If provided, will be shown before `text`.
*
* You'll probably want to use `text` instead.
*/
children
?:
ReactNode
|
undefined
;
}
interface
HelpDialogProps
extends
HelpProps
,
DialogProps
{}
const
HelpfulDialog
:
React
.
FC
<
HelpDialogProps
>
=
(
props
:
HelpDialogProps
)
=>
{
const
{
t
}
=
useTranslation
();
const
{
children
,
destroy
,
icon
}
=
props
;
const
LucideIcon
=
icon
as
Icon
.
LucideIcon
;
const
handleCloseBtnClick
=
()
=>
{
destroy
();
};
return
(
<>
<
div
className=
"dialog-header-container"
>
<
LucideIcon
size=
"24"
/>
<
p
className=
"title-text text-left"
>
{
props
.
title
}
</
p
>
<
button
className=
"btn close-btn"
onClick=
{
handleCloseBtnClick
}
>
<
Icon
.
X
/>
</
button
>
</
div
>
<
div
className=
"dialog-content-container max-w-sm"
>
{
children
}
{
props
.
text
?
props
.
text
.
split
(
/
\n
|
\\
n/
).
map
((
text
)
=>
{
return
(
<
p
key=
{
text
}
className=
"mt-2 break-words text-justify"
>
{
text
}
</
p
>
);
})
:
null
}
<
div
className=
"mt-2 w-full flex flex-row justify-end space-x-2"
>
{
props
.
url
?
(
<
Button
className=
"btn-normal"
variant=
"outlined"
color=
{
props
.
color
}
onClick=
{
()
=>
openUrl
(
props
.
url
)
}
>
{
t
(
"common.learn-more"
)
}
<
Icon
.
ExternalLink
className=
"ml-1 w-4 h-4 opacity-80"
/>
</
Button
>
)
:
null
}
<
Button
className=
"btn-normal"
variant=
"outlined"
color=
{
props
.
color
}
onClick=
{
handleCloseBtnClick
}
>
{
t
(
"common.close"
)
}
</
Button
>
</
div
>
</
div
>
</>
);
};
function
showHelpDialog
(
props
:
HelpProps
)
{
generateDialog
(
{
className
:
"help-dialog"
,
dialogName
:
"help-dialog"
,
clickSpaceDestroy
:
true
,
},
HelpfulDialog
,
props
);
}
/**
* Show a helpful `IconButton` that behaves differently depending on the props.
*
* The main purpose of this component is to avoid UI clutter.
*
* Use the property `icon` to set the icon and infer the title and hint automatically.
*
* Use cases:
* - Button with just a hover hint
* - Button with a hover hint and link
* - Button with a hover hint that opens a dialog with text and a link.
*
* @example
* <Helpful hint="Hint" />
* <Helpful hint="This is a hint with a link" url="https://usememos.com/" />
* <Helpful icon="warn" text={t("i18n.key.long-dialog-text")} url="https://usememos.com/" />
* <Helpful />
*
* <div className="flex flex-row">
* <span className="ml-2">Sample alignment</span>
* <Helpful hint="Button with hint" />
* </div>
* @param props.title - The title of the dialog. Defaults to "Learn more" i18n key.
* @param props.text - Plain text to show in the dialog. Line breaks are supported.
* @param props.url - External memos documentation URL.
* @param props.hint - The hint when hovering the button.
* @param props.hintPlacement - The placement of the hovering hint. Defaults to "top".
* @param props.icon - The icon to show in the button.
* @param props.className - The class name for the button.
* @param {HelpProps} props - See {@link HelpDialogProps} for all exposed props.
*/
const
HelpButton
=
(
props
:
HelpProps
):
JSX
.
Element
=>
{
const
{
t
}
=
useTranslation
();
const
color
=
props
.
color
??
"neutral"
;
const
variant
=
props
.
variant
??
"plain"
;
const
className
=
props
.
className
??
""
;
const
hintPlacement
=
props
.
hintPlacement
??
"top"
;
const
iconButtonSize
=
"sm"
;
const
dialogAvailable
=
props
.
text
||
props
.
children
;
const
clickActionAvailable
=
props
.
url
||
dialogAvailable
;
const
onlyUrlAvailable
=
props
.
url
&&
!
dialogAvailable
;
let
LucideIcon
=
(()
=>
{
switch
(
props
.
icon
)
{
case
"info"
:
return
Icon
.
Info
;
case
"help"
:
return
Icon
.
HelpCircle
;
case
"warn"
:
case
"alert"
:
return
Icon
.
AlertTriangle
;
case
"link"
:
return
Icon
.
ExternalLink
;
default
:
return
Icon
.
HelpCircle
;
}
})()
as
Icon
.
LucideIcon
;
const
hint
=
(()
=>
{
switch
(
props
.
hint
)
{
case
undefined
:
return
t
(
(()
=>
{
if
(
!
dialogAvailable
)
{
LucideIcon
=
Icon
.
ExternalLink
;
}
switch
(
LucideIcon
)
{
case
Icon
.
Info
:
return
"common.dialog.info"
;
case
Icon
.
AlertTriangle
:
return
"common.dialog.warning"
;
case
Icon
.
ExternalLink
:
return
"common.learn-more"
;
case
Icon
.
HelpCircle
:
default
:
return
"common.dialog.help"
;
}
})()
);
case
""
:
case
"none"
:
case
"false"
:
case
"disabled"
:
return
undefined
;
default
:
return
props
.
hint
;
}
})();
const
sizePx
=
(()
=>
{
switch
(
props
.
size
)
{
case
"sm"
:
return
14
;
case
"lg"
:
return
18
;
case
"md"
:
default
:
return
16
;
}
})();
if
(
!
dialogAvailable
&&
!
clickActionAvailable
&&
!
props
.
hint
)
{
return
(
<
IconButton
className=
{
className
}
color=
{
color
}
variant=
{
variant
}
size=
{
iconButtonSize
}
>
<
LucideIcon
size=
{
sizePx
}
/>
</
IconButton
>
);
}
const
wrapInTooltip
=
(
element
:
JSX
.
Element
)
=>
{
if
(
!
hint
)
{
return
element
;
}
return
(
<
Tooltip
placement=
{
hintPlacement
}
title=
{
hint
}
color=
{
color
}
variant=
{
variant
}
size=
{
props
.
size
}
>
{
element
}
</
Tooltip
>
);
};
if
(
clickActionAvailable
)
{
props
=
{
...
props
,
title
:
props
.
title
??
hint
,
hint
:
hint
,
color
:
color
,
variant
:
variant
,
icon
:
LucideIcon
};
const
clickAction
=
()
=>
{
dialogAvailable
?
showHelpDialog
(
props
)
:
openUrl
(
props
.
url
);
};
LucideIcon
=
dialogAvailable
||
onlyUrlAvailable
?
LucideIcon
:
Icon
.
ExternalLink
;
return
wrapInTooltip
(
<
IconButton
className=
{
className
}
color=
{
color
}
variant=
{
variant
}
size=
{
iconButtonSize
}
onClick=
{
clickAction
}
>
<
LucideIcon
size=
{
sizePx
}
/>
</
IconButton
>
);
}
return
wrapInTooltip
(
<
IconButton
className=
{
className
}
color=
{
color
}
variant=
{
variant
}
size=
{
iconButtonSize
}
>
<
LucideIcon
size=
{
sizePx
}
/>
</
IconButton
>
);
};
export
default
HelpButton
;
web/src/less/memo-filter.less
View file @
0292f472
.filter-query-container {
.filter-query-container {
@apply flex flex-row justify-start items-start w-full flex-wrap px-2 p
t
-2 text-sm font-mono leading-7 dark:text-gray-300;
@apply flex flex-row justify-start items-start w-full flex-wrap px-2 p
b
-2 text-sm font-mono leading-7 dark:text-gray-300;
> .filter-item-container {
> .filter-item-container {
@apply flex flex-row justify-start items-center px-2 mr-2 cursor-pointer dark:text-gray-300 bg-gray-200 dark:bg-zinc-700 rounded whitespace-nowrap truncate hover:line-through;
@apply flex flex-row justify-start items-center px-2 mr-2 cursor-pointer dark:text-gray-300 bg-gray-200 dark:bg-zinc-700 rounded whitespace-nowrap truncate hover:line-through;
...
...
web/src/less/memo.less
View file @
0292f472
.memo-wrapper {
.memo-wrapper {
@apply relative flex flex-col justify-start items-start w-full p-4 pt-3 m
t
-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-600 hover:border-gray-200 dark:hover:border-zinc-600;
@apply relative flex flex-col justify-start items-start w-full p-4 pt-3 m
b
-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-600 hover:border-gray-200 dark:hover:border-zinc-600;
&.pinned {
&.pinned {
@apply border-gray-200 border-2 dark:border-zinc-600;
@apply border-gray-200 border-2 dark:border-zinc-600;
...
...
web/src/less/setting.less
View file @
0292f472
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
}
}
> .section-content-container {
> .section-content-container {
@apply w-full sm:w-auto pl-2 grow flex flex-col justify-start items-start h-full;
@apply w-full sm:w-auto pl-2
pb-4
grow flex flex-col justify-start items-start h-full;
> .section-container {
> .section-container {
@apply flex flex-col justify-start items-start w-full;
@apply flex flex-col justify-start items-start w-full;
...
...
web/src/locales/en.json
View file @
0292f472
...
@@ -324,9 +324,8 @@
...
@@ -324,9 +324,8 @@
"or"
:
"Or"
"or"
:
"Or"
},
},
"message"
:
{
"message"
:
{
"no-
memos"
:
"no memos 🌃
"
,
"no-
data"
:
"Maybe no data was found, maybe it should be another option.
"
,
"memos-ready"
:
"all memos are ready 🎉"
,
"memos-ready"
:
"all memos are ready 🎉"
,
"no-resource"
:
"no resource 🌃"
,
"resource-ready"
:
"all resource are ready 🎉"
,
"resource-ready"
:
"all resource are ready 🎉"
,
"restored-successfully"
:
"Restored successfully"
,
"restored-successfully"
:
"Restored successfully"
,
"memo-updated-datetime"
:
"Memo created datetime changed."
,
"memo-updated-datetime"
:
"Memo created datetime changed."
,
...
...
web/src/locales/zh-Hans.json
View file @
0292f472
...
@@ -225,8 +225,7 @@
...
@@ -225,8 +225,7 @@
"memo-updated-datetime"
:
"备忘录创建日期时间已更改。"
,
"memo-updated-datetime"
:
"备忘录创建日期时间已更改。"
,
"memos-ready"
:
"所有备忘录已就绪 🎉"
,
"memos-ready"
:
"所有备忘录已就绪 🎉"
,
"new-password-not-match"
:
"新密码不匹配。"
,
"new-password-not-match"
:
"新密码不匹配。"
,
"no-memos"
:
"没有备忘录了 🌃"
,
"no-data"
:
"也许寻觅无果,也许可重新设定标准。"
,
"no-resource"
:
"没有资源了 🌃"
,
"not-allow-chinese"
:
"不允许包含中文"
,
"not-allow-chinese"
:
"不允许包含中文"
,
"not-allow-space"
:
"不允许包含空格"
,
"not-allow-space"
:
"不允许包含空格"
,
"page-not-found"
:
"404 - 未找到网页 😥"
,
"page-not-found"
:
"404 - 未找到网页 😥"
,
...
@@ -450,4 +449,4 @@
...
@@ -450,4 +449,4 @@
"tag-name"
:
"标签名称"
,
"tag-name"
:
"标签名称"
,
"tip-text"
:
"输入`#tag`来创建标签"
"tip-text"
:
"输入`#tag`来创建标签"
}
}
}
}
\ No newline at end of file
web/src/locales/zh-Hant.json
View file @
0292f472
...
@@ -339,9 +339,8 @@
...
@@ -339,9 +339,8 @@
"or"
:
"或"
"or"
:
"或"
},
},
"message"
:
{
"message"
:
{
"no-
memos"
:
"沒有 Memo 了 🌃
"
,
"no-
data"
:
"或許尋覓虛空,或者改換選擇之軌跡。
"
,
"memos-ready"
:
"所有 Memo 已就緒 🎉"
,
"memos-ready"
:
"所有 Memo 已就緒 🎉"
,
"no-resource"
:
"沒有 Resource 了 🌃"
,
"resource-ready"
:
"所有 Resource 已就緒 🎉"
,
"resource-ready"
:
"所有 Resource 已就緒 🎉"
,
"restored-successfully"
:
"還原成功"
,
"restored-successfully"
:
"還原成功"
,
"memo-updated-datetime"
:
"Memo 建立日期時間已變更。"
,
"memo-updated-datetime"
:
"Memo 建立日期時間已變更。"
,
...
@@ -451,4 +450,4 @@
...
@@ -451,4 +450,4 @@
"search"
:
{
"search"
:
{
"quickly-filter"
:
"快速過濾"
"quickly-filter"
:
"快速過濾"
}
}
}
}
\ No newline at end of file
web/src/pages/Archived.tsx
View file @
0292f472
...
@@ -5,6 +5,7 @@ import { useMemoStore } from "@/store/module";
...
@@ -5,6 +5,7 @@ import { useMemoStore } from "@/store/module";
import
useLoading
from
"@/hooks/useLoading"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
ArchivedMemo
from
"@/components/ArchivedMemo"
;
import
ArchivedMemo
from
"@/components/ArchivedMemo"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
Empty
from
"@/components/Empty"
;
import
"@/less/archived.less"
;
import
"@/less/archived.less"
;
const
Archived
=
()
=>
{
const
Archived
=
()
=>
{
...
@@ -30,7 +31,7 @@ const Archived = () => {
...
@@ -30,7 +31,7 @@ const Archived = () => {
},
[
memos
]);
},
[
memos
]);
return
(
return
(
<
section
className=
"w-full min-h-full flex flex-col md:flex-row justify-start items-start px-4 sm:px-2
pt-2
pb-8 bg-zinc-100 dark:bg-zinc-800"
>
<
section
className=
"w-full min-h-full flex flex-col md:flex-row justify-start items-start px-4 sm:px-2
sm:pt-4
pb-8 bg-zinc-100 dark:bg-zinc-800"
>
<
MobileHeader
showSearch=
{
false
}
/>
<
MobileHeader
showSearch=
{
false
}
/>
<
div
className=
"archived-memo-page"
>
<
div
className=
"archived-memo-page"
>
{
loadingState
.
isLoading
?
(
{
loadingState
.
isLoading
?
(
...
@@ -38,8 +39,9 @@ const Archived = () => {
...
@@ -38,8 +39,9 @@ const Archived = () => {
<
p
className=
"tip-text"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
<
p
className=
"tip-text"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
archivedMemos
.
length
===
0
?
(
)
:
archivedMemos
.
length
===
0
?
(
<
div
className=
"tip-text-container"
>
<
div
className=
"w-full mt-16 mb-8 flex flex-col justify-center items-center italic"
>
<
p
className=
"tip-text"
>
{
t
(
"memo.no-archived-memos"
)
}
</
p
>
<
Empty
/>
<
p
className=
"mt-4 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
(
)
:
(
<
div
className=
"archived-memos-container"
>
<
div
className=
"archived-memos-container"
>
...
...
web/src/pages/DailyReview.tsx
View file @
0292f472
...
@@ -14,6 +14,7 @@ import DailyMemo from "@/components/DailyMemo";
...
@@ -14,6 +14,7 @@ import DailyMemo from "@/components/DailyMemo";
import
i18n
from
"@/i18n"
;
import
i18n
from
"@/i18n"
;
import
{
findNearestLanguageMatch
}
from
"@/utils/i18n"
;
import
{
findNearestLanguageMatch
}
from
"@/utils/i18n"
;
import
{
convertToMillis
,
getDateStampByDate
,
getNormalizedDateString
,
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
{
convertToMillis
,
getDateStampByDate
,
getNormalizedDateString
,
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
Empty
from
"@/components/Empty"
;
const
DailyReview
=
()
=>
{
const
DailyReview
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -117,7 +118,7 @@ const DailyReview = () => {
...
@@ -117,7 +118,7 @@ const DailyReview = () => {
</
button
>
</
button
>
</
div
>
</
div
>
<
DatePicker
<
DatePicker
className=
{
`absolute top-8 mt-2 z-20 mx-auto border bg-white dark:bg-zinc-800 dark:border-zinc-800 rounded-lg mb-6 ${
className=
{
`absolute top-8 mt-2 z-20 mx-auto border bg-white
shadow
dark:bg-zinc-800 dark:border-zinc-800 rounded-lg mb-6 ${
showDatePicker ? "" : "!hidden"
showDatePicker ? "" : "!hidden"
}`
}
}`
}
datestamp=
{
currentDateStamp
}
datestamp=
{
currentDateStamp
}
...
@@ -141,8 +142,9 @@ const DailyReview = () => {
...
@@ -141,8 +142,9 @@ const DailyReview = () => {
</
div
>
</
div
>
</
div
>
</
div
>
{
dailyMemos
.
length
===
0
?
(
{
dailyMemos
.
length
===
0
?
(
<
div
className=
"mx-auto pt-4 pb-5 px-0"
>
<
div
className=
"w-full mt-4 mb-8 flex flex-col justify-center items-center italic"
>
<
p
className=
"italic text-gray-400"
>
{
t
(
"daily-review.no-memos"
)
}
</
p
>
<
Empty
/>
<
p
className=
"mt-4 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
</
div
>
)
:
(
)
:
(
<
div
className=
"flex flex-col justify-start items-start w-full mt-2"
>
<
div
className=
"flex flex-col justify-start items-start w-full mt-2"
>
...
...
web/src/pages/Explore.tsx
View file @
0292f472
...
@@ -9,6 +9,7 @@ import useLoading from "@/hooks/useLoading";
...
@@ -9,6 +9,7 @@ import useLoading from "@/hooks/useLoading";
import
MemoFilter
from
"@/components/MemoFilter"
;
import
MemoFilter
from
"@/components/MemoFilter"
;
import
Memo
from
"@/components/Memo"
;
import
Memo
from
"@/components/Memo"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
Empty
from
"@/components/Empty"
;
interface
State
{
interface
State
{
memos
:
Memo
[];
memos
:
Memo
[];
...
@@ -66,6 +67,7 @@ const Explore = () => {
...
@@ -66,6 +67,7 @@ const Explore = () => {
:
state
.
memos
;
:
state
.
memos
;
const
sortedMemos
=
shownMemos
.
filter
((
m
)
=>
m
.
rowStatus
===
"NORMAL"
);
const
sortedMemos
=
shownMemos
.
filter
((
m
)
=>
m
.
rowStatus
===
"NORMAL"
);
const
handleFetchMoreClick
=
async
()
=>
{
const
handleFetchMoreClick
=
async
()
=>
{
try
{
try
{
const
fetchedMemos
=
await
memoStore
.
fetchAllMemos
(
DEFAULT_MEMO_LIMIT
,
state
.
memos
.
length
);
const
fetchedMemos
=
await
memoStore
.
fetchAllMemos
(
DEFAULT_MEMO_LIMIT
,
state
.
memos
.
length
);
...
@@ -87,15 +89,18 @@ const Explore = () => {
...
@@ -87,15 +89,18 @@ const Explore = () => {
<
section
className=
"w-full max-w-3xl 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"
>
<
section
className=
"w-full max-w-3xl 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
}
/>
<
MobileHeader
showSearch=
{
false
}
/>
{
!
loadingState
.
isLoading
&&
(
{
!
loadingState
.
isLoading
&&
(
<
main
className=
"relative w-full h-auto flex flex-col justify-start items-start
-mt-2
"
>
<
main
className=
"relative w-full h-auto flex flex-col justify-start items-start"
>
<
MemoFilter
/>
<
MemoFilter
/>
{
sortedMemos
.
map
((
memo
)
=>
{
{
sortedMemos
.
map
((
memo
)
=>
{
return
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
readonly=
{
true
}
/>;
return
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
showCreator
/>;
})
}
})
}
{
isComplete
?
(
{
isComplete
?
(
state
.
memos
.
length
===
0
?
(
state
.
memos
.
length
===
0
&&
(
<
p
className=
"w-full text-center mt-12 text-gray-600"
>
{
t
(
"message.no-memos"
)
}
</
p
>
<
div
className=
"w-full mt-16 mb-8 flex flex-col justify-center items-center italic"
>
)
:
null
<
Empty
/>
<
p
className=
"mt-4 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
)
:
(
)
:
(
<
p
className=
"m-auto text-center mt-4 italic cursor-pointer text-gray-500 hover:text-green-600"
onClick=
{
handleFetchMoreClick
}
>
<
p
className=
"m-auto text-center mt-4 italic cursor-pointer text-gray-500 hover:text-green-600"
onClick=
{
handleFetchMoreClick
}
>
{
t
(
"memo.fetch-more"
)
}
{
t
(
"memo.fetch-more"
)
}
...
...
web/src/pages/Home.tsx
View file @
0292f472
...
@@ -35,7 +35,7 @@ function Home() {
...
@@ -35,7 +35,7 @@ function Home() {
<
div
className=
"flex-grow shrink w-auto px-4 sm:px-2 sm:pt-4"
>
<
div
className=
"flex-grow shrink w-auto px-4 sm:px-2 sm:pt-4"
>
<
MobileHeader
/>
<
MobileHeader
/>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg"
>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg"
>
{
!
userStore
.
isVisitorMode
()
&&
<
MemoEditor
/>
}
{
!
userStore
.
isVisitorMode
()
&&
<
MemoEditor
className=
"mb-2"
/>
}
<
MemoFilter
/>
<
MemoFilter
/>
</
div
>
</
div
>
<
MemoList
/>
<
MemoList
/>
...
...
web/src/pages/MemoDetail.tsx
View file @
0292f472
...
@@ -56,7 +56,7 @@ const MemoDetail = () => {
...
@@ -56,7 +56,7 @@ const MemoDetail = () => {
{
!
loadingState
.
isLoading
&&
(
{
!
loadingState
.
isLoading
&&
(
<>
<>
<
main
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"
>
<
main
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"
>
<
Memo
memo=
{
state
.
memo
}
readonly
showRelatedMemos
/>
<
Memo
memo=
{
state
.
memo
}
showCreator
showRelatedMemos
/>
</
main
>
</
main
>
<
div
className=
"mt-4 w-full flex flex-row justify-center items-center gap-2"
>
<
div
className=
"mt-4 w-full flex flex-row justify-center items-center gap-2"
>
<
Link
<
Link
...
...
web/src/pages/ResourcesDashboard.tsx
View file @
0292f472
...
@@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
...
@@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"@/helpers/consts"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"@/helpers/consts"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useEvent
from
"@/hooks/useEvent"
;
import
{
useResourceStore
}
from
"@/store/module"
;
import
{
useResourceStore
}
from
"@/store/module"
;
import
Icon
from
"@/components/Icon"
;
import
Icon
from
"@/components/Icon"
;
import
ResourceCard
from
"@/components/ResourceCard"
;
import
ResourceCard
from
"@/components/ResourceCard"
;
...
@@ -13,7 +14,7 @@ import Dropdown from "@/components/kit/Dropdown";
...
@@ -13,7 +14,7 @@ import Dropdown from "@/components/kit/Dropdown";
import
ResourceItem
from
"@/components/ResourceItem"
;
import
ResourceItem
from
"@/components/ResourceItem"
;
import
{
showCommonDialog
}
from
"@/components/Dialog/CommonDialog"
;
import
{
showCommonDialog
}
from
"@/components/Dialog/CommonDialog"
;
import
showCreateResourceDialog
from
"@/components/CreateResourceDialog"
;
import
showCreateResourceDialog
from
"@/components/CreateResourceDialog"
;
import
useEvent
from
"@/hooks/useEvent
"
;
import
Empty
from
"@/components/Empty
"
;
const
ResourcesDashboard
=
()
=>
{
const
ResourcesDashboard
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -297,7 +298,10 @@ const ResourcesDashboard = () => {
...
@@ -297,7 +298,10 @@ const ResourcesDashboard = () => {
</
div
>
</
div
>
)
}
)
}
{
resourceList
.
length
===
0
?
(
{
resourceList
.
length
===
0
?
(
<
p
className=
"w-full text-center text-base my-6 mt-8"
>
{
t
(
"resource.no-resources"
)
}
</
p
>
<
div
className=
"w-full mt-8 mb-8 flex flex-col justify-center items-center italic"
>
<
Empty
/>
<
p
className=
"mt-4 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
:
(
)
:
(
resourceList
resourceList
)
}
)
}
...
...
web/src/store/module/user.ts
View file @
0292f472
...
@@ -15,9 +15,9 @@ const defaultSetting: Setting = {
...
@@ -15,9 +15,9 @@ const defaultSetting: Setting = {
};
};
const
defaultLocalSetting
:
LocalSetting
=
{
const
defaultLocalSetting
:
LocalSetting
=
{
enableDoubleClickEditing
:
true
,
enableDoubleClickEditing
:
false
,
enableAutoCollapse
:
false
,
dailyReviewTimeOffset
:
0
,
dailyReviewTimeOffset
:
0
,
enableAutoCollapse
:
true
,
};
};
export
const
convertResponseModelUser
=
(
user
:
User
):
User
=>
{
export
const
convertResponseModelUser
=
(
user
:
User
):
User
=>
{
...
...
web/src/types/modules/setting.d.ts
View file @
0292f472
...
@@ -13,8 +13,8 @@ interface Setting {
...
@@ -13,8 +13,8 @@ interface Setting {
interface
LocalSetting
{
interface
LocalSetting
{
enableDoubleClickEditing
:
boolean
;
enableDoubleClickEditing
:
boolean
;
dailyReviewTimeOffset
:
number
;
enableAutoCollapse
:
boolean
;
enableAutoCollapse
:
boolean
;
dailyReviewTimeOffset
:
number
;
}
}
interface
UserLocaleSetting
{
interface
UserLocaleSetting
{
...
...
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