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
4af0d03e
Unverified
Commit
4af0d03e
authored
Aug 25, 2023
by
boojack
Committed by
GitHub
Aug 25, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: add user profile page (#2175)
chore: some enhancements
parent
8c312e64
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
173 additions
and
132 deletions
+173
-132
auth.go
api/v1/auth.go
+18
-0
resource.go
api/v1/resource.go
+4
-6
storage.go
api/v1/storage.go
+2
-1
system.go
api/v1/system.go
+1
-1
10002__memo.sql
store/db/seed/10002__memo.sql
+5
-5
10003__memo_organizer.sql
store/db/seed/10003__memo_organizer.sql
+2
-2
FloatingNavButton.tsx
web/src/components/FloatingNavButton.tsx
+27
-0
Memo.tsx
web/src/components/Memo.tsx
+13
-50
MemoList.tsx
web/src/components/MemoList.tsx
+2
-7
MemberSection.tsx
web/src/components/Settings/MemberSection.tsx
+4
-1
UserAvatar.tsx
web/src/components/UserAvatar.tsx
+1
-1
datetime.ts
web/src/helpers/datetime.ts
+0
-7
memo-content.less
web/src/less/memo-content.less
+1
-1
memo.less
web/src/less/memo.less
+1
-25
Explore.tsx
web/src/pages/Explore.tsx
+1
-1
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+2
-2
UserProfile.tsx
web/src/pages/UserProfile.tsx
+74
-0
index.tsx
web/src/router/index.tsx
+15
-22
No files found.
api/v1/auth.go
View file @
4af0d03e
...
@@ -169,6 +169,24 @@ func (s *APIV1Service) SignInSSO(c echo.Context) error {
...
@@ -169,6 +169,24 @@ func (s *APIV1Service) SignInSSO(c echo.Context) error {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Incorrect login credentials, please try again"
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Incorrect login credentials, please try again"
)
}
}
if
user
==
nil
{
if
user
==
nil
{
allowSignUpSetting
,
err
:=
s
.
Store
.
GetSystemSetting
(
ctx
,
&
store
.
FindSystemSetting
{
Name
:
SystemSettingAllowSignUpName
.
String
(),
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find system setting"
)
.
SetInternal
(
err
)
}
allowSignUpSettingValue
:=
false
if
allowSignUpSetting
!=
nil
{
err
=
json
.
Unmarshal
([]
byte
(
allowSignUpSetting
.
Value
),
&
allowSignUpSettingValue
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal system setting allow signup"
)
.
SetInternal
(
err
)
}
}
if
!
allowSignUpSettingValue
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"signup is disabled"
)
.
SetInternal
(
err
)
}
userCreate
:=
&
store
.
User
{
userCreate
:=
&
store
.
User
{
Username
:
userInfo
.
Identifier
,
Username
:
userInfo
.
Identifier
,
// The new signup user should be normal user by default.
// The new signup user should be normal user by default.
...
...
api/v1/resource.go
View file @
4af0d03e
...
@@ -656,7 +656,7 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
...
@@ -656,7 +656,7 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
return
fmt
.
Errorf
(
"Failed to find SystemSettingStorageServiceIDName: %s"
,
err
)
return
fmt
.
Errorf
(
"Failed to find SystemSettingStorageServiceIDName: %s"
,
err
)
}
}
storageServiceID
:=
Database
Storage
storageServiceID
:=
Local
Storage
if
systemSettingStorageServiceID
!=
nil
{
if
systemSettingStorageServiceID
!=
nil
{
err
=
json
.
Unmarshal
([]
byte
(
systemSettingStorageServiceID
.
Value
),
&
storageServiceID
)
err
=
json
.
Unmarshal
([]
byte
(
systemSettingStorageServiceID
.
Value
),
&
storageServiceID
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -672,15 +672,13 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
...
@@ -672,15 +672,13 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
}
}
create
.
Blob
=
fileBytes
create
.
Blob
=
fileBytes
return
nil
return
nil
}
}
else
if
storageServiceID
==
LocalStorage
{
// `LocalStorage` means save blob into local disk
// `LocalStorage` means save blob into local disk
if
storageServiceID
==
LocalStorage
{
systemSettingLocalStoragePath
,
err
:=
s
.
GetSystemSetting
(
ctx
,
&
store
.
FindSystemSetting
{
Name
:
SystemSettingLocalStoragePathName
.
String
()})
systemSettingLocalStoragePath
,
err
:=
s
.
GetSystemSetting
(
ctx
,
&
store
.
FindSystemSetting
{
Name
:
SystemSettingLocalStoragePathName
.
String
()})
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to find SystemSettingLocalStoragePathName: %s"
,
err
)
return
fmt
.
Errorf
(
"Failed to find SystemSettingLocalStoragePathName: %s"
,
err
)
}
}
localStoragePath
:=
"assets/{filename}"
localStoragePath
:=
"assets/{
timestamp}_{
filename}"
if
systemSettingLocalStoragePath
!=
nil
&&
systemSettingLocalStoragePath
.
Value
!=
""
{
if
systemSettingLocalStoragePath
!=
nil
&&
systemSettingLocalStoragePath
.
Value
!=
""
{
err
=
json
.
Unmarshal
([]
byte
(
systemSettingLocalStoragePath
.
Value
),
&
localStoragePath
)
err
=
json
.
Unmarshal
([]
byte
(
systemSettingLocalStoragePath
.
Value
),
&
localStoragePath
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
api/v1/storage.go
View file @
4af0d03e
...
@@ -13,6 +13,7 @@ import (
...
@@ -13,6 +13,7 @@ import (
const
(
const
(
// LocalStorage means the storage service is local file system.
// LocalStorage means the storage service is local file system.
// Default storage service is local file system.
LocalStorage
int32
=
-
1
LocalStorage
int32
=
-
1
// DatabaseStorage means the storage service is database.
// DatabaseStorage means the storage service is database.
DatabaseStorage
int32
=
0
DatabaseStorage
int32
=
0
...
@@ -214,7 +215,7 @@ func (s *APIV1Service) DeleteStorage(c echo.Context) error {
...
@@ -214,7 +215,7 @@ func (s *APIV1Service) DeleteStorage(c echo.Context) error {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find storage"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find storage"
)
.
SetInternal
(
err
)
}
}
if
systemSetting
!=
nil
{
if
systemSetting
!=
nil
{
storageServiceID
:=
Database
Storage
storageServiceID
:=
Local
Storage
err
=
json
.
Unmarshal
([]
byte
(
systemSetting
.
Value
),
&
storageServiceID
)
err
=
json
.
Unmarshal
([]
byte
(
systemSetting
.
Value
),
&
storageServiceID
)
if
err
!=
nil
{
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal storage service id"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal storage service id"
)
.
SetInternal
(
err
)
...
...
api/v1/system.go
View file @
4af0d03e
...
@@ -89,7 +89,7 @@ func (s *APIV1Service) GetSystemStatus(c echo.Context) error {
...
@@ -89,7 +89,7 @@ func (s *APIV1Service) GetSystemStatus(c echo.Context) error {
Appearance
:
"system"
,
Appearance
:
"system"
,
ExternalURL
:
""
,
ExternalURL
:
""
,
},
},
StorageServiceID
:
Database
Storage
,
StorageServiceID
:
Local
Storage
,
LocalStoragePath
:
"assets/{timestamp}_{filename}"
,
LocalStoragePath
:
"assets/{timestamp}_{filename}"
,
MemoDisplayWithUpdatedTs
:
false
,
MemoDisplayWithUpdatedTs
:
false
,
}
}
...
...
store/db/seed/10002__memo.sql
View file @
4af0d03e
...
@@ -2,7 +2,7 @@ INSERT INTO
...
@@ -2,7 +2,7 @@ INSERT INTO
memo
(
`id`
,
`content`
,
`creator_id`
)
memo
(
`id`
,
`content`
,
`creator_id`
)
VALUES
VALUES
(
(
1
001
,
1
,
"#Hello 👋 Welcome to memos."
,
"#Hello 👋 Welcome to memos."
,
101
101
);
);
...
@@ -16,7 +16,7 @@ INSERT INTO
...
@@ -16,7 +16,7 @@ INSERT INTO
)
)
VALUES
VALUES
(
(
100
2
,
2
,
'#TODO
'#TODO
- [x] Take more photos about **🌄 sunset**;
- [x] Take more photos about **🌄 sunset**;
- [x] Clean the room;
- [x] Clean the room;
...
@@ -35,7 +35,7 @@ INSERT INTO
...
@@ -35,7 +35,7 @@ INSERT INTO
)
)
VALUES
VALUES
(
(
100
3
,
3
,
"**[Slash](https://github.com/boojack/slash)**: A bookmarking and url shortener, save and share your links very easily.
"**[Slash](https://github.com/boojack/slash)**: A bookmarking and url shortener, save and share your links very easily.


...
@@ -54,7 +54,7 @@ INSERT INTO
...
@@ -54,7 +54,7 @@ INSERT INTO
)
)
VALUES
VALUES
(
(
100
4
,
4
,
'#TODO
'#TODO
- [x] Take more photos about **🌄 sunset**;
- [x] Take more photos about **🌄 sunset**;
- [ ] Clean the classroom;
- [ ] Clean the classroom;
...
@@ -74,7 +74,7 @@ INSERT INTO
...
@@ -74,7 +74,7 @@ INSERT INTO
)
)
VALUES
VALUES
(
(
100
5
,
5
,
'三人行,必有我师焉!👨🏫'
,
'三人行,必有我师焉!👨🏫'
,
102
,
102
,
'PUBLIC'
'PUBLIC'
...
...
store/db/seed/10003__memo_organizer.sql
View file @
4af0d03e
INSERT
INTO
INSERT
INTO
memo_organizer
(
`memo_id`
,
`user_id`
,
`pinned`
)
memo_organizer
(
`memo_id`
,
`user_id`
,
`pinned`
)
VALUES
VALUES
(
1
001
,
101
,
1
);
(
1
,
101
,
1
);
INSERT
INTO
INSERT
INTO
memo_organizer
(
`memo_id`
,
`user_id`
,
`pinned`
)
memo_organizer
(
`memo_id`
,
`user_id`
,
`pinned`
)
VALUES
VALUES
(
1003
,
101
,
1
);
(
3
,
101
,
1
);
\ No newline at end of file
\ No newline at end of file
web/src/components/FloatingNavButton.tsx
0 → 100644
View file @
4af0d03e
import
{
Dropdown
,
IconButton
,
Menu
,
MenuButton
,
MenuItem
}
from
"@mui/joy"
;
import
{
useNavigate
}
from
"react-router-dom"
;
import
Icon
from
"./Icon"
;
const
FloatingNavButton
=
()
=>
{
const
navigate
=
useNavigate
();
return
(
<>
<
Dropdown
>
<
div
className=
"fixed bottom-6 right-6"
>
<
MenuButton
slots=
{
{
root
:
IconButton
}
}
slotProps=
{
{
root
:
{
className
:
"!bg-white dark:!bg-zinc-900 drop-shadow"
,
variant
:
"outlined"
,
color
:
"neutral"
}
}
}
>
<
Icon
.
MoreVertical
className=
"w-5 h-auto"
/>
</
MenuButton
>
</
div
>
<
Menu
placement=
"top-end"
>
<
MenuItem
onClick=
{
()
=>
navigate
(
"/"
)
}
>
Back to home
</
MenuItem
>
</
Menu
>
</
Dropdown
>
</>
);
};
export
default
FloatingNavButton
;
web/src/components/Memo.tsx
View file @
4af0d03e
import
{
Divider
}
from
"@mui/joy"
;
import
{
Divider
}
from
"@mui/joy"
;
import
{
isEqual
,
uniqWith
}
from
"lodash-es"
;
import
{
memo
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
memo
,
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"
;
...
@@ -7,7 +6,7 @@ import { Link } from "react-router-dom";
...
@@ -7,7 +6,7 @@ import { Link } from "react-router-dom";
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
getRelativeTimeString
}
from
"@/helpers/datetime"
;
import
{
getRelativeTimeString
}
from
"@/helpers/datetime"
;
import
{
useFilterStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
{
useFilterStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
{
use
MemoCacheStore
,
use
UserV1Store
}
from
"@/store/v1"
;
import
{
useUserV1Store
}
from
"@/store/v1"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showChangeMemoCreatedTsDialog
from
"./ChangeMemoCreatedTsDialog"
;
import
showChangeMemoCreatedTsDialog
from
"./ChangeMemoCreatedTsDialog"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
...
@@ -23,24 +22,20 @@ import "@/less/memo.less";
...
@@ -23,24 +22,20 @@ import "@/less/memo.less";
interface
Props
{
interface
Props
{
memo
:
Memo
;
memo
:
Memo
;
showCreator
?:
boolean
;
showVisibility
?:
boolean
;
showVisibility
?:
boolean
;
showRelatedMemos
?:
boolean
;
lazyRendering
?:
boolean
;
lazyRendering
?:
boolean
;
}
}
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
memo
,
showCreator
,
showRelatedMemos
,
lazyRendering
}
=
props
;
const
{
memo
,
lazyRendering
}
=
props
;
const
{
i18n
}
=
useTranslation
();
const
{
i18n
}
=
useTranslation
();
const
t
=
useTranslate
();
const
t
=
useTranslate
();
const
filterStore
=
useFilterStore
();
const
filterStore
=
useFilterStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
const
memoStore
=
useMemoStore
();
const
memoStore
=
useMemoStore
();
const
memoCacheStore
=
useMemoCacheStore
();
const
userV1Store
=
useUserV1Store
();
const
userV1Store
=
useUserV1Store
();
const
[
shouldRender
,
setShouldRender
]
=
useState
<
boolean
>
(
lazyRendering
?
false
:
true
);
const
[
shouldRender
,
setShouldRender
]
=
useState
<
boolean
>
(
lazyRendering
?
false
:
true
);
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
displayTs
));
const
[
displayTime
,
setDisplayTime
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
displayTs
));
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
readonly
=
userStore
.
isVisitorMode
()
||
userStore
.
getCurrentUsername
()
!==
memo
.
creatorUsername
;
const
readonly
=
userStore
.
isVisitorMode
()
||
userStore
.
getCurrentUsername
()
!==
memo
.
creatorUsername
;
const
creator
=
userV1Store
.
getUserByUsername
(
memo
.
creatorUsername
);
const
creator
=
userV1Store
.
getUserByUsername
(
memo
.
creatorUsername
);
...
@@ -50,27 +45,12 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -50,27 +45,12 @@ const Memo: React.FC<Props> = (props: Props) => {
userV1Store
.
getOrFetchUserByUsername
(
memo
.
creatorUsername
);
userV1Store
.
getOrFetchUserByUsername
(
memo
.
creatorUsername
);
},
[
memo
.
creatorUsername
]);
},
[
memo
.
creatorUsername
]);
// Prepare related memos.
useEffect
(()
=>
{
Promise
.
allSettled
(
memo
.
relationList
.
map
((
memoRelation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
memoRelation
.
relatedMemoId
))).
then
(
(
results
)
=>
{
const
memoList
=
[];
for
(
const
result
of
results
)
{
if
(
result
.
status
===
"fulfilled"
)
{
memoList
.
push
(
result
.
value
);
}
}
setRelatedMemoList
(
uniqWith
(
memoList
,
isEqual
));
}
);
},
[
memo
.
relationList
]);
// Update display time string.
// Update display time string.
useEffect
(()
=>
{
useEffect
(()
=>
{
let
intervalFlag
:
any
=
-
1
;
let
intervalFlag
:
any
=
-
1
;
if
(
Date
.
now
()
-
memo
.
displayTs
<
1000
*
60
*
60
*
24
)
{
if
(
Date
.
now
()
-
memo
.
displayTs
<
1000
*
60
*
60
*
24
)
{
intervalFlag
=
setInterval
(()
=>
{
intervalFlag
=
setInterval
(()
=>
{
set
CreatedTimeStr
(
getRelativeTimeString
(
memo
.
displayTs
));
set
DisplayTime
(
getRelativeTimeString
(
memo
.
displayTs
));
},
1000
*
1
);
},
1000
*
1
);
}
}
...
@@ -246,26 +226,25 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -246,26 +226,25 @@ const Memo: React.FC<Props> = (props: Props) => {
<>
<>
<
div
className=
{
`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`
}
ref=
{
memoContainerRef
}
>
<
div
className=
{
`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`
}
ref=
{
memoContainerRef
}
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"status-text-container
"
>
<
p
className=
"w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1
"
>
{
showCreator
&&
creator
&&
(
{
creator
&&
(
<>
<>
<
Link
className=
"flex flex-row justify-start items-center"
to=
{
`/u/${memo.creatorUsername}`
}
>
<
Link
className=
"flex flex-row justify-start items-center"
to=
{
`/u/${memo.creatorUsername}`
}
>
<
UserAvatar
className=
"!w-5 !h-auto mr-1"
avatarUrl=
{
creator
.
avatarUrl
}
/>
<
UserAvatar
className=
"!w-5 !h-auto mr-1"
avatarUrl=
{
creator
.
avatarUrl
}
/>
<
span
className=
"text-sm text-gray-600 dark:text-zinc-300"
>
{
creator
.
nickname
}
</
span
>
<
span
className=
"text-sm text-gray-600
max-w-[8em] truncate
dark:text-zinc-300"
>
{
creator
.
nickname
}
</
span
>
</
Link
>
</
Link
>
<
Icon
.
Dot
className=
"w-4 h-auto text-gray-400 dark:text-zinc-400"
/>
<
Icon
.
Dot
className=
"w-4 h-auto text-gray-400 dark:text-zinc-400"
/>
</>
</>
)
}
)
}
<
Link
className=
"time-text"
to=
{
`/m/${memo.id}`
}
onClick=
{
handleMemoCreatedTimeClick
}
>
<
span
className=
"text-sm text-gray-400"
onClick=
{
handleMemoCreatedTimeClick
}
>
{
createdTimeStr
}
{
displayTime
}
</
Link
>
</
span
>
</
div
>
</
p
>
<
div
className=
"btns-container space-x-2"
>
<
div
className=
"btns-container space-x-2"
>
{
memo
.
pinned
&&
<
Icon
.
Bookmark
className=
"w-4 h-auto rounded text-green-600"
/>
}
{
!
readonly
&&
(
{
!
readonly
&&
(
<>
<>
<
span
className=
"btn more-action-btn"
>
<
span
className=
"btn more-action-btn"
>
<
Icon
.
More
Horizont
al
className=
"icon-img"
/>
<
Icon
.
More
Vertic
al
className=
"icon-img"
/>
</
span
>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container min-w-[6em]"
>
<
div
className=
"more-action-btns-container min-w-[6em]"
>
...
@@ -306,24 +285,8 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -306,24 +285,8 @@ const Memo: React.FC<Props> = (props: Props) => {
onMemoContentDoubleClick=
{
handleMemoContentDoubleClick
}
onMemoContentDoubleClick=
{
handleMemoContentDoubleClick
}
/>
/>
<
MemoResourceListView
resourceList=
{
memo
.
resourceList
}
/>
<
MemoResourceListView
resourceList=
{
memo
.
resourceList
}
/>
{
!
showRelatedMemos
&&
<
MemoRelationListView
relationList=
{
memo
.
relationList
}
/>
}
<
MemoRelationListView
relationList=
{
memo
.
relationList
}
/>
</
div
>
</
div
>
{
showRelatedMemos
&&
relatedMemoList
.
length
>
0
&&
(
<>
<
p
className=
"text-sm dark:text-gray-300 my-2 pl-4 opacity-50 flex flex-row items-center"
>
<
Icon
.
Link
className=
"w-4 h-auto mr-1"
/>
<
span
>
Related memos
</
span
>
</
p
>
{
relatedMemoList
.
map
((
relatedMemo
)
=>
{
return
(
<
div
key=
{
relatedMemo
.
id
}
className=
"w-full"
>
<
Memo
memo=
{
relatedMemo
}
showCreator
/>
</
div
>
);
})
}
</>
)
}
</>
</>
);
);
};
};
...
...
web/src/components/MemoList.tsx
View file @
4af0d03e
...
@@ -9,12 +9,7 @@ import Empty from "./Empty";
...
@@ -9,12 +9,7 @@ import Empty from "./Empty";
import
Memo
from
"./Memo"
;
import
Memo
from
"./Memo"
;
import
"@/less/memo-list.less"
;
import
"@/less/memo-list.less"
;
interface
Props
{
const
MemoList
:
React
.
FC
=
()
=>
{
showCreator
?:
boolean
;
}
const
MemoList
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
showCreator
}
=
props
;
const
t
=
useTranslate
();
const
t
=
useTranslate
();
const
memoStore
=
useMemoStore
();
const
memoStore
=
useMemoStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
...
@@ -142,7 +137,7 @@ const MemoList: React.FC<Props> = (props: Props) => {
...
@@ -142,7 +137,7 @@ const MemoList: React.FC<Props> = (props: Props) => {
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
}
lazyRendering
showVisibility
showCreator=
{
showCreator
}
/>
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
lazyRendering
showVisibility
/>
))
}
))
}
{
isFetching
?
(
{
isFetching
?
(
<
div
className=
"status-text-container fetching-tip"
>
<
div
className=
"status-text-container fetching-tip"
>
...
...
web/src/components/Settings/MemberSection.tsx
View file @
4af0d03e
...
@@ -156,7 +156,10 @@ const PreferencesSection = () => {
...
@@ -156,7 +156,10 @@ const PreferencesSection = () => {
{
userList
.
map
((
user
)
=>
(
{
userList
.
map
((
user
)
=>
(
<
tr
key=
{
user
.
id
}
>
<
tr
key=
{
user
.
id
}
>
<
td
className=
"whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900"
>
{
user
.
id
}
</
td
>
<
td
className=
"whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900"
>
{
user
.
id
}
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
username
}
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
username
}
<
span
className=
"ml-1 italic"
>
{
user
.
rowStatus
===
"ARCHIVED"
&&
"(Archived)"
}
</
span
>
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
nickname
}
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
nickname
}
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
email
}
</
td
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-500"
>
{
user
.
email
}
</
td
>
<
td
className=
"relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end"
>
<
td
className=
"relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end"
>
...
...
web/src/components/UserAvatar.tsx
View file @
4af0d03e
...
@@ -8,7 +8,7 @@ interface Props {
...
@@ -8,7 +8,7 @@ interface Props {
const
UserAvatar
=
(
props
:
Props
)
=>
{
const
UserAvatar
=
(
props
:
Props
)
=>
{
const
{
avatarUrl
,
className
}
=
props
;
const
{
avatarUrl
,
className
}
=
props
;
return
(
return
(
<
div
className=
{
classNames
(
`w-8 h-
8 overflow-clip
`
,
className
)
}
>
<
div
className=
{
classNames
(
`w-8 h-
auto overflow-clip rounded-full
`
,
className
)
}
>
<
img
className=
"w-full h-auto rounded-full min-w-full min-h-full object-cover"
src=
{
avatarUrl
||
"/logo.webp"
}
alt=
""
/>
<
img
className=
"w-full h-auto rounded-full min-w-full min-h-full object-cover"
src=
{
avatarUrl
||
"/logo.webp"
}
alt=
""
/>
</
div
>
</
div
>
);
);
...
...
web/src/helpers/datetime.ts
View file @
4af0d03e
...
@@ -130,29 +130,22 @@ export const getRelativeTimeString = (time: number, locale = i18n.language, form
...
@@ -130,29 +130,22 @@ export const getRelativeTimeString = (time: number, locale = i18n.language, form
// numeric: "auto" provides "yesterday" for 1 day ago, "always" provides "1 day ago"
// numeric: "auto" provides "yesterday" for 1 day ago, "always" provides "1 day ago"
const
formatOpts
=
{
style
:
formatStyle
,
numeric
:
"auto"
}
as
Intl
.
RelativeTimeFormatOptions
;
const
formatOpts
=
{
style
:
formatStyle
,
numeric
:
"auto"
}
as
Intl
.
RelativeTimeFormatOptions
;
const
relTime
=
new
Intl
.
RelativeTimeFormat
(
locale
,
formatOpts
);
const
relTime
=
new
Intl
.
RelativeTimeFormat
(
locale
,
formatOpts
);
if
(
pastTimeMillis
<
minMillis
)
{
if
(
pastTimeMillis
<
minMillis
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
secMillis
),
"second"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
secMillis
),
"second"
);
}
}
if
(
pastTimeMillis
<
hourMillis
)
{
if
(
pastTimeMillis
<
hourMillis
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
minMillis
),
"minute"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
minMillis
),
"minute"
);
}
}
if
(
pastTimeMillis
<
dayMillis
)
{
if
(
pastTimeMillis
<
dayMillis
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
hourMillis
),
"hour"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
hourMillis
),
"hour"
);
}
}
if
(
pastTimeMillis
<
dayMillis
*
7
)
{
if
(
pastTimeMillis
<
dayMillis
*
7
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
dayMillis
),
"day"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
dayMillis
),
"day"
);
}
}
if
(
pastTimeMillis
<
dayMillis
*
30
)
{
if
(
pastTimeMillis
<
dayMillis
*
30
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
(
dayMillis
*
7
)),
"week"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
(
dayMillis
*
7
)),
"week"
);
}
}
if
(
pastTimeMillis
<
dayMillis
*
365
)
{
if
(
pastTimeMillis
<
dayMillis
*
365
)
{
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
(
dayMillis
*
30
)),
"month"
);
return
relTime
.
format
(
-
Math
.
round
(
pastTimeMillis
/
(
dayMillis
*
30
)),
"month"
);
}
}
...
...
web/src/less/memo-content.less
View file @
4af0d03e
.memo-content-wrapper {
.memo-content-wrapper {
@apply w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-
2
00;
@apply w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-
3
00;
> .memo-content-text {
> .memo-content-text {
@apply w-full max-w-full word-break text-base leading-6;
@apply w-full max-w-full word-break text-base leading-6;
...
...
web/src/less/memo.less
View file @
4af0d03e
...
@@ -8,30 +8,6 @@
...
@@ -8,30 +8,6 @@
> .memo-top-wrapper {
> .memo-top-wrapper {
@apply flex flex-row justify-between items-center w-full h-6 mb-1;
@apply flex flex-row justify-between items-center w-full h-6 mb-1;
> .status-text-container {
@apply flex flex-row justify-start items-center;
> .time-text {
@apply text-sm text-gray-400;
}
> .name-text {
@apply ml-1 text-sm text-gray-400 cursor-pointer hover:opacity-80;
}
> .status-text {
@apply text-xs cursor-pointer ml-2 rounded border px-1;
&.public {
@apply border-green-600 text-green-600;
}
&.protected {
@apply border-gray-400 text-gray-400;
}
}
}
> .btns-container {
> .btns-container {
@apply flex flex-row justify-end items-center relative shrink-0;
@apply flex flex-row justify-end items-center relative shrink-0;
...
@@ -56,7 +32,7 @@
...
@@ -56,7 +32,7 @@
@apply flex flex-row justify-center items-center leading-6 text-sm rounded hover:bg-gray-200 dark:hover:bg-zinc-600;
@apply flex flex-row justify-center items-center leading-6 text-sm rounded hover:bg-gray-200 dark:hover:bg-zinc-600;
&.more-action-btn {
&.more-action-btn {
@apply w-auto opacity-
6
0 cursor-default hover:bg-transparent;
@apply w-auto opacity-
5
0 cursor-default hover:bg-transparent;
> .icon-img {
> .icon-img {
@apply w-4 h-auto dark:text-gray-300;
@apply w-4 h-auto dark:text-gray-300;
...
...
web/src/pages/Explore.tsx
View file @
4af0d03e
...
@@ -93,7 +93,7 @@ const Explore = () => {
...
@@ -93,7 +93,7 @@ const Explore = () => {
<
main
className=
"relative w-full h-auto flex flex-col justify-start items-start"
>
<
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
}
showCreator
/>;
return
<
Memo
key=
{
`${memo.id}-${memo.displayTs}`
}
memo=
{
memo
}
/>;
})
}
})
}
{
isComplete
?
(
{
isComplete
?
(
memos
.
length
===
0
&&
(
memos
.
length
===
0
&&
(
...
...
web/src/pages/MemoDetail.tsx
View file @
4af0d03e
...
@@ -33,7 +33,7 @@ const MemoDetail = () => {
...
@@ -33,7 +33,7 @@ const MemoDetail = () => {
},
[
location
]);
},
[
location
]);
return
(
return
(
<
section
className=
"relative top-0 w-full
h-full overflow-y-auto
overflow-x-hidden bg-zinc-100 dark:bg-zinc-800"
>
<
section
className=
"relative top-0 w-full
min-h-full
overflow-x-hidden bg-zinc-100 dark:bg-zinc-800"
>
<
div
className=
"relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-6"
>
<
div
className=
"relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-6"
>
<
div
className=
"max-w-2xl w-full flex flex-row justify-center items-center px-4 py-2 mt-2 bg-zinc-100 dark:bg-zinc-800"
>
<
div
className=
"max-w-2xl w-full flex flex-row justify-center items-center px-4 py-2 mt-2 bg-zinc-100 dark:bg-zinc-800"
>
<
div
className=
"detail-header flex flex-row justify-start items-center"
>
<
div
className=
"detail-header flex flex-row justify-start items-center"
>
...
@@ -45,7 +45,7 @@ const MemoDetail = () => {
...
@@ -45,7 +45,7 @@ const MemoDetail = () => {
(
memo
?
(
(
memo
?
(
<>
<>
<
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=
{
memo
}
showCreator
showRelatedMemos
/>
<
Memo
memo=
{
memo
}
/>
</
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/UserProfile.tsx
0 → 100644
View file @
4af0d03e
import
{
useEffect
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
FloatingNavButton
from
"@/components/FloatingNavButton"
;
import
MemoFilter
from
"@/components/MemoFilter"
;
import
MemoList
from
"@/components/MemoList"
;
import
UserAvatar
from
"@/components/UserAvatar"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
{
useGlobalStore
,
useUserStore
}
from
"@/store/module"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
const
UserProfile
=
()
=>
{
const
t
=
useTranslate
();
const
globalStore
=
useGlobalStore
();
const
userStore
=
useUserStore
();
const
loadingState
=
useLoading
();
const
user
=
userStore
.
state
.
user
;
useEffect
(()
=>
{
const
currentUsername
=
userStore
.
getCurrentUsername
();
userStore
.
getUserByUsername
(
currentUsername
)
.
then
(()
=>
{
loadingState
.
setFinish
();
})
.
catch
((
error
)
=>
{
console
.
error
(
error
);
toast
.
error
(
t
(
"message.user-not-found"
));
});
},
[
userStore
.
getCurrentUsername
()]);
useEffect
(()
=>
{
if
(
user
?.
setting
.
locale
)
{
globalStore
.
setLocale
(
user
.
setting
.
locale
);
}
},
[
user
?.
setting
.
locale
]);
return
(
<>
<
section
className=
"relative top-0 w-full min-h-full overflow-x-hidden bg-zinc-100 dark:bg-zinc-800"
>
<
div
className=
"relative w-full min-h-full mx-auto flex flex-col justify-start items-center"
>
{
!
loadingState
.
isLoading
&&
(
user
?
(
<>
<
main
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"
>
<
div
className=
"w-full flex flex-row justify-start items-start"
>
<
div
className=
"flex-grow shrink w-full"
>
<
div
className=
"w-full flex flex-col justify-start items-center py-8"
>
<
UserAvatar
className=
"w-16 h-auto mb-4 drop-shadow"
avatarUrl=
{
user
?.
avatarUrl
}
/>
<
div
>
<
p
className=
"text-2xl font-bold text-gray-700 dark:text-gray-300"
>
{
user
?.
nickname
}
</
p
>
</
div
>
</
div
>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg"
>
<
MemoFilter
/>
</
div
>
<
MemoList
/>
</
div
>
</
div
>
</
main
>
</>
)
:
(
<>
<
p
>
Not found
</
p
>
</>
))
}
</
div
>
</
section
>
<
FloatingNavButton
/>
</>
);
};
export
default
UserProfile
;
web/src/router/index.tsx
View file @
4af0d03e
...
@@ -13,6 +13,7 @@ const Auth = lazy(() => import("@/pages/Auth"));
...
@@ -13,6 +13,7 @@ const Auth = lazy(() => import("@/pages/Auth"));
const
AuthCallback
=
lazy
(()
=>
import
(
"@/pages/AuthCallback"
));
const
AuthCallback
=
lazy
(()
=>
import
(
"@/pages/AuthCallback"
));
const
Explore
=
lazy
(()
=>
import
(
"@/pages/Explore"
));
const
Explore
=
lazy
(()
=>
import
(
"@/pages/Explore"
));
const
Home
=
lazy
(()
=>
import
(
"@/pages/Home"
));
const
Home
=
lazy
(()
=>
import
(
"@/pages/Home"
));
const
UserProfile
=
lazy
(()
=>
import
(
"@/pages/UserProfile"
));
const
MemoDetail
=
lazy
(()
=>
import
(
"@/pages/MemoDetail"
));
const
MemoDetail
=
lazy
(()
=>
import
(
"@/pages/MemoDetail"
));
const
EmbedMemo
=
lazy
(()
=>
import
(
"@/pages/EmbedMemo"
));
const
EmbedMemo
=
lazy
(()
=>
import
(
"@/pages/EmbedMemo"
));
const
NotFound
=
lazy
(()
=>
import
(
"@/pages/NotFound"
));
const
NotFound
=
lazy
(()
=>
import
(
"@/pages/NotFound"
));
...
@@ -78,28 +79,6 @@ const router = createBrowserRouter([
...
@@ -78,28 +79,6 @@ const router = createBrowserRouter([
return
redirect
(
"/explore"
);
return
redirect
(
"/explore"
);
},
},
},
},
{
path
:
"u/:username"
,
element
:
<
Home
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
const
{
user
}
=
store
.
getState
().
user
;
const
{
systemStatus
}
=
store
.
getState
().
global
;
if
(
isNullorUndefined
(
user
)
&&
systemStatus
.
disablePublicMemos
)
{
return
redirect
(
"/auth"
);
}
return
null
;
},
},
{
{
path
:
"explore"
,
path
:
"explore"
,
element
:
<
Explore
/>,
element
:
<
Explore
/>,
...
@@ -238,6 +217,20 @@ const router = createBrowserRouter([
...
@@ -238,6 +217,20 @@ const router = createBrowserRouter([
return
null
;
return
null
;
},
},
},
},
{
path
:
"u/:username"
,
element
:
<
UserProfile
/>,
loader
:
async
()
=>
{
await
initialGlobalStateLoader
();
try
{
await
initialUserState
();
}
catch
(
error
)
{
// do nth
}
return
null
;
},
},
{
{
path
:
"*"
,
path
:
"*"
,
element
:
<
NotFound
/>,
element
:
<
NotFound
/>,
...
...
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