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
6f32643d
Unverified
Commit
6f32643d
authored
Jul 07, 2022
by
boojack
Committed by
GitHub
Jul 07, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: visitor view (#107)
* refactor: update api * refactor: visitor view * chore: update seed data
parent
346d219c
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
251 additions
and
256 deletions
+251
-256
auth.go
api/auth.go
+1
-1
auth.go
server/auth.go
+10
-10
memo.go
server/memo.go
+9
-23
server.go
server/server.go
+1
-1
shortcut.go
server/shortcut.go
+10
-23
tag.go
server/tag.go
+11
-25
10002__memo.sql
store/db/seed/10002__memo.sql
+19
-1
package.json
web/package.json
+2
-0
Memo.tsx
web/src/components/Memo.tsx
+33
-31
MenuBtnsPopup.tsx
web/src/components/MenuBtnsPopup.tsx
+2
-2
ShortcutList.tsx
web/src/components/ShortcutList.tsx
+23
-27
Sidebar.tsx
web/src/components/Sidebar.tsx
+14
-15
TagList.tsx
web/src/components/TagList.tsx
+1
-4
UserBanner.tsx
web/src/components/UserBanner.tsx
+13
-10
api.ts
web/src/helpers/api.ts
+23
-12
utils.ts
web/src/helpers/utils.ts
+0
-32
Home.tsx
web/src/pages/Home.tsx
+14
-22
Signin.tsx
web/src/pages/Signin.tsx
+1
-1
locationService.ts
web/src/services/locationService.ts
+2
-2
memoService.ts
web/src/services/memoService.ts
+14
-4
shortcutService.ts
web/src/services/shortcutService.ts
+5
-2
userService.ts
web/src/services/userService.ts
+16
-7
location.ts
web/src/store/modules/location.ts
+2
-1
memo.d.ts
web/src/types/modules/memo.d.ts
+5
-0
shortcut.d.ts
web/src/types/modules/shortcut.d.ts
+5
-0
tag.d.ts
web/src/types/modules/tag.d.ts
+3
-0
yarn.lock
web/yarn.lock
+12
-0
No files found.
api/auth.go
View file @
6f32643d
package
api
type
Log
in
struct
{
type
Sign
in
struct
{
Email
string
`json:"email"`
Password
string
`json:"password"`
}
...
...
server/auth.go
View file @
6f32643d
...
...
@@ -13,33 +13,33 @@ import (
)
func
(
s
*
Server
)
registerAuthRoutes
(
g
*
echo
.
Group
)
{
g
.
POST
(
"/auth/
log
in"
,
func
(
c
echo
.
Context
)
error
{
login
:=
&
api
.
Log
in
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
log
in
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted
log
in request"
)
.
SetInternal
(
err
)
g
.
POST
(
"/auth/
sign
in"
,
func
(
c
echo
.
Context
)
error
{
signin
:=
&
api
.
Sign
in
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
sign
in
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted
sign
in request"
)
.
SetInternal
(
err
)
}
userFind
:=
&
api
.
UserFind
{
Email
:
&
log
in
.
Email
,
Email
:
&
sign
in
.
Email
,
}
user
,
err
:=
s
.
Store
.
FindUser
(
userFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Failed to find user by email %s"
,
log
in
.
Email
))
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Failed to find user by email %s"
,
sign
in
.
Email
))
.
SetInternal
(
err
)
}
if
user
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
fmt
.
Sprintf
(
"User not found with email %s"
,
log
in
.
Email
))
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
fmt
.
Sprintf
(
"User not found with email %s"
,
sign
in
.
Email
))
}
else
if
user
.
RowStatus
==
api
.
Archived
{
return
echo
.
NewHTTPError
(
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"User has been archived with email %s"
,
log
in
.
Email
))
return
echo
.
NewHTTPError
(
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"User has been archived with email %s"
,
sign
in
.
Email
))
}
// Compare the stored hashed password, with the hashed version of the password that was received.
if
err
:=
bcrypt
.
CompareHashAndPassword
([]
byte
(
user
.
PasswordHash
),
[]
byte
(
log
in
.
Password
));
err
!=
nil
{
if
err
:=
bcrypt
.
CompareHashAndPassword
([]
byte
(
user
.
PasswordHash
),
[]
byte
(
sign
in
.
Password
));
err
!=
nil
{
// If the two passwords don't match, return a 401 status.
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Incorrect password"
)
.
SetInternal
(
err
)
}
if
err
=
setUserSession
(
c
,
user
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to set
log
in session"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to set
sign
in session"
)
.
SetInternal
(
err
)
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
...
...
server/memo.go
View file @
6f32643d
...
...
@@ -60,31 +60,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
})
g
.
GET
(
"/memo"
,
func
(
c
echo
.
Context
)
error
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
if
c
.
QueryParam
(
"userID"
)
!=
""
{
var
err
error
userID
,
err
=
strconv
.
Atoi
(
c
.
QueryParam
(
"userID"
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
QueryParam
(
"userID"
)))
}
}
else
{
ownerUserType
:=
api
.
Owner
ownerUser
,
err
:=
s
.
Store
.
FindUser
(
&
api
.
UserFind
{
Role
:
&
ownerUserType
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find owner user"
)
.
SetInternal
(
err
)
}
if
ownerUser
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
"Owner user do not exist"
)
}
userID
=
ownerUser
.
ID
memoFind
:=
&
api
.
MemoFind
{}
if
userID
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"creatorId"
));
err
==
nil
{
memoFind
.
CreatorID
=
&
userID
}
else
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing creatorId to find memo"
)
}
}
memoFind
:=
&
api
.
MemoFind
{
CreatorID
:
&
userID
,
memoFind
.
CreatorID
=
&
userID
}
rowStatus
:=
api
.
RowStatus
(
c
.
QueryParam
(
"rowStatus"
))
...
...
server/server.go
View file @
6f32643d
...
...
@@ -45,7 +45,7 @@ func NewServer(profile *profile.Profile) *Server {
HTML5
:
true
,
}))
// In dev mode, set the const secret key to make
log
in session persistence.
// In dev mode, set the const secret key to make
sign
in session persistence.
secret
:=
[]
byte
(
"usememos"
)
if
profile
.
Mode
==
"prod"
{
secret
=
securecookie
.
GenerateRandomKey
(
16
)
...
...
server/shortcut.go
View file @
6f32643d
...
...
@@ -59,32 +59,19 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
})
g
.
GET
(
"/shortcut"
,
func
(
c
echo
.
Context
)
error
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
if
c
.
QueryParam
(
"userID"
)
!=
""
{
var
err
error
userID
,
err
=
strconv
.
Atoi
(
c
.
QueryParam
(
"userID"
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
QueryParam
(
"userID"
)))
}
}
else
{
ownerUserType
:=
api
.
Owner
ownerUser
,
err
:=
s
.
Store
.
FindUser
(
&
api
.
UserFind
{
Role
:
&
ownerUserType
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find owner user"
)
.
SetInternal
(
err
)
}
if
ownerUser
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
"Owner user do not exist"
)
}
userID
=
ownerUser
.
ID
shortcutFind
:=
&
api
.
ShortcutFind
{}
if
userID
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"creatorId"
));
err
==
nil
{
shortcutFind
.
CreatorID
=
&
userID
}
else
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing creatorId to find shortcut"
)
}
}
shortcutFind
:=
&
api
.
ShortcutFind
{
CreatorID
:
&
userID
,
shortcutFind
.
CreatorID
=
&
userID
}
list
,
err
:=
s
.
Store
.
FindShortcutList
(
shortcutFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch shortcut list"
)
.
SetInternal
(
err
)
...
...
server/tag.go
View file @
6f32643d
...
...
@@ -2,7 +2,6 @@ package server
import
(
"encoding/json"
"fmt"
"net/http"
"regexp"
"sort"
...
...
@@ -15,37 +14,24 @@ import (
func
(
s
*
Server
)
registerTagRoutes
(
g
*
echo
.
Group
)
{
g
.
GET
(
"/tag"
,
func
(
c
echo
.
Context
)
error
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
if
c
.
QueryParam
(
"userID"
)
!=
""
{
var
err
error
userID
,
err
=
strconv
.
Atoi
(
c
.
QueryParam
(
"userID"
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
QueryParam
(
"userID"
)))
}
}
else
{
ownerUserType
:=
api
.
Owner
ownerUser
,
err
:=
s
.
Store
.
FindUser
(
&
api
.
UserFind
{
Role
:
&
ownerUserType
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find owner user"
)
.
SetInternal
(
err
)
}
if
ownerUser
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
"Owner user do not exist"
)
}
userID
=
ownerUser
.
ID
}
}
contentSearch
:=
"#"
normalRowStatus
:=
api
.
Normal
memoFind
:=
api
.
MemoFind
{
CreatorID
:
&
userID
,
ContentSearch
:
&
contentSearch
,
RowStatus
:
&
normalRowStatus
,
}
if
userID
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"creatorId"
));
err
==
nil
{
memoFind
.
CreatorID
=
&
userID
}
else
{
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing creatorId to find shortcut"
)
}
memoFind
.
CreatorID
=
&
userID
}
memoList
,
err
:=
s
.
Store
.
FindMemoList
(
&
memoFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo list"
)
.
SetInternal
(
err
)
...
...
store/db/seed/10002__memo.sql
View file @
6f32643d
...
...
@@ -52,6 +52,24 @@ INSERT INTO
VALUES
(
104
,
'好好学习,天天向上。🤜🤛'
,
'#TODO
- [x] Take more photos about **🌄 sunset**;
- [ ] Clean the classroom;
- [ ] Watch *👦 The Boys*;
(👆 click to toggle status)
'
,
102
);
INSERT
INTO
memo
(
`id`
,
`content`
,
`creator_id`
)
VALUES
(
105
,
'三人行,必有我师焉!👨🏫'
,
102
);
web/package.json
View file @
6f32643d
...
...
@@ -11,12 +11,14 @@
"@reduxjs/toolkit"
:
"^1.8.1"
,
"axios"
:
"^0.27.2"
,
"lodash-es"
:
"^4.17.21"
,
"qs"
:
"^6.11.0"
,
"react"
:
"^18.1.0"
,
"react-dom"
:
"^18.1.0"
,
"react-redux"
:
"^8.0.1"
},
"devDependencies"
:
{
"@types/lodash-es"
:
"^4.17.5"
,
"@types/qs"
:
"^6.9.7"
,
"@types/react"
:
"^18.0.9"
,
"@types/react-dom"
:
"^18.0.4"
,
"@typescript-eslint/eslint-plugin"
:
"^5.6.0"
,
...
...
web/src/components/Memo.tsx
View file @
6f32643d
...
...
@@ -112,7 +112,11 @@ const Memo: React.FC<Props> = (props: Props) => {
}
else
{
locationService
.
setTagQuery
(
tagName
);
}
}
else
if
(
targetEl
.
classList
.
contains
(
"todo-block"
)
&&
userService
.
isNotVisitor
())
{
}
else
if
(
targetEl
.
classList
.
contains
(
"todo-block"
))
{
if
(
userService
.
isVisitorMode
())
{
return
;
}
const
status
=
targetEl
.
dataset
?.
value
;
const
todoElementList
=
[...(
memoContainerRef
.
current
?.
querySelectorAll
(
`span.todo-block[data-value=
${
status
}
]`
)
??
[])];
for
(
const
element
of
todoElementList
)
{
...
...
@@ -158,40 +162,38 @@ const Memo: React.FC<Props> = (props: Props) => {
<
span
className=
"ml-2"
>
PINNED
</
span
>
</
Only
>
</
span
>
{
userService
.
isNotVisitor
()
&&
(
<
div
className=
"btns-container"
>
<
span
className=
"btn more-action-btn"
>
<
img
className=
"icon-img"
src=
"/icons/more.svg"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container"
>
<
div
className=
"btns-container"
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
img
className=
"icon-img"
src=
"/icons/pin.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
"Unpin"
:
"Pin"
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
img
className=
"icon-img"
src=
"/icons/edit.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
Edit
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
img
className=
"icon-img"
src=
"/icons/share.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
Share
</
span
>
</
div
>
<
div
className=
{
`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`
}
>
<
span
className=
"btn more-action-btn"
>
<
img
className=
"icon-img"
src=
"/icons/more.svg"
/>
</
span
>
<
div
className=
"more-action-btns-wrapper"
>
<
div
className=
"more-action-btns-container"
>
<
div
className=
"btns-container"
>
<
div
className=
"btn"
onClick=
{
handleTogglePinMemoBtnClick
}
>
<
img
className=
"icon-img"
src=
"/icons/pin.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
{
memo
.
pinned
?
"Unpin"
:
"Pin"
}
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleEditMemoClick
}
>
<
img
className=
"icon-img"
src=
"/icons/edit.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
Edit
</
span
>
</
div
>
<
div
className=
"btn"
onClick=
{
handleGenMemoImageBtnClick
}
>
<
img
className=
"icon-img"
src=
"/icons/share.svg"
alt=
""
/>
<
span
className=
"tip-text"
>
Share
</
span
>
</
div
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
Mark
</
span
>
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
View Story
</
span
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
Archive
</
span
>
</
div
>
<
span
className=
"btn"
onClick=
{
handleMarkMemoClick
}
>
Mark
</
span
>
<
span
className=
"btn"
onClick=
{
handleShowMemoStoryDialog
}
>
View Story
</
span
>
<
span
className=
"btn archive-btn"
onClick=
{
handleArchiveMemoClick
}
>
Archive
</
span
>
</
div
>
</
div
>
)
}
</
div
>
</
div
>
<
div
ref=
{
memoContainerRef
}
...
...
web/src/components/MenuBtnsPopup.tsx
View file @
6f32643d
...
...
@@ -67,8 +67,8 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
<
button
className=
"btn action-btn"
onClick=
{
handlePingBtnClick
}
>
<
span
className=
"icon"
>
🎯
</
span
>
Ping
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
userService
.
isNotVisitor
()
?
handleSignOutBtnClick
:
handleSignInBtnClick
}
>
<
span
className=
"icon"
>
👋
</
span
>
{
userService
.
isNotVisitor
()
?
"Sign out"
:
"Sign in"
}
<
button
className=
"btn action-btn"
onClick=
{
!
userService
.
isVisitorMode
()
?
handleSignOutBtnClick
:
handleSignInBtnClick
}
>
<
span
className=
"icon"
>
👋
</
span
>
{
!
userService
.
isVisitorMode
()
?
"Sign out"
:
"Sign in"
}
</
button
>
</
div
>
);
...
...
web/src/components/ShortcutList.tsx
View file @
6f32643d
...
...
@@ -4,6 +4,7 @@ import { useAppSelector } from "../store";
import
*
as
utils
from
"../helpers/utils"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Only
from
"./common/OnlyWhen"
;
import
toastHelper
from
"./Toast"
;
import
showCreateShortcutDialog
from
"./CreateShortcutDialog"
;
import
"../less/shortcut-list.less"
;
...
...
@@ -38,11 +39,11 @@ const ShortcutList: React.FC<Props> = () => {
<
div
className=
"shortcuts-wrapper"
>
<
p
className=
"title-text"
>
<
span
className=
"normal-text"
>
Shortcuts
</
span
>
{
userService
.
isNotVisitor
()
&&
(
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
span
className=
"btn"
onClick=
{
()
=>
showCreateShortcutDialog
()
}
>
<
img
src=
"/icons/add.svg"
alt=
"add shortcut"
/>
</
span
>
)
}
</
Only
>
</
p
>
<
div
className=
"shortcuts-container"
>
{
sortedShortcuts
.
map
((
s
)
=>
{
...
...
@@ -66,9 +67,6 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
if
(
isActive
)
{
locationService
.
setMemoShortcut
(
undefined
);
}
else
{
if
(
!
[
"/"
].
includes
(
locationService
.
getState
().
pathname
))
{
locationService
.
setPathname
(
"/"
);
}
locationService
.
setMemoShortcut
(
shortcut
.
id
);
}
};
...
...
@@ -116,30 +114,28 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
<
div
className=
"shortcut-text-container"
>
<
span
className=
"shortcut-text"
>
{
shortcut
.
title
}
</
span
>
</
div
>
{
userService
.
isNotVisitor
()
&&
(
<
div
className=
"btns-container"
>
<
span
className=
"action-btn toggle-btn"
>
<
img
className=
"icon-img"
src=
"/icons/more.svg"
/>
</
span
>
<
div
className=
"action-btns-wrapper"
>
<
div
className=
"action-btns-container"
>
<
span
className=
"btn"
onClick=
{
handlePinShortcutBtnClick
}
>
{
shortcut
.
rowStatus
===
"ARCHIVED"
?
"Unpin"
:
"Pin"
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleEditShortcutBtnClick
}
>
Edit
</
span
>
<
span
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
onClick=
{
handleDeleteMemoClick
}
onMouseLeave=
{
handleDeleteBtnMouseLeave
}
>
{
showConfirmDeleteBtn
?
"Delete!"
:
"Delete"
}
</
span
>
</
div
>
<
div
className=
{
`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`
}
>
<
span
className=
"action-btn toggle-btn"
>
<
img
className=
"icon-img"
src=
"/icons/more.svg"
/>
</
span
>
<
div
className=
"action-btns-wrapper"
>
<
div
className=
"action-btns-container"
>
<
span
className=
"btn"
onClick=
{
handlePinShortcutBtnClick
}
>
{
shortcut
.
rowStatus
===
"ARCHIVED"
?
"Unpin"
:
"Pin"
}
</
span
>
<
span
className=
"btn"
onClick=
{
handleEditShortcutBtnClick
}
>
Edit
</
span
>
<
span
className=
{
`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`
}
onClick=
{
handleDeleteMemoClick
}
onMouseLeave=
{
handleDeleteBtnMouseLeave
}
>
{
showConfirmDeleteBtn
?
"Delete!"
:
"Delete"
}
</
span
>
</
div
>
</
div
>
)
}
</
div
>
</
div
>
</>
);
...
...
web/src/components/Sidebar.tsx
View file @
6f32643d
import
{
useAppSelector
}
from
"../store"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
userService
}
from
"../services"
;
import
Only
from
"./common/OnlyWhen"
;
import
showDailyReviewDialog
from
"./DailyReviewDialog"
;
import
showSettingDialog
from
"./SettingDialog"
;
import
showArchivedMemoDialog
from
"./ArchivedMemoDialog"
;
...
...
@@ -49,21 +50,19 @@ const Sidebar: React.FC<Props> = () => {
</
div
>
</
div
>
<
UsageHeatMap
/>
{
userService
.
isNotVisitor
()
&&
(
<>
<
div
className=
"action-btns-container"
>
<
button
className=
"btn action-btn"
onClick=
{
()
=>
showDailyReviewDialog
()
}
>
<
span
className=
"icon"
>
📅
</
span
>
Daily Review
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleMyAccountBtnClick
}
>
<
span
className=
"icon"
>
⚙️
</
span
>
Setting
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleArchivedBtnClick
}
>
<
span
className=
"icon"
>
🗂
</
span
>
Archived
</
button
>
</
div
>
</>
)
}
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
div
className=
"action-btns-container"
>
<
button
className=
"btn action-btn"
onClick=
{
()
=>
showDailyReviewDialog
()
}
>
<
span
className=
"icon"
>
📅
</
span
>
Daily Review
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleMyAccountBtnClick
}
>
<
span
className=
"icon"
>
⚙️
</
span
>
Setting
</
button
>
<
button
className=
"btn action-btn"
onClick=
{
handleArchivedBtnClick
}
>
<
span
className=
"icon"
>
🗂
</
span
>
Archived
</
button
>
</
div
>
</
Only
>
<
ShortcutList
/>
<
TagList
/>
</
aside
>
...
...
web/src/components/TagList.tsx
View file @
6f32643d
...
...
@@ -71,7 +71,7 @@ const TagList: React.FC<Props> = () => {
{
tags
.
map
((
t
,
idx
)
=>
(
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
query
?.
tag
}
/>
))
}
<
Only
when=
{
userService
.
isNotVisitor
()
&&
tags
.
length
<
5
}
>
<
Only
when=
{
!
userService
.
isVisitorMode
()
&&
tags
.
length
<
5
}
>
<
p
className=
"tag-tip-container"
>
Enter
<
span
className=
"code-text"
>
#tag
</
span
>
to create a tag
</
p
>
...
...
@@ -97,9 +97,6 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
locationService
.
setTagQuery
(
undefined
);
}
else
{
utils
.
copyTextToClipboard
(
`#
${
tag
.
text
}
`
);
if
(
!
[
"/"
].
includes
(
locationService
.
getState
().
pathname
))
{
locationService
.
setPathname
(
"/"
);
}
locationService
.
setTagQuery
(
tag
.
text
);
}
};
...
...
web/src/components/UserBanner.tsx
View file @
6f32643d
import
{
useCallback
,
useEffect
,
useState
}
from
"react"
;
import
*
as
api
from
"../helpers/api"
;
import
{
getUserIdFromPath
}
from
"../services/userService"
;
import
userService
from
"../services/userService"
;
import
{
locationService
}
from
"../services"
;
import
{
useAppSelector
}
from
"../store"
;
import
toastHelper
from
"./Toast"
;
...
...
@@ -31,15 +31,18 @@ const UserBanner: React.FC<Props> = () => {
setUsername
(
status
.
owner
.
name
);
});
}
else
{
api
.
getUserNameById
(
Number
(
getUserIdFromPath
()))
.
then
(({
data
})
=>
{
const
{
data
:
username
}
=
data
;
setUsername
(
username
);
})
.
catch
(()
=>
{
toastHelper
.
error
(
"User not found"
);
});
const
currentUserId
=
userService
.
getCurrentUserId
();
if
(
currentUserId
)
{
api
.
getUserNameById
(
currentUserId
)
.
then
(({
data
})
=>
{
const
{
data
:
username
}
=
data
;
setUsername
(
username
);
})
.
catch
(()
=>
{
toastHelper
.
error
(
"User not found"
);
});
}
}
}
},
[]);
...
...
web/src/helpers/api.ts
View file @
6f32643d
...
...
@@ -12,8 +12,8 @@ export function getSystemStatus() {
return
axios
.
get
<
ResponseObject
<
SystemStatus
>>
(
"/api/status"
);
}
export
function
log
in
(
email
:
string
,
password
:
string
)
{
return
axios
.
post
<
ResponseObject
<
User
>>
(
"/api/auth/
log
in"
,
{
export
function
sign
in
(
email
:
string
,
password
:
string
)
{
return
axios
.
post
<
ResponseObject
<
User
>>
(
"/api/auth/
sign
in"
,
{
email
,
password
,
});
...
...
@@ -52,12 +52,15 @@ export function patchUser(userPatch: UserPatch) {
return
axios
.
patch
<
ResponseObject
<
User
>>
(
"/api/user/me"
,
userPatch
);
}
export
function
getMemoList
(
userId
?:
number
)
{
return
axios
.
get
<
ResponseObject
<
Memo
[]
>>
(
`/api/memo
${
userId
?
"?userID="
+
userId
:
""
}
`
);
}
export
function
getArchivedMemoList
(
userId
?:
number
)
{
return
axios
.
get
<
ResponseObject
<
Memo
[]
>>
(
`/api/memo?rowStatus=ARCHIVED
${
userId
?
"&userID="
+
userId
:
""
}
`
);
export
function
getMemoList
(
memoFind
?:
MemoFind
)
{
const
queryList
=
[];
if
(
memoFind
?.
creatorId
)
{
queryList
.
push
(
`creatorId=
${
memoFind
.
creatorId
}
`
);
}
if
(
memoFind
?.
rowStatus
)
{
queryList
.
push
(
`rowStatus=
${
memoFind
.
rowStatus
}
`
);
}
return
axios
.
get
<
ResponseObject
<
Memo
[]
>>
(
`/api/memo?
${
queryList
.
join
(
"&"
)}
`
);
}
export
function
createMemo
(
memoCreate
:
MemoCreate
)
{
...
...
@@ -84,8 +87,12 @@ export function deleteMemo(memoId: MemoId) {
return
axios
.
delete
(
`/api/memo/
${
memoId
}
`
);
}
export
function
getShortcutList
(
userId
?:
number
)
{
return
axios
.
get
<
ResponseObject
<
Shortcut
[]
>>
(
`/api/shortcut
${
userId
?
"?userID="
+
userId
:
""
}
`
);
export
function
getShortcutList
(
shortcutFind
:
ShortcutFind
)
{
const
queryList
=
[];
if
(
shortcutFind
?.
creatorId
)
{
queryList
.
push
(
`creatorId=
${
shortcutFind
.
creatorId
}
`
);
}
return
axios
.
get
<
ResponseObject
<
Shortcut
[]
>>
(
`/api/shortcut?
${
queryList
.
join
(
"&"
)}
`
);
}
export
function
createShortcut
(
shortcutCreate
:
ShortcutCreate
)
{
...
...
@@ -104,6 +111,10 @@ export function uploadFile(formData: FormData) {
return
axios
.
post
<
ResponseObject
<
Resource
>>
(
"/api/resource"
,
formData
);
}
export
function
getTagList
(
userId
?:
number
)
{
return
axios
.
get
<
ResponseObject
<
string
[]
>>
(
`/api/tag
${
userId
?
"?userID="
+
userId
:
""
}
`
);
export
function
getTagList
(
tagFind
?:
TagFind
)
{
const
queryList
=
[];
if
(
tagFind
?.
creatorId
)
{
queryList
.
push
(
`creatorId=
${
tagFind
.
creatorId
}
`
);
}
return
axios
.
get
<
ResponseObject
<
string
[]
>>
(
`/api/tag?
${
queryList
.
join
(
"&"
)}
`
);
}
web/src/helpers/utils.ts
View file @
6f32643d
...
...
@@ -126,38 +126,6 @@ export function throttle(fn: FunctionType, delay: number) {
};
}
export
function
transformObjectToParamsString
(
object
:
KVObject
):
string
{
const
params
=
[];
const
keys
=
Object
.
keys
(
object
).
sort
();
for
(
const
key
of
keys
)
{
const
val
=
object
[
key
];
if
(
val
)
{
if
(
typeof
val
===
"object"
)
{
params
.
push
(...
transformObjectToParamsString
(
val
).
split
(
"&"
));
}
else
{
params
.
push
(
`
${
key
}
=
${
val
}
`
);
}
}
}
return
params
.
join
(
"&"
);
}
export
function
transformParamsStringToObject
(
paramsString
:
string
):
KVObject
{
const
object
:
KVObject
=
{};
const
params
=
paramsString
.
split
(
"&"
);
for
(
const
p
of
params
)
{
const
[
key
,
val
]
=
p
.
split
(
"="
);
if
(
key
&&
val
)
{
object
[
key
]
=
val
;
}
}
return
object
;
}
export
function
filterObjectNullKeys
(
object
:
KVObject
):
KVObject
{
if
(
!
object
)
{
return
{};
...
...
web/src/pages/Home.tsx
View file @
6f32643d
import
{
useEffect
}
from
"react"
;
import
{
locationService
,
userService
}
from
"../services"
;
import
Sidebar
from
"../components/Sidebar"
;
import
{
userService
}
from
"../services"
;
import
useLoading
from
"../hooks/useLoading"
;
import
Only
from
"../components/common/OnlyWhen"
;
import
Sidebar
from
"../components/Sidebar"
;
import
MemosHeader
from
"../components/MemosHeader"
;
import
MemoEditor
from
"../components/MemoEditor"
;
import
MemoFilter
from
"../components/MemoFilter"
;
...
...
@@ -12,25 +13,14 @@ function Home() {
const
loadingState
=
useLoading
();
useEffect
(()
=>
{
if
(
window
.
location
.
pathname
!==
locationService
.
getState
().
pathname
)
{
locationService
.
replaceHistory
(
"/"
);
}
const
{
user
}
=
userService
.
getState
();
if
(
!
user
)
{
userService
.
doSignIn
()
.
catch
(()
=>
{
// do nth
})
.
finally
(()
=>
{
if
(
userService
.
getState
().
user
&&
locationService
.
getState
().
pathname
!==
"/"
)
{
locationService
.
replaceHistory
(
"/"
);
}
loadingState
.
setFinish
();
});
}
else
{
loadingState
.
setFinish
();
}
userService
.
doSignIn
()
.
catch
(()
=>
{
// do nth
})
.
finally
(()
=>
{
loadingState
.
setFinish
();
});
},
[]);
return
(
...
...
@@ -41,7 +31,9 @@ function Home() {
<
main
className=
"memos-wrapper"
>
<
div
className=
"memos-editor-wrapper"
>
<
MemosHeader
/>
{
userService
.
isNotVisitor
()
&&
<
MemoEditor
/>
}
<
Only
when=
{
!
userService
.
isVisitorMode
()
}
>
<
MemoEditor
/>
</
Only
>
<
MemoFilter
/>
</
div
>
<
MemoList
/>
...
...
web/src/pages/Signin.tsx
View file @
6f32643d
...
...
@@ -63,7 +63,7 @@ const Signin: React.FC<Props> = () => {
try
{
actionBtnLoadingState
.
setLoading
();
await
api
.
log
in
(
email
,
password
);
await
api
.
sign
in
(
email
,
password
);
const
user
=
await
userService
.
doSignIn
();
if
(
user
)
{
locationService
.
replaceHistory
(
"/"
);
...
...
web/src/services/locationService.ts
View file @
6f32643d
import
*
as
utils
from
"../helpers/util
s"
;
import
{
stringify
}
from
"q
s"
;
import
store
from
"../store"
;
import
{
setQuery
,
setPathname
,
Query
}
from
"../store/modules/location"
;
const
updateLocationUrl
=
(
method
:
"replace"
|
"push"
=
"replace"
)
=>
{
const
{
query
,
pathname
,
hash
}
=
store
.
getState
().
location
;
let
queryString
=
utils
.
transformObjectToParamsString
(
query
??
{}
);
let
queryString
=
stringify
(
query
);
if
(
queryString
)
{
queryString
=
"?"
+
queryString
;
}
else
{
...
...
web/src/services/memoService.ts
View file @
6f32643d
import
*
as
api
from
"../helpers/api"
;
import
{
createMemo
,
patchMemo
,
setMemos
,
setTags
}
from
"../store/modules/memo"
;
import
store
from
"../store"
;
import
{
getUserIdFromPath
}
from
"./userService"
;
import
userService
from
"./userService"
;
const
convertResponseModelMemo
=
(
memo
:
Memo
):
Memo
=>
{
return
{
...
...
@@ -17,7 +17,10 @@ const memoService = {
},
fetchAllMemos
:
async
()
=>
{
const
{
data
}
=
(
await
api
.
getMemoList
(
getUserIdFromPath
())).
data
;
const
memoFind
:
MemoFind
=
{
creatorId
:
userService
.
getCurrentUserId
(),
};
const
{
data
}
=
(
await
api
.
getMemoList
(
memoFind
)).
data
;
const
memos
=
data
.
filter
((
m
)
=>
m
.
rowStatus
!==
"ARCHIVED"
).
map
((
m
)
=>
convertResponseModelMemo
(
m
));
store
.
dispatch
(
setMemos
(
memos
));
...
...
@@ -25,7 +28,11 @@ const memoService = {
},
fetchArchivedMemos
:
async
()
=>
{
const
{
data
}
=
(
await
api
.
getArchivedMemoList
(
getUserIdFromPath
())).
data
;
const
memoFind
:
MemoFind
=
{
creatorId
:
userService
.
getCurrentUserId
(),
rowStatus
:
"ARCHIVED"
,
};
const
{
data
}
=
(
await
api
.
getMemoList
(
memoFind
)).
data
;
const
archivedMemos
=
data
.
map
((
m
)
=>
{
return
convertResponseModelMemo
(
m
);
});
...
...
@@ -43,7 +50,10 @@ const memoService = {
},
updateTagsState
:
async
()
=>
{
const
{
data
}
=
(
await
api
.
getTagList
(
getUserIdFromPath
())).
data
;
const
tagFind
:
TagFind
=
{
creatorId
:
userService
.
getCurrentUserId
(),
};
const
{
data
}
=
(
await
api
.
getTagList
(
tagFind
)).
data
;
store
.
dispatch
(
setTags
(
data
));
},
...
...
web/src/services/shortcutService.ts
View file @
6f32643d
import
*
as
api
from
"../helpers/api"
;
import
store
from
"../store/"
;
import
{
createShortcut
,
deleteShortcut
,
patchShortcut
,
setShortcuts
}
from
"../store/modules/shortcut"
;
import
{
getUserIdFromPath
}
from
"./userService"
;
import
userService
from
"./userService"
;
const
convertResponseModelShortcut
=
(
shortcut
:
Shortcut
):
Shortcut
=>
{
return
{
...
...
@@ -17,7 +17,10 @@ const shortcutService = {
},
getMyAllShortcuts
:
async
()
=>
{
const
{
data
}
=
(
await
api
.
getShortcutList
(
getUserIdFromPath
())).
data
;
const
shortcutFind
:
ShortcutFind
=
{
creatorId
:
userService
.
getCurrentUserId
(),
};
const
{
data
}
=
(
await
api
.
getShortcutList
(
shortcutFind
)).
data
;
const
shortcuts
=
data
.
map
((
s
)
=>
convertResponseModelShortcut
(
s
));
store
.
dispatch
(
setShortcuts
(
shortcuts
));
},
...
...
web/src/services/userService.ts
View file @
6f32643d
import
{
isUndefined
}
from
"lodash-es"
;
import
{
locationService
}
from
"."
;
import
*
as
api
from
"../helpers/api"
;
import
store
from
"../store"
;
...
...
@@ -11,18 +12,26 @@ const convertResponseModelUser = (user: User): User => {
};
};
export
const
getUserIdFromPath
=
()
=>
{
const
path
=
locationService
.
getState
().
pathname
.
slice
(
3
);
return
!
isNaN
(
Number
(
path
))
?
Number
(
path
)
:
undefined
;
};
const
userService
=
{
getState
:
()
=>
{
return
store
.
getState
().
user
;
},
isNotVisitor
:
()
=>
{
return
store
.
getState
().
user
.
user
!==
undefined
;
isVisitorMode
:
()
=>
{
return
!
isUndefined
(
userService
.
getUserIdFromPath
());
},
getCurrentUserId
:
()
=>
{
return
userService
.
getUserIdFromPath
()
??
store
.
getState
().
user
.
user
?.
id
;
},
getUserIdFromPath
:
()
=>
{
const
userIdRegex
=
/^
\/
u
\/(\d
+
)
.*/
;
const
result
=
locationService
.
getState
().
pathname
.
match
(
userIdRegex
);
if
(
result
&&
result
.
length
===
2
)
{
return
Number
(
result
[
1
]);
}
return
undefined
;
},
doSignIn
:
async
()
=>
{
...
...
web/src/store/modules/location.ts
View file @
6f32643d
...
...
@@ -20,7 +20,8 @@ interface State {
}
const
getValidPathname
=
(
pathname
:
string
):
string
=>
{
if
([
"/"
,
"/signin"
].
includes
(
pathname
)
||
pathname
.
match
(
/^
\/
u
\/(\d
+
)
/
))
{
const
userPageUrlRegex
=
/^
\/
u
\/\d
+.*/
;
if
([
"/"
,
"/signin"
].
includes
(
pathname
)
||
userPageUrlRegex
.
test
(
pathname
))
{
return
pathname
;
}
else
{
return
"/"
;
...
...
web/src/types/modules/memo.d.ts
View file @
6f32643d
...
...
@@ -22,3 +22,8 @@ interface MemoPatch {
content
?:
string
;
rowStatus
?:
RowStatus
;
}
interface
MemoFind
{
creatorId
?:
UserId
;
rowStatus
?:
RowStatus
;
}
web/src/types/modules/shortcut.d.ts
View file @
6f32643d
...
...
@@ -3,6 +3,7 @@ type ShortcutId = number;
interface
Shortcut
{
id
:
ShortcutId
;
creatorId
:
UserId
;
rowStatus
:
RowStatus
;
createdTs
:
TimeStamp
;
updatedTs
:
TimeStamp
;
...
...
@@ -22,3 +23,7 @@ interface ShortcutPatch {
payload
?:
string
;
rowStatus
?:
RowStatus
;
}
interface
ShortcutFind
{
creatorId
?:
UserId
;
}
web/src/types/modules/tag.d.ts
0 → 100644
View file @
6f32643d
interface
TagFind
{
creatorId
?:
UserId
;
}
web/yarn.lock
View file @
6f32643d
...
...
@@ -358,6 +358,11 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/qs@^6.9.7":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
"@types/react-dom@^18.0.4":
version "18.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375"
...
...
@@ -1988,6 +1993,13 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
...
...
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