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
a7a01df7
Commit
a7a01df7
authored
Aug 19, 2022
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update i18n
parent
e3fac742
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
190 additions
and
150 deletions
+190
-150
AboutSiteDialog.tsx
web/src/components/AboutSiteDialog.tsx
+2
-4
ArchivedMemo.tsx
web/src/components/ArchivedMemo.tsx
+5
-2
ChangePasswordDialog.tsx
web/src/components/ChangePasswordDialog.tsx
+4
-2
Editor.tsx
web/src/components/Editor/Editor.tsx
+4
-2
Memo.tsx
web/src/components/Memo.tsx
+14
-11
MemoEditor.tsx
web/src/components/MemoEditor.tsx
+4
-2
MenuBtnsPopup.tsx
web/src/components/MenuBtnsPopup.tsx
+4
-2
SettingDialog.tsx
web/src/components/SettingDialog.tsx
+8
-6
MemberSection.tsx
web/src/components/Settings/MemberSection.tsx
+11
-9
MyAccountSection.tsx
web/src/components/Settings/MyAccountSection.tsx
+10
-9
PreferencesSection.tsx
web/src/components/Settings/PreferencesSection.tsx
+4
-75
ShareMemoImageDialog.tsx
web/src/components/ShareMemoImageDialog.tsx
+4
-1
ShortcutList.tsx
web/src/components/ShortcutList.tsx
+6
-3
Sidebar.tsx
web/src/components/Sidebar.tsx
+7
-6
TagList.tsx
web/src/components/TagList.tsx
+3
-3
about-site-dialog.less
web/src/less/about-site-dialog.less
+2
-2
tag-list.less
web/src/less/tag-list.less
+2
-6
en.json
web/src/locales/en.json
+45
-1
zh.json
web/src/locales/zh.json
+46
-1
Auth.tsx
web/src/pages/Auth.tsx
+1
-1
Home.tsx
web/src/pages/Home.tsx
+4
-2
No files found.
web/src/components/AboutSiteDialog.tsx
View file @
a7a01df7
...
@@ -45,15 +45,13 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
...
@@ -45,15 +45,13 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
</
button
>
</
button
>
</
div
>
</
div
>
<
div
className=
"dialog-content-container"
>
<
div
className=
"dialog-content-container"
>
<
p
>
<
p
>
{
t
(
"slogan"
)
}
</
p
>
Memos is an
<
i
>
open source
</
i
>
,
<
i
>
self-hosted
</
i
>
knowledge base that works with a SQLite db file.
</
p
>
<
br
/>
<
br
/>
<
div
className=
"addtion-info-container"
>
<
div
className=
"addtion-info-container"
>
<
GitHubBadge
/>
<
GitHubBadge
/>
<
Only
when=
{
profile
!==
undefined
}
>
<
Only
when=
{
profile
!==
undefined
}
>
<>
<>
version
:
{
t
(
"common.version"
)
}
:
<
span
className=
"pre-text"
>
<
span
className=
"pre-text"
>
{
profile
?.
version
}
-
{
profile
?.
mode
}
{
profile
?.
version
}
-
{
profile
?.
mode
}
</
span
>
</
span
>
...
...
web/src/components/ArchivedMemo.tsx
View file @
a7a01df7
import
{
IMAGE_URL_REG
}
from
"../helpers/consts"
;
import
{
IMAGE_URL_REG
}
from
"../helpers/consts"
;
import
*
as
utils
from
"../helpers/utils"
;
import
*
as
utils
from
"../helpers/utils"
;
import
useI18n
from
"../hooks/useI18n"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useToggle
from
"../hooks/useToggle"
;
import
{
memoService
}
from
"../services"
;
import
{
memoService
}
from
"../services"
;
import
{
formatMemoContent
}
from
"../helpers/marked"
;
import
{
formatMemoContent
}
from
"../helpers/marked"
;
...
@@ -19,6 +20,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
...
@@ -19,6 +20,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
createdAtStr
:
utils
.
getDateTimeString
(
propsMemo
.
createdTs
),
createdAtStr
:
utils
.
getDateTimeString
(
propsMemo
.
createdTs
),
archivedAtStr
:
utils
.
getDateTimeString
(
propsMemo
.
updatedTs
??
Date
.
now
()),
archivedAtStr
:
utils
.
getDateTimeString
(
propsMemo
.
updatedTs
??
Date
.
now
()),
};
};
const
{
t
}
=
useI18n
();
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
imageUrls
=
Array
.
from
(
memo
.
content
.
match
(
IMAGE_URL_REG
)
??
[]).
map
((
s
)
=>
s
.
replace
(
IMAGE_URL_REG
,
"$1"
));
const
imageUrls
=
Array
.
from
(
memo
.
content
.
match
(
IMAGE_URL_REG
)
??
[]).
map
((
s
)
=>
s
.
replace
(
IMAGE_URL_REG
,
"$1"
));
...
@@ -60,10 +62,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
...
@@ -60,10 +62,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
<
span
className=
"time-text"
>
Archived at
{
memo
.
archivedAtStr
}
</
span
>
<
span
className=
"time-text"
>
Archived at
{
memo
.
archivedAtStr
}
</
span
>
<
div
className=
"btns-container"
>
<
div
className=
"btns-container"
>
<
span
className=
"btn restore-btn"
onClick=
{
handleRestoreMemoClick
}
>
<
span
className=
"btn restore-btn"
onClick=
{
handleRestoreMemoClick
}
>
Restore
{
t
(
"common.restore"
)
}
</
span
>
</
span
>
<
span
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
onClick=
{
handleDeleteMemoClick
}
>
<
span
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
onClick=
{
handleDeleteMemoClick
}
>
{
showConfirmDeleteBtn
?
"Delete!"
:
"Delete"
}
{
t
(
"common.delete"
)
}
{
showConfirmDeleteBtn
?
"!"
:
""
}
</
span
>
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/ChangePasswordDialog.tsx
View file @
a7a01df7
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
useI18n
from
"../hooks/useI18n"
;
import
{
userService
}
from
"../services"
;
import
{
userService
}
from
"../services"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
...
@@ -16,6 +17,7 @@ const validateConfig: ValidatorConfig = {
...
@@ -16,6 +17,7 @@ const validateConfig: ValidatorConfig = {
interface
Props
extends
DialogProps
{}
interface
Props
extends
DialogProps
{}
const
ChangePasswordDialog
:
React
.
FC
<
Props
>
=
({
destroy
}:
Props
)
=>
{
const
ChangePasswordDialog
:
React
.
FC
<
Props
>
=
({
destroy
}:
Props
)
=>
{
const
{
t
}
=
useI18n
();
const
[
newPassword
,
setNewPassword
]
=
useState
(
""
);
const
[
newPassword
,
setNewPassword
]
=
useState
(
""
);
const
[
newPasswordAgain
,
setNewPasswordAgain
]
=
useState
(
""
);
const
[
newPasswordAgain
,
setNewPasswordAgain
]
=
useState
(
""
);
...
@@ -85,10 +87,10 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
...
@@ -85,10 +87,10 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
</
label
>
</
label
>
<
div
className=
"btns-container"
>
<
div
className=
"btns-container"
>
<
span
className=
"btn cancel-btn"
onClick=
{
handleCloseBtnClick
}
>
<
span
className=
"btn cancel-btn"
onClick=
{
handleCloseBtnClick
}
>
Cancel
{
t
(
"common.cancel"
)
}
</
span
>
</
span
>
<
span
className=
"btn confirm-btn"
onClick=
{
handleSaveBtnClick
}
>
<
span
className=
"btn confirm-btn"
onClick=
{
handleSaveBtnClick
}
>
Save
{
t
(
"common.save"
)
}
</
span
>
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/Editor/Editor.tsx
View file @
a7a01df7
import
{
forwardRef
,
ReactNode
,
useCallback
,
useEffect
,
useImperativeHandle
,
useRef
}
from
"react"
;
import
{
forwardRef
,
ReactNode
,
useCallback
,
useEffect
,
useImperativeHandle
,
useRef
}
from
"react"
;
import
useI18n
from
"../../hooks/useI18n"
;
import
useRefresh
from
"../../hooks/useRefresh"
;
import
useRefresh
from
"../../hooks/useRefresh"
;
import
Only
from
"../common/OnlyWhen"
;
import
Only
from
"../common/OnlyWhen"
;
import
"../../less/editor.less"
;
import
"../../less/editor.less"
;
...
@@ -37,6 +38,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
...
@@ -37,6 +38,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
onCancelBtnClick
:
handleCancelBtnClickCallback
,
onCancelBtnClick
:
handleCancelBtnClickCallback
,
onContentChange
:
handleContentChangeCallback
,
onContentChange
:
handleContentChangeCallback
,
}
=
props
;
}
=
props
;
const
{
t
}
=
useI18n
();
const
editorRef
=
useRef
<
HTMLTextAreaElement
>
(
null
);
const
editorRef
=
useRef
<
HTMLTextAreaElement
>
(
null
);
const
refresh
=
useRefresh
();
const
refresh
=
useRefresh
();
...
@@ -130,12 +132,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
...
@@ -130,12 +132,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
<
div
className=
"btns-container"
>
<
div
className=
"btns-container"
>
<
Only
when=
{
showCancelBtn
}
>
<
Only
when=
{
showCancelBtn
}
>
<
button
className=
"action-btn cancel-btn"
onClick=
{
handleCommonCancelBtnClick
}
>
<
button
className=
"action-btn cancel-btn"
onClick=
{
handleCommonCancelBtnClick
}
>
Cancel editting
{
t
(
"editor.cancel-edit"
)
}
</
button
>
</
button
>
</
Only
>
</
Only
>
<
Only
when=
{
showConfirmBtn
}
>
<
Only
when=
{
showConfirmBtn
}
>
<
button
className=
"action-btn confirm-btn"
disabled=
{
editorRef
.
current
?.
value
===
""
}
onClick=
{
handleCommonConfirmBtnClick
}
>
<
button
className=
"action-btn confirm-btn"
disabled=
{
editorRef
.
current
?.
value
===
""
}
onClick=
{
handleCommonConfirmBtnClick
}
>
Save
<
span
className=
"icon-text"
>
✍️
</
span
>
{
t
(
"editor.save"
)
}
<
span
className=
"icon-text"
>
✍️
</
span
>
</
button
>
</
button
>
</
Only
>
</
Only
>
</
div
>
</
div
>
...
...
web/src/components/Memo.tsx
View file @
a7a01df7
...
@@ -2,6 +2,8 @@ import { memo, useEffect, useRef, useState } from "react";
...
@@ -2,6 +2,8 @@ import { memo, useEffect, useRef, useState } from "react";
import
{
indexOf
}
from
"lodash-es"
;
import
{
indexOf
}
from
"lodash-es"
;
import
dayjs
from
"dayjs"
;
import
dayjs
from
"dayjs"
;
import
relativeTime
from
"dayjs/plugin/relativeTime"
;
import
relativeTime
from
"dayjs/plugin/relativeTime"
;
import
"dayjs/locale/zh"
;
import
useI18n
from
"../hooks/useI18n"
;
import
{
IMAGE_URL_REG
,
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
IMAGE_URL_REG
,
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
DONE_BLOCK_REG
,
formatMemoContent
,
TODO_BLOCK_REG
}
from
"../helpers/marked"
;
import
{
DONE_BLOCK_REG
,
formatMemoContent
,
TODO_BLOCK_REG
}
from
"../helpers/marked"
;
import
{
editorStateService
,
locationService
,
memoService
,
userService
}
from
"../services"
;
import
{
editorStateService
,
locationService
,
memoService
,
userService
}
from
"../services"
;
...
@@ -27,20 +29,21 @@ interface State {
...
@@ -27,20 +29,21 @@ interface State {
expandButtonStatus
:
ExpandButtonStatus
;
expandButtonStatus
:
ExpandButtonStatus
;
}
}
export
const
getFormatedMemoCreatedAtStr
=
(
createdTs
:
number
):
string
=>
{
export
const
getFormatedMemoCreatedAtStr
=
(
createdTs
:
number
,
locale
=
"en"
):
string
=>
{
if
(
Date
.
now
()
-
createdTs
<
1000
*
60
*
60
*
24
)
{
if
(
Date
.
now
()
-
createdTs
<
1000
*
60
*
60
*
24
)
{
return
dayjs
(
createdTs
).
fromNow
();
return
dayjs
(
createdTs
).
locale
(
locale
).
fromNow
();
}
else
{
}
else
{
return
dayjs
(
createdTs
).
format
(
"YYYY/MM/DD HH:mm:ss"
);
return
dayjs
(
createdTs
).
locale
(
locale
).
format
(
"YYYY/MM/DD HH:mm:ss"
);
}
}
};
};
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
memo
=
props
.
memo
;
const
memo
=
props
.
memo
;
const
{
t
,
locale
}
=
useI18n
();
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
expandButtonStatus
:
-
1
,
expandButtonStatus
:
-
1
,
});
});
const
[
createdAtStr
,
setCreatedAtStr
]
=
useState
<
string
>
(
getFormatedMemoCreatedAtStr
(
memo
.
createdTs
));
const
[
createdAtStr
,
setCreatedAtStr
]
=
useState
<
string
>
(
getFormatedMemoCreatedAtStr
(
memo
.
createdTs
,
locale
));
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
imageUrls
=
Array
.
from
(
memo
.
content
.
match
(
IMAGE_URL_REG
)
??
[]).
map
((
s
)
=>
s
.
replace
(
IMAGE_URL_REG
,
"$1"
));
const
imageUrls
=
Array
.
from
(
memo
.
content
.
match
(
IMAGE_URL_REG
)
??
[]).
map
((
s
)
=>
s
.
replace
(
IMAGE_URL_REG
,
"$1"
));
const
isVisitorMode
=
userService
.
isVisitorMode
();
const
isVisitorMode
=
userService
.
isVisitorMode
();
...
@@ -59,10 +62,10 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -59,10 +62,10 @@ const Memo: React.FC<Props> = (props: Props) => {
if
(
Date
.
now
()
-
memo
.
createdTs
<
1000
*
60
*
60
*
24
)
{
if
(
Date
.
now
()
-
memo
.
createdTs
<
1000
*
60
*
60
*
24
)
{
setInterval
(()
=>
{
setInterval
(()
=>
{
setCreatedAtStr
(
dayjs
(
memo
.
createdTs
).
fromNow
(
));
setCreatedAtStr
(
getFormatedMemoCreatedAtStr
(
memo
.
createdTs
,
locale
));
},
1000
*
1
);
},
1000
*
1
);
}
}
},
[]);
},
[
locale
]);
const
handleShowMemoStoryDialog
=
()
=>
{
const
handleShowMemoStoryDialog
=
()
=>
{
showMemoCardDialog
(
memo
);
showMemoCardDialog
(
memo
);
...
@@ -186,25 +189,25 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -186,25 +189,25 @@ const Memo: React.FC<Props> = (props: Props) => {
<
div
className=
"btns-container"
>
<
div
className=
"btns-container"
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
Icon
.
MapPin
className=
{
`icon-img ${memo.pinned ? "" : "opacity-20"}`
}
/>
<
Icon
.
MapPin
className=
{
`icon-img ${memo.pinned ? "" : "opacity-20"}`
}
/>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
"Unpin"
:
"Pin"
}
</
span
>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
span
>
</
div
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
Icon
.
Edit3
className=
"icon-img"
/>
<
Icon
.
Edit3
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
Edit
</
span
>
<
span
className=
"tip-text"
>
{
t
(
"common.edit"
)
}
</
span
>
</
div
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
Icon
.
Share
className=
"icon-img"
/>
<
Icon
.
Share
className=
"icon-img"
/>
<
span
className=
"tip-text"
>
Share
</
span
>
<
span
className=
"tip-text"
>
{
t
(
"common.share"
)
}
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
Mark
{
t
(
"common.mark"
)
}
</
span
>
</
span
>
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
View Story
View Story
</
span
>
</
span
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
Archive
{
t
(
"common.archive"
)
}
</
span
>
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/MemoEditor.tsx
View file @
a7a01df7
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
"react"
;
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
"react"
;
import
{
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
editorStateService
,
locationService
,
memoService
,
resourceService
}
from
"../services"
;
import
{
editorStateService
,
locationService
,
memoService
,
resourceService
}
from
"../services"
;
import
useI18n
from
"../hooks/useI18n"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
useAppSelector
}
from
"../store"
;
import
*
as
storage
from
"../helpers/storage"
;
import
*
as
storage
from
"../helpers/storage"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
...
@@ -16,6 +17,7 @@ interface State {
...
@@ -16,6 +17,7 @@ interface State {
}
}
const
MemoEditor
:
React
.
FC
<
Props
>
=
()
=>
{
const
MemoEditor
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
,
locale
}
=
useI18n
();
const
editorState
=
useAppSelector
((
state
)
=>
state
.
editor
);
const
editorState
=
useAppSelector
((
state
)
=>
state
.
editor
);
const
tags
=
useAppSelector
((
state
)
=>
state
.
memo
.
tags
);
const
tags
=
useAppSelector
((
state
)
=>
state
.
memo
.
tags
);
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
...
@@ -212,7 +214,7 @@ const MemoEditor: React.FC<Props> = () => {
...
@@ -212,7 +214,7 @@ const MemoEditor: React.FC<Props> = () => {
()
=>
({
()
=>
({
className
:
"memo-editor"
,
className
:
"memo-editor"
,
initialContent
:
getEditorContentCache
(),
initialContent
:
getEditorContentCache
(),
placeholder
:
"Any thoughts..."
,
placeholder
:
t
(
"editor.placeholder"
)
,
fullscreen
:
state
.
fullscreen
,
fullscreen
:
state
.
fullscreen
,
showConfirmBtn
:
true
,
showConfirmBtn
:
true
,
showCancelBtn
:
isEditing
,
showCancelBtn
:
isEditing
,
...
@@ -220,7 +222,7 @@ const MemoEditor: React.FC<Props> = () => {
...
@@ -220,7 +222,7 @@ const MemoEditor: React.FC<Props> = () => {
onCancelBtnClick
:
handleCancelBtnClick
,
onCancelBtnClick
:
handleCancelBtnClick
,
onContentChange
:
handleContentChange
,
onContentChange
:
handleContentChange
,
}),
}),
[
isEditing
,
state
.
fullscreen
]
[
isEditing
,
state
.
fullscreen
,
locale
]
);
);
return
(
return
(
...
...
web/src/components/MenuBtnsPopup.tsx
View file @
a7a01df7
import
{
useEffect
,
useRef
}
from
"react"
;
import
{
useEffect
,
useRef
}
from
"react"
;
import
*
as
api
from
"../helpers/api"
;
import
*
as
api
from
"../helpers/api"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
useI18n
from
"../hooks/useI18n"
;
import
toastHelper
from
"./Toast"
;
import
toastHelper
from
"./Toast"
;
import
Only
from
"./common/OnlyWhen"
;
import
Only
from
"./common/OnlyWhen"
;
import
showAboutSiteDialog
from
"./AboutSiteDialog"
;
import
showAboutSiteDialog
from
"./AboutSiteDialog"
;
...
@@ -13,6 +14,7 @@ interface Props {
...
@@ -13,6 +14,7 @@ interface Props {
const
MenuBtnsPopup
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
MenuBtnsPopup
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
shownStatus
,
setShownStatus
}
=
props
;
const
{
shownStatus
,
setShownStatus
}
=
props
;
const
{
t
}
=
useI18n
();
const
popupElRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
popupElRef
=
useRef
<
HTMLDivElement
>
(
null
);
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -63,14 +65,14 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
...
@@ -63,14 +65,14 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
return
(
return
(
<
div
className=
{
`menu-btns-popup ${shownStatus ? "" : "hidden"}`
}
ref=
{
popupElRef
}
>
<
div
className=
{
`menu-btns-popup ${shownStatus ? "" : "hidden"}`
}
ref=
{
popupElRef
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleAboutBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleAboutBtnClick
}
>
<
span
className=
"icon"
>
🤠
</
span
>
About
<
span
className=
"icon"
>
🤠
</
span
>
{
t
(
"common.about"
)
}
</
button
>
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handlePingBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handlePingBtnClick
}
>
<
span
className=
"icon"
>
🎯
</
span
>
Ping
<
span
className=
"icon"
>
🎯
</
span
>
Ping
</
button
>
</
button
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleSignOutBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleSignOutBtnClick
}
>
<
span
className=
"icon"
>
👋
</
span
>
Sign out
<
span
className=
"icon"
>
👋
</
span
>
{
t
(
"common.sign-out"
)
}
</
button
>
</
button
>
</
Only
>
</
Only
>
</
div
>
</
div
>
...
...
web/src/components/SettingDialog.tsx
View file @
a7a01df7
import
{
useState
}
from
"react"
;
import
{
useState
}
from
"react"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
useAppSelector
}
from
"../store"
;
import
useI18n
from
"../hooks/useI18n"
;
import
Icon
from
"./Icon"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
MyAccountSection
from
"./Settings/MyAccountSection"
;
import
MyAccountSection
from
"./Settings/MyAccountSection"
;
import
PreferencesSection
from
"./Settings/PreferencesSection"
;
import
PreferencesSection
from
"./Settings/PreferencesSection"
;
import
MemberSection
from
"./Settings/MemberSection"
;
import
MemberSection
from
"./Settings/MemberSection"
;
import
"../less/setting-dialog.less"
;
import
"../less/setting-dialog.less"
;
import
Icon
from
"./Icon"
;
interface
Props
extends
DialogProps
{}
interface
Props
extends
DialogProps
{}
...
@@ -17,6 +18,7 @@ interface State {
...
@@ -17,6 +18,7 @@ interface State {
const
SettingDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
SettingDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
}
=
props
;
const
{
destroy
}
=
props
;
const
{
t
}
=
useI18n
();
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
selectedSection
:
"my-account"
,
selectedSection
:
"my-account"
,
...
@@ -34,30 +36,30 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
...
@@ -34,30 +36,30 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<
Icon
.
X
className=
"icon-img"
/>
<
Icon
.
X
className=
"icon-img"
/>
</
button
>
</
button
>
<
div
className=
"section-selector-container"
>
<
div
className=
"section-selector-container"
>
<
span
className=
"section-title"
>
Basic
</
span
>
<
span
className=
"section-title"
>
{
t
(
"common.basic"
)
}
</
span
>
<
div
className=
"section-items-container"
>
<
div
className=
"section-items-container"
>
<
span
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"my-account"
)
}
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"my-account"
)
}
className=
{
`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`
}
className=
{
`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`
}
>
>
<
span
className=
"icon-text"
>
🤠
</
span
>
My account
<
span
className=
"icon-text"
>
🤠
</
span
>
{
t
(
"setting.my-account"
)
}
</
span
>
</
span
>
<
span
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"preferences"
)
}
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"preferences"
)
}
className=
{
`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`
}
className=
{
`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`
}
>
>
<
span
className=
"icon-text"
>
🏟
</
span
>
Preferences
<
span
className=
"icon-text"
>
🏟
</
span
>
{
t
(
"setting.preference"
)
}
</
span
>
</
span
>
</
div
>
</
div
>
{
user
?.
role
===
"HOST"
?
(
{
user
?.
role
===
"HOST"
?
(
<>
<>
<
span
className=
"section-title"
>
Admin
</
span
>
<
span
className=
"section-title"
>
{
t
(
"common.admin"
)
}
</
span
>
<
div
className=
"section-items-container"
>
<
div
className=
"section-items-container"
>
<
span
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"member"
)
}
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"member"
)
}
className=
{
`section-item ${state.selectedSection === "member" ? "selected" : ""}`
}
className=
{
`section-item ${state.selectedSection === "member" ? "selected" : ""}`
}
>
>
<
span
className=
"icon-text"
>
👤
</
span
>
Member
<
span
className=
"icon-text"
>
👤
</
span
>
{
t
(
"setting.member"
)
}
</
span
>
</
span
>
</
div
>
</
div
>
</>
</>
...
...
web/src/components/Settings/MemberSection.tsx
View file @
a7a01df7
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
isEmpty
}
from
"lodash-es"
;
import
{
isEmpty
}
from
"lodash-es"
;
import
useI18n
from
"../../hooks/useI18n"
;
import
{
userService
}
from
"../../services"
;
import
{
userService
}
from
"../../services"
;
import
{
useAppSelector
}
from
"../../store"
;
import
{
useAppSelector
}
from
"../../store"
;
import
*
as
api
from
"../../helpers/api"
;
import
*
as
api
from
"../../helpers/api"
;
...
@@ -16,6 +17,7 @@ interface State {
...
@@ -16,6 +17,7 @@ interface State {
}
}
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
}
=
useI18n
();
const
currentUser
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
currentUser
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
[
state
,
setState
]
=
useState
<
State
>
({
const
[
state
,
setState
]
=
useState
<
State
>
({
createUserEmail
:
""
,
createUserEmail
:
""
,
...
@@ -110,18 +112,18 @@ const PreferencesSection: React.FC<Props> = () => {
...
@@ -110,18 +112,18 @@ const PreferencesSection: React.FC<Props> = () => {
return
(
return
(
<
div
className=
"section-container member-section-container"
>
<
div
className=
"section-container member-section-container"
>
<
p
className=
"title-text"
>
Create a member
</
p
>
<
p
className=
"title-text"
>
{
t
(
"setting.member-section.create-a-member"
)
}
</
p
>
<
div
className=
"create-member-container"
>
<
div
className=
"create-member-container"
>
<
div
className=
"input-form-container"
>
<
div
className=
"input-form-container"
>
<
span
className=
"field-text"
>
Email
</
span
>
<
span
className=
"field-text"
>
{
t
(
"common.email"
)
}
</
span
>
<
input
type=
"email"
placeholder=
"Email"
value=
{
state
.
createUserEmail
}
onChange=
{
handleEmailInputChange
}
/>
<
input
type=
"email"
placeholder=
{
t
(
"common.email"
)
}
value=
{
state
.
createUserEmail
}
onChange=
{
handleEmailInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"input-form-container"
>
<
div
className=
"input-form-container"
>
<
span
className=
"field-text"
>
Password
</
span
>
<
span
className=
"field-text"
>
{
t
(
"common.password"
)
}
</
span
>
<
input
type=
"text"
placeholder=
"Password"
value=
{
state
.
createUserPassword
}
onChange=
{
handlePasswordInputChange
}
/>
<
input
type=
"text"
placeholder=
{
t
(
"common.password"
)
}
value=
{
state
.
createUserPassword
}
onChange=
{
handlePasswordInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"btns-container"
>
<
div
className=
"btns-container"
>
<
button
onClick=
{
handleCreateUserBtnClick
}
>
Create
</
button
>
<
button
onClick=
{
handleCreateUserBtnClick
}
>
{
t
(
"common.create"
)
}
</
button
>
</
div
>
</
div
>
</
div
>
</
div
>
<
p
className=
"title-text"
>
Member list
</
p
>
<
p
className=
"title-text"
>
Member list
</
p
>
...
@@ -140,12 +142,12 @@ const PreferencesSection: React.FC<Props> = () => {
...
@@ -140,12 +142,12 @@ const PreferencesSection: React.FC<Props> = () => {
)
:
(
)
:
(
<
Dropdown
className=
"actions-dropdown"
>
<
Dropdown
className=
"actions-dropdown"
>
{
user
.
rowStatus
===
"NORMAL"
?
(
{
user
.
rowStatus
===
"NORMAL"
?
(
<
button
onClick=
{
()
=>
handleArchiveUserClick
(
user
)
}
>
Archive
</
button
>
<
button
onClick=
{
()
=>
handleArchiveUserClick
(
user
)
}
>
{
t
(
"common.archive"
)
}
</
button
>
)
:
(
)
:
(
<>
<>
<
button
onClick=
{
()
=>
handleRestoreUserClick
(
user
)
}
>
Restore
</
button
>
<
button
onClick=
{
()
=>
handleRestoreUserClick
(
user
)
}
>
{
t
(
"common.restore"
)
}
</
button
>
<
button
className=
"delete"
onClick=
{
()
=>
handleDeleteUserClick
(
user
)
}
>
<
button
className=
"delete"
onClick=
{
()
=>
handleDeleteUserClick
(
user
)
}
>
Delete
{
t
(
"common.delete"
)
}
</
button
>
</
button
>
</>
</>
)
}
)
}
...
...
web/src/components/Settings/MyAccountSection.tsx
View file @
a7a01df7
import
{
useState
}
from
"react"
;
import
{
useState
}
from
"react"
;
import
useI18n
from
"../../hooks/useI18n"
;
import
{
useAppSelector
}
from
"../../store"
;
import
{
useAppSelector
}
from
"../../store"
;
import
{
userService
}
from
"../../services"
;
import
{
userService
}
from
"../../services"
;
import
{
validate
,
ValidatorConfig
}
from
"../../helpers/validator"
;
import
{
validate
,
ValidatorConfig
}
from
"../../helpers/validator"
;
...
@@ -17,6 +18,7 @@ const validateConfig: ValidatorConfig = {
...
@@ -17,6 +18,7 @@ const validateConfig: ValidatorConfig = {
interface
Props
{}
interface
Props
{}
const
MyAccountSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
MyAccountSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
}
=
useI18n
();
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
as
User
);
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
as
User
);
const
[
username
,
setUsername
]
=
useState
<
string
>
(
user
.
name
);
const
[
username
,
setUsername
]
=
useState
<
string
>
(
user
.
name
);
const
openAPIRoute
=
`
${
window
.
location
.
origin
}
/api/memo?openId=
${
user
.
openId
}
`
;
const
openAPIRoute
=
`
${
window
.
location
.
origin
}
/api/memo?openId=
${
user
.
openId
}
`
;
...
@@ -68,17 +70,17 @@ const MyAccountSection: React.FC<Props> = () => {
...
@@ -68,17 +70,17 @@ const MyAccountSection: React.FC<Props> = () => {
return
(
return
(
<>
<>
<
div
className=
"section-container account-section-container"
>
<
div
className=
"section-container account-section-container"
>
<
p
className=
"title-text"
>
Account Information
</
p
>
<
p
className=
"title-text"
>
{
t
(
"setting.account-section.title"
)
}
</
p
>
<
label
className=
"form-label"
>
<
label
className=
"form-label"
>
<
span
className=
"normal-text"
>
Email
:
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"common.email"
)
}
:
</
span
>
<
span
className=
"normal-text"
>
{
user
.
email
}
</
span
>
<
span
className=
"normal-text"
>
{
user
.
email
}
</
span
>
</
label
>
</
label
>
<
label
className=
"form-label input-form-label username-label"
>
<
label
className=
"form-label input-form-label username-label"
>
<
span
className=
"normal-text"
>
Username
:
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"common.username"
)
}
:
</
span
>
<
input
type=
"text"
value=
{
username
}
onChange=
{
handleUsernameChanged
}
/>
<
input
type=
"text"
value=
{
username
}
onChange=
{
handleUsernameChanged
}
/>
<
div
className=
{
`btns-container ${username === user.name ? "!hidden" : ""}`
}
onClick=
{
handlePreventDefault
}
>
<
div
className=
{
`btns-container ${username === user.name ? "!hidden" : ""}`
}
onClick=
{
handlePreventDefault
}
>
<
span
className=
"btn confirm-btn"
onClick=
{
handleConfirmEditUsernameBtnClick
}
>
<
span
className=
"btn confirm-btn"
onClick=
{
handleConfirmEditUsernameBtnClick
}
>
Save
{
t
(
"common.save"
)
}
</
span
>
</
span
>
<
span
<
span
className=
"btn cancel-btn"
className=
"btn cancel-btn"
...
@@ -86,14 +88,14 @@ const MyAccountSection: React.FC<Props> = () => {
...
@@ -86,14 +88,14 @@ const MyAccountSection: React.FC<Props> = () => {
setUsername
(
user
.
name
);
setUsername
(
user
.
name
);
}
}
}
}
>
>
Cancel
{
t
(
"common.cancel"
)
}
</
span
>
</
span
>
</
div
>
</
div
>
</
label
>
</
label
>
<
label
className=
"form-label password-label"
>
<
label
className=
"form-label password-label"
>
<
span
className=
"normal-text"
>
Password
:
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"common.password"
)
}
:
</
span
>
<
span
className=
"btn"
onClick=
{
handleChangePasswordBtnClick
}
>
<
span
className=
"btn"
onClick=
{
handleChangePasswordBtnClick
}
>
Change it
{
t
(
"common.change"
)
}
</
span
>
</
span
>
</
label
>
</
label
>
</
div
>
</
div
>
...
@@ -101,10 +103,9 @@ const MyAccountSection: React.FC<Props> = () => {
...
@@ -101,10 +103,9 @@ const MyAccountSection: React.FC<Props> = () => {
<
p
className=
"title-text"
>
Open API
</
p
>
<
p
className=
"title-text"
>
Open API
</
p
>
<
p
className=
"value-text"
>
{
openAPIRoute
}
</
p
>
<
p
className=
"value-text"
>
{
openAPIRoute
}
</
p
>
<
span
className=
"reset-btn"
onClick=
{
handleResetOpenIdBtnClick
}
>
<
span
className=
"reset-btn"
onClick=
{
handleResetOpenIdBtnClick
}
>
Reset
API
{
t
(
"common.reset"
)
}
API
</
span
>
</
span
>
<
div
className=
"usage-guide-container"
>
<
div
className=
"usage-guide-container"
>
<
p
className=
"title-text"
>
Usage guide:
</
p
>
<
pre
>
{
`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello #memos from ${window.location.origin}"\n}`
}
</
pre
>
<
pre
>
{
`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello #memos from ${window.location.origin}"\n}`
}
</
pre
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/Settings/PreferencesSection.tsx
View file @
a7a01df7
import
{
globalService
,
memoService
,
userService
}
from
"../../services"
;
import
{
globalService
,
userService
}
from
"../../services"
;
import
*
as
utils
from
"../../helpers/utils"
;
import
{
useAppSelector
}
from
"../../store"
;
import
{
useAppSelector
}
from
"../../store"
;
import
Only
from
"../common/OnlyWhen"
;
import
useI18n
from
"../../hooks/useI18n"
;
import
toastHelper
from
"../Toast"
;
import
Selector
from
"../common/Selector"
;
import
Selector
from
"../common/Selector"
;
import
"../../less/settings/preferences-section.less"
;
import
"../../less/settings/preferences-section.less"
;
...
@@ -20,66 +18,9 @@ const localeSelectorItems = [
...
@@ -20,66 +18,9 @@ const localeSelectorItems = [
];
];
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
}
=
useI18n
();
const
{
setting
}
=
useAppSelector
((
state
)
=>
state
.
user
.
user
as
User
);
const
{
setting
}
=
useAppSelector
((
state
)
=>
state
.
user
.
user
as
User
);
const
handleExportBtnClick
=
async
()
=>
{
const
formatedMemos
=
memoService
.
getState
().
memos
.
map
((
m
)
=>
{
return
{
content
:
m
.
content
,
createdTs
:
m
.
createdTs
,
};
});
const
jsonStr
=
JSON
.
stringify
(
formatedMemos
);
const
element
=
document
.
createElement
(
"a"
);
element
.
setAttribute
(
"href"
,
"data:text/json;charset=utf-8,"
+
encodeURIComponent
(
jsonStr
));
element
.
setAttribute
(
"download"
,
`memos-
${
utils
.
getDateTimeString
(
Date
.
now
())}
.json`
);
element
.
style
.
display
=
"none"
;
document
.
body
.
appendChild
(
element
);
element
.
click
();
document
.
body
.
removeChild
(
element
);
};
const
handleImportBtnClick
=
async
()
=>
{
const
fileInputEl
=
document
.
createElement
(
"input"
);
fileInputEl
.
type
=
"file"
;
fileInputEl
.
accept
=
"application/JSON"
;
fileInputEl
.
onchange
=
()
=>
{
if
(
fileInputEl
.
files
?.
length
&&
fileInputEl
.
files
.
length
>
0
)
{
const
reader
=
new
FileReader
();
reader
.
readAsText
(
fileInputEl
.
files
[
0
]);
reader
.
onload
=
async
(
event
)
=>
{
const
memoList
=
JSON
.
parse
(
event
.
target
?.
result
as
string
)
as
Memo
[];
if
(
!
Array
.
isArray
(
memoList
))
{
toastHelper
.
error
(
"Unexpected data type."
);
}
let
succeedAmount
=
0
;
for
(
const
memo
of
memoList
)
{
const
content
=
memo
.
content
||
""
;
const
createdTs
=
(
memo
as
any
).
createdAt
||
memo
.
createdTs
||
Date
.
now
();
try
{
const
memoCreate
=
{
content
,
createdTs
:
Math
.
floor
(
utils
.
getTimeStampByDate
(
createdTs
)
/
1000
),
};
await
memoService
.
createMemo
(
memoCreate
);
succeedAmount
++
;
}
catch
(
error
)
{
// do nth
}
}
await
memoService
.
fetchAllMemos
();
toastHelper
.
success
(
`
${
succeedAmount
}
memos successfully imported.`
);
};
}
};
fileInputEl
.
click
();
};
const
handleLocaleChanged
=
async
(
value
:
string
)
=>
{
const
handleLocaleChanged
=
async
(
value
:
string
)
=>
{
globalService
.
setLocale
(
value
as
Locale
);
globalService
.
setLocale
(
value
as
Locale
);
await
userService
.
upsertUserSetting
(
"locale"
,
value
);
await
userService
.
upsertUserSetting
(
"locale"
,
value
);
...
@@ -87,22 +28,10 @@ const PreferencesSection: React.FC<Props> = () => {
...
@@ -87,22 +28,10 @@ const PreferencesSection: React.FC<Props> = () => {
return
(
return
(
<
div
className=
"section-container preferences-section-container"
>
<
div
className=
"section-container preferences-section-container"
>
{
/* Hide export/import buttons */
}
<
label
className=
"form-label"
>
<
label
className=
"form-label"
>
<
span
className=
"normal-text"
>
Language
:
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"common.language"
)
}
:
</
span
>
<
Selector
className=
"ml-2 w-28"
value=
{
setting
.
locale
}
dataSource=
{
localeSelectorItems
}
handleValueChanged=
{
handleLocaleChanged
}
/>
<
Selector
className=
"ml-2 w-28"
value=
{
setting
.
locale
}
dataSource=
{
localeSelectorItems
}
handleValueChanged=
{
handleLocaleChanged
}
/>
</
label
>
</
label
>
<
Only
when=
{
false
}
>
<
p
className=
"title-text"
>
Others
</
p
>
<
div
className=
"btns-container"
>
<
button
className=
"btn"
onClick=
{
handleExportBtnClick
}
>
Export data as JSON
</
button
>
<
button
className=
"btn"
onClick=
{
handleImportBtnClick
}
>
Import from JSON
</
button
>
</
div
>
</
Only
>
</
div
>
</
div
>
);
);
};
};
...
...
web/src/components/ShareMemoImageDialog.tsx
View file @
a7a01df7
...
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
...
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import
{
userService
}
from
"../services"
;
import
{
userService
}
from
"../services"
;
import
toImage
from
"../labs/html2image"
;
import
toImage
from
"../labs/html2image"
;
import
{
ANIMATION_DURATION
,
IMAGE_URL_REG
}
from
"../helpers/consts"
;
import
{
ANIMATION_DURATION
,
IMAGE_URL_REG
}
from
"../helpers/consts"
;
import
useI18n
from
"../hooks/useI18n"
;
import
*
as
utils
from
"../helpers/utils"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
formatMemoContent
}
from
"../helpers/marked"
;
import
{
formatMemoContent
}
from
"../helpers/marked"
;
import
Only
from
"./common/OnlyWhen"
;
import
Only
from
"./common/OnlyWhen"
;
...
@@ -16,6 +17,7 @@ interface Props extends DialogProps {
...
@@ -16,6 +17,7 @@ interface Props extends DialogProps {
const
ShareMemoImageDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
ShareMemoImageDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
memo
:
propsMemo
,
destroy
}
=
props
;
const
{
memo
:
propsMemo
,
destroy
}
=
props
;
const
{
t
}
=
useI18n
();
const
{
user
:
userinfo
}
=
userService
.
getState
();
const
{
user
:
userinfo
}
=
userService
.
getState
();
const
memo
=
{
const
memo
=
{
...
propsMemo
,
...
propsMemo
,
...
@@ -73,7 +75,8 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
...
@@ -73,7 +75,8 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
<>
<>
<
div
className=
"dialog-header-container"
>
<
div
className=
"dialog-header-container"
>
<
p
className=
"title-text"
>
<
p
className=
"title-text"
>
<
span
className=
"icon-text"
>
🌄
</
span
>
Share Memo
<
span
className=
"icon-text"
>
🌄
</
span
>
{
t
(
"common.share"
)
}
Memo
</
p
>
</
p
>
<
button
className=
"btn close-btn"
onClick=
{
handleCloseBtnClick
}
>
<
button
className=
"btn close-btn"
onClick=
{
handleCloseBtnClick
}
>
<
Icon
.
X
className=
"icon-img"
/>
<
Icon
.
X
className=
"icon-img"
/>
...
...
web/src/components/ShortcutList.tsx
View file @
a7a01df7
import
{
useEffect
}
from
"react"
;
import
{
useEffect
}
from
"react"
;
import
{
locationService
,
shortcutService
}
from
"../services"
;
import
{
locationService
,
shortcutService
}
from
"../services"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
useAppSelector
}
from
"../store"
;
import
useI18n
from
"../hooks/useI18n"
;
import
*
as
utils
from
"../helpers/utils"
;
import
*
as
utils
from
"../helpers/utils"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useLoading
from
"../hooks/useLoading"
;
import
useLoading
from
"../hooks/useLoading"
;
...
@@ -59,6 +60,7 @@ interface ShortcutContainerProps {
...
@@ -59,6 +60,7 @@ interface ShortcutContainerProps {
const
ShortcutContainer
:
React
.
FC
<
ShortcutContainerProps
>
=
(
props
:
ShortcutContainerProps
)
=>
{
const
ShortcutContainer
:
React
.
FC
<
ShortcutContainerProps
>
=
(
props
:
ShortcutContainerProps
)
=>
{
const
{
shortcut
,
isActive
}
=
props
;
const
{
shortcut
,
isActive
}
=
props
;
const
{
t
}
=
useI18n
();
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
handleShortcutClick
=
()
=>
{
const
handleShortcutClick
=
()
=>
{
...
@@ -119,17 +121,18 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
...
@@ -119,17 +121,18 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
<
div
className=
"action-btns-wrapper"
>
<
div
className=
"action-btns-wrapper"
>
<
div
className=
"action-btns-container"
>
<
div
className=
"action-btns-container"
>
<
span
className=
"btn"
onClick=
{
handlePinShortcutBtnClick
}
>
<
span
className=
"btn"
onClick=
{
handlePinShortcutBtnClick
}
>
{
shortcut
.
rowStatus
===
"ARCHIVED"
?
"Unpin"
:
"Pin"
}
{
shortcut
.
rowStatus
===
"ARCHIVED"
?
t
(
"common.unpin"
)
:
t
(
"common.pin"
)
}
</
span
>
</
span
>
<
span
className=
"btn"
onClick=
{
handleEditShortcutBtnClick
}
>
<
span
className=
"btn"
onClick=
{
handleEditShortcutBtnClick
}
>
Edit
{
t
(
"common.edit"
)
}
</
span
>
</
span
>
<
span
<
span
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
onClick=
{
handleDeleteMemoClick
}
onClick=
{
handleDeleteMemoClick
}
onMouseLeave=
{
handleDeleteBtnMouseLeave
}
onMouseLeave=
{
handleDeleteBtnMouseLeave
}
>
>
{
showConfirmDeleteBtn
?
"Delete!"
:
"Delete"
}
{
t
(
"common.delete"
)
}
{
showConfirmDeleteBtn
?
"!"
:
""
}
</
span
>
</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/Sidebar.tsx
View file @
a7a01df7
import
{
userService
}
from
"../services"
;
import
{
userService
}
from
"../services"
;
import
useI18n
from
"../hooks/useI18n"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
Only
from
"./common/OnlyWhen"
;
import
Only
from
"./common/OnlyWhen"
;
import
showDailyReviewDialog
from
"./DailyReviewDialog"
;
import
showDailyReviewDialog
from
"./DailyReviewDialog"
;
...
@@ -14,14 +15,14 @@ import "../less/siderbar.less";
...
@@ -14,14 +15,14 @@ import "../less/siderbar.less";
interface
Props
{}
interface
Props
{}
const
Sidebar
:
React
.
FC
<
Props
>
=
()
=>
{
const
Sidebar
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
}
=
useI18n
();
const
handleMyAccountBtnClick
=
()
=>
{
const
handleMyAccountBtnClick
=
()
=>
{
showSettingDialog
();
showSettingDialog
();
};
};
const
handleResourcesBtnClick
=
()
=>
{
const
handleResourcesBtnClick
=
()
=>
{
showResourcesDialog
();
showResourcesDialog
();
};
};
const
handleArchivedBtnClick
=
()
=>
{
const
handleArchivedBtnClick
=
()
=>
{
showArchivedMemoDialog
();
showArchivedMemoDialog
();
};
};
...
@@ -37,17 +38,17 @@ const Sidebar: React.FC<Props> = () => {
...
@@ -37,17 +38,17 @@ const Sidebar: React.FC<Props> = () => {
<
UsageHeatMap
/>
<
UsageHeatMap
/>
<
div
className=
"action-btns-container"
>
<
div
className=
"action-btns-container"
>
<
button
className=
"btn action-btn"
onClick=
{
()
=>
showDailyReviewDialog
()
}
>
<
button
className=
"btn action-btn"
onClick=
{
()
=>
showDailyReviewDialog
()
}
>
<
span
className=
"icon"
>
📅
</
span
>
Daily Review
<
span
className=
"icon"
>
📅
</
span
>
{
t
(
"sidebar.daily-review"
)
}
</
button
>
</
button
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleResourcesBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleResourcesBtnClick
}
>
<
span
className=
"icon"
>
🌄
</
span
>
Resources
<
span
className=
"icon"
>
🌄
</
span
>
{
t
(
"sidebar.resources"
)
}
</
button
>
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleMyAccountBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleMyAccountBtnClick
}
>
<
span
className=
"icon"
>
⚙️
</
span
>
Setting
<
span
className=
"icon"
>
⚙️
</
span
>
{
t
(
"sidebar.setting"
)
}
</
button
>
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleArchivedBtnClick
}
>
<
button
className=
"btn action-btn"
onClick=
{
handleArchivedBtnClick
}
>
<
span
className=
"icon"
>
🗂
</
span
>
Archived
<
span
className=
"icon"
>
🗂
</
span
>
{
t
(
"sidebar.archived"
)
}
</
button
>
</
button
>
</
Only
>
</
Only
>
</
div
>
</
div
>
...
...
web/src/components/TagList.tsx
View file @
a7a01df7
...
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
...
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import
*
as
utils
from
"../helpers/utils"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
locationService
,
memoService
,
userService
}
from
"../services"
;
import
{
locationService
,
memoService
,
userService
}
from
"../services"
;
import
useI18n
from
"../hooks/useI18n"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useToggle
from
"../hooks/useToggle"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
Only
from
"./common/OnlyWhen"
;
import
Only
from
"./common/OnlyWhen"
;
...
@@ -16,6 +17,7 @@ interface Tag {
...
@@ -16,6 +17,7 @@ interface Tag {
interface
Props
{}
interface
Props
{}
const
TagList
:
React
.
FC
<
Props
>
=
()
=>
{
const
TagList
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
t
}
=
useI18n
();
const
{
memos
,
tags
:
tagsText
}
=
useAppSelector
((
state
)
=>
state
.
memo
);
const
{
memos
,
tags
:
tagsText
}
=
useAppSelector
((
state
)
=>
state
.
memo
);
const
query
=
useAppSelector
((
state
)
=>
state
.
location
.
query
);
const
query
=
useAppSelector
((
state
)
=>
state
.
location
.
query
);
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
...
@@ -75,9 +77,7 @@ const TagList: React.FC<Props> = () => {
...
@@ -75,9 +77,7 @@ const TagList: React.FC<Props> = () => {
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
query
?.
tag
}
/>
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
query
?.
tag
}
/>
))
}
))
}
<
Only
when=
{
!
userService
.
isVisitorMode
()
&&
tags
.
length
<
5
}
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
&&
tags
.
length
<
5
}
>
<
p
className=
"tag-tip-container"
>
<
p
className=
"tip-text"
>
{
t
(
"tag-list.tip-text"
)
}
</
p
>
Enter
<
span
className=
"code-text"
>
#tag
</
span
>
to create a tag
</
p
>
</
Only
>
</
Only
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/less/about-site-dialog.less
View file @
a7a01df7
...
@@ -18,10 +18,10 @@
...
@@ -18,10 +18,10 @@
}
}
> .addtion-info-container {
> .addtion-info-container {
@apply flex flex-row justify-start items-center;
@apply flex flex-row
text-sm
justify-start items-center;
> .github-badge-container {
> .github-badge-container {
@apply mr-
2
;
@apply mr-
4
;
}
}
}
}
}
}
...
...
web/src/less/tag-list.less
View file @
a7a01df7
...
@@ -61,12 +61,8 @@
...
@@ -61,12 +61,8 @@
}
}
}
}
> .tag-tip-container {
> .tip-text {
@apply w-full mt-2 pl-4 text-sm text-gray-400;
@apply w-full mt-2 pl-4 text-sm text-gray-400 font-mono;
> .code-text {
@apply p-1 mx-1 text-blue-600 font-mono whitespace-pre-line bg-blue-100 rounded;
}
}
}
}
}
}
}
web/src/locales/en.json
View file @
a7a01df7
...
@@ -3,12 +3,56 @@
...
@@ -3,12 +3,56 @@
"about"
:
"About"
,
"about"
:
"About"
,
"email"
:
"Email"
,
"email"
:
"Email"
,
"password"
:
"Password"
,
"password"
:
"Password"
,
"sign-in"
:
"Sign in"
"username"
:
"Username"
,
"save"
:
"Save"
,
"cancel"
:
"Cancel"
,
"create"
:
"Create"
,
"change"
:
"Change"
,
"reset"
:
"Reset"
,
"language"
:
"Language"
,
"version"
:
"Version"
,
"pin"
:
"Pin"
,
"unpin"
:
"Unpin"
,
"edit"
:
"Edit"
,
"delete"
:
"Delete"
,
"share"
:
"Share"
,
"mark"
:
"Mark"
,
"archive"
:
"Archive"
,
"basic"
:
"Basic"
,
"admin"
:
"Admin"
,
"sign-in"
:
"Sign in"
,
"sign-out"
:
"Sign out"
,
"back-to-home"
:
"Back to Home"
},
},
"slogan"
:
"An open source, self-hosted knowledge base that works with a SQLite db file."
,
"slogan"
:
"An open source, self-hosted knowledge base that works with a SQLite db file."
,
"auth"
:
{
"auth"
:
{
"signup-as-host"
:
"Sign up as Host"
,
"signup-as-host"
:
"Sign up as Host"
,
"host-tip"
:
"You are registering as the Site Host."
,
"host-tip"
:
"You are registering as the Site Host."
,
"not-host-tip"
:
"If you don't have an account, please contact the site host."
"not-host-tip"
:
"If you don't have an account, please contact the site host."
},
"sidebar"
:
{
"daily-review"
:
"Daily Review"
,
"resources"
:
"Resources"
,
"setting"
:
"Setting"
,
"archived"
:
"Archived"
},
"editor"
:
{
"save"
:
"Save"
,
"cancel-edit"
:
"Cancel edit"
,
"placeholder"
:
"Any thoughts..."
},
"tag-list"
:
{
"tip-text"
:
"Enter `#tag ` to create a tag"
},
"setting"
:
{
"my-account"
:
"My Account"
,
"preference"
:
"Preference"
,
"member"
:
"Member"
,
"account-section"
:
{
"title"
:
"Account Information"
},
"member-section"
:
{
"create-a-member"
:
"Create a member"
}
}
}
}
}
web/src/locales/zh.json
View file @
a7a01df7
...
@@ -3,12 +3,57 @@
...
@@ -3,12 +3,57 @@
"about"
:
"关于"
,
"about"
:
"关于"
,
"email"
:
"邮箱"
,
"email"
:
"邮箱"
,
"password"
:
"密码"
,
"password"
:
"密码"
,
"sign-in"
:
"登录"
"username"
:
"用户名"
,
"save"
:
"保存"
,
"cancel"
:
"退出"
,
"create"
:
"创建"
,
"change"
:
"修改"
,
"reset"
:
"重置"
,
"restore"
:
"恢复"
,
"language"
:
"语言"
,
"version"
:
"版本"
,
"pin"
:
"置顶"
,
"unpin"
:
"取消置顶"
,
"edit"
:
"编辑"
,
"delete"
:
"删除"
,
"share"
:
"分享"
,
"mark"
:
"Mark"
,
"archive"
:
"归档"
,
"basic"
:
"基础"
,
"admin"
:
"管理员"
,
"sign-in"
:
"登录"
,
"sign-out"
:
"退出登录"
,
"back-to-home"
:
"回到主页"
},
},
"slogan"
:
"一个开源的、支持私有化部署的碎片化知识卡片管理工具。"
,
"slogan"
:
"一个开源的、支持私有化部署的碎片化知识卡片管理工具。"
,
"auth"
:
{
"auth"
:
{
"signup-as-host"
:
"注册为 Host"
,
"signup-as-host"
:
"注册为 Host"
,
"host-tip"
:
"你正在注册为 Host 用户账号。"
,
"host-tip"
:
"你正在注册为 Host 用户账号。"
,
"not-host-tip"
:
"如果你没有账号,请联系站点 Host"
"not-host-tip"
:
"如果你没有账号,请联系站点 Host"
},
"sidebar"
:
{
"daily-review"
:
"每日回顾"
,
"resources"
:
"资源"
,
"setting"
:
"设置"
,
"archived"
:
"已归档"
},
"editor"
:
{
"save"
:
"记下"
,
"cancel-edit"
:
"退出编辑"
,
"placeholder"
:
"现在的想法是..."
},
"tag-list"
:
{
"tip-text"
:
"输入`#tag `来创建标签"
},
"setting"
:
{
"my-account"
:
"我的账号"
,
"preference"
:
"偏好设置"
,
"member"
:
"成员"
,
"account-section"
:
{
"title"
:
"账号信息"
},
"member-section"
:
{
"create-a-member"
:
"创建成员"
}
}
}
}
}
web/src/pages/Auth.tsx
View file @
a7a01df7
...
@@ -11,7 +11,7 @@ import "../less/auth.less";
...
@@ -11,7 +11,7 @@ import "../less/auth.less";
interface
Props
{}
interface
Props
{}
const
validateConfig
:
ValidatorConfig
=
{
const
validateConfig
:
ValidatorConfig
=
{
minLength
:
4
,
minLength
:
6
,
maxLength
:
24
,
maxLength
:
24
,
noSpace
:
true
,
noSpace
:
true
,
noChinese
:
true
,
noChinese
:
true
,
...
...
web/src/pages/Home.tsx
View file @
a7a01df7
import
{
useEffect
}
from
"react"
;
import
{
useEffect
}
from
"react"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
{
useAppSelector
}
from
"../store"
;
import
{
useAppSelector
}
from
"../store"
;
import
useI18n
from
"../hooks/useI18n"
;
import
useLoading
from
"../hooks/useLoading"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Only
from
"../components/common/OnlyWhen"
;
import
Only
from
"../components/common/OnlyWhen"
;
import
Sidebar
from
"../components/Sidebar"
;
import
Sidebar
from
"../components/Sidebar"
;
...
@@ -12,6 +13,7 @@ import toastHelper from "../components/Toast";
...
@@ -12,6 +13,7 @@ import toastHelper from "../components/Toast";
import
"../less/home.less"
;
import
"../less/home.less"
;
function
Home
()
{
function
Home
()
{
const
{
t
}
=
useI18n
();
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
user
=
useAppSelector
((
state
)
=>
state
.
user
.
user
);
const
location
=
useAppSelector
((
state
)
=>
state
.
location
);
const
location
=
useAppSelector
((
state
)
=>
state
.
location
);
const
loadingState
=
useLoading
();
const
loadingState
=
useLoading
();
...
@@ -58,11 +60,11 @@ function Home() {
...
@@ -58,11 +60,11 @@ function Home() {
<
div
className=
"addtion-btn-container"
>
<
div
className=
"addtion-btn-container"
>
{
user
?
(
{
user
?
(
<
button
className=
"btn"
onClick=
{
()
=>
(
window
.
location
.
href
=
"/"
)
}
>
<
button
className=
"btn"
onClick=
{
()
=>
(
window
.
location
.
href
=
"/"
)
}
>
<
span
className=
"icon"
>
🏠
</
span
>
Back to Home
<
span
className=
"icon"
>
🏠
</
span
>
{
t
(
"common.back-to-home"
)
}
</
button
>
</
button
>
)
:
(
)
:
(
<
button
className=
"btn"
onClick=
{
()
=>
(
window
.
location
.
href
=
"/auth"
)
}
>
<
button
className=
"btn"
onClick=
{
()
=>
(
window
.
location
.
href
=
"/auth"
)
}
>
<
span
className=
"icon"
>
👉
</
span
>
Sign in
<
span
className=
"icon"
>
👉
</
span
>
{
t
(
"common.sign-in"
)
}
</
button
>
</
button
>
)
}
)
}
</
div
>
</
div
>
...
...
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