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
a07d5d38
Unverified
Commit
a07d5d38
authored
May 18, 2023
by
boojack
Committed by
GitHub
May 18, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: memo relation part1 (#1677)
* feat: memo relation part1 * chore: update
parent
ca585929
Changes
24
Show whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
425 additions
and
207 deletions
+425
-207
memo.go
server/memo.go
+86
-3
memo.go
store/memo.go
+5
-3
memo_relation.go
store/memo_relation.go
+1
-0
AskAIDialog.tsx
web/src/components/AskAIDialog.tsx
+1
-1
Header.tsx
web/src/components/Header.tsx
+1
-1
HomeSidebar.tsx
web/src/components/HomeSidebar.tsx
+1
-1
Memo.tsx
web/src/components/Memo.tsx
+109
-61
RelationListView.tsx
web/src/components/MemoEditor/RelationListView.tsx
+60
-0
ResourceListView.tsx
web/src/components/MemoEditor/ResourceListView.tsx
+9
-9
index.tsx
web/src/components/MemoEditor/index.tsx
+10
-4
MemoRelationListView.tsx
web/src/components/MemoRelationListView.tsx
+46
-0
Root.tsx
web/src/layouts/Root.tsx
+1
-1
create-shortcut-dialog.less
web/src/less/create-shortcut-dialog.less
+1
-1
memo-detail.less
web/src/less/memo-detail.less
+0
-65
memo-editor.less
web/src/less/memo-editor.less
+0
-20
memo.less
web/src/less/memo.less
+1
-1
Auth.tsx
web/src/pages/Auth.tsx
+2
-2
Home.tsx
web/src/pages/Home.tsx
+1
-1
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+18
-26
editor.ts
web/src/store/module/editor.ts
+3
-3
memo.ts
web/src/store/module/memo.ts
+17
-3
editor.ts
web/src/store/reducer/editor.ts
+9
-1
index.ts
web/src/store/zustand/index.ts
+2
-0
memo.ts
web/src/store/zustand/memo.ts
+41
-0
No files found.
server/memo.go
View file @
a07d5d38
...
@@ -160,8 +160,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
...
@@ -160,8 +160,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
if
err
!=
nil
{
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to patch memo"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to patch memo"
)
.
SetInternal
(
err
)
}
}
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo"
)
.
SetInternal
(
err
)
}
for
_
,
resourceID
:=
range
memoPatch
.
ResourceIDList
{
resourceIDList
:=
make
([]
int
,
0
)
for
_
,
resource
:=
range
memo
.
ResourceList
{
resourceIDList
=
append
(
resourceIDList
,
resource
.
ID
)
}
addedResourceIDList
,
removedResourceIDList
:=
getIDListDiff
(
resourceIDList
,
memoPatch
.
ResourceIDList
)
for
_
,
resourceID
:=
range
addedResourceIDList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoResource
(
ctx
,
&
api
.
MemoResourceUpsert
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoResource
(
ctx
,
&
api
.
MemoResourceUpsert
{
MemoID
:
memo
.
ID
,
MemoID
:
memo
.
ID
,
ResourceID
:
resourceID
,
ResourceID
:
resourceID
,
...
@@ -169,19 +178,47 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
...
@@ -169,19 +178,47 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo resource"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo resource"
)
.
SetInternal
(
err
)
}
}
}
}
for
_
,
resourceID
:=
range
removedResourceIDList
{
if
err
:=
s
.
Store
.
DeleteMemoResource
(
ctx
,
&
api
.
MemoResourceDelete
{
MemoID
:
&
memo
.
ID
,
ResourceID
:
&
resourceID
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to delete memo resource"
)
.
SetInternal
(
err
)
}
}
if
s
.
Profile
.
IsDev
()
{
if
s
.
Profile
.
IsDev
()
{
patchMemoRelationList
:=
make
([]
*
api
.
MemoRelation
,
0
)
for
_
,
memoRelationUpsert
:=
range
memoPatch
.
RelationList
{
for
_
,
memoRelationUpsert
:=
range
memoPatch
.
RelationList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoRelation
(
ctx
,
&
store
.
MemoRelationMessage
{
patchMemoRelationList
=
append
(
patchMemoRelationList
,
&
api
.
MemoRelation
{
MemoID
:
memo
.
ID
,
MemoID
:
memo
.
ID
,
RelatedMemoID
:
memoRelationUpsert
.
RelatedMemoID
,
RelatedMemoID
:
memoRelationUpsert
.
RelatedMemoID
,
Type
:
store
.
MemoRelationType
(
memoRelationUpsert
.
Type
),
Type
:
memoRelationUpsert
.
Type
,
})
}
addedMemoRelationList
,
removedMemoRelationList
:=
getMemoRelationListDiff
(
memo
.
RelationList
,
patchMemoRelationList
)
for
_
,
memoRelation
:=
range
addedMemoRelationList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoRelation
(
ctx
,
&
store
.
MemoRelationMessage
{
MemoID
:
memo
.
ID
,
RelatedMemoID
:
memoRelation
.
RelatedMemoID
,
Type
:
store
.
MemoRelationType
(
memoRelation
.
Type
),
});
err
!=
nil
{
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo relation"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo relation"
)
.
SetInternal
(
err
)
}
}
}
}
for
_
,
memoRelation
:=
range
removedMemoRelationList
{
memoRelationType
:=
store
.
MemoRelationType
(
memoRelation
.
Type
)
if
err
:=
s
.
Store
.
DeleteMemoRelation
(
ctx
,
&
store
.
DeleteMemoRelationMessage
{
MemoID
:
&
memo
.
ID
,
RelatedMemoID
:
&
memoRelation
.
RelatedMemoID
,
Type
:
&
memoRelationType
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to delete memo relation"
)
.
SetInternal
(
err
)
}
}
}
}
// After patching memo resources and relations, we need to re-compose it to get the latest data.
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
if
err
!=
nil
{
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo"
)
.
SetInternal
(
err
)
...
@@ -452,3 +489,49 @@ func (s *Server) createMemoCreateActivity(c echo.Context, memo *api.Memo) error
...
@@ -452,3 +489,49 @@ func (s *Server) createMemoCreateActivity(c echo.Context, memo *api.Memo) error
}
}
return
err
return
err
}
}
func
getIDListDiff
(
oldList
,
newList
[]
int
)
(
addedList
,
removedList
[]
int
)
{
oldMap
:=
map
[
int
]
bool
{}
for
_
,
id
:=
range
oldList
{
oldMap
[
id
]
=
true
}
newMap
:=
map
[
int
]
bool
{}
for
_
,
id
:=
range
newList
{
newMap
[
id
]
=
true
}
for
id
:=
range
oldMap
{
if
!
newMap
[
id
]
{
removedList
=
append
(
removedList
,
id
)
}
}
for
id
:=
range
newMap
{
if
!
oldMap
[
id
]
{
addedList
=
append
(
addedList
,
id
)
}
}
return
addedList
,
removedList
}
func
getMemoRelationListDiff
(
oldList
,
newList
[]
*
api
.
MemoRelation
)
(
addedList
,
removedList
[]
*
api
.
MemoRelation
)
{
oldMap
:=
map
[
string
]
bool
{}
for
_
,
relation
:=
range
oldList
{
oldMap
[
fmt
.
Sprintf
(
"%d-%s"
,
relation
.
RelatedMemoID
,
relation
.
Type
)]
=
true
}
newMap
:=
map
[
string
]
bool
{}
for
_
,
relation
:=
range
newList
{
newMap
[
fmt
.
Sprintf
(
"%d-%s"
,
relation
.
RelatedMemoID
,
relation
.
Type
)]
=
true
}
for
_
,
relation
:=
range
oldList
{
key
:=
fmt
.
Sprintf
(
"%d-%s"
,
relation
.
RelatedMemoID
,
relation
.
Type
)
if
!
newMap
[
key
]
{
removedList
=
append
(
removedList
,
relation
)
}
}
for
_
,
relation
:=
range
newList
{
key
:=
fmt
.
Sprintf
(
"%d-%s"
,
relation
.
RelatedMemoID
,
relation
.
Type
)
if
!
oldMap
[
key
]
{
addedList
=
append
(
addedList
,
relation
)
}
}
return
addedList
,
removedList
}
store/memo.go
View file @
a07d5d38
...
@@ -43,6 +43,8 @@ func (raw *memoRaw) toMemo() *api.Memo {
...
@@ -43,6 +43,8 @@ func (raw *memoRaw) toMemo() *api.Memo {
Content
:
raw
.
Content
,
Content
:
raw
.
Content
,
Visibility
:
raw
.
Visibility
,
Visibility
:
raw
.
Visibility
,
Pinned
:
raw
.
Pinned
,
Pinned
:
raw
.
Pinned
,
ResourceList
:
[]
*
api
.
Resource
{},
RelationList
:
[]
*
api
.
MemoRelation
{},
}
}
}
}
...
...
store/memo_relation.go
View file @
a07d5d38
...
@@ -18,6 +18,7 @@ func (s *Store) ComposeMemoRelationList(ctx context.Context, memo *api.Memo) err
...
@@ -18,6 +18,7 @@ func (s *Store) ComposeMemoRelationList(ctx context.Context, memo *api.Memo) err
return
err
return
err
}
}
memo
.
RelationList
=
[]
*
api
.
MemoRelation
{}
for
_
,
memoRelation
:=
range
memoRelationList
{
for
_
,
memoRelation
:=
range
memoRelationList
{
memo
.
RelationList
=
append
(
memo
.
RelationList
,
&
api
.
MemoRelation
{
memo
.
RelationList
=
append
(
memo
.
RelationList
,
&
api
.
MemoRelation
{
MemoID
:
memoRelation
.
MemoID
,
MemoID
:
memoRelation
.
MemoID
,
...
...
web/src/components/AskAIDialog.tsx
View file @
a07d5d38
...
@@ -98,7 +98,7 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
...
@@ -98,7 +98,7 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
</
div
>
</
div
>
)
:
(
)
:
(
<
div
className=
"w-full flex flex-row justify-start items-start pr-8 space-x-2"
>
<
div
className=
"w-full flex flex-row justify-start items-start pr-8 space-x-2"
>
<
Icon
.
Bot
className=
"mt-2
flex-
shrink-0 mr-1 w-6 h-auto opacity-80"
/>
<
Icon
.
Bot
className=
"mt-2 shrink-0 mr-1 w-6 h-auto opacity-80"
/>
<
div
className=
"memo-content-wrapper !w-auto flex flex-col justify-start items-start shadow rounded-lg rounded-tl-none px-3 py-2 bg-gray-100 dark:bg-zinc-700"
>
<
div
className=
"memo-content-wrapper !w-auto flex flex-col justify-start items-start shadow rounded-lg rounded-tl-none px-3 py-2 bg-gray-100 dark:bg-zinc-700"
>
<
div
className=
"memo-content-text"
>
{
marked
(
message
.
content
)
}
</
div
>
<
div
className=
"memo-content-text"
>
{
marked
(
message
.
content
)
}
</
div
>
</
div
>
</
div
>
...
...
web/src/components/Header.tsx
View file @
a07d5d38
...
@@ -32,7 +32,7 @@ const Header = () => {
...
@@ -32,7 +32,7 @@ const Header = () => {
return
(
return
(
<
div
<
div
className=
{
`fixed sm:sticky top-0 left-0 w-full sm:w-56 h-full
flex-
shrink-0 pointer-events-none sm:pointer-events-auto z-20 ${
className=
{
`fixed sm:sticky top-0 left-0 w-full sm:w-56 h-full shrink-0 pointer-events-none sm:pointer-events-auto z-20 ${
showHeader && "pointer-events-auto"
showHeader && "pointer-events-auto"
}`
}
}`
}
>
>
...
...
web/src/components/HomeSidebar.tsx
View file @
a07d5d38
...
@@ -42,7 +42,7 @@ const HomeSidebar = () => {
...
@@ -42,7 +42,7 @@ const HomeSidebar = () => {
return
(
return
(
<
div
<
div
className=
{
`fixed md:sticky top-0 left-0 w-full md:w-56 h-full
flex-
shrink-0 pointer-events-none md:pointer-events-auto z-10 ${
className=
{
`fixed md:sticky top-0 left-0 w-full md:w-56 h-full shrink-0 pointer-events-none md:pointer-events-auto z-10 ${
showHomeSidebar && "pointer-events-auto"
showHomeSidebar && "pointer-events-auto"
}`
}
}`
}
>
>
...
...
web/src/components/Memo.tsx
View file @
a07d5d38
import
{
getRelativeTimeString
}
from
"@/helpers/datetime
"
;
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"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
useEditorStore
,
useFilterStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
{
useEditorStore
,
useFilterStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
{
getRelativeTimeString
}
from
"@/helpers/datetime"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
useMemoCacheStore
}
from
"@/store/zustand"
;
import
Tooltip
from
"./kit/Tooltip"
;
import
Tooltip
from
"./kit/Tooltip"
;
import
Divider
from
"./kit/Divider"
;
import
Divider
from
"./kit/Divider"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
...
@@ -13,24 +16,34 @@ import MemoResources from "./MemoResources";
...
@@ -13,24 +16,34 @@ import MemoResources from "./MemoResources";
import
showShareMemo
from
"./ShareMemoDialog"
;
import
showShareMemo
from
"./ShareMemoDialog"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
showChangeMemoCreatedTsDialog
from
"./ChangeMemoCreatedTsDialog"
;
import
showChangeMemoCreatedTsDialog
from
"./ChangeMemoCreatedTsDialog"
;
import
MemoRelationListView
from
"./MemoRelationListView"
;
import
"@/less/memo.less"
;
import
"@/less/memo.less"
;
interface
Props
{
interface
Props
{
memo
:
Memo
;
memo
:
Memo
;
readonly
?:
boolean
;
readonly
?:
boolean
;
showRelatedMemos
?:
boolean
;
}
}
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
Memo
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
memo
,
readonly
}
=
props
;
const
{
memo
,
readonly
,
showRelatedMemos
}
=
props
;
const
{
t
,
i18n
}
=
useTranslation
();
const
{
t
,
i18n
}
=
useTranslation
();
const
editorStore
=
useEditorStore
();
const
editorStore
=
useEditorStore
();
const
filterStore
=
useFilterStore
();
const
filterStore
=
useFilterStore
();
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
const
memoStore
=
useMemoStore
();
const
memoStore
=
useMemoStore
();
const
memoCacheStore
=
useMemoCacheStore
();
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
createdTs
));
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getRelativeTimeString
(
memo
.
createdTs
));
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
memoContainerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
isVisitorMode
=
userStore
.
isVisitorMode
()
||
readonly
;
const
isVisitorMode
=
userStore
.
isVisitorMode
()
||
readonly
;
useEffect
(()
=>
{
Promise
.
all
(
memo
.
relationList
.
map
((
memoRelation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
memoRelation
.
relatedMemoId
))).
then
((
memoList
)
=>
{
setRelatedMemoList
(
uniqWith
(
memoList
,
isEqual
));
});
},
[
memo
.
relationList
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
let
intervalFlag
:
any
=
-
1
;
let
intervalFlag
:
any
=
-
1
;
if
(
Date
.
now
()
-
memo
.
createdTs
<
1000
*
60
*
60
*
24
)
{
if
(
Date
.
now
()
-
memo
.
createdTs
<
1000
*
60
*
60
*
24
)
{
...
@@ -178,8 +191,21 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -178,8 +191,21 @@ const Memo: React.FC<Props> = (props: Props) => {
}
}
};
};
const
handleMarkMemo
=
()
=>
{
const
relation
:
MemoRelation
=
{
memoId
:
UNKNOWN_ID
,
relatedMemoId
:
memo
.
id
,
type
:
"REFERENCE"
,
};
editorStore
.
setRelationList
(
uniqWith
([...
editorStore
.
state
.
relationList
,
relation
],
isEqual
));
};
return
(
return
(
<
div
className=
{
`memo-wrapper ${"memos-" + memo.id} ${memo.pinned ? "pinned" : ""}`
}
ref=
{
memoContainerRef
}
>
<>
<
div
className=
{
`memo-wrapper ${"memos-" + memo.id} ${relatedMemoList.length > 0 && "pinned"} ${memo.pinned ? "pinned" : ""}`
}
ref=
{
memoContainerRef
}
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"memo-top-wrapper"
>
<
div
className=
"status-text-container"
>
<
div
className=
"status-text-container"
>
<
Link
className=
"time-text"
to=
{
`/m/${memo.id}`
}
onClick=
{
handleMemoCreatedTimeClick
}
>
<
Link
className=
"time-text"
to=
{
`/m/${memo.id}`
}
onClick=
{
handleMemoCreatedTimeClick
}
>
...
@@ -222,6 +248,10 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -222,6 +248,10 @@ const Memo: React.FC<Props> = (props: Props) => {
<
Icon
.
Share
className=
"w-4 h-auto mr-2"
/>
<
Icon
.
Share
className=
"w-4 h-auto mr-2"
/>
{
t
(
"common.share"
)
}
{
t
(
"common.share"
)
}
</
span
>
</
span
>
<
span
className=
"btn"
onClick=
{
handleMarkMemo
}
>
<
Icon
.
Link
className=
"w-4 h-auto mr-2"
/>
Mark
</
span
>
<
Divider
/>
<
Divider
/>
<
span
className=
"btn text-orange-500"
onClick=
{
handleArchiveMemoClick
}
>
<
span
className=
"btn text-orange-500"
onClick=
{
handleArchiveMemoClick
}
>
<
Icon
.
Archive
className=
"w-4 h-auto mr-2"
/>
<
Icon
.
Archive
className=
"w-4 h-auto mr-2"
/>
...
@@ -242,7 +272,25 @@ const Memo: React.FC<Props> = (props: Props) => {
...
@@ -242,7 +272,25 @@ const Memo: React.FC<Props> = (props: Props) => {
onMemoContentDoubleClick=
{
handleMemoContentDoubleClick
}
onMemoContentDoubleClick=
{
handleMemoContentDoubleClick
}
/>
/>
<
MemoResources
resourceList=
{
memo
.
resourceList
}
/>
<
MemoResources
resourceList=
{
memo
.
resourceList
}
/>
{
!
showRelatedMemos
&&
<
MemoRelationListView
relationList=
{
memo
.
relationList
}
/>
}
</
div
>
</
div
>
{
showRelatedMemos
&&
relatedMemoList
.
length
>
0
&&
(
<>
<
p
className=
"font-mono text-sm mt-4 mb-1 pl-4 opacity-60 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
}
readonly=
{
readonly
}
/>
</
div
>
);
})
}
</>
)
}
</>
);
);
};
};
...
...
web/src/components/MemoEditor/RelationListView.tsx
0 → 100644
View file @
a07d5d38
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEditorStore
}
from
"@/store/module"
;
import
{
useMemoCacheStore
}
from
"@/store/zustand"
;
import
Icon
from
"../Icon"
;
interface
FormatedMemoRelation
extends
MemoRelation
{
relatedMemo
:
Memo
;
}
const
RelationListView
=
()
=>
{
const
editorStore
=
useEditorStore
();
const
memoCacheStore
=
useMemoCacheStore
();
const
[
formatedMemoRelationList
,
setFormatedMemoRelationList
]
=
useState
<
FormatedMemoRelation
[]
>
([]);
const
relationList
=
editorStore
.
state
.
relationList
;
useEffect
(()
=>
{
const
fetchRelatedMemoList
=
async
()
=>
{
const
requests
=
relationList
.
map
(
async
(
relation
)
=>
{
const
relatedMemo
=
await
memoCacheStore
.
getOrFetchMemoById
(
relation
.
relatedMemoId
);
return
{
...
relation
,
relatedMemo
,
};
});
const
list
=
await
Promise
.
all
(
requests
);
setFormatedMemoRelationList
(
list
);
};
fetchRelatedMemoList
();
},
[
relationList
]);
const
handleDeleteRelation
=
async
(
memoRelation
:
FormatedMemoRelation
)
=>
{
const
newRelationList
=
relationList
.
filter
((
relation
)
=>
relation
.
relatedMemoId
!==
memoRelation
.
relatedMemoId
);
editorStore
.
setRelationList
(
newRelationList
);
};
return
(
<>
{
formatedMemoRelationList
.
length
>
0
&&
(
<
div
className=
"w-full flex flex-row gap-2 mt-2 flex-wrap"
>
{
formatedMemoRelationList
.
map
((
memoRelation
)
=>
{
return
(
<
div
key=
{
memoRelation
.
relatedMemoId
}
className=
"w-auto max-w-[50%] overflow-hidden flex flex-row justify-start items-center bg-gray-100 hover:bg-gray-200 rounded text-sm p-1 px-2 text-gray-500 cursor-pointer"
>
<
Icon
.
Link
className=
"w-4 h-auto shrink-0"
/>
<
span
className=
"mx-1 max-w-full text-ellipsis font-mono whitespace-nowrap overflow-hidden"
>
{
memoRelation
.
relatedMemo
.
content
}
</
span
>
<
Icon
.
X
className=
"w-4 h-auto hover:opacity-80 shrink-0"
onClick=
{
()
=>
handleDeleteRelation
(
memoRelation
)
}
/>
</
div
>
);
})
}
</
div
>
)
}
</>
);
};
export
default
RelationListView
;
web/src/components/MemoEditor/ResourceListView.tsx
View file @
a07d5d38
import
{
useEditorStore
}
from
"@/store/module"
;
import
{
useEditorStore
}
from
"@/store/module"
;
import
{
deleteMemoResource
}
from
"@/helpers/api"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
import
ResourceIcon
from
"../ResourceIcon"
;
import
ResourceIcon
from
"../ResourceIcon"
;
...
@@ -9,21 +8,21 @@ const ResourceListView = () => {
...
@@ -9,21 +8,21 @@ const ResourceListView = () => {
const
handleDeleteResource
=
async
(
resourceId
:
ResourceId
)
=>
{
const
handleDeleteResource
=
async
(
resourceId
:
ResourceId
)
=>
{
editorStore
.
setResourceList
(
editorState
.
resourceList
.
filter
((
resource
)
=>
resource
.
id
!==
resourceId
));
editorStore
.
setResourceList
(
editorState
.
resourceList
.
filter
((
resource
)
=>
resource
.
id
!==
resourceId
));
if
(
editorState
.
editMemoId
)
{
await
deleteMemoResource
(
editorState
.
editMemoId
,
resourceId
);
}
};
};
return
(
return
(
<>
<>
{
editorState
.
resourceList
&&
editorState
.
resourceList
.
length
>
0
&&
(
{
editorState
.
resourceList
&&
editorState
.
resourceList
.
length
>
0
&&
(
<
div
className=
"
resource-list-wrapper
"
>
<
div
className=
"
w-full flex flex-row justify-start flex-wrap gap-2 mt-2
"
>
{
editorState
.
resourceList
.
map
((
resource
)
=>
{
{
editorState
.
resourceList
.
map
((
resource
)
=>
{
return
(
return
(
<
div
key=
{
resource
.
id
}
className=
"resource-container"
>
<
div
<
ResourceIcon
resourceType=
"resource.type"
className=
"icon-img"
/>
key=
{
resource
.
id
}
<
span
className=
"name-text"
>
{
resource
.
filename
}
</
span
>
className=
"max-w-full flex flex-row justify-start items-center flex-nowrap bg-gray-100 px-2 py-1 rounded cursor-pointer text-gray-500 hover:bg-gray-200 dark:opacity-60"
<
Icon
.
X
className=
"close-icon"
onClick=
{
()
=>
handleDeleteResource
(
resource
.
id
)
}
/>
>
<
ResourceIcon
resourceType=
{
resource
.
type
}
className=
"w-4 h-auto mr-1"
/>
<
span
className=
"text-sm max-w-xs truncate font-mono"
>
{
resource
.
filename
}
</
span
>
<
Icon
.
X
className=
"w-4 h-auto ml-1 hover:opacity-80"
onClick=
{
()
=>
handleDeleteResource
(
resource
.
id
)
}
/>
</
div
>
</
div
>
);
);
})
}
})
}
...
@@ -32,4 +31,5 @@ const ResourceListView = () => {
...
@@ -32,4 +31,5 @@ const ResourceListView = () => {
</>
</>
);
);
};
};
export
default
ResourceListView
;
export
default
ResourceListView
;
web/src/components/MemoEditor/index.tsx
View file @
a07d5d38
...
@@ -13,8 +13,9 @@ import Editor, { EditorRefActions } from "./Editor";
...
@@ -13,8 +13,9 @@ import Editor, { EditorRefActions } from "./Editor";
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
ResourceSelector
from
"./ActionButton/ResourceSelector"
;
import
ResourceSelector
from
"./ActionButton/ResourceSelector"
;
import
MemoVisibilitySelector
from
"./ActionButton/MemoVisibilitySelector"
;
import
MemoVisibilitySelector
from
"./ActionButton/MemoVisibilitySelector"
;
import
"@/less/memo-editor.less"
;
import
ResourceListView
from
"./ResourceListView"
;
import
ResourceListView
from
"./ResourceListView"
;
import
RelationListView
from
"./RelationListView"
;
import
"@/less/memo-editor.less"
;
const
listItemSymbolList
=
[
"- [ ] "
,
"- [x] "
,
"- [X] "
,
"* "
,
"- "
];
const
listItemSymbolList
=
[
"- [ ] "
,
"- [x] "
,
"- [X] "
,
"* "
,
"- "
];
const
emptyOlReg
=
/^
(\d
+
)\.
$/
;
const
emptyOlReg
=
/^
(\d
+
)\.
$/
;
...
@@ -73,6 +74,7 @@ const MemoEditor = () => {
...
@@ -73,6 +74,7 @@ const MemoEditor = () => {
handleEditorFocus
();
handleEditorFocus
();
editorStore
.
setMemoVisibility
(
memo
.
visibility
);
editorStore
.
setMemoVisibility
(
memo
.
visibility
);
editorStore
.
setResourceList
(
memo
.
resourceList
);
editorStore
.
setResourceList
(
memo
.
resourceList
);
editorStore
.
setRelationList
(
memo
.
relationList
);
editorRef
.
current
?.
setContent
(
memo
.
content
??
""
);
editorRef
.
current
?.
setContent
(
memo
.
content
??
""
);
}
}
});
});
...
@@ -232,6 +234,7 @@ const MemoEditor = () => {
...
@@ -232,6 +234,7 @@ const MemoEditor = () => {
content
,
content
,
visibility
:
editorState
.
memoVisibility
,
visibility
:
editorState
.
memoVisibility
,
resourceIdList
:
editorState
.
resourceList
.
map
((
resource
)
=>
resource
.
id
),
resourceIdList
:
editorState
.
resourceList
.
map
((
resource
)
=>
resource
.
id
),
relationList
:
editorState
.
relationList
,
});
});
}
}
editorStore
.
clearEditMemo
();
editorStore
.
clearEditMemo
();
...
@@ -240,7 +243,7 @@ const MemoEditor = () => {
...
@@ -240,7 +243,7 @@ const MemoEditor = () => {
content
,
content
,
visibility
:
editorState
.
memoVisibility
,
visibility
:
editorState
.
memoVisibility
,
resourceIdList
:
editorState
.
resourceList
.
map
((
resource
)
=>
resource
.
id
),
resourceIdList
:
editorState
.
resourceList
.
map
((
resource
)
=>
resource
.
id
),
relationList
:
[]
,
relationList
:
editorState
.
relationList
,
});
});
filterStore
.
clearFilter
();
filterStore
.
clearFilter
();
}
}
...
@@ -268,7 +271,8 @@ const MemoEditor = () => {
...
@@ -268,7 +271,8 @@ const MemoEditor = () => {
fullscreen
:
false
,
fullscreen
:
false
,
};
};
});
});
editorStore
.
clearResourceList
();
editorStore
.
setResourceList
([]);
editorStore
.
setRelationList
([]);
setEditorContentCache
(
""
);
setEditorContentCache
(
""
);
editorRef
.
current
?.
setContent
(
""
);
editorRef
.
current
?.
setContent
(
""
);
clearContentQueryParam
();
clearContentQueryParam
();
...
@@ -277,7 +281,8 @@ const MemoEditor = () => {
...
@@ -277,7 +281,8 @@ const MemoEditor = () => {
const
handleCancelEdit
=
()
=>
{
const
handleCancelEdit
=
()
=>
{
if
(
editorState
.
editMemoId
)
{
if
(
editorState
.
editMemoId
)
{
editorStore
.
clearEditMemo
();
editorStore
.
clearEditMemo
();
editorStore
.
clearResourceList
();
editorStore
.
setResourceList
([]);
editorStore
.
setRelationList
([]);
editorRef
.
current
?.
setContent
(
""
);
editorRef
.
current
?.
setContent
(
""
);
setEditorContentCache
(
""
);
setEditorContentCache
(
""
);
}
}
...
@@ -381,6 +386,7 @@ const MemoEditor = () => {
...
@@ -381,6 +386,7 @@ const MemoEditor = () => {
</
div
>
</
div
>
</
div
>
</
div
>
<
ResourceListView
/>
<
ResourceListView
/>
<
RelationListView
/>
<
div
className=
"editor-footer-container"
>
<
div
className=
"editor-footer-container"
>
<
MemoVisibilitySelector
/>
<
MemoVisibilitySelector
/>
<
div
className=
"buttons-container"
>
<
div
className=
"buttons-container"
>
...
...
web/src/components/MemoRelationListView.tsx
0 → 100644
View file @
a07d5d38
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useMemoCacheStore
}
from
"@/store/zustand"
;
import
Icon
from
"./Icon"
;
interface
Props
{
relationList
:
MemoRelation
[];
}
const
MemoRelationListView
=
(
props
:
Props
)
=>
{
const
memoCacheStore
=
useMemoCacheStore
();
const
[
relatedMemoList
,
setRelatedMemoList
]
=
useState
<
Memo
[]
>
([]);
const
relationList
=
props
.
relationList
;
useEffect
(()
=>
{
const
fetchRelatedMemoList
=
async
()
=>
{
const
requests
=
relationList
.
map
((
relation
)
=>
memoCacheStore
.
getOrFetchMemoById
(
relation
.
relatedMemoId
));
const
memoList
=
await
Promise
.
all
(
requests
);
setRelatedMemoList
(
memoList
);
};
fetchRelatedMemoList
();
},
[
relationList
]);
return
(
<>
{
relatedMemoList
.
length
>
0
&&
(
<
div
className=
"w-full max-w-full overflow-hidden grid grid-cols-1 gap-1 mt-2"
>
{
relatedMemoList
.
map
((
memo
)
=>
{
return
(
<
div
key=
{
memo
.
id
}
className=
"w-auto flex flex-row justify-start items-center hover:bg-gray-100 dark:hover:bg-zinc-800 rounded text-sm p-1 text-gray-500 dark:text-gray-400 cursor-pointer"
>
<
div
className=
"w-5 h-5 flex justify-center items-center shrink-0 bg-gray-100 dark:bg-zinc-800 rounded-full"
>
<
Icon
.
Link
className=
"w-3 h-auto"
/>
</
div
>
<
span
className=
"mx-1 w-auto truncate"
>
{
memo
.
content
}
</
span
>
</
div
>
);
})
}
</
div
>
)
}
</>
);
};
export
default
MemoRelationListView
;
web/src/layouts/Root.tsx
View file @
a07d5d38
...
@@ -10,7 +10,7 @@ function Root() {
...
@@ -10,7 +10,7 @@ function Root() {
</
div
>
</
div
>
<
div
className=
"w-full max-w-6xl mx-auto flex flex-row justify-center items-start"
>
<
div
className=
"w-full max-w-6xl mx-auto flex flex-row justify-center items-start"
>
<
Header
/>
<
Header
/>
<
main
className=
"w-auto
flex-grow
flex flex-col justify-start items-start"
>
<
main
className=
"w-auto
max-w-full flex-grow shrink
flex flex-col justify-start items-start"
>
<
Outlet
/>
<
Outlet
/>
</
main
>
</
main
>
</
div
>
</
div
>
...
...
web/src/less/create-shortcut-dialog.less
View file @
a07d5d38
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
@apply w-full mt-2 py-1 flex sm:flex-row flex-col justify-start items-start;
@apply w-full mt-2 py-1 flex sm:flex-row flex-col justify-start items-start;
> .normal-text {
> .normal-text {
@apply block
flex-
shrink-0 w-12 mr-3 sm:text-right text-left text-sm leading-8;
@apply block shrink-0 w-12 mr-3 sm:text-right text-left text-sm leading-8;
color: gray;
color: gray;
}
}
...
...
web/src/less/memo-detail.less
deleted
100644 → 0
View file @
ca585929
.page-wrapper.memo-detail {
@apply relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800;
> .page-container {
@apply relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-8;
> .page-header {
@apply sticky top-0 z-10 max-w-2xl w-full min-h-full flex flex-row justify-between items-center px-4 pt-6 mb-2 bg-zinc-100 dark:bg-zinc-800;
> .title-container {
@apply flex flex-row justify-start items-center;
> .logo-img {
@apply h-12 sm:h-14 w-auto mr-2;
}
> .logo-text {
@apply text-4xl tracking-wide text-black dark:text-white;
}
> .title-text {
@apply text-xl sm:text-3xl font-mono text-gray-700;
}
}
> .action-button-container {
> .btn {
@apply block text-gray-600 dark:text-gray-300 font-mono text-base py-1 border px-3 leading-8 rounded-xl hover:opacity-80 hover:underline;
> .icon {
@apply text-lg;
}
}
}
}
> .memos-wrapper {
@apply relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4;
> .memo-container {
@apply flex flex-col justify-start items-start w-full p-4 mt-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-800 hover:border-gray-200 dark:hover:border-zinc-700;
> .memo-header {
@apply mb-2 w-full flex flex-row justify-between items-center;
> .status-container {
@apply flex flex-row justify-start items-center text-sm text-gray-400;
> .name-text {
@apply ml-2 hover:text-green-600 hover:underline;
}
}
}
> .memo-content {
@apply cursor-default;
> * {
@apply cursor-default;
}
}
}
}
}
}
web/src/less/memo-editor.less
View file @
a07d5d38
...
@@ -50,26 +50,6 @@
...
@@ -50,26 +50,6 @@
}
}
}
}
> .resource-list-wrapper {
@apply w-full flex flex-row justify-start flex-wrap;
> .resource-container {
@apply max-w-full mt-1 mr-1 flex flex-row justify-start items-center flex-nowrap bg-gray-100 px-2 py-1 rounded cursor-pointer hover:bg-gray-200;
> .icon-img {
@apply w-4 h-auto mr-1 text-gray-500;
}
> .name-text {
@apply text-gray-500 text-sm max-w-xs truncate font-mono;
}
> .close-icon {
@apply w-4 h-auto ml-1 text-gray-500 hover:text-gray-800;
}
}
}
> .editor-footer-container {
> .editor-footer-container {
@apply w-full flex flex-row justify-between items-center border-t border-t-gray-100 dark:border-t-zinc-500 py-3 mt-2;
@apply w-full flex flex-row justify-between items-center border-t border-t-gray-100 dark:border-t-zinc-500 py-3 mt-2;
...
...
web/src/less/memo.less
View file @
a07d5d38
...
@@ -41,7 +41,7 @@
...
@@ -41,7 +41,7 @@
}
}
> .btns-container {
> .btns-container {
@apply flex flex-row justify-end items-center relative
flex-
shrink-0;
@apply flex flex-row justify-end items-center relative shrink-0;
> .more-action-btns-wrapper {
> .more-action-btns-wrapper {
@apply hidden flex-col justify-start items-center absolute top-2 -right-4 flex-nowrap hover:flex p-3;
@apply hidden flex-col justify-start items-center absolute top-2 -right-4 flex-nowrap hover:flex p-3;
...
...
web/src/pages/Auth.tsx
View file @
a07d5d38
...
@@ -140,7 +140,7 @@ const Auth = () => {
...
@@ -140,7 +140,7 @@ const Auth = () => {
<
div
className=
{
`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading && "opacity-80"}`
}
>
<
div
className=
{
`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading && "opacity-80"}`
}
>
<
div
className=
"flex flex-col justify-start items-start relative w-full text-base mt-2 py-2"
>
<
div
className=
"flex flex-col justify-start items-start relative w-full text-base mt-2 py-2"
>
<
span
<
span
className=
{
`absolute top-3 left-3 px-1 leading-10
flex-
shrink-0 text-base cursor-text text-gray-400 transition-all select-none pointer-events-none ${
className=
{
`absolute top-3 left-3 px-1 leading-10 shrink-0 text-base cursor-text text-gray-400 transition-all select-none pointer-events-none ${
username ? "!text-sm !top-0 !z-10 !leading-4 bg-white dark:bg-zinc-800 rounded" : ""
username ? "!text-sm !top-0 !z-10 !leading-4 bg-white dark:bg-zinc-800 rounded" : ""
}`
}
}`
}
>
>
...
@@ -156,7 +156,7 @@ const Auth = () => {
...
@@ -156,7 +156,7 @@ const Auth = () => {
</
div
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start relative w-full text-base mt-2 py-2"
>
<
div
className=
"flex flex-col justify-start items-start relative w-full text-base mt-2 py-2"
>
<
span
<
span
className=
{
`absolute top-3 left-3 px-1 leading-10
flex-
shrink-0 text-base cursor-text text-gray-400 transition-all select-none pointer-events-none ${
className=
{
`absolute top-3 left-3 px-1 leading-10 shrink-0 text-base cursor-text text-gray-400 transition-all select-none pointer-events-none ${
password ? "!text-sm !top-0 !z-10 !leading-4 bg-white dark:bg-zinc-800 rounded" : ""
password ? "!text-sm !top-0 !z-10 !leading-4 bg-white dark:bg-zinc-800 rounded" : ""
}`
}
}`
}
>
>
...
...
web/src/pages/Home.tsx
View file @
a07d5d38
...
@@ -32,7 +32,7 @@ function Home() {
...
@@ -32,7 +32,7 @@ function Home() {
return
(
return
(
<
div
className=
"w-full flex flex-row justify-start items-start"
>
<
div
className=
"w-full flex flex-row justify-start items-start"
>
<
div
className=
"flex-grow w-auto max-w-2xl px-4 sm:px-2 sm:pt-4"
>
<
div
className=
"flex-grow
shrink
w-auto max-w-2xl px-4 sm:px-2 sm:pt-4"
>
<
MobileHeader
/>
<
MobileHeader
/>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg"
>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg"
>
{
!
userStore
.
isVisitorMode
()
&&
<
MemoEditor
/>
}
{
!
userStore
.
isVisitorMode
()
&&
<
MemoEditor
/>
}
...
...
web/src/pages/MemoDetail.tsx
View file @
a07d5d38
...
@@ -5,10 +5,7 @@ import { Link, useLocation, useParams } from "react-router-dom";
...
@@ -5,10 +5,7 @@ import { Link, useLocation, useParams } from "react-router-dom";
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
UNKNOWN_ID
}
from
"@/helpers/consts"
;
import
{
useGlobalStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
{
useGlobalStore
,
useMemoStore
,
useUserStore
}
from
"@/store/module"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
MemoContent
from
"@/components/MemoContent"
;
import
Memo
from
"@/components/Memo"
;
import
MemoResources
from
"@/components/MemoResources"
;
import
{
getDateTimeString
}
from
"@/helpers/datetime"
;
import
"@/less/memo-detail.less"
;
interface
State
{
interface
State
{
memo
:
Memo
;
memo
:
Memo
;
...
@@ -49,23 +46,29 @@ const MemoDetail = () => {
...
@@ -49,23 +46,29 @@ const MemoDetail = () => {
},
[
location
]);
},
[
location
]);
return
(
return
(
<
section
className=
"
page-wrapper memo-detail
"
>
<
section
className=
"
relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800
"
>
<
div
className=
"
page-container
"
>
<
div
className=
"
relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-8
"
>
<
div
className=
"
page-header
"
>
<
div
className=
"
sticky top-0 z-10 max-w-2xl w-full min-h-full flex flex-row justify-between items-center px-4 py-2 mt-2 bg-zinc-100 dark:bg-zinc-800
"
>
<
div
className=
"
title-contain
er"
>
<
div
className=
"
flex flex-row justify-start items-cent
er"
>
<
img
className=
"h-10 w-auto rounded-lg mr-2"
src=
{
customizedProfile
.
logoUrl
}
alt=
""
/>
<
img
className=
"h-10 w-auto rounded-lg mr-2"
src=
{
customizedProfile
.
logoUrl
}
alt=
""
/>
<
p
className=
"
logo-text
"
>
{
customizedProfile
.
name
}
</
p
>
<
p
className=
"
text-4xl tracking-wide text-black dark:text-white
"
>
{
customizedProfile
.
name
}
</
p
>
</
div
>
</
div
>
<
div
className=
"action-button-container"
>
<
div
className=
"action-button-container"
>
{
!
loadingState
.
isLoading
&&
(
{
!
loadingState
.
isLoading
&&
(
<>
<>
{
user
?
(
{
user
?
(
<
Link
to=
"/"
className=
"btn"
>
<
Link
<
span
className=
"icon"
>
🏠
</
span
>
{
t
(
"router.back-to-home"
)
}
to=
"/"
className=
"block text-gray-600 dark:text-gray-300 font-mono text-base py-1 border px-3 leading-8 rounded-xl hover:opacity-80 hover:underline"
>
<
span
className=
"text-lg"
>
🏠
</
span
>
{
t
(
"router.back-to-home"
)
}
</
Link
>
</
Link
>
)
:
(
)
:
(
<
Link
to=
"/auth"
className=
"btn"
>
<
Link
<
span
className=
"icon"
>
👉
</
span
>
{
t
(
"common.sign-in"
)
}
to=
"/auth"
className=
"block text-gray-600 dark:text-gray-300 font-mono text-base py-1 border px-3 leading-8 rounded-xl hover:opacity-80 hover:underline"
>
<
span
className=
"text-lg"
>
👉
</
span
>
{
t
(
"common.sign-in"
)
}
</
Link
>
</
Link
>
)
}
)
}
</>
</>
...
@@ -73,19 +76,8 @@ const MemoDetail = () => {
...
@@ -73,19 +76,8 @@ const MemoDetail = () => {
</
div
>
</
div
>
</
div
>
</
div
>
{
!
loadingState
.
isLoading
&&
(
{
!
loadingState
.
isLoading
&&
(
<
main
className=
"memos-wrapper"
>
<
main
className=
"relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"
>
<
div
className=
"memo-container"
>
<
Memo
memo=
{
state
.
memo
}
readonly
showRelatedMemos
/>
<
div
className=
"memo-header"
>
<
div
className=
"status-container"
>
<
span
className=
"time-text"
>
{
getDateTimeString
(
state
.
memo
.
createdTs
)
}
</
span
>
<
a
className=
"name-text"
href=
{
`/u/${state.memo.creatorId}`
}
>
@
{
state
.
memo
.
creatorName
}
</
a
>
</
div
>
</
div
>
<
MemoContent
className=
"memo-content"
content=
{
state
.
memo
.
content
}
showFull=
{
true
}
onMemoContentClick=
{
()
=>
undefined
}
/>
<
MemoResources
resourceList=
{
state
.
memo
.
resourceList
}
/>
</
div
>
</
main
>
</
main
>
)
}
)
}
</
div
>
</
div
>
...
...
web/src/store/module/editor.ts
View file @
a07d5d38
import
store
,
{
useAppSelector
}
from
".."
;
import
store
,
{
useAppSelector
}
from
".."
;
import
{
setEditMemoId
,
setMemoVisibility
,
setResourceList
}
from
"../reducer/editor"
;
import
{
setEditMemoId
,
setMemoVisibility
,
setRe
lationList
,
setRe
sourceList
}
from
"../reducer/editor"
;
export
const
useEditorStore
=
()
=>
{
export
const
useEditorStore
=
()
=>
{
const
state
=
useAppSelector
((
state
)
=>
state
.
editor
);
const
state
=
useAppSelector
((
state
)
=>
state
.
editor
);
...
@@ -21,8 +21,8 @@ export const useEditorStore = () => {
...
@@ -21,8 +21,8 @@ export const useEditorStore = () => {
setResourceList
:
(
resourceList
:
Resource
[])
=>
{
setResourceList
:
(
resourceList
:
Resource
[])
=>
{
store
.
dispatch
(
setResourceList
(
resourceList
));
store
.
dispatch
(
setResourceList
(
resourceList
));
},
},
clearResourceList
:
(
)
=>
{
setRelationList
:
(
relationList
:
MemoRelation
[]
)
=>
{
store
.
dispatch
(
setRe
sourceList
([]
));
store
.
dispatch
(
setRe
lationList
(
relationList
));
},
},
};
};
};
};
web/src/store/module/memo.ts
View file @
a07d5d38
...
@@ -4,8 +4,9 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
...
@@ -4,8 +4,9 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import
{
useUserStore
}
from
"./"
;
import
{
useUserStore
}
from
"./"
;
import
store
,
{
useAppSelector
}
from
"../"
;
import
store
,
{
useAppSelector
}
from
"../"
;
import
{
createMemo
,
deleteMemo
,
patchMemo
,
setIsFetching
,
upsertMemos
}
from
"../reducer/memo"
;
import
{
createMemo
,
deleteMemo
,
patchMemo
,
setIsFetching
,
upsertMemos
}
from
"../reducer/memo"
;
import
{
useMemoCacheStore
}
from
"../zustand/memo"
;
const
convertResponseModelMemo
=
(
memo
:
Memo
):
Memo
=>
{
export
const
convertResponseModelMemo
=
(
memo
:
Memo
):
Memo
=>
{
return
{
return
{
...
memo
,
...
memo
,
createdTs
:
memo
.
createdTs
*
1000
,
createdTs
:
memo
.
createdTs
*
1000
,
...
@@ -16,6 +17,7 @@ const convertResponseModelMemo = (memo: Memo): Memo => {
...
@@ -16,6 +17,7 @@ const convertResponseModelMemo = (memo: Memo): Memo => {
export
const
useMemoStore
=
()
=>
{
export
const
useMemoStore
=
()
=>
{
const
state
=
useAppSelector
((
state
)
=>
state
.
memo
);
const
state
=
useAppSelector
((
state
)
=>
state
.
memo
);
const
userStore
=
useUserStore
();
const
userStore
=
useUserStore
();
const
memoCacheStore
=
useMemoCacheStore
();
const
fetchMemoById
=
async
(
memoId
:
MemoId
)
=>
{
const
fetchMemoById
=
async
(
memoId
:
MemoId
)
=>
{
const
{
data
}
=
(
await
api
.
getMemoById
(
memoId
)).
data
;
const
{
data
}
=
(
await
api
.
getMemoById
(
memoId
)).
data
;
...
@@ -44,6 +46,10 @@ export const useMemoStore = () => {
...
@@ -44,6 +46,10 @@ export const useMemoStore = () => {
store
.
dispatch
(
upsertMemos
(
fetchedMemos
));
store
.
dispatch
(
upsertMemos
(
fetchedMemos
));
store
.
dispatch
(
setIsFetching
(
false
));
store
.
dispatch
(
setIsFetching
(
false
));
for
(
const
m
of
fetchedMemos
)
{
memoCacheStore
.
setMemoCache
(
m
);
}
return
fetchedMemos
;
return
fetchedMemos
;
},
},
fetchAllMemos
:
async
(
limit
=
DEFAULT_MEMO_LIMIT
,
offset
?:
number
)
=>
{
fetchAllMemos
:
async
(
limit
=
DEFAULT_MEMO_LIMIT
,
offset
?:
number
)
=>
{
...
@@ -54,8 +60,13 @@ export const useMemoStore = () => {
...
@@ -54,8 +60,13 @@ export const useMemoStore = () => {
};
};
const
{
data
}
=
(
await
api
.
getAllMemos
(
memoFind
)).
data
;
const
{
data
}
=
(
await
api
.
getAllMemos
(
memoFind
)).
data
;
const
memos
=
data
.
map
((
m
)
=>
convertResponseModelMemo
(
m
));
const
fetchedMemos
=
data
.
map
((
m
)
=>
convertResponseModelMemo
(
m
));
return
memos
;
for
(
const
m
of
fetchedMemos
)
{
memoCacheStore
.
setMemoCache
(
m
);
}
return
fetchedMemos
;
},
},
fetchArchivedMemos
:
async
()
=>
{
fetchArchivedMemos
:
async
()
=>
{
const
memoFind
:
MemoFind
=
{
const
memoFind
:
MemoFind
=
{
...
@@ -88,12 +99,14 @@ export const useMemoStore = () => {
...
@@ -88,12 +99,14 @@ export const useMemoStore = () => {
const
{
data
}
=
(
await
api
.
createMemo
(
memoCreate
)).
data
;
const
{
data
}
=
(
await
api
.
createMemo
(
memoCreate
)).
data
;
const
memo
=
convertResponseModelMemo
(
data
);
const
memo
=
convertResponseModelMemo
(
data
);
store
.
dispatch
(
createMemo
(
memo
));
store
.
dispatch
(
createMemo
(
memo
));
memoCacheStore
.
setMemoCache
(
memo
);
return
memo
;
return
memo
;
},
},
patchMemo
:
async
(
memoPatch
:
MemoPatch
):
Promise
<
Memo
>
=>
{
patchMemo
:
async
(
memoPatch
:
MemoPatch
):
Promise
<
Memo
>
=>
{
const
{
data
}
=
(
await
api
.
patchMemo
(
memoPatch
)).
data
;
const
{
data
}
=
(
await
api
.
patchMemo
(
memoPatch
)).
data
;
const
memo
=
convertResponseModelMemo
(
data
);
const
memo
=
convertResponseModelMemo
(
data
);
store
.
dispatch
(
patchMemo
(
omit
(
memo
,
"pinned"
)));
store
.
dispatch
(
patchMemo
(
omit
(
memo
,
"pinned"
)));
memoCacheStore
.
setMemoCache
(
memo
);
return
memo
;
return
memo
;
},
},
pinMemo
:
async
(
memoId
:
MemoId
)
=>
{
pinMemo
:
async
(
memoId
:
MemoId
)
=>
{
...
@@ -117,6 +130,7 @@ export const useMemoStore = () => {
...
@@ -117,6 +130,7 @@ export const useMemoStore = () => {
deleteMemoById
:
async
(
memoId
:
MemoId
)
=>
{
deleteMemoById
:
async
(
memoId
:
MemoId
)
=>
{
await
api
.
deleteMemo
(
memoId
);
await
api
.
deleteMemo
(
memoId
);
store
.
dispatch
(
deleteMemo
(
memoId
));
store
.
dispatch
(
deleteMemo
(
memoId
));
memoCacheStore
.
deleteMemoCache
(
memoId
);
},
},
};
};
};
};
web/src/store/reducer/editor.ts
View file @
a07d5d38
...
@@ -3,6 +3,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
...
@@ -3,6 +3,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface
State
{
interface
State
{
memoVisibility
:
Visibility
;
memoVisibility
:
Visibility
;
resourceList
:
Resource
[];
resourceList
:
Resource
[];
relationList
:
MemoRelation
[];
editMemoId
?:
MemoId
;
editMemoId
?:
MemoId
;
}
}
...
@@ -11,6 +12,7 @@ const editorSlice = createSlice({
...
@@ -11,6 +12,7 @@ const editorSlice = createSlice({
initialState
:
{
initialState
:
{
memoVisibility
:
"PRIVATE"
,
memoVisibility
:
"PRIVATE"
,
resourceList
:
[],
resourceList
:
[],
relationList
:
[],
}
as
State
,
}
as
State
,
reducers
:
{
reducers
:
{
setEditMemoId
:
(
state
,
action
:
PayloadAction
<
Option
<
MemoId
>>
)
=>
{
setEditMemoId
:
(
state
,
action
:
PayloadAction
<
Option
<
MemoId
>>
)
=>
{
...
@@ -31,9 +33,15 @@ const editorSlice = createSlice({
...
@@ -31,9 +33,15 @@ const editorSlice = createSlice({
resourceList
:
action
.
payload
,
resourceList
:
action
.
payload
,
};
};
},
},
setRelationList
:
(
state
,
action
:
PayloadAction
<
MemoRelation
[]
>
)
=>
{
return
{
...
state
,
relationList
:
action
.
payload
,
};
},
},
},
});
});
export
const
{
setEditMemoId
,
setMemoVisibility
,
setResourceList
}
=
editorSlice
.
actions
;
export
const
{
setEditMemoId
,
setMemoVisibility
,
setResourceList
,
setRelationList
}
=
editorSlice
.
actions
;
export
default
editorSlice
.
reducer
;
export
default
editorSlice
.
reducer
;
web/src/store/zustand/index.ts
0 → 100644
View file @
a07d5d38
export
{
useMemoCacheStore
}
from
"./memo"
;
export
{
useMessageStore
}
from
"./message"
;
web/src/store/zustand/memo.ts
0 → 100644
View file @
a07d5d38
import
{
create
}
from
"zustand"
;
import
{
combine
}
from
"zustand/middleware"
;
import
*
as
api
from
"@/helpers/api"
;
import
{
convertResponseModelMemo
}
from
"../module"
;
export
const
useMemoCacheStore
=
create
(
combine
({
memoById
:
new
Map
<
MemoId
,
Memo
>
()
},
(
set
,
get
)
=>
({
getState
:
()
=>
get
(),
getOrFetchMemoById
:
async
(
memoId
:
MemoId
)
=>
{
const
memo
=
get
().
memoById
.
get
(
memoId
);
if
(
memo
)
{
return
memo
;
}
const
{
data
}
=
(
await
api
.
getMemoById
(
memoId
)).
data
;
const
formatedMemo
=
convertResponseModelMemo
(
data
);
set
((
state
)
=>
{
state
.
memoById
.
set
(
memoId
,
formatedMemo
);
return
state
;
});
return
formatedMemo
;
},
getMemoById
:
(
memoId
:
MemoId
)
=>
{
return
get
().
memoById
.
get
(
memoId
);
},
setMemoCache
:
(
memo
:
Memo
)
=>
{
set
((
state
)
=>
{
state
.
memoById
.
set
(
memo
.
id
,
memo
);
return
state
;
});
},
deleteMemoCache
:
(
memoId
:
MemoId
)
=>
{
set
((
state
)
=>
{
state
.
memoById
.
delete
(
memoId
);
return
state
;
});
},
}))
);
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