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
7efa749c
Unverified
Commit
7efa749c
authored
Dec 22, 2022
by
boojack
Committed by
GitHub
Dec 22, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: customize system profile (#828)
parent
72daa4e1
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
171 additions
and
141 deletions
+171
-141
system_setting.go
api/system_setting.go
+19
-2
user_setting.go
api/user_setting.go
+7
-37
go.mod
go.mod
+5
-2
go.sum
go.sum
+4
-2
acl.go
server/acl.go
+1
-0
system.go
server/system.go
+9
-1
AppearanceSelect.tsx
web/src/components/AppearanceSelect.tsx
+13
-13
LocaleSelect.tsx
web/src/components/LocaleSelect.tsx
+37
-0
PreferencesSection.tsx
web/src/components/Settings/PreferencesSection.tsx
+12
-56
UpdateCustomizedProfileDialog.tsx
web/src/components/UpdateCustomizedProfileDialog.tsx
+37
-2
Auth.tsx
web/src/pages/Auth.tsx
+14
-24
global.ts
web/src/store/module/global.ts
+6
-1
global.ts
web/src/store/reducer/global.ts
+4
-1
system.d.ts
web/src/types/modules/system.d.ts
+3
-0
No files found.
api/system_setting.go
View file @
7efa749c
...
@@ -3,6 +3,8 @@ package api
...
@@ -3,6 +3,8 @@ package api
import
(
import
(
"encoding/json"
"encoding/json"
"fmt"
"fmt"
"golang.org/x/exp/slices"
)
)
type
SystemSettingName
string
type
SystemSettingName
string
...
@@ -24,6 +26,12 @@ type CustomizedProfile struct {
...
@@ -24,6 +26,12 @@ type CustomizedProfile struct {
Name
string
`json:"name"`
Name
string
`json:"name"`
// IconURL is the url of icon image.
// IconURL is the url of icon image.
IconURL
string
`json:"iconUrl"`
IconURL
string
`json:"iconUrl"`
// Description is the server description.
Description
string
`json:"description"`
// Locale is the server default locale.
Locale
string
`json:"locale"`
// Appearance is the server default appearance.
Appearance
string
`json:"appearance"`
// ExternalURL is the external url of server. e.g. https://usermemos.com
// ExternalURL is the external url of server. e.g. https://usermemos.com
ExternalURL
string
`json:"externalUrl"`
ExternalURL
string
`json:"externalUrl"`
}
}
...
@@ -90,15 +98,24 @@ func (upsert SystemSettingUpsert) Validate() error {
...
@@ -90,15 +98,24 @@ func (upsert SystemSettingUpsert) Validate() error {
return
fmt
.
Errorf
(
"failed to unmarshal system setting additional script value"
)
return
fmt
.
Errorf
(
"failed to unmarshal system setting additional script value"
)
}
}
}
else
if
upsert
.
Name
==
SystemSettingCustomizedProfileName
{
}
else
if
upsert
.
Name
==
SystemSettingCustomizedProfileName
{
valu
e
:=
CustomizedProfile
{
customizedProfil
e
:=
CustomizedProfile
{
Name
:
"memos"
,
Name
:
"memos"
,
IconURL
:
""
,
IconURL
:
""
,
Description
:
""
,
Locale
:
"en"
,
Appearance
:
"system"
,
ExternalURL
:
""
,
ExternalURL
:
""
,
}
}
err
:=
json
.
Unmarshal
([]
byte
(
upsert
.
Value
),
&
valu
e
)
err
:=
json
.
Unmarshal
([]
byte
(
upsert
.
Value
),
&
customizedProfil
e
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal system setting customized profile value"
)
return
fmt
.
Errorf
(
"failed to unmarshal system setting customized profile value"
)
}
}
if
!
slices
.
Contains
(
UserSettingLocaleValue
,
customizedProfile
.
Locale
)
{
return
fmt
.
Errorf
(
"invalid locale value"
)
}
if
!
slices
.
Contains
(
UserSettingAppearanceValue
,
customizedProfile
.
Appearance
)
{
return
fmt
.
Errorf
(
"invalid appearance value"
)
}
}
else
{
}
else
{
return
fmt
.
Errorf
(
"invalid system setting name"
)
return
fmt
.
Errorf
(
"invalid system setting name"
)
}
}
...
...
api/user_setting.go
View file @
7efa749c
...
@@ -3,6 +3,8 @@ package api
...
@@ -3,6 +3,8 @@ package api
import
(
import
(
"encoding/json"
"encoding/json"
"fmt"
"fmt"
"golang.org/x/exp/slices"
)
)
type
UserSettingKey
string
type
UserSettingKey
string
...
@@ -60,32 +62,16 @@ func (upsert UserSettingUpsert) Validate() error {
...
@@ -60,32 +62,16 @@ func (upsert UserSettingUpsert) Validate() error {
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal user setting locale value"
)
return
fmt
.
Errorf
(
"failed to unmarshal user setting locale value"
)
}
}
if
!
slices
.
Contains
(
UserSettingLocaleValue
,
localeValue
)
{
invalid
:=
true
for
_
,
value
:=
range
UserSettingLocaleValue
{
if
localeValue
==
value
{
invalid
=
false
break
}
}
if
invalid
{
return
fmt
.
Errorf
(
"invalid user setting locale value"
)
return
fmt
.
Errorf
(
"invalid user setting locale value"
)
}
}
}
else
if
upsert
.
Key
==
UserSettingAppearanceKey
{
}
else
if
upsert
.
Key
==
UserSettingAppearanceKey
{
appearanceValue
:=
"
light
"
appearanceValue
:=
"
system
"
err
:=
json
.
Unmarshal
([]
byte
(
upsert
.
Value
),
&
appearanceValue
)
err
:=
json
.
Unmarshal
([]
byte
(
upsert
.
Value
),
&
appearanceValue
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal user setting appearance value"
)
return
fmt
.
Errorf
(
"failed to unmarshal user setting appearance value"
)
}
}
if
!
slices
.
Contains
(
UserSettingAppearanceValue
,
appearanceValue
)
{
invalid
:=
true
for
_
,
value
:=
range
UserSettingAppearanceValue
{
if
appearanceValue
==
value
{
invalid
=
false
break
}
}
if
invalid
{
return
fmt
.
Errorf
(
"invalid user setting appearance value"
)
return
fmt
.
Errorf
(
"invalid user setting appearance value"
)
}
}
}
else
if
upsert
.
Key
==
UserSettingMemoVisibilityKey
{
}
else
if
upsert
.
Key
==
UserSettingMemoVisibilityKey
{
...
@@ -94,15 +80,7 @@ func (upsert UserSettingUpsert) Validate() error {
...
@@ -94,15 +80,7 @@ func (upsert UserSettingUpsert) Validate() error {
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal user setting memo visibility value"
)
return
fmt
.
Errorf
(
"failed to unmarshal user setting memo visibility value"
)
}
}
if
!
slices
.
Contains
(
UserSettingMemoVisibilityValue
,
memoVisibilityValue
)
{
invalid
:=
true
for
_
,
value
:=
range
UserSettingMemoVisibilityValue
{
if
memoVisibilityValue
==
value
{
invalid
=
false
break
}
}
if
invalid
{
return
fmt
.
Errorf
(
"invalid user setting memo visibility value"
)
return
fmt
.
Errorf
(
"invalid user setting memo visibility value"
)
}
}
}
else
if
upsert
.
Key
==
UserSettingMemoDisplayTsOptionKey
{
}
else
if
upsert
.
Key
==
UserSettingMemoDisplayTsOptionKey
{
...
@@ -111,15 +89,7 @@ func (upsert UserSettingUpsert) Validate() error {
...
@@ -111,15 +89,7 @@ func (upsert UserSettingUpsert) Validate() error {
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal user setting memo display ts option"
)
return
fmt
.
Errorf
(
"failed to unmarshal user setting memo display ts option"
)
}
}
if
!
slices
.
Contains
(
UserSettingMemoDisplayTsOptionKeyValue
,
memoDisplayTsOption
)
{
invalid
:=
true
for
_
,
value
:=
range
UserSettingMemoDisplayTsOptionKeyValue
{
if
memoDisplayTsOption
==
value
{
invalid
=
false
break
}
}
if
invalid
{
return
fmt
.
Errorf
(
"invalid user setting memo display ts option value"
)
return
fmt
.
Errorf
(
"invalid user setting memo display ts option value"
)
}
}
}
else
{
}
else
{
...
...
go.mod
View file @
7efa749c
...
@@ -38,11 +38,14 @@ require (
...
@@ -38,11 +38,14 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
golang.org/x/sys v0.
0.0-20220728004956-3c1f35247d1
0 // indirect
golang.org/x/sys v0.
1.
0 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
)
require github.com/segmentio/analytics-go v3.1.0+incompatible
require (
github.com/segmentio/analytics-go v3.1.0+incompatible
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
)
go.sum
View file @
7efa749c
...
@@ -70,14 +70,16 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB
...
@@ -70,14 +70,16 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201 h1:bvOltf3SADAfG05iRml8lAB3qjoEX5RCyN4K6G5v3N0=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201 h1:bvOltf3SADAfG05iRml8lAB3qjoEX5RCyN4K6G5v3N0=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.
0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg
=
golang.org/x/sys v0.
1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U
=
golang.org/x/sys v0.
0.0-20220728004956-3c1f35247d1
0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.
1.
0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
...
...
server/acl.go
View file @
7efa749c
...
@@ -27,6 +27,7 @@ func setUserSession(ctx echo.Context, user *api.User) error {
...
@@ -27,6 +27,7 @@ func setUserSession(ctx echo.Context, user *api.User) error {
Path
:
"/"
,
Path
:
"/"
,
MaxAge
:
3600
*
24
*
30
,
MaxAge
:
3600
*
24
*
30
,
HttpOnly
:
true
,
HttpOnly
:
true
,
Secure
:
true
,
}
}
sess
.
Values
[
userIDContextKey
]
=
user
.
ID
sess
.
Values
[
userIDContextKey
]
=
user
.
ID
err
:=
sess
.
Save
(
ctx
.
Request
(),
ctx
.
Response
())
err
:=
sess
.
Save
(
ctx
.
Request
(),
ctx
.
Response
())
...
...
server/system.go
View file @
7efa749c
...
@@ -47,7 +47,12 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
...
@@ -47,7 +47,12 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
AdditionalStyle
:
""
,
AdditionalStyle
:
""
,
AdditionalScript
:
""
,
AdditionalScript
:
""
,
CustomizedProfile
:
api
.
CustomizedProfile
{
CustomizedProfile
:
api
.
CustomizedProfile
{
Name
:
"memos"
,
Name
:
"memos"
,
IconURL
:
""
,
Description
:
""
,
Locale
:
"en"
,
Appearance
:
"system"
,
ExternalURL
:
""
,
},
},
}
}
...
@@ -73,6 +78,9 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
...
@@ -73,6 +78,9 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
systemStatus
.
CustomizedProfile
=
api
.
CustomizedProfile
{
systemStatus
.
CustomizedProfile
=
api
.
CustomizedProfile
{
Name
:
valueMap
[
"name"
]
.
(
string
),
Name
:
valueMap
[
"name"
]
.
(
string
),
IconURL
:
valueMap
[
"iconUrl"
]
.
(
string
),
IconURL
:
valueMap
[
"iconUrl"
]
.
(
string
),
Description
:
valueMap
[
"description"
]
.
(
string
),
Locale
:
valueMap
[
"locale"
]
.
(
string
),
Appearance
:
valueMap
[
"appearance"
]
.
(
string
),
ExternalURL
:
valueMap
[
"externalUrl"
]
.
(
string
),
ExternalURL
:
valueMap
[
"externalUrl"
]
.
(
string
),
}
}
}
}
...
...
web/src/components/AppearanceSelect.tsx
View file @
7efa749c
import
{
Option
,
Select
}
from
"@mui/joy"
;
import
{
Option
,
Select
}
from
"@mui/joy"
;
import
{
FC
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useGlobalStore
,
useUserStore
}
from
"../store/module"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
interface
Props
{
value
:
Appearance
;
onChange
:
(
appearance
:
Appearance
)
=>
void
;
className
?:
string
;
}
const
appearanceList
=
[
"system"
,
"light"
,
"dark"
];
const
appearanceList
=
[
"system"
,
"light"
,
"dark"
];
const
AppearanceSelect
=
()
=>
{
const
AppearanceSelect
:
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
onChange
,
value
,
className
}
=
props
;
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
globalStore
=
useGlobalStore
();
const
userStore
=
useUserStore
();
const
{
appearance
}
=
globalStore
.
state
;
const
user
=
userStore
.
state
.
user
;
const
getPrefixIcon
=
(
apperance
:
Appearance
)
=>
{
const
getPrefixIcon
=
(
apperance
:
Appearance
)
=>
{
const
className
=
"w-4 h-auto"
;
const
className
=
"w-4 h-auto"
;
...
@@ -24,22 +27,19 @@ const AppearanceSelect = () => {
...
@@ -24,22 +27,19 @@ const AppearanceSelect = () => {
};
};
const
handleSelectChange
=
async
(
appearance
:
Appearance
)
=>
{
const
handleSelectChange
=
async
(
appearance
:
Appearance
)
=>
{
if
(
user
)
{
onChange
(
appearance
);
await
userStore
.
upsertUserSetting
(
"appearance"
,
appearance
);
}
globalStore
.
setAppearance
(
appearance
);
};
};
return
(
return
(
<
Select
<
Select
className=
"!min-w-[10rem] w-auto text-sm"
className=
{
`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`
}
value=
{
appearanc
e
}
value=
{
valu
e
}
onChange=
{
(
_
,
appearance
)
=>
{
onChange=
{
(
_
,
appearance
)
=>
{
if
(
appearance
)
{
if
(
appearance
)
{
handleSelectChange
(
appearance
);
handleSelectChange
(
appearance
);
}
}
}
}
}
}
startDecorator=
{
getPrefixIcon
(
appearanc
e
)
}
startDecorator=
{
getPrefixIcon
(
valu
e
)
}
>
>
{
appearanceList
.
map
((
item
)
=>
(
{
appearanceList
.
map
((
item
)
=>
(
<
Option
key=
{
item
}
value=
{
item
}
className=
"whitespace-nowrap"
>
<
Option
key=
{
item
}
value=
{
item
}
className=
"whitespace-nowrap"
>
...
...
web/src/components/LocaleSelect.tsx
0 → 100644
View file @
7efa749c
import
{
Option
,
Select
}
from
"@mui/joy"
;
import
{
FC
}
from
"react"
;
import
Icon
from
"./Icon"
;
interface
Props
{
value
:
Locale
;
onChange
:
(
locale
:
Locale
)
=>
void
;
className
?:
string
;
}
const
LocaleSelect
:
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
onChange
,
value
,
className
}
=
props
;
const
handleSelectChange
=
async
(
locale
:
Locale
)
=>
{
onChange
(
locale
);
};
return
(
<
Select
className=
{
`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`
}
startDecorator=
{
<
Icon
.
Globe
className=
"w-4 h-auto"
/>
}
value=
{
value
}
onChange=
{
(
_
,
value
)
=>
handleSelectChange
(
value
as
Locale
)
}
>
<
Option
value=
"en"
>
English
</
Option
>
<
Option
value=
"zh"
>
中文
</
Option
>
<
Option
value=
"vi"
>
Tiếng Việt
</
Option
>
<
Option
value=
"fr"
>
French
</
Option
>
<
Option
value=
"nl"
>
Nederlands
</
Option
>
<
Option
value=
"sv"
>
Svenska
</
Option
>
<
Option
value=
"de"
>
German
</
Option
>
<
Option
value=
"es"
>
Español
</
Option
>
</
Select
>
);
};
export
default
LocaleSelect
;
web/src/components/Settings/PreferencesSection.tsx
View file @
7efa749c
...
@@ -2,49 +2,15 @@ import { Select, Switch, Option } from "@mui/joy";
...
@@ -2,49 +2,15 @@ import { Select, Switch, Option } from "@mui/joy";
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useGlobalStore
,
useUserStore
}
from
"../../store/module"
;
import
{
useGlobalStore
,
useUserStore
}
from
"../../store/module"
;
import
{
VISIBILITY_SELECTOR_ITEMS
,
MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS
}
from
"../../helpers/consts"
;
import
{
VISIBILITY_SELECTOR_ITEMS
,
MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS
}
from
"../../helpers/consts"
;
import
Icon
from
"../Icon"
;
import
AppearanceSelect
from
"../AppearanceSelect"
;
import
AppearanceSelect
from
"../AppearanceSelect"
;
import
LocaleSelect
from
"../LocaleSelect"
;
import
"../../less/settings/preferences-section.less"
;
import
"../../less/settings/preferences-section.less"
;
const
localeSelectorItems
=
[
{
text
:
"English"
,
value
:
"en"
,
},
{
text
:
"中文"
,
value
:
"zh"
,
},
{
text
:
"Tiếng Việt"
,
value
:
"vi"
,
},
{
text
:
"French"
,
value
:
"fr"
,
},
{
text
:
"Nederlands"
,
value
:
"nl"
,
},
{
text
:
"Svenska"
,
value
:
"sv"
,
},
{
text
:
"German"
,
value
:
"de"
,
},
{
text
:
"Español"
,
value
:
"es"
,
},
];
const
PreferencesSection
=
()
=>
{
const
PreferencesSection
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
globalStore
=
useGlobalStore
();
const
globalStore
=
useGlobalStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
const
{
appearance
,
locale
}
=
globalStore
.
state
;
const
{
setting
,
localSetting
}
=
userStore
.
state
.
user
as
User
;
const
{
setting
,
localSetting
}
=
userStore
.
state
.
user
as
User
;
const
visibilitySelectorItems
=
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
{
const
visibilitySelectorItems
=
VISIBILITY_SELECTOR_ITEMS
.
map
((
item
)
=>
{
return
{
return
{
...
@@ -60,9 +26,14 @@ const PreferencesSection = () => {
...
@@ -60,9 +26,14 @@ const PreferencesSection = () => {
};
};
});
});
const
handleLocaleChanged
=
async
(
value
:
string
)
=>
{
const
handleLocaleSelectChange
=
async
(
locale
:
Locale
)
=>
{
await
userStore
.
upsertUserSetting
(
"locale"
,
value
);
await
userStore
.
upsertUserSetting
(
"locale"
,
locale
);
globalStore
.
setLocale
(
value
as
Locale
);
globalStore
.
setLocale
(
locale
);
};
const
handleAppearanceSelectChange
=
async
(
appearance
:
Appearance
)
=>
{
await
userStore
.
upsertUserSetting
(
"appearance"
,
appearance
);
globalStore
.
setAppearance
(
appearance
);
};
};
const
handleDefaultMemoVisibilityChanged
=
async
(
value
:
string
)
=>
{
const
handleDefaultMemoVisibilityChanged
=
async
(
value
:
string
)
=>
{
...
@@ -82,26 +53,11 @@ const PreferencesSection = () => {
...
@@ -82,26 +53,11 @@ const PreferencesSection = () => {
<
p
className=
"title-text"
>
{
t
(
"common.basic"
)
}
</
p
>
<
p
className=
"title-text"
>
{
t
(
"common.basic"
)
}
</
p
>
<
div
className=
"form-label selector"
>
<
div
className=
"form-label selector"
>
<
span
className=
"normal-text"
>
{
t
(
"common.language"
)
}
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"common.language"
)
}
</
span
>
<
Select
<
LocaleSelect
value=
{
locale
}
onChange=
{
handleLocaleSelectChange
}
/>
className=
"!min-w-[10rem] w-auto text-sm"
value=
{
setting
.
locale
}
onChange=
{
(
_
,
locale
)
=>
{
if
(
locale
)
{
handleLocaleChanged
(
locale
);
}
}
}
startDecorator=
{
<
Icon
.
Globe
className=
"w-4 h-auto"
/>
}
>
{
localeSelectorItems
.
map
((
item
)
=>
(
<
Option
key=
{
item
.
value
}
value=
{
item
.
value
}
className=
"whitespace-nowrap"
>
{
item
.
text
}
</
Option
>
))
}
</
Select
>
</
div
>
</
div
>
<
div
className=
"form-label selector"
>
<
div
className=
"form-label selector"
>
<
span
className=
"normal-text"
>
{
t
(
"setting.preference-section.theme"
)
}
</
span
>
<
span
className=
"normal-text"
>
{
t
(
"setting.preference-section.theme"
)
}
</
span
>
<
AppearanceSelect
/>
<
AppearanceSelect
value=
{
appearance
}
onChange=
{
handleAppearanceSelectChange
}
/>
</
div
>
</
div
>
<
p
className=
"title-text"
>
{
t
(
"setting.preference"
)
}
</
p
>
<
p
className=
"title-text"
>
{
t
(
"setting.preference"
)
}
</
p
>
<
div
className=
"form-label selector"
>
<
div
className=
"form-label selector"
>
...
...
web/src/components/UpdateCustomizedProfileDialog.tsx
View file @
7efa749c
...
@@ -5,6 +5,8 @@ import * as api from "../helpers/api";
...
@@ -5,6 +5,8 @@ import * as api from "../helpers/api";
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
toastHelper
from
"./Toast"
;
import
toastHelper
from
"./Toast"
;
import
LocaleSelect
from
"./LocaleSelect"
;
import
AppearanceSelect
from
"./AppearanceSelect"
;
type
Props
=
DialogProps
;
type
Props
=
DialogProps
;
...
@@ -39,6 +41,33 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
...
@@ -39,6 +41,33 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
});
});
};
};
const
handleDescriptionChanged
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setState
((
state
)
=>
{
return
{
...
state
,
description
:
e
.
target
.
value
as
string
,
};
});
};
const
handleLocaleSelectChange
=
(
locale
:
Locale
)
=>
{
setState
((
state
)
=>
{
return
{
...
state
,
locale
:
locale
,
};
});
};
const
handleAppearanceSelectChange
=
(
appearance
:
Appearance
)
=>
{
setState
((
state
)
=>
{
return
{
...
state
,
appearance
:
appearance
,
};
});
};
const
handleSaveBtnClick
=
async
()
=>
{
const
handleSaveBtnClick
=
async
()
=>
{
if
(
state
.
name
===
""
||
state
.
iconUrl
===
""
)
{
if
(
state
.
name
===
""
||
state
.
iconUrl
===
""
)
{
toastHelper
.
error
(
t
(
"message.fill-all"
));
toastHelper
.
error
(
t
(
"message.fill-all"
));
...
@@ -61,13 +90,13 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
...
@@ -61,13 +90,13 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
return
(
return
(
<>
<>
<
div
className=
"dialog-header-container
!w-64
"
>
<
div
className=
"dialog-header-container"
>
<
p
className=
"title-text"
>
{
t
(
"setting.system-section.customize-server.title"
)
}
</
p
>
<
p
className=
"title-text"
>
{
t
(
"setting.system-section.customize-server.title"
)
}
</
p
>
<
button
className=
"btn close-btn"
onClick=
{
handleCloseBtnClick
}
>
<
button
className=
"btn close-btn"
onClick=
{
handleCloseBtnClick
}
>
<
Icon
.
X
/>
<
Icon
.
X
/>
</
button
>
</
button
>
</
div
>
</
div
>
<
div
className=
"dialog-content-container"
>
<
div
className=
"dialog-content-container
!w-80
"
>
<
p
className=
"text-sm mb-1"
>
<
p
className=
"text-sm mb-1"
>
{
t
(
"setting.system-section.server-name"
)
}
{
t
(
"setting.system-section.server-name"
)
}
<
span
className=
"text-sm text-gray-400 ml-1"
>
(
{
t
(
"setting.system-section.customize-server.default"
)
}
)
</
span
>
<
span
className=
"text-sm text-gray-400 ml-1"
>
(
{
t
(
"setting.system-section.customize-server.default"
)
}
)
</
span
>
...
@@ -75,6 +104,12 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
...
@@ -75,6 +104,12 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
<
input
type=
"text"
className=
"input-text"
value=
{
state
.
name
}
onChange=
{
handleNameChanged
}
/>
<
input
type=
"text"
className=
"input-text"
value=
{
state
.
name
}
onChange=
{
handleNameChanged
}
/>
<
p
className=
"text-sm mb-1 mt-2"
>
{
t
(
"setting.system-section.customize-server.icon-url"
)
}
</
p
>
<
p
className=
"text-sm mb-1 mt-2"
>
{
t
(
"setting.system-section.customize-server.icon-url"
)
}
</
p
>
<
input
type=
"text"
className=
"input-text"
value=
{
state
.
iconUrl
}
onChange=
{
handleIconUrlChanged
}
/>
<
input
type=
"text"
className=
"input-text"
value=
{
state
.
iconUrl
}
onChange=
{
handleIconUrlChanged
}
/>
<
p
className=
"text-sm mb-1 mt-2"
>
Description
</
p
>
<
input
type=
"text"
className=
"input-text"
value=
{
state
.
description
}
onChange=
{
handleDescriptionChanged
}
/>
<
p
className=
"text-sm mb-1 mt-2"
>
Server locale
</
p
>
<
LocaleSelect
className=
"w-full"
value=
{
state
.
locale
}
onChange=
{
handleLocaleSelectChange
}
/>
<
p
className=
"text-sm mb-1 mt-2"
>
Server appearance
</
p
>
<
AppearanceSelect
className=
"w-full"
value=
{
state
.
appearance
}
onChange=
{
handleAppearanceSelectChange
}
/>
<
div
className=
"mt-4 w-full flex flex-row justify-end items-center space-x-2"
>
<
div
className=
"mt-4 w-full flex flex-row justify-end items-center space-x-2"
>
<
span
className=
"btn-text"
onClick=
{
handleCloseBtnClick
}
>
<
span
className=
"btn-text"
onClick=
{
handleCloseBtnClick
}
>
{
t
(
"common.cancel"
)
}
{
t
(
"common.cancel"
)
}
...
...
web/src/pages/Auth.tsx
View file @
7efa749c
import
{
Option
,
Select
}
from
"@mui/joy"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useNavigate
}
from
"react-router-dom"
;
import
{
useNavigate
}
from
"react-router-dom"
;
...
@@ -9,6 +8,7 @@ import useLoading from "../hooks/useLoading";
...
@@ -9,6 +8,7 @@ import useLoading from "../hooks/useLoading";
import
Icon
from
"../components/Icon"
;
import
Icon
from
"../components/Icon"
;
import
toastHelper
from
"../components/Toast"
;
import
toastHelper
from
"../components/Toast"
;
import
AppearanceSelect
from
"../components/AppearanceSelect"
;
import
AppearanceSelect
from
"../components/AppearanceSelect"
;
import
LocaleSelect
from
"../components/LocaleSelect"
;
import
"../less/auth.less"
;
import
"../less/auth.less"
;
const
validateConfig
:
ValidatorConfig
=
{
const
validateConfig
:
ValidatorConfig
=
{
...
@@ -19,12 +19,12 @@ const validateConfig: ValidatorConfig = {
...
@@ -19,12 +19,12 @@ const validateConfig: ValidatorConfig = {
};
};
const
Auth
=
()
=>
{
const
Auth
=
()
=>
{
const
{
t
,
i18n
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
const
globalStore
=
useGlobalStore
();
const
globalStore
=
useGlobalStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
const
actionBtnLoadingState
=
useLoading
(
false
);
const
actionBtnLoadingState
=
useLoading
(
false
);
const
systemStatus
=
globalStore
.
state
.
systemStatus
;
const
{
appearance
,
locale
,
systemStatus
}
=
globalStore
.
state
;
const
mode
=
systemStatus
.
profile
.
mode
;
const
mode
=
systemStatus
.
profile
.
mode
;
const
[
username
,
setUsername
]
=
useState
(
mode
===
"dev"
?
"demohero"
:
""
);
const
[
username
,
setUsername
]
=
useState
(
mode
===
"dev"
?
"demohero"
:
""
);
const
[
password
,
setPassword
]
=
useState
(
mode
===
"dev"
?
"secret"
:
""
);
const
[
password
,
setPassword
]
=
useState
(
mode
===
"dev"
?
"secret"
:
""
);
...
@@ -43,6 +43,14 @@ const Auth = () => {
...
@@ -43,6 +43,14 @@ const Auth = () => {
setPassword
(
text
);
setPassword
(
text
);
};
};
const
handleLocaleSelectChange
=
(
locale
:
Locale
)
=>
{
globalStore
.
setLocale
(
locale
);
};
const
handleAppearanceSelectChange
=
(
appearance
:
Appearance
)
=>
{
globalStore
.
setAppearance
(
appearance
);
};
const
handleSigninBtnsClick
=
async
()
=>
{
const
handleSigninBtnsClick
=
async
()
=>
{
if
(
actionBtnLoadingState
.
isLoading
)
{
if
(
actionBtnLoadingState
.
isLoading
)
{
return
;
return
;
...
@@ -109,10 +117,6 @@ const Auth = () => {
...
@@ -109,10 +117,6 @@ const Auth = () => {
actionBtnLoadingState
.
setFinish
();
actionBtnLoadingState
.
setFinish
();
};
};
const
handleLocaleItemClick
=
(
locale
:
Locale
)
=>
{
globalStore
.
setLocale
(
locale
);
};
return
(
return
(
<
div
className=
"page-wrapper auth"
>
<
div
className=
"page-wrapper auth"
>
<
div
className=
"page-container"
>
<
div
className=
"page-container"
>
...
@@ -122,7 +126,7 @@ const Auth = () => {
...
@@ -122,7 +126,7 @@ const Auth = () => {
<
img
className=
"logo-img"
src=
{
systemStatus
.
customizedProfile
.
iconUrl
}
alt=
""
/>
<
img
className=
"logo-img"
src=
{
systemStatus
.
customizedProfile
.
iconUrl
}
alt=
""
/>
<
p
className=
"logo-text"
>
{
systemStatus
.
customizedProfile
.
name
}
</
p
>
<
p
className=
"logo-text"
>
{
systemStatus
.
customizedProfile
.
name
}
</
p
>
</
div
>
</
div
>
<
p
className=
"slogan-text"
>
{
t
(
"slogan"
)
}
</
p
>
<
p
className=
"slogan-text"
>
{
systemStatus
.
customizedProfile
.
description
||
t
(
"slogan"
)
}
</
p
>
</
div
>
</
div
>
<
div
className=
{
`page-content-container ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
>
<
div
className=
{
`page-content-container ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
>
<
div
className=
"form-item-container input-form-container"
>
<
div
className=
"form-item-container input-form-container"
>
...
@@ -167,22 +171,8 @@ const Auth = () => {
...
@@ -167,22 +171,8 @@ const Auth = () => {
{
!
systemStatus
?.
host
&&
<
p
className=
"tip-text"
>
{
t
(
"auth.host-tip"
)
}
</
p
>
}
{
!
systemStatus
?.
host
&&
<
p
className=
"tip-text"
>
{
t
(
"auth.host-tip"
)
}
</
p
>
}
</
div
>
</
div
>
<
div
className=
"flex flex-row items-center justify-center w-full gap-2"
>
<
div
className=
"flex flex-row items-center justify-center w-full gap-2"
>
<
Select
<
LocaleSelect
value=
{
locale
}
onChange=
{
handleLocaleSelectChange
}
/>
className=
"!min-w-[9rem] w-auto whitespace-nowrap"
<
AppearanceSelect
value=
{
appearance
}
onChange=
{
handleAppearanceSelectChange
}
/>
startDecorator=
{
<
Icon
.
Globe
className=
"w-4 h-auto"
/>
}
value=
{
i18n
.
language
}
onChange=
{
(
_
,
value
)
=>
handleLocaleItemClick
(
value
as
Locale
)
}
>
<
Option
value=
"en"
>
English
</
Option
>
<
Option
value=
"zh"
>
中文
</
Option
>
<
Option
value=
"vi"
>
Tiếng Việt
</
Option
>
<
Option
value=
"fr"
>
French
</
Option
>
<
Option
value=
"nl"
>
Nederlands
</
Option
>
<
Option
value=
"sv"
>
Svenska
</
Option
>
<
Option
value=
"de"
>
German
</
Option
>
<
Option
value=
"es"
>
Español
</
Option
>
</
Select
>
<
AppearanceSelect
/>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/store/module/global.ts
View file @
7efa749c
...
@@ -13,7 +13,10 @@ export const initialGlobalState = async () => {
...
@@ -13,7 +13,10 @@ export const initialGlobalState = async () => {
additionalScript
:
""
,
additionalScript
:
""
,
customizedProfile
:
{
customizedProfile
:
{
name
:
"memos"
,
name
:
"memos"
,
iconUrl
:
"/logo.webp"
,
iconUrl
:
"https://usememos.com/logo.webp"
,
description
:
""
,
locale
:
"en"
,
appearance
:
"system"
,
externalUrl
:
""
,
externalUrl
:
""
,
},
},
}
as
SystemStatus
,
}
as
SystemStatus
,
...
@@ -31,6 +34,8 @@ export const initialGlobalState = async () => {
...
@@ -31,6 +34,8 @@ export const initialGlobalState = async () => {
const
{
data
}
=
(
await
api
.
getSystemStatus
()).
data
;
const
{
data
}
=
(
await
api
.
getSystemStatus
()).
data
;
if
(
data
)
{
if
(
data
)
{
defaultGlobalState
.
systemStatus
=
data
;
defaultGlobalState
.
systemStatus
=
data
;
defaultGlobalState
.
locale
=
data
.
customizedProfile
.
locale
;
defaultGlobalState
.
appearance
=
data
.
customizedProfile
.
appearance
;
}
}
}
catch
(
error
)
{
}
catch
(
error
)
{
// do nth
// do nth
...
...
web/src/store/reducer/global.ts
View file @
7efa749c
...
@@ -23,7 +23,10 @@ const globalSlice = createSlice({
...
@@ -23,7 +23,10 @@ const globalSlice = createSlice({
additionalScript
:
""
,
additionalScript
:
""
,
customizedProfile
:
{
customizedProfile
:
{
name
:
"memos"
,
name
:
"memos"
,
iconUrl
:
"/logo.webp"
,
iconUrl
:
"https://usememos.com/logo.webp"
,
description
:
""
,
locale
:
"en"
,
appearance
:
"system"
,
externalUrl
:
""
,
externalUrl
:
""
,
},
},
},
},
...
...
web/src/types/modules/system.d.ts
View file @
7efa749c
...
@@ -6,6 +6,9 @@ interface Profile {
...
@@ -6,6 +6,9 @@ interface Profile {
interface
CustomizedProfile
{
interface
CustomizedProfile
{
name
:
string
;
name
:
string
;
iconUrl
:
string
;
iconUrl
:
string
;
description
:
string
;
locale
:
Locale
;
appearance
:
Appearance
;
externalUrl
:
string
;
externalUrl
:
string
;
}
}
...
...
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