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
a9218ed5
Unverified
Commit
a9218ed5
authored
Mar 11, 2023
by
boojack
Committed by
GitHub
Mar 11, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: filter store (#1331)
parent
f3f0efba
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
215 additions
and
205 deletions
+215
-205
App.tsx
web/src/App.tsx
+1
-9
AboutSiteDialog.tsx
web/src/components/AboutSiteDialog.tsx
+12
-4
Header.tsx
web/src/components/Header.tsx
+4
-3
HomeSidebar.tsx
web/src/components/HomeSidebar.tsx
+4
-2
Memo.tsx
web/src/components/Memo.tsx
+8
-8
MemoEditor.tsx
web/src/components/MemoEditor.tsx
+3
-11
MemoFilter.tsx
web/src/components/MemoFilter.tsx
+17
-10
MemoList.tsx
web/src/components/MemoList.tsx
+6
-6
MobileHeader.tsx
web/src/components/MobileHeader.tsx
+6
-6
SearchBar.tsx
web/src/components/SearchBar.tsx
+5
-5
ShortcutList.tsx
web/src/components/ShortcutList.tsx
+9
-9
TagList.tsx
web/src/components/TagList.tsx
+7
-7
UsageHeatMap.tsx
web/src/components/UsageHeatMap.tsx
+5
-5
Explore.tsx
web/src/pages/Explore.tsx
+6
-5
MemoDetail.tsx
web/src/pages/MemoDetail.tsx
+3
-4
index.ts
web/src/store/index.ts
+2
-2
filter.ts
web/src/store/module/filter.ts
+77
-0
index.ts
web/src/store/module/index.ts
+1
-1
user.ts
web/src/store/module/user.ts
+1
-1
filter.ts
web/src/store/reducer/filter.ts
+38
-0
location.ts
web/src/store/reducer/location.ts
+0
-107
location.d.ts
web/src/types/location.d.ts
+0
-0
No files found.
web/src/App.tsx
View file @
a9218ed5
...
...
@@ -4,7 +4,7 @@ import { Toaster } from "react-hot-toast";
import
{
useTranslation
}
from
"react-i18next"
;
import
{
RouterProvider
}
from
"react-router-dom"
;
import
router
from
"./router"
;
import
{
use
LocationStore
,
use
GlobalStore
}
from
"./store/module"
;
import
{
useGlobalStore
}
from
"./store/module"
;
import
*
as
storage
from
"./helpers/storage"
;
import
{
getSystemColorScheme
}
from
"./helpers/utils"
;
import
Loading
from
"./pages/Loading"
;
...
...
@@ -12,17 +12,9 @@ import Loading from "./pages/Loading";
const
App
=
()
=>
{
const
{
i18n
}
=
useTranslation
();
const
globalStore
=
useGlobalStore
();
const
locationStore
=
useLocationStore
();
const
{
mode
,
setMode
}
=
useColorScheme
();
const
{
appearance
,
locale
,
systemStatus
}
=
globalStore
.
state
;
useEffect
(()
=>
{
locationStore
.
updateStateWithLocation
();
window
.
onpopstate
=
()
=>
{
locationStore
.
updateStateWithLocation
();
};
},
[]);
useEffect
(()
=>
{
const
darkMediaQuery
=
window
.
matchMedia
(
"(prefers-color-scheme: dark)"
);
const
handleColorSchemeChange
=
(
e
:
MediaQueryListEvent
)
=>
{
...
...
web/src/components/AboutSiteDialog.tsx
View file @
a9218ed5
...
...
@@ -26,9 +26,9 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
<
Icon
.
X
/>
</
button
>
</
div
>
<
div
className=
"flex flex-col justify-start items-start max-w-full"
>
<
div
className=
"flex flex-col justify-start items-start max-w-full
w-96
"
>
<
p
className=
"text-sm"
>
{
customizedProfile
.
description
||
"No description"
}
</
p
>
<
div
className=
"mt-4 flex flex-row text-sm justify-start items-center"
>
<
div
className=
"mt-4
w-full
flex flex-row text-sm justify-start items-center"
>
<
div
className=
"flex flex-row justify-start items-center mr-2"
>
Powered by
<
a
href=
"https://usememos.com"
target=
"_blank"
className=
"flex flex-row justify-start items-center mr-1 hover:underline"
>
...
...
@@ -39,8 +39,8 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
</
div
>
<
GitHubBadge
/>
</
div
>
<
div
className=
"border-t
mt-3 pt-2 text-sm flex flex-row justify-start items-center
"
>
<
span
className=
"text-gray-500
mr-2
"
>
Other projects:
</
span
>
<
div
className=
"border-t
w-full mt-3 pt-2 text-sm flex flex-row justify-start items-center space-x-2
"
>
<
span
className=
"text-gray-500"
>
Other projects:
</
span
>
<
a
href=
"https://github.com/boojack/sticky-notes"
target=
"_blank"
...
...
@@ -53,6 +53,14 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
/>
<
span
>
Sticky notes
</
span
>
</
a
>
<
a
href=
"https://github.com/boojack/sticky-notes"
target=
"_blank"
className=
"flex items-center underline text-blue-600 hover:opacity-80"
>
<
img
className=
"w-4 h-auto mr-1"
src=
"https://star-history.com/icon.png"
alt=
""
/>
<
span
>
Star history
</
span
>
</
a
>
</
div
>
</
div
>
</>
...
...
web/src/components/Header.tsx
View file @
a9218ed5
import
{
useEffect
}
from
"react"
;
import
{
NavLink
}
from
"react-router-dom"
;
import
{
NavLink
,
useLocation
}
from
"react-router-dom"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useLayoutStore
,
useUserStore
}
from
"../store/module"
;
import
{
resolution
}
from
"../utils/layout"
;
...
...
@@ -12,6 +12,7 @@ import UserBanner from "./UserBanner";
const
Header
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
location
=
useLocation
();
const
userStore
=
useUserStore
();
const
layoutStore
=
useLayoutStore
();
const
showHeader
=
layoutStore
.
state
.
showHeader
;
...
...
@@ -27,7 +28,7 @@ const Header = () => {
};
window
.
addEventListener
(
"resize"
,
handleWindowResize
);
handleWindowResize
();
},
[]);
},
[
location
]);
return
(
<
div
...
...
@@ -42,7 +43,7 @@ const Header = () => {
onClick=
{
()
=>
layoutStore
.
setHeaderStatus
(
false
)
}
></
div
>
<
header
className=
{
`relative w-56 sm:w-full h-full max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start py-4 z-30 bg-
white
dark:bg-zinc-800 sm:bg-transparent sm:shadow-none transition-all duration-300 -translate-x-full sm:translate-x-0 ${
className=
{
`relative w-56 sm:w-full h-full max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start py-4 z-30 bg-
zinc-100
dark:bg-zinc-800 sm:bg-transparent sm:shadow-none transition-all duration-300 -translate-x-full sm:translate-x-0 ${
showHeader && "translate-x-0 shadow-2xl"
}`
}
>
...
...
web/src/components/HomeSidebar.tsx
View file @
a9218ed5
...
...
@@ -5,8 +5,10 @@ import ShortcutList from "./ShortcutList";
import
TagList
from
"./TagList"
;
import
SearchBar
from
"./SearchBar"
;
import
UsageHeatMap
from
"./UsageHeatMap"
;
import
{
useLocation
}
from
"react-router-dom"
;
const
HomeSidebar
=
()
=>
{
const
location
=
useLocation
();
const
layoutStore
=
useLayoutStore
();
const
showHomeSidebar
=
layoutStore
.
state
.
showHomeSidebar
;
...
...
@@ -20,7 +22,7 @@ const HomeSidebar = () => {
};
window
.
addEventListener
(
"resize"
,
handleWindowResize
);
handleWindowResize
();
},
[]);
},
[
location
]);
return
(
<
div
...
...
@@ -35,7 +37,7 @@ const HomeSidebar = () => {
onClick=
{
()
=>
layoutStore
.
setHomeSidebarStatus
(
false
)
}
></
div
>
<
aside
className=
{
`absolute md:relative top-0 right-0 w-56 pr-2 md:w-full h-full max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start py-4 z-30 bg-
white
dark:bg-zinc-800 md:bg-transparent md:shadow-none transition-all duration-300 translate-x-full md:translate-x-0 ${
className=
{
`absolute md:relative top-0 right-0 w-56 pr-2 md:w-full h-full max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start py-4 z-30 bg-
zinc-100
dark:bg-zinc-800 md:bg-transparent md:shadow-none transition-all duration-300 translate-x-full md:translate-x-0 ${
showHomeSidebar && "!translate-x-0 shadow-2xl"
}`
}
>
...
...
web/src/components/Memo.tsx
View file @
a9218ed5
...
...
@@ -4,7 +4,7 @@ import { memo, useEffect, useRef, useState } from "react";
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
Link
,
useNavigate
}
from
"react-router-dom"
;
import
{
useEditorStore
,
use
Location
Store
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
{
useEditorStore
,
use
Filter
Store
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
Icon
from
"./Icon"
;
import
MemoContent
from
"./MemoContent"
;
import
MemoResources
from
"./MemoResources"
;
...
...
@@ -32,7 +32,7 @@ const Memo: React.FC<Props> = (props: Props) => {
const
{
t
,
i18n
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
editorStore
=
useEditorStore
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
userStore
=
useUserStore
();
const
memoStore
=
useMemoStore
();
const
[
createdTimeStr
,
setCreatedTimeStr
]
=
useState
<
string
>
(
getFormatedMemoTimeStr
(
memo
.
createdTs
,
i18n
.
language
));
...
...
@@ -106,11 +106,11 @@ const Memo: React.FC<Props> = (props: Props) => {
if
(
targetEl
.
className
===
"tag-span"
)
{
const
tagName
=
targetEl
.
innerText
.
slice
(
1
);
const
currTagQuery
=
locationStore
.
getState
().
query
?
.
tag
;
const
currTagQuery
=
filterStore
.
getState
()
.
tag
;
if
(
currTagQuery
===
tagName
)
{
locationStore
.
setTagQuery
(
undefined
);
filterStore
.
setTagFilter
(
undefined
);
}
else
{
locationStore
.
setTagQuery
(
tagName
);
filterStore
.
setTagFilter
(
tagName
);
}
}
else
if
(
targetEl
.
classList
.
contains
(
"todo-block"
))
{
if
(
isVisitorMode
)
{
...
...
@@ -176,11 +176,11 @@ const Memo: React.FC<Props> = (props: Props) => {
};
const
handleMemoVisibilityClick
=
(
visibility
:
Visibility
)
=>
{
const
currVisibilityQuery
=
locationStore
.
getState
().
query
?
.
visibility
;
const
currVisibilityQuery
=
filterStore
.
getState
()
.
visibility
;
if
(
currVisibilityQuery
===
visibility
)
{
locationStore
.
setMemoVisibilityQuery
(
undefined
);
filterStore
.
setMemoVisibilityFilter
(
undefined
);
}
else
{
locationStore
.
setMemoVisibilityQuery
(
visibility
);
filterStore
.
setMemoVisibilityFilter
(
visibility
);
}
};
...
...
web/src/components/MemoEditor.tsx
View file @
a9218ed5
...
...
@@ -5,15 +5,7 @@ import { useTranslation } from "react-i18next";
import
{
getMatchedNodes
}
from
"../labs/marked"
;
import
{
deleteMemoResource
,
upsertMemoResource
}
from
"../helpers/api"
;
import
{
TAB_SPACE_WIDTH
,
UNKNOWN_ID
,
VISIBILITY_SELECTOR_ITEMS
}
from
"../helpers/consts"
;
import
{
useEditorStore
,
useGlobalStore
,
useLocationStore
,
useMemoStore
,
useResourceStore
,
useTagStore
,
useUserStore
,
}
from
"../store/module"
;
import
{
useEditorStore
,
useGlobalStore
,
useFilterStore
,
useMemoStore
,
useResourceStore
,
useTagStore
,
useUserStore
}
from
"../store/module"
;
import
*
as
storage
from
"../helpers/storage"
;
import
Icon
from
"./Icon"
;
import
Selector
from
"./base/Selector"
;
...
...
@@ -46,7 +38,7 @@ const MemoEditor = () => {
const
{
t
,
i18n
}
=
useTranslation
();
const
userStore
=
useUserStore
();
const
editorStore
=
useEditorStore
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
memoStore
=
useMemoStore
();
const
tagStore
=
useTagStore
();
const
resourceStore
=
useResourceStore
();
...
...
@@ -289,7 +281,7 @@ const MemoEditor = () => {
visibility
:
editorState
.
memoVisibility
,
resourceIdList
:
editorState
.
resourceList
.
map
((
resource
)
=>
resource
.
id
),
});
locationStore
.
clearQuery
();
filterStore
.
clearFilter
();
}
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
...
...
web/src/components/MemoFilter.tsx
View file @
a9218ed5
import
{
useEffect
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useLocationStore
,
useShortcutStore
}
from
"../store/module"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
{
useFilterStore
,
useShortcutStore
}
from
"../store/module"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
getTextWithMemoType
}
from
"../helpers/filter"
;
import
Icon
from
"./Icon"
;
...
...
@@ -7,20 +9,25 @@ import "../less/memo-filter.less";
const
MemoFilter
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocationStore
();
const
location
=
useLocation
();
const
filterStore
=
useFilterStore
();
const
shortcutStore
=
useShortcutStore
();
const
query
=
locationStore
.
state
.
query
;
const
{
tag
:
tagQuery
,
duration
,
type
:
memoType
,
text
:
textQuery
,
shortcutId
,
visibility
}
=
query
;
const
filter
=
filterStore
.
state
;
const
{
tag
:
tagQuery
,
duration
,
type
:
memoType
,
text
:
textQuery
,
shortcutId
,
visibility
}
=
filter
;
const
shortcut
=
shortcutId
?
shortcutStore
.
getShortcutById
(
shortcutId
)
:
null
;
const
showFilter
=
Boolean
(
tagQuery
||
(
duration
&&
duration
.
from
<
duration
.
to
)
||
memoType
||
textQuery
||
shortcut
||
visibility
);
useEffect
(()
=
>
{
filterStore
.
clearFilter
();
}
, [location]);
return (
<
div
className=
{
`filter-query-container ${showFilter ? "" : "!hidden"}`
}
>
<
span
className=
"mx-2 text-gray-400"
>
{
t
(
"common.filter"
)
}
:
</
span
>
<
div
className=
{
"filter-item-container "
+
(
shortcut
?
""
:
"!hidden"
)
}
onClick=
{
()
=>
{
location
Store
.
setMemoShortcut
(
undefined
);
filter
Store
.
setMemoShortcut
(
undefined
);
}
}
>
<
Icon
.
Target
className=
"icon-text"
/>
{
shortcut
?.
title
}
...
...
@@ -28,7 +35,7 @@ const MemoFilter = () => {
<
div
className=
{
"filter-item-container "
+
(
tagQuery
?
""
:
"!hidden"
)
}
onClick=
{
()
=>
{
locationStore
.
setTagQuery
(
undefined
);
filterStore
.
setTagFilter
(
undefined
);
}
}
>
<
Icon
.
Tag
className=
"icon-text"
/>
{
tagQuery
}
...
...
@@ -36,7 +43,7 @@ const MemoFilter = () => {
<
div
className=
{
"filter-item-container "
+
(
memoType
?
""
:
"!hidden"
)
}
onClick=
{
()
=>
{
locationStore
.
setMemoTypeQuery
(
undefined
);
filterStore
.
setMemoTypeFilter
(
undefined
);
}
}
>
<
Icon
.
Box
className=
"icon-text"
/>
{
t
(
getTextWithMemoType
(
memoType
as
MemoSpecType
))
}
...
...
@@ -44,7 +51,7 @@ const MemoFilter = () => {
<
div
className=
{
"filter-item-container "
+
(
visibility
?
""
:
"!hidden"
)
}
onClick=
{
()
=>
{
locationStore
.
setMemoVisibilityQuery
(
undefined
);
filterStore
.
setMemoVisibilityFilter
(
undefined
);
}
}
>
<
Icon
.
Eye
className=
"icon-text"
/>
{
visibility
}
...
...
@@ -53,7 +60,7 @@ const MemoFilter = () => {
<
div
className=
"filter-item-container"
onClick=
{
()
=>
{
locationStore
.
setFromAndToQuery
();
filterStore
.
setFromAndToFilter
();
}
}
>
<
Icon
.
Calendar
className=
"icon-text"
/>
{
utils
.
getDateString
(
duration
.
from
)
}
to
{
utils
.
getDateString
(
duration
.
to
)
}
...
...
@@ -62,7 +69,7 @@ const MemoFilter = () => {
<
div
className=
{
"filter-item-container "
+
(
textQuery
?
""
:
"!hidden"
)
}
onClick=
{
()
=>
{
locationStore
.
setTextQuery
(
undefined
);
filterStore
.
setTextFilter
(
undefined
);
}
}
>
<
Icon
.
Search
className=
"icon-text"
/>
{
textQuery
}
...
...
web/src/components/MemoList.tsx
View file @
a9218ed5
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
use
Location
Store
,
useMemoStore
,
useShortcutStore
,
useUserStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useMemoStore
,
useShortcutStore
,
useUserStore
}
from
"../store/module"
;
import
{
TAG_REG
,
LINK_REG
}
from
"../labs/marked/parser"
;
import
*
as
utils
from
"../helpers/utils"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"../helpers/consts"
;
...
...
@@ -14,13 +14,13 @@ const MemoList = () => {
const
memoStore
=
useMemoStore
();
const
userStore
=
useUserStore
();
const
shortcutStore
=
useShortcutStore
();
const
locationStore
=
useLocation
Store
();
const
query
=
locationStore
.
state
.
query
;
const
filterStore
=
useFilter
Store
();
const
filter
=
filterStore
.
state
;
const
{
memos
,
isFetching
}
=
memoStore
.
state
;
const
[
isComplete
,
setIsComplete
]
=
useState
<
boolean
>
(
false
);
const
currentUserId
=
userStore
.
getCurrentUserId
();
const
{
tag
:
tagQuery
,
duration
,
type
:
memoType
,
text
:
textQuery
,
shortcutId
,
visibility
}
=
query
??
{}
;
const
{
tag
:
tagQuery
,
duration
,
type
:
memoType
,
text
:
textQuery
,
shortcutId
,
visibility
}
=
filter
;
const
shortcut
=
shortcutId
?
shortcutStore
.
getShortcutById
(
shortcutId
)
:
null
;
const
showMemoFilter
=
Boolean
(
tagQuery
||
(
duration
&&
duration
.
from
<
duration
.
to
)
||
memoType
||
textQuery
||
shortcut
||
visibility
);
...
...
@@ -107,7 +107,7 @@ const MemoList = () => {
if
(
pageWrapper
)
{
pageWrapper
.
scrollTo
(
0
,
0
);
}
}
, [
query
]);
}
, [
filter
]);
useEffect(() =
>
{
if
(
isFetching
||
isComplete
)
{
...
...
@@ -116,7 +116,7 @@ const MemoList = () => {
if
(
sortedMemos
.
length
<
DEFAULT_MEMO_LIMIT
)
{
handleFetchMoreClick
();
}
}
, [isFetching, isComplete,
query
, sortedMemos.length]);
}
, [isFetching, isComplete,
filter
, sortedMemos.length]);
const handleFetchMoreClick = async () =
>
{
try
{
...
...
web/src/components/MobileHeader.tsx
View file @
a9218ed5
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useLayoutStore
,
use
Location
Store
,
useShortcutStore
}
from
"../store/module"
;
import
{
useLayoutStore
,
use
Filter
Store
,
useShortcutStore
}
from
"../store/module"
;
import
Icon
from
"./Icon"
;
interface
Props
{
...
...
@@ -8,24 +8,24 @@ interface Props {
const
MobileHeader
=
(
props
:
Props
)
=>
{
const
{
showSearch
=
true
}
=
props
;
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
shortcutStore
=
useShortcutStore
();
const
layoutStore
=
useLayoutStore
();
const
query
=
locationStore
.
state
.
query
;
const
filter
=
filterStore
.
state
;
const
shortcuts
=
shortcutStore
.
state
.
shortcuts
;
const
[
titleText
,
setTitleText
]
=
useState
(
"MEMOS"
);
useEffect
(()
=>
{
if
(
!
query
?
.
shortcutId
)
{
if
(
!
filter
.
shortcutId
)
{
setTitleText
(
"MEMOS"
);
return
;
}
const
shortcut
=
shortcutStore
.
getShortcutById
(
query
?
.
shortcutId
);
const
shortcut
=
shortcutStore
.
getShortcutById
(
filter
.
shortcutId
);
if
(
shortcut
)
{
setTitleText
(
shortcut
.
title
);
}
},
[
query
,
shortcuts
]);
},
[
filter
,
shortcuts
]);
return
(
<
div
className=
"sticky top-0 pt-4 pb-1 mb-1 backdrop-blur-sm flex sm:hidden flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-1"
>
...
...
web/src/components/SearchBar.tsx
View file @
a9218ed5
import
{
useEffect
,
useState
,
useRef
}
from
"react"
;
import
useDebounce
from
"../hooks/useDebounce"
;
import
{
use
Location
Store
,
useDialogStore
,
useLayoutStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useDialogStore
,
useLayoutStore
}
from
"../store/module"
;
import
{
resolution
}
from
"../utils/layout"
;
import
Icon
from
"./Icon"
;
const
SearchBar
=
()
=>
{
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
dialogStore
=
useDialogStore
();
const
layoutStore
=
useLayoutStore
();
const
[
queryText
,
setQueryText
]
=
useState
(
""
);
...
...
@@ -33,9 +33,9 @@ const SearchBar = () => {
},
[]);
useEffect
(()
=>
{
const
text
=
locationStore
.
getState
().
query
.
text
;
const
text
=
filterStore
.
getState
()
.
text
;
setQueryText
(
text
===
undefined
?
""
:
text
);
},
[
locationStore
.
state
.
query
.
text
]);
},
[
filterStore
.
state
.
text
]);
useEffect
(()
=>
{
if
(
layoutStore
.
state
.
showHomeSidebar
)
{
...
...
@@ -47,7 +47,7 @@ const SearchBar = () => {
useDebounce
(
()
=>
{
locationStore
.
setTextQuery
(
queryText
.
length
===
0
?
undefined
:
queryText
);
filterStore
.
setTextFilter
(
queryText
.
length
===
0
?
undefined
:
queryText
);
},
200
,
[
queryText
]
...
...
web/src/components/ShortcutList.tsx
View file @
a9218ed5
import
{
useEffect
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
use
Location
Store
,
useShortcutStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useShortcutStore
}
from
"../store/module"
;
import
*
as
utils
from
"../helpers/utils"
;
import
useToggle
from
"../hooks/useToggle"
;
import
useLoading
from
"../hooks/useLoading"
;
...
...
@@ -10,9 +10,9 @@ import showCreateShortcutDialog from "./CreateShortcutDialog";
const
ShortcutList
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
shortcutStore
=
useShortcutStore
();
const
query
=
locationStore
.
state
.
query
;
const
filter
=
filterStore
.
state
;
const
shortcuts
=
shortcutStore
.
state
.
shortcuts
;
const
loadingState
=
useLoading
();
...
...
@@ -48,7 +48,7 @@ const ShortcutList = () => {
</
div
>
<
div
className=
"flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mb-2"
>
{
sortedShortcuts
.
map
((
s
)
=>
{
return
<
ShortcutContainer
key=
{
s
.
id
}
shortcut=
{
s
}
isActive=
{
s
.
id
===
Number
(
query
?.
shortcutId
)
}
/>;
return
<
ShortcutContainer
key=
{
s
.
id
}
shortcut=
{
s
}
isActive=
{
s
.
id
===
Number
(
filter
?.
shortcutId
)
}
/>;
})
}
</
div
>
</
div
>
...
...
@@ -63,15 +63,15 @@ interface ShortcutContainerProps {
const
ShortcutContainer
:
React
.
FC
<
ShortcutContainerProps
>
=
(
props
:
ShortcutContainerProps
)
=>
{
const
{
shortcut
,
isActive
}
=
props
;
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
shortcutStore
=
useShortcutStore
();
const
[
showConfirmDeleteBtn
,
toggleConfirmDeleteBtn
]
=
useToggle
(
false
);
const
handleShortcutClick
=
()
=>
{
if
(
isActive
)
{
location
Store
.
setMemoShortcut
(
undefined
);
filter
Store
.
setMemoShortcut
(
undefined
);
}
else
{
location
Store
.
setMemoShortcut
(
shortcut
.
id
);
filter
Store
.
setMemoShortcut
(
shortcut
.
id
);
}
};
...
...
@@ -81,9 +81,9 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
if
(
showConfirmDeleteBtn
)
{
try
{
await
shortcutStore
.
deleteShortcutById
(
shortcut
.
id
);
if
(
locationStore
.
getState
().
query
?
.
shortcutId
===
shortcut
.
id
)
{
if
(
filterStore
.
getState
()
.
shortcutId
===
shortcut
.
id
)
{
// need clear shortcut filter
location
Store
.
setMemoShortcut
(
undefined
);
filter
Store
.
setMemoShortcut
(
undefined
);
}
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
...
...
web/src/components/TagList.tsx
View file @
a9218ed5
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
use
Location
Store
,
useTagStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useTagStore
}
from
"../store/module"
;
import
useToggle
from
"../hooks/useToggle"
;
import
Icon
from
"./Icon"
;
import
showCreateTagDialog
from
"./CreateTagDialog"
;
...
...
@@ -13,10 +13,10 @@ interface Tag {
const
TagList
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
tagStore
=
useTagStore
();
const
tagsText
=
tagStore
.
state
.
tags
;
const
query
=
locationStore
.
state
.
query
;
const
filter
=
filterStore
.
state
;
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
useEffect
(()
=>
{
...
...
@@ -80,7 +80,7 @@ const TagList = () => {
</
div
>
<
div
className=
"flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mt-2 mb-2"
>
{
tags
.
map
((
t
,
idx
)
=>
(
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
query
?
.
tag
}
/>
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
filter
.
tag
}
/>
))
}
</
div
>
</
div
>
...
...
@@ -93,7 +93,7 @@ interface TagItemContainerProps {
}
const
TagItemContainer
:
React
.
FC
<
TagItemContainerProps
>
=
(
props
:
TagItemContainerProps
)
=>
{
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
{
tag
,
tagQuery
}
=
props
;
const
isActive
=
tagQuery
===
tag
.
text
;
const
hasSubTags
=
tag
.
subTags
.
length
>
0
;
...
...
@@ -101,9 +101,9 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
const
handleTagClick
=
()
=>
{
if
(
isActive
)
{
locationStore
.
setTagQuery
(
undefined
);
filterStore
.
setTagFilter
(
undefined
);
}
else
{
locationStore
.
setTagQuery
(
tag
.
text
);
filterStore
.
setTagFilter
(
tag
.
text
);
}
};
...
...
web/src/components/UsageHeatMap.tsx
View file @
a9218ed5
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
use
Location
Store
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
getMemoStats
}
from
"../helpers/api"
;
import
{
DAILY_TIMESTAMP
}
from
"../helpers/consts"
;
...
...
@@ -29,7 +29,7 @@ interface DailyUsageStat {
const
UsageHeatMap
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocation
Store
();
const
filterStore
=
useFilter
Store
();
const
userStore
=
useUserStore
();
const
memoStore
=
useMemoStore
();
const
todayTimeStamp
=
utils
.
getDateStampByDate
(
Date
.
now
());
...
...
@@ -87,11 +87,11 @@ const UsageHeatMap = () => {
},
[]);
const
handleUsageStatItemClick
=
useCallback
((
item
:
DailyUsageStat
)
=>
{
if
(
locationStore
.
getState
().
query
?
.
duration
?.
from
===
item
.
timestamp
)
{
locationStore
.
setFromAndToQuery
();
if
(
filterStore
.
getState
()
.
duration
?.
from
===
item
.
timestamp
)
{
filterStore
.
setFromAndToFilter
();
setCurrentStat
(
null
);
}
else
if
(
item
.
count
>
0
)
{
locationStore
.
setFromAndToQuery
(
item
.
timestamp
,
item
.
timestamp
+
DAILY_TIMESTAMP
);
filterStore
.
setFromAndToFilter
(
item
.
timestamp
,
item
.
timestamp
+
DAILY_TIMESTAMP
);
setCurrentStat
(
item
);
}
},
[]);
...
...
web/src/pages/Explore.tsx
View file @
a9218ed5
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
use
Location
Store
,
useMemoStore
}
from
"../store/module"
;
import
{
use
Filter
Store
,
useMemoStore
}
from
"../store/module"
;
import
{
TAG_REG
}
from
"../labs/marked/parser"
;
import
{
DEFAULT_MEMO_LIMIT
}
from
"../helpers/consts"
;
import
useLoading
from
"../hooks/useLoading"
;
import
MemoFilter
from
"../components/MemoFilter"
;
import
Memo
from
"../components/Memo"
;
import
MobileHeader
from
"../components/MobileHeader"
;
import
{
useLocation
}
from
"react-router-dom"
;
interface
State
{
memos
:
Memo
[];
...
...
@@ -15,15 +16,15 @@ interface State {
const
Explore
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
locationStore
=
useLocationStore
();
const
location
=
useLocation
();
const
filterStore
=
useFilterStore
();
const
memoStore
=
useMemoStore
();
const
query
=
locationStore
.
state
.
query
;
const
filter
=
filterStore
.
state
;
const
[
state
,
setState
]
=
useState
<
State
>
({
memos
:
[],
});
const
[
isComplete
,
setIsComplete
]
=
useState
<
boolean
>
(
false
);
const
loadingState
=
useLoading
();
const
location
=
locationStore
.
state
;
useEffect
(()
=>
{
memoStore
.
fetchAllMemos
(
DEFAULT_MEMO_LIMIT
,
0
).
then
((
memos
)
=>
{
...
...
@@ -37,7 +38,7 @@ const Explore = () => {
});
},
[
location
]);
const
{
tag
:
tagQuery
,
text
:
textQuery
}
=
query
??
{}
;
const
{
tag
:
tagQuery
,
text
:
textQuery
}
=
filter
;
const
showMemoFilter
=
Boolean
(
tagQuery
||
textQuery
);
const
shownMemos
=
showMemoFilter
...
...
web/src/pages/MemoDetail.tsx
View file @
a9218ed5
...
...
@@ -2,9 +2,9 @@ import dayjs from "dayjs";
import
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
Link
,
useParams
}
from
"react-router-dom"
;
import
{
Link
,
use
Location
,
use
Params
}
from
"react-router-dom"
;
import
{
UNKNOWN_ID
}
from
"../helpers/consts"
;
import
{
useGlobalStore
,
use
LocationStore
,
use
MemoStore
,
useUserStore
}
from
"../store/module"
;
import
{
useGlobalStore
,
useMemoStore
,
useUserStore
}
from
"../store/module"
;
import
useLoading
from
"../hooks/useLoading"
;
import
MemoContent
from
"../components/MemoContent"
;
import
MemoResources
from
"../components/MemoResources"
;
...
...
@@ -17,8 +17,8 @@ interface State {
const
MemoDetail
=
()
=>
{
const
{
t
,
i18n
}
=
useTranslation
();
const
params
=
useParams
();
const
location
=
useLocation
();
const
globalStore
=
useGlobalStore
();
const
locationStore
=
useLocationStore
();
const
memoStore
=
useMemoStore
();
const
userStore
=
useUserStore
();
const
[
state
,
setState
]
=
useState
<
State
>
({
...
...
@@ -29,7 +29,6 @@ const MemoDetail = () => {
const
loadingState
=
useLoading
();
const
customizedProfile
=
globalStore
.
state
.
systemStatus
.
customizedProfile
;
const
user
=
userStore
.
state
.
user
;
const
location
=
locationStore
.
state
;
useEffect
(()
=>
{
const
memoId
=
Number
(
params
.
memoId
);
...
...
web/src/store/index.ts
View file @
a9218ed5
...
...
@@ -5,7 +5,7 @@ import userReducer from "./reducer/user";
import
memoReducer
from
"./reducer/memo"
;
import
editorReducer
from
"./reducer/editor"
;
import
shortcutReducer
from
"./reducer/shortcut"
;
import
locationReducer
from
"./reducer/location
"
;
import
filterReducer
from
"./reducer/filter
"
;
import
resourceReducer
from
"./reducer/resource"
;
import
dialogReducer
from
"./reducer/dialog"
;
import
tagReducer
from
"./reducer/tag"
;
...
...
@@ -19,7 +19,7 @@ const store = configureStore({
tag
:
tagReducer
,
editor
:
editorReducer
,
shortcut
:
shortcutReducer
,
location
:
location
Reducer
,
filter
:
filter
Reducer
,
resource
:
resourceReducer
,
dialog
:
dialogReducer
,
layout
:
layoutReducer
,
...
...
web/src/store/module/
location
.ts
→
web/src/store/module/
filter
.ts
View file @
a9218ed5
import
{
stringify
}
from
"qs"
;
import
store
,
{
useAppSelector
}
from
"../"
;
import
{
setQuery
,
setPathname
,
Query
,
updateStateWithLocation
,
updatePathnameStateWithLocation
}
from
"../reducer/location"
;
import
store
,
{
useAppSelector
}
from
".."
;
import
{
setFilter
,
Filter
}
from
"../reducer/filter"
;
const
updateLocationUrl
=
(
method
:
"replace"
|
"push"
=
"replace"
)
=>
{
// avoid pathname confusion when entering from non-home page
store
.
dispatch
(
updatePathnameStateWithLocation
());
const
{
query
,
pathname
,
hash
}
=
store
.
getState
().
location
;
let
queryString
=
stringify
(
query
);
if
(
queryString
)
{
queryString
=
"?"
+
queryString
;
}
else
{
queryString
=
""
;
}
if
(
method
===
"replace"
)
{
window
.
history
.
replaceState
(
null
,
""
,
pathname
+
hash
+
queryString
);
}
else
{
window
.
history
.
pushState
(
null
,
""
,
pathname
+
hash
+
queryString
);
}
store
.
dispatch
(
updateStateWithLocation
());
};
export
const
useLocationStore
=
()
=>
{
const
state
=
useAppSelector
((
state
)
=>
state
.
location
);
export
const
useFilterStore
=
()
=>
{
const
state
=
useAppSelector
((
state
)
=>
state
.
filter
);
return
{
state
,
getState
:
()
=>
{
return
store
.
getState
().
location
;
},
updateStateWithLocation
:
()
=>
{
store
.
dispatch
(
updateStateWithLocation
());
return
store
.
getState
().
filter
;
},
setPathname
:
(
pathname
:
string
)
=>
{
store
.
dispatch
(
setPathname
(
pathname
));
updateLocationUrl
();
setFilter
:
(
filter
:
Filter
)
=>
{
store
.
dispatch
(
setFilter
(
filter
));
},
pushHistory
:
(
pathname
:
string
)
=>
{
store
.
dispatch
(
setPathname
(
pathname
));
updateLocationUrl
(
"push"
);
},
replaceHistory
:
(
pathname
:
string
)
=>
{
store
.
dispatch
(
setPathname
(
pathname
));
updateLocationUrl
(
"replace"
);
},
setQuery
:
(
query
:
Query
)
=>
{
store
.
dispatch
(
setQuery
(
query
));
updateLocationUrl
();
},
clearQuery
:
()
=>
{
clearFilter
:
()
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
tag
:
undefined
,
type
:
undefined
,
duration
:
undefined
,
...
...
@@ -60,41 +23,36 @@ export const useLocationStore = () => {
visibility
:
undefined
,
})
);
updateLocationUrl
();
},
setMemoType
Query
:
(
type
?:
MemoSpecType
)
=>
{
setMemoType
Filter
:
(
type
?:
MemoSpecType
)
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
type
:
type
,
})
);
updateLocationUrl
();
},
setMemoShortcut
:
(
shortcutId
?:
ShortcutId
)
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
shortcutId
:
shortcutId
,
})
);
updateLocationUrl
();
},
setText
Query
:
(
text
?:
string
)
=>
{
setText
Filter
:
(
text
?:
string
)
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
text
:
text
,
})
);
updateLocationUrl
();
},
setTag
Query
:
(
tag
?:
string
)
=>
{
setTag
Filter
:
(
tag
?:
string
)
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
tag
:
tag
,
})
);
updateLocationUrl
();
},
setFromAndTo
Query
:
(
from
?:
number
,
to
?:
number
)
=>
{
setFromAndTo
Filter
:
(
from
?:
number
,
to
?:
number
)
=>
{
let
duration
=
undefined
;
if
(
from
&&
to
&&
from
<
to
)
{
duration
=
{
...
...
@@ -102,21 +60,18 @@ export const useLocationStore = () => {
to
,
};
}
store
.
dispatch
(
set
Query
({
set
Filter
({
duration
,
})
);
updateLocationUrl
();
},
setMemoVisibility
Query
:
(
visibility
?:
Visibility
)
=>
{
setMemoVisibility
Filter
:
(
visibility
?:
Visibility
)
=>
{
store
.
dispatch
(
set
Query
({
set
Filter
({
visibility
:
visibility
,
})
);
updateLocationUrl
();
},
};
};
web/src/store/module/index.ts
View file @
a9218ed5
export
*
from
"./editor"
;
export
*
from
"./global"
;
export
*
from
"./
location
"
;
export
*
from
"./
filter
"
;
export
*
from
"./memo"
;
export
*
from
"./tag"
;
export
*
from
"./resource"
;
...
...
web/src/store/module/user.ts
View file @
a9218ed5
...
...
@@ -64,7 +64,7 @@ export const initialUserState = async () => {
};
const
getUserIdFromPath
=
()
=>
{
const
pathname
=
location
.
pathname
;
const
pathname
=
window
.
location
.
pathname
;
const
userIdRegex
=
/^
\/
u
\/(\d
+
)
.*/
;
const
result
=
pathname
.
match
(
userIdRegex
);
if
(
result
&&
result
.
length
===
2
)
{
...
...
web/src/store/reducer/filter.ts
0 → 100644
View file @
a9218ed5
import
{
createSlice
,
PayloadAction
}
from
"@reduxjs/toolkit"
;
interface
Duration
{
from
:
number
;
to
:
number
;
}
interface
State
{
tag
?:
string
;
duration
?:
Duration
;
type
?:
MemoSpecType
;
text
?:
string
;
shortcutId
?:
ShortcutId
;
visibility
?:
Visibility
;
}
export
type
Filter
=
State
;
const
filterSlice
=
createSlice
({
name
:
"filter"
,
initialState
:
{}
as
State
,
reducers
:
{
setFilter
:
(
state
,
action
:
PayloadAction
<
Partial
<
State
>>
)
=>
{
if
(
JSON
.
stringify
(
action
.
payload
)
===
state
)
{
return
state
;
}
return
{
...
state
,
...
action
.
payload
,
};
},
},
});
export
const
{
setFilter
}
=
filterSlice
.
actions
;
export
default
filterSlice
.
reducer
;
web/src/store/reducer/location.ts
deleted
100644 → 0
View file @
f3f0efba
import
{
createSlice
,
PayloadAction
}
from
"@reduxjs/toolkit"
;
import
{
parse
,
ParsedQs
}
from
"qs"
;
interface
Duration
{
from
:
number
;
to
:
number
;
}
export
interface
Query
{
tag
?:
string
;
duration
?:
Duration
;
type
?:
MemoSpecType
;
text
?:
string
;
shortcutId
?:
ShortcutId
;
visibility
?:
Visibility
;
}
interface
State
{
pathname
:
string
;
hash
:
string
;
query
:
Query
;
}
const
getValidPathname
=
(
pathname
:
string
):
string
=>
{
const
userPageUrlRegex
=
/^
\/
u
\/\d
+.*/
;
if
([
"/"
,
"/auth"
,
"/explore"
].
includes
(
pathname
)
||
userPageUrlRegex
.
test
(
pathname
))
{
return
pathname
;
}
else
{
return
"/"
;
}
};
const
getStateFromLocation
=
()
=>
{
const
{
pathname
,
search
,
hash
}
=
window
.
location
;
const
urlParams
=
parse
(
search
.
slice
(
1
));
const
state
:
State
=
{
pathname
:
getValidPathname
(
pathname
),
hash
:
hash
,
query
:
{},
};
if
(
search
!==
""
)
{
state
.
query
=
{};
state
.
query
.
tag
=
urlParams
[
"tag"
]
as
string
;
state
.
query
.
type
=
urlParams
[
"type"
]
as
MemoSpecType
;
state
.
query
.
text
=
urlParams
[
"text"
]
as
string
;
const
shortcutIdStr
=
urlParams
[
"shortcutId"
]
as
string
;
state
.
query
.
shortcutId
=
shortcutIdStr
?
Number
(
shortcutIdStr
)
:
undefined
;
const
durationObj
=
urlParams
[
"duration"
]
as
ParsedQs
;
if
(
durationObj
)
{
const
duration
:
Duration
=
{
from
:
Number
(
durationObj
[
"from"
]),
to
:
Number
(
durationObj
[
"to"
]),
};
if
(
duration
.
to
>
duration
.
from
&&
duration
.
to
!==
0
)
{
state
.
query
.
duration
=
duration
;
}
}
state
.
query
.
visibility
=
urlParams
[
"visibility"
]
as
Visibility
;
}
return
state
;
};
const
locationSlice
=
createSlice
({
name
:
"location"
,
initialState
:
getStateFromLocation
(),
reducers
:
{
updateStateWithLocation
:
()
=>
{
return
getStateFromLocation
();
},
updatePathnameStateWithLocation
:
(
state
)
=>
{
const
{
pathname
}
=
window
.
location
;
return
{
...
state
,
pathname
:
getValidPathname
(
pathname
),
};
},
setPathname
:
(
state
,
action
:
PayloadAction
<
string
>
)
=>
{
if
(
state
.
pathname
===
action
.
payload
)
{
return
state
;
}
return
{
...
state
,
pathname
:
action
.
payload
,
};
},
setQuery
:
(
state
,
action
:
PayloadAction
<
Partial
<
Query
>>
)
=>
{
if
(
JSON
.
stringify
(
action
.
payload
)
===
state
.
query
)
{
return
state
;
}
return
{
...
state
,
query
:
{
...
state
.
query
,
...
action
.
payload
,
},
};
},
},
});
export
const
{
setPathname
,
setQuery
,
updateStateWithLocation
,
updatePathnameStateWithLocation
}
=
locationSlice
.
actions
;
export
default
locationSlice
.
reducer
;
web/src/types/location.d.ts
deleted
100644 → 0
View file @
f3f0efba
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