mirror of
				https://github.com/usememos/memos.git
				synced 2025-06-05 22:09:59 +02:00 
			
		
		
		
	refactor: filter store (#1331)
This commit is contained in:
		| @@ -4,7 +4,7 @@ import { Toaster } from "react-hot-toast"; | |||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
| import { RouterProvider } from "react-router-dom"; | import { RouterProvider } from "react-router-dom"; | ||||||
| import router from "./router"; | import router from "./router"; | ||||||
| import { useLocationStore, useGlobalStore } from "./store/module"; | import { useGlobalStore } from "./store/module"; | ||||||
| import * as storage from "./helpers/storage"; | import * as storage from "./helpers/storage"; | ||||||
| import { getSystemColorScheme } from "./helpers/utils"; | import { getSystemColorScheme } from "./helpers/utils"; | ||||||
| import Loading from "./pages/Loading"; | import Loading from "./pages/Loading"; | ||||||
| @@ -12,17 +12,9 @@ import Loading from "./pages/Loading"; | |||||||
| const App = () => { | const App = () => { | ||||||
|   const { i18n } = useTranslation(); |   const { i18n } = useTranslation(); | ||||||
|   const globalStore = useGlobalStore(); |   const globalStore = useGlobalStore(); | ||||||
|   const locationStore = useLocationStore(); |  | ||||||
|   const { mode, setMode } = useColorScheme(); |   const { mode, setMode } = useColorScheme(); | ||||||
|   const { appearance, locale, systemStatus } = globalStore.state; |   const { appearance, locale, systemStatus } = globalStore.state; | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     locationStore.updateStateWithLocation(); |  | ||||||
|     window.onpopstate = () => { |  | ||||||
|       locationStore.updateStateWithLocation(); |  | ||||||
|     }; |  | ||||||
|   }, []); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); |     const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); | ||||||
|     const handleColorSchemeChange = (e: MediaQueryListEvent) => { |     const handleColorSchemeChange = (e: MediaQueryListEvent) => { | ||||||
|   | |||||||
| @@ -26,9 +26,9 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => { | |||||||
|           <Icon.X /> |           <Icon.X /> | ||||||
|         </button> |         </button> | ||||||
|       </div> |       </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> |         <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"> |           <div className="flex flex-row justify-start items-center mr-2"> | ||||||
|             Powered by |             Powered by | ||||||
|             <a href="https://usememos.com" target="_blank" className="flex flex-row justify-start items-center mr-1 hover:underline"> |             <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> |           </div> | ||||||
|           <GitHubBadge /> |           <GitHubBadge /> | ||||||
|         </div> |         </div> | ||||||
|         <div className="border-t mt-3 pt-2 text-sm flex flex-row justify-start items-center"> |         <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 mr-2">Other projects:</span> |           <span className="text-gray-500">Other projects:</span> | ||||||
|           <a |           <a | ||||||
|             href="https://github.com/boojack/sticky-notes" |             href="https://github.com/boojack/sticky-notes" | ||||||
|             target="_blank" |             target="_blank" | ||||||
| @@ -53,6 +53,14 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => { | |||||||
|             /> |             /> | ||||||
|             <span>Sticky notes</span> |             <span>Sticky notes</span> | ||||||
|           </a> |           </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> | ||||||
|       </div> |       </div> | ||||||
|     </> |     </> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { useEffect } from "react"; | import { useEffect } from "react"; | ||||||
| import { NavLink } from "react-router-dom"; | import { NavLink, useLocation } from "react-router-dom"; | ||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
| import { useLayoutStore, useUserStore } from "../store/module"; | import { useLayoutStore, useUserStore } from "../store/module"; | ||||||
| import { resolution } from "../utils/layout"; | import { resolution } from "../utils/layout"; | ||||||
| @@ -12,6 +12,7 @@ import UserBanner from "./UserBanner"; | |||||||
|  |  | ||||||
| const Header = () => { | const Header = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|  |   const location = useLocation(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const layoutStore = useLayoutStore(); |   const layoutStore = useLayoutStore(); | ||||||
|   const showHeader = layoutStore.state.showHeader; |   const showHeader = layoutStore.state.showHeader; | ||||||
| @@ -27,7 +28,7 @@ const Header = () => { | |||||||
|     }; |     }; | ||||||
|     window.addEventListener("resize", handleWindowResize); |     window.addEventListener("resize", handleWindowResize); | ||||||
|     handleWindowResize(); |     handleWindowResize(); | ||||||
|   }, []); |   }, [location]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
| @@ -42,7 +43,7 @@ const Header = () => { | |||||||
|         onClick={() => layoutStore.setHeaderStatus(false)} |         onClick={() => layoutStore.setHeaderStatus(false)} | ||||||
|       ></div> |       ></div> | ||||||
|       <header |       <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" |           showHeader && "translate-x-0 shadow-2xl" | ||||||
|         }`} |         }`} | ||||||
|       > |       > | ||||||
|   | |||||||
| @@ -5,8 +5,10 @@ import ShortcutList from "./ShortcutList"; | |||||||
| import TagList from "./TagList"; | import TagList from "./TagList"; | ||||||
| import SearchBar from "./SearchBar"; | import SearchBar from "./SearchBar"; | ||||||
| import UsageHeatMap from "./UsageHeatMap"; | import UsageHeatMap from "./UsageHeatMap"; | ||||||
|  | import { useLocation } from "react-router-dom"; | ||||||
|  |  | ||||||
| const HomeSidebar = () => { | const HomeSidebar = () => { | ||||||
|  |   const location = useLocation(); | ||||||
|   const layoutStore = useLayoutStore(); |   const layoutStore = useLayoutStore(); | ||||||
|   const showHomeSidebar = layoutStore.state.showHomeSidebar; |   const showHomeSidebar = layoutStore.state.showHomeSidebar; | ||||||
|  |  | ||||||
| @@ -20,7 +22,7 @@ const HomeSidebar = () => { | |||||||
|     }; |     }; | ||||||
|     window.addEventListener("resize", handleWindowResize); |     window.addEventListener("resize", handleWindowResize); | ||||||
|     handleWindowResize(); |     handleWindowResize(); | ||||||
|   }, []); |   }, [location]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
| @@ -35,7 +37,7 @@ const HomeSidebar = () => { | |||||||
|         onClick={() => layoutStore.setHomeSidebarStatus(false)} |         onClick={() => layoutStore.setHomeSidebarStatus(false)} | ||||||
|       ></div> |       ></div> | ||||||
|       <aside |       <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" |           showHomeSidebar && "!translate-x-0 shadow-2xl" | ||||||
|         }`} |         }`} | ||||||
|       > |       > | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ 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, useNavigate } from "react-router-dom"; | import { Link, useNavigate } from "react-router-dom"; | ||||||
| import { useEditorStore, useLocationStore, useMemoStore, useUserStore } from "../store/module"; | import { useEditorStore, useFilterStore, useMemoStore, useUserStore } from "../store/module"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
| import MemoContent from "./MemoContent"; | import MemoContent from "./MemoContent"; | ||||||
| import MemoResources from "./MemoResources"; | import MemoResources from "./MemoResources"; | ||||||
| @@ -32,7 +32,7 @@ const Memo: React.FC<Props> = (props: Props) => { | |||||||
|   const { t, i18n } = useTranslation(); |   const { t, i18n } = useTranslation(); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|   const editorStore = useEditorStore(); |   const editorStore = useEditorStore(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const [createdTimeStr, setCreatedTimeStr] = useState<string>(getFormatedMemoTimeStr(memo.createdTs, i18n.language)); |   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") { |     if (targetEl.className === "tag-span") { | ||||||
|       const tagName = targetEl.innerText.slice(1); |       const tagName = targetEl.innerText.slice(1); | ||||||
|       const currTagQuery = locationStore.getState().query?.tag; |       const currTagQuery = filterStore.getState().tag; | ||||||
|       if (currTagQuery === tagName) { |       if (currTagQuery === tagName) { | ||||||
|         locationStore.setTagQuery(undefined); |         filterStore.setTagFilter(undefined); | ||||||
|       } else { |       } else { | ||||||
|         locationStore.setTagQuery(tagName); |         filterStore.setTagFilter(tagName); | ||||||
|       } |       } | ||||||
|     } else if (targetEl.classList.contains("todo-block")) { |     } else if (targetEl.classList.contains("todo-block")) { | ||||||
|       if (isVisitorMode) { |       if (isVisitorMode) { | ||||||
| @@ -176,11 +176,11 @@ const Memo: React.FC<Props> = (props: Props) => { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleMemoVisibilityClick = (visibility: Visibility) => { |   const handleMemoVisibilityClick = (visibility: Visibility) => { | ||||||
|     const currVisibilityQuery = locationStore.getState().query?.visibility; |     const currVisibilityQuery = filterStore.getState().visibility; | ||||||
|     if (currVisibilityQuery === visibility) { |     if (currVisibilityQuery === visibility) { | ||||||
|       locationStore.setMemoVisibilityQuery(undefined); |       filterStore.setMemoVisibilityFilter(undefined); | ||||||
|     } else { |     } else { | ||||||
|       locationStore.setMemoVisibilityQuery(visibility); |       filterStore.setMemoVisibilityFilter(visibility); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,15 +5,7 @@ import { useTranslation } from "react-i18next"; | |||||||
| import { getMatchedNodes } from "../labs/marked"; | import { getMatchedNodes } from "../labs/marked"; | ||||||
| import { deleteMemoResource, upsertMemoResource } from "../helpers/api"; | import { deleteMemoResource, upsertMemoResource } from "../helpers/api"; | ||||||
| import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts"; | import { TAB_SPACE_WIDTH, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts"; | ||||||
| import { | import { useEditorStore, useGlobalStore, useFilterStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "../store/module"; | ||||||
|   useEditorStore, |  | ||||||
|   useGlobalStore, |  | ||||||
|   useLocationStore, |  | ||||||
|   useMemoStore, |  | ||||||
|   useResourceStore, |  | ||||||
|   useTagStore, |  | ||||||
|   useUserStore, |  | ||||||
| } from "../store/module"; |  | ||||||
| import * as storage from "../helpers/storage"; | import * as storage from "../helpers/storage"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
| import Selector from "./base/Selector"; | import Selector from "./base/Selector"; | ||||||
| @@ -46,7 +38,7 @@ const MemoEditor = () => { | |||||||
|   const { t, i18n } = useTranslation(); |   const { t, i18n } = useTranslation(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const editorStore = useEditorStore(); |   const editorStore = useEditorStore(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const tagStore = useTagStore(); |   const tagStore = useTagStore(); | ||||||
|   const resourceStore = useResourceStore(); |   const resourceStore = useResourceStore(); | ||||||
| @@ -289,7 +281,7 @@ const MemoEditor = () => { | |||||||
|           visibility: editorState.memoVisibility, |           visibility: editorState.memoVisibility, | ||||||
|           resourceIdList: editorState.resourceList.map((resource) => resource.id), |           resourceIdList: editorState.resourceList.map((resource) => resource.id), | ||||||
|         }); |         }); | ||||||
|         locationStore.clearQuery(); |         filterStore.clearFilter(); | ||||||
|       } |       } | ||||||
|     } catch (error: any) { |     } catch (error: any) { | ||||||
|       console.error(error); |       console.error(error); | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
|  | import { useEffect } from "react"; | ||||||
| import { useTranslation } from "react-i18next"; | 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 * as utils from "../helpers/utils"; | ||||||
| import { getTextWithMemoType } from "../helpers/filter"; | import { getTextWithMemoType } from "../helpers/filter"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
| @@ -7,20 +9,25 @@ import "../less/memo-filter.less"; | |||||||
|  |  | ||||||
| const MemoFilter = () => { | const MemoFilter = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const location = useLocation(); | ||||||
|  |   const filterStore = useFilterStore(); | ||||||
|   const shortcutStore = useShortcutStore(); |   const shortcutStore = useShortcutStore(); | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   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 shortcut = shortcutId ? shortcutStore.getShortcutById(shortcutId) : null; | ||||||
|   const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility); |   const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     filterStore.clearFilter(); | ||||||
|  |   }, [location]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className={`filter-query-container ${showFilter ? "" : "!hidden"}`}> |     <div className={`filter-query-container ${showFilter ? "" : "!hidden"}`}> | ||||||
|       <span className="mx-2 text-gray-400">{t("common.filter")}:</span> |       <span className="mx-2 text-gray-400">{t("common.filter")}:</span> | ||||||
|       <div |       <div | ||||||
|         className={"filter-item-container " + (shortcut ? "" : "!hidden")} |         className={"filter-item-container " + (shortcut ? "" : "!hidden")} | ||||||
|         onClick={() => { |         onClick={() => { | ||||||
|           locationStore.setMemoShortcut(undefined); |           filterStore.setMemoShortcut(undefined); | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Icon.Target className="icon-text" /> {shortcut?.title} |         <Icon.Target className="icon-text" /> {shortcut?.title} | ||||||
| @@ -28,7 +35,7 @@ const MemoFilter = () => { | |||||||
|       <div |       <div | ||||||
|         className={"filter-item-container " + (tagQuery ? "" : "!hidden")} |         className={"filter-item-container " + (tagQuery ? "" : "!hidden")} | ||||||
|         onClick={() => { |         onClick={() => { | ||||||
|           locationStore.setTagQuery(undefined); |           filterStore.setTagFilter(undefined); | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Icon.Tag className="icon-text" /> {tagQuery} |         <Icon.Tag className="icon-text" /> {tagQuery} | ||||||
| @@ -36,7 +43,7 @@ const MemoFilter = () => { | |||||||
|       <div |       <div | ||||||
|         className={"filter-item-container " + (memoType ? "" : "!hidden")} |         className={"filter-item-container " + (memoType ? "" : "!hidden")} | ||||||
|         onClick={() => { |         onClick={() => { | ||||||
|           locationStore.setMemoTypeQuery(undefined); |           filterStore.setMemoTypeFilter(undefined); | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))} |         <Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))} | ||||||
| @@ -44,7 +51,7 @@ const MemoFilter = () => { | |||||||
|       <div |       <div | ||||||
|         className={"filter-item-container " + (visibility ? "" : "!hidden")} |         className={"filter-item-container " + (visibility ? "" : "!hidden")} | ||||||
|         onClick={() => { |         onClick={() => { | ||||||
|           locationStore.setMemoVisibilityQuery(undefined); |           filterStore.setMemoVisibilityFilter(undefined); | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Icon.Eye className="icon-text" /> {visibility} |         <Icon.Eye className="icon-text" /> {visibility} | ||||||
| @@ -53,7 +60,7 @@ const MemoFilter = () => { | |||||||
|         <div |         <div | ||||||
|           className="filter-item-container" |           className="filter-item-container" | ||||||
|           onClick={() => { |           onClick={() => { | ||||||
|             locationStore.setFromAndToQuery(); |             filterStore.setFromAndToFilter(); | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <Icon.Calendar className="icon-text" /> {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)} |           <Icon.Calendar className="icon-text" /> {utils.getDateString(duration.from)} to {utils.getDateString(duration.to)} | ||||||
| @@ -62,7 +69,7 @@ const MemoFilter = () => { | |||||||
|       <div |       <div | ||||||
|         className={"filter-item-container " + (textQuery ? "" : "!hidden")} |         className={"filter-item-container " + (textQuery ? "" : "!hidden")} | ||||||
|         onClick={() => { |         onClick={() => { | ||||||
|           locationStore.setTextQuery(undefined); |           filterStore.setTextFilter(undefined); | ||||||
|         }} |         }} | ||||||
|       > |       > | ||||||
|         <Icon.Search className="icon-text" /> {textQuery} |         <Icon.Search className="icon-text" /> {textQuery} | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, 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 { useLocationStore, useMemoStore, useShortcutStore, useUserStore } from "../store/module"; | import { useFilterStore, useMemoStore, useShortcutStore, useUserStore } from "../store/module"; | ||||||
| import { TAG_REG, LINK_REG } from "../labs/marked/parser"; | import { TAG_REG, LINK_REG } from "../labs/marked/parser"; | ||||||
| import * as utils from "../helpers/utils"; | import * as utils from "../helpers/utils"; | ||||||
| import { DEFAULT_MEMO_LIMIT } from "../helpers/consts"; | import { DEFAULT_MEMO_LIMIT } from "../helpers/consts"; | ||||||
| @@ -14,13 +14,13 @@ const MemoList = () => { | |||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const shortcutStore = useShortcutStore(); |   const shortcutStore = useShortcutStore(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   const { memos, isFetching } = memoStore.state; |   const { memos, isFetching } = memoStore.state; | ||||||
|   const [isComplete, setIsComplete] = useState<boolean>(false); |   const [isComplete, setIsComplete] = useState<boolean>(false); | ||||||
|  |  | ||||||
|   const currentUserId = userStore.getCurrentUserId(); |   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 shortcut = shortcutId ? shortcutStore.getShortcutById(shortcutId) : null; | ||||||
|   const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility); |   const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut || visibility); | ||||||
|  |  | ||||||
| @@ -107,7 +107,7 @@ const MemoList = () => { | |||||||
|     if (pageWrapper) { |     if (pageWrapper) { | ||||||
|       pageWrapper.scrollTo(0, 0); |       pageWrapper.scrollTo(0, 0); | ||||||
|     } |     } | ||||||
|   }, [query]); |   }, [filter]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (isFetching || isComplete) { |     if (isFetching || isComplete) { | ||||||
| @@ -116,7 +116,7 @@ const MemoList = () => { | |||||||
|     if (sortedMemos.length < DEFAULT_MEMO_LIMIT) { |     if (sortedMemos.length < DEFAULT_MEMO_LIMIT) { | ||||||
|       handleFetchMoreClick(); |       handleFetchMoreClick(); | ||||||
|     } |     } | ||||||
|   }, [isFetching, isComplete, query, sortedMemos.length]); |   }, [isFetching, isComplete, filter, sortedMemos.length]); | ||||||
|  |  | ||||||
|   const handleFetchMoreClick = async () => { |   const handleFetchMoreClick = async () => { | ||||||
|     try { |     try { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import { useLayoutStore, useLocationStore, useShortcutStore } from "../store/module"; | import { useLayoutStore, useFilterStore, useShortcutStore } from "../store/module"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
|  |  | ||||||
| interface Props { | interface Props { | ||||||
| @@ -8,24 +8,24 @@ interface Props { | |||||||
|  |  | ||||||
| const MobileHeader = (props: Props) => { | const MobileHeader = (props: Props) => { | ||||||
|   const { showSearch = true } = props; |   const { showSearch = true } = props; | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const shortcutStore = useShortcutStore(); |   const shortcutStore = useShortcutStore(); | ||||||
|   const layoutStore = useLayoutStore(); |   const layoutStore = useLayoutStore(); | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   const shortcuts = shortcutStore.state.shortcuts; |   const shortcuts = shortcutStore.state.shortcuts; | ||||||
|   const [titleText, setTitleText] = useState("MEMOS"); |   const [titleText, setTitleText] = useState("MEMOS"); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (!query?.shortcutId) { |     if (!filter.shortcutId) { | ||||||
|       setTitleText("MEMOS"); |       setTitleText("MEMOS"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const shortcut = shortcutStore.getShortcutById(query?.shortcutId); |     const shortcut = shortcutStore.getShortcutById(filter.shortcutId); | ||||||
|     if (shortcut) { |     if (shortcut) { | ||||||
|       setTitleText(shortcut.title); |       setTitleText(shortcut.title); | ||||||
|     } |     } | ||||||
|   }, [query, shortcuts]); |   }, [filter, shortcuts]); | ||||||
|  |  | ||||||
|   return ( |   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"> |     <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"> | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import { useEffect, useState, useRef } from "react"; | import { useEffect, useState, useRef } from "react"; | ||||||
| import useDebounce from "../hooks/useDebounce"; | import useDebounce from "../hooks/useDebounce"; | ||||||
| import { useLocationStore, useDialogStore, useLayoutStore } from "../store/module"; | import { useFilterStore, useDialogStore, useLayoutStore } from "../store/module"; | ||||||
| import { resolution } from "../utils/layout"; | import { resolution } from "../utils/layout"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
|  |  | ||||||
| const SearchBar = () => { | const SearchBar = () => { | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const dialogStore = useDialogStore(); |   const dialogStore = useDialogStore(); | ||||||
|   const layoutStore = useLayoutStore(); |   const layoutStore = useLayoutStore(); | ||||||
|   const [queryText, setQueryText] = useState(""); |   const [queryText, setQueryText] = useState(""); | ||||||
| @@ -33,9 +33,9 @@ const SearchBar = () => { | |||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const text = locationStore.getState().query.text; |     const text = filterStore.getState().text; | ||||||
|     setQueryText(text === undefined ? "" : text); |     setQueryText(text === undefined ? "" : text); | ||||||
|   }, [locationStore.state.query.text]); |   }, [filterStore.state.text]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (layoutStore.state.showHomeSidebar) { |     if (layoutStore.state.showHomeSidebar) { | ||||||
| @@ -47,7 +47,7 @@ const SearchBar = () => { | |||||||
|  |  | ||||||
|   useDebounce( |   useDebounce( | ||||||
|     () => { |     () => { | ||||||
|       locationStore.setTextQuery(queryText.length === 0 ? undefined : queryText); |       filterStore.setTextFilter(queryText.length === 0 ? undefined : queryText); | ||||||
|     }, |     }, | ||||||
|     200, |     200, | ||||||
|     [queryText] |     [queryText] | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { useEffect } from "react"; | import { useEffect } 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 { useLocationStore, useShortcutStore } from "../store/module"; | import { useFilterStore, useShortcutStore } from "../store/module"; | ||||||
| import * as utils from "../helpers/utils"; | import * as utils from "../helpers/utils"; | ||||||
| import useToggle from "../hooks/useToggle"; | import useToggle from "../hooks/useToggle"; | ||||||
| import useLoading from "../hooks/useLoading"; | import useLoading from "../hooks/useLoading"; | ||||||
| @@ -10,9 +10,9 @@ import showCreateShortcutDialog from "./CreateShortcutDialog"; | |||||||
|  |  | ||||||
| const ShortcutList = () => { | const ShortcutList = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const shortcutStore = useShortcutStore(); |   const shortcutStore = useShortcutStore(); | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   const shortcuts = shortcutStore.state.shortcuts; |   const shortcuts = shortcutStore.state.shortcuts; | ||||||
|   const loadingState = useLoading(); |   const loadingState = useLoading(); | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ const ShortcutList = () => { | |||||||
|       </div> |       </div> | ||||||
|       <div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mb-2"> |       <div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mb-2"> | ||||||
|         {sortedShortcuts.map((s) => { |         {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> | ||||||
|     </div> |     </div> | ||||||
| @@ -63,15 +63,15 @@ interface ShortcutContainerProps { | |||||||
| const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => { | const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => { | ||||||
|   const { shortcut, isActive } = props; |   const { shortcut, isActive } = props; | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const shortcutStore = useShortcutStore(); |   const shortcutStore = useShortcutStore(); | ||||||
|   const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); |   const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); | ||||||
|  |  | ||||||
|   const handleShortcutClick = () => { |   const handleShortcutClick = () => { | ||||||
|     if (isActive) { |     if (isActive) { | ||||||
|       locationStore.setMemoShortcut(undefined); |       filterStore.setMemoShortcut(undefined); | ||||||
|     } else { |     } else { | ||||||
|       locationStore.setMemoShortcut(shortcut.id); |       filterStore.setMemoShortcut(shortcut.id); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -81,9 +81,9 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont | |||||||
|     if (showConfirmDeleteBtn) { |     if (showConfirmDeleteBtn) { | ||||||
|       try { |       try { | ||||||
|         await shortcutStore.deleteShortcutById(shortcut.id); |         await shortcutStore.deleteShortcutById(shortcut.id); | ||||||
|         if (locationStore.getState().query?.shortcutId === shortcut.id) { |         if (filterStore.getState().shortcutId === shortcut.id) { | ||||||
|           // need clear shortcut filter |           // need clear shortcut filter | ||||||
|           locationStore.setMemoShortcut(undefined); |           filterStore.setMemoShortcut(undefined); | ||||||
|         } |         } | ||||||
|       } catch (error: any) { |       } catch (error: any) { | ||||||
|         console.error(error); |         console.error(error); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
| import { useLocationStore, useTagStore } from "../store/module"; | import { useFilterStore, useTagStore } from "../store/module"; | ||||||
| import useToggle from "../hooks/useToggle"; | import useToggle from "../hooks/useToggle"; | ||||||
| import Icon from "./Icon"; | import Icon from "./Icon"; | ||||||
| import showCreateTagDialog from "./CreateTagDialog"; | import showCreateTagDialog from "./CreateTagDialog"; | ||||||
| @@ -13,10 +13,10 @@ interface Tag { | |||||||
|  |  | ||||||
| const TagList = () => { | const TagList = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const tagStore = useTagStore(); |   const tagStore = useTagStore(); | ||||||
|   const tagsText = tagStore.state.tags; |   const tagsText = tagStore.state.tags; | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   const [tags, setTags] = useState<Tag[]>([]); |   const [tags, setTags] = useState<Tag[]>([]); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
| @@ -80,7 +80,7 @@ const TagList = () => { | |||||||
|       </div> |       </div> | ||||||
|       <div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mt-2 mb-2"> |       <div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mt-2 mb-2"> | ||||||
|         {tags.map((t, idx) => ( |         {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> | ||||||
|     </div> |     </div> | ||||||
| @@ -93,7 +93,7 @@ interface TagItemContainerProps { | |||||||
| } | } | ||||||
|  |  | ||||||
| const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => { | const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => { | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const { tag, tagQuery } = props; |   const { tag, tagQuery } = props; | ||||||
|   const isActive = tagQuery === tag.text; |   const isActive = tagQuery === tag.text; | ||||||
|   const hasSubTags = tag.subTags.length > 0; |   const hasSubTags = tag.subTags.length > 0; | ||||||
| @@ -101,9 +101,9 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain | |||||||
|  |  | ||||||
|   const handleTagClick = () => { |   const handleTagClick = () => { | ||||||
|     if (isActive) { |     if (isActive) { | ||||||
|       locationStore.setTagQuery(undefined); |       filterStore.setTagFilter(undefined); | ||||||
|     } else { |     } else { | ||||||
|       locationStore.setTagQuery(tag.text); |       filterStore.setTagFilter(tag.text); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { useCallback, useEffect, useRef, useState } from "react"; | import { useCallback, useEffect, useRef, useState } from "react"; | ||||||
| import { useLocationStore, useMemoStore, useUserStore } from "../store/module"; | import { useFilterStore, useMemoStore, useUserStore } from "../store/module"; | ||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
| import { getMemoStats } from "../helpers/api"; | import { getMemoStats } from "../helpers/api"; | ||||||
| import { DAILY_TIMESTAMP } from "../helpers/consts"; | import { DAILY_TIMESTAMP } from "../helpers/consts"; | ||||||
| @@ -29,7 +29,7 @@ interface DailyUsageStat { | |||||||
|  |  | ||||||
| const UsageHeatMap = () => { | const UsageHeatMap = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const filterStore = useFilterStore(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const todayTimeStamp = utils.getDateStampByDate(Date.now()); |   const todayTimeStamp = utils.getDateStampByDate(Date.now()); | ||||||
| @@ -87,11 +87,11 @@ const UsageHeatMap = () => { | |||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => { |   const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => { | ||||||
|     if (locationStore.getState().query?.duration?.from === item.timestamp) { |     if (filterStore.getState().duration?.from === item.timestamp) { | ||||||
|       locationStore.setFromAndToQuery(); |       filterStore.setFromAndToFilter(); | ||||||
|       setCurrentStat(null); |       setCurrentStat(null); | ||||||
|     } else if (item.count > 0) { |     } else if (item.count > 0) { | ||||||
|       locationStore.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP); |       filterStore.setFromAndToFilter(item.timestamp, item.timestamp + DAILY_TIMESTAMP); | ||||||
|       setCurrentStat(item); |       setCurrentStat(item); | ||||||
|     } |     } | ||||||
|   }, []); |   }, []); | ||||||
|   | |||||||
| @@ -1,13 +1,14 @@ | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, 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 { useLocationStore, useMemoStore } from "../store/module"; | import { useFilterStore, useMemoStore } from "../store/module"; | ||||||
| import { TAG_REG } from "../labs/marked/parser"; | import { TAG_REG } from "../labs/marked/parser"; | ||||||
| import { DEFAULT_MEMO_LIMIT } from "../helpers/consts"; | import { DEFAULT_MEMO_LIMIT } from "../helpers/consts"; | ||||||
| import useLoading from "../hooks/useLoading"; | import useLoading from "../hooks/useLoading"; | ||||||
| import MemoFilter from "../components/MemoFilter"; | import MemoFilter from "../components/MemoFilter"; | ||||||
| import Memo from "../components/Memo"; | import Memo from "../components/Memo"; | ||||||
| import MobileHeader from "../components/MobileHeader"; | import MobileHeader from "../components/MobileHeader"; | ||||||
|  | import { useLocation } from "react-router-dom"; | ||||||
|  |  | ||||||
| interface State { | interface State { | ||||||
|   memos: Memo[]; |   memos: Memo[]; | ||||||
| @@ -15,15 +16,15 @@ interface State { | |||||||
|  |  | ||||||
| const Explore = () => { | const Explore = () => { | ||||||
|   const { t } = useTranslation(); |   const { t } = useTranslation(); | ||||||
|   const locationStore = useLocationStore(); |   const location = useLocation(); | ||||||
|  |   const filterStore = useFilterStore(); | ||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const query = locationStore.state.query; |   const filter = filterStore.state; | ||||||
|   const [state, setState] = useState<State>({ |   const [state, setState] = useState<State>({ | ||||||
|     memos: [], |     memos: [], | ||||||
|   }); |   }); | ||||||
|   const [isComplete, setIsComplete] = useState<boolean>(false); |   const [isComplete, setIsComplete] = useState<boolean>(false); | ||||||
|   const loadingState = useLoading(); |   const loadingState = useLoading(); | ||||||
|   const location = locationStore.state; |  | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, 0).then((memos) => { |     memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, 0).then((memos) => { | ||||||
| @@ -37,7 +38,7 @@ const Explore = () => { | |||||||
|     }); |     }); | ||||||
|   }, [location]); |   }, [location]); | ||||||
|  |  | ||||||
|   const { tag: tagQuery, text: textQuery } = query ?? {}; |   const { tag: tagQuery, text: textQuery } = filter; | ||||||
|   const showMemoFilter = Boolean(tagQuery || textQuery); |   const showMemoFilter = Boolean(tagQuery || textQuery); | ||||||
|  |  | ||||||
|   const shownMemos = showMemoFilter |   const shownMemos = showMemoFilter | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ import dayjs from "dayjs"; | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, 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, useParams } from "react-router-dom"; | import { Link, useLocation, useParams } from "react-router-dom"; | ||||||
| import { UNKNOWN_ID } from "../helpers/consts"; | import { UNKNOWN_ID } from "../helpers/consts"; | ||||||
| import { useGlobalStore, useLocationStore, 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 MemoContent from "../components/MemoContent"; | ||||||
| import MemoResources from "../components/MemoResources"; | import MemoResources from "../components/MemoResources"; | ||||||
| @@ -17,8 +17,8 @@ interface State { | |||||||
| const MemoDetail = () => { | const MemoDetail = () => { | ||||||
|   const { t, i18n } = useTranslation(); |   const { t, i18n } = useTranslation(); | ||||||
|   const params = useParams(); |   const params = useParams(); | ||||||
|  |   const location = useLocation(); | ||||||
|   const globalStore = useGlobalStore(); |   const globalStore = useGlobalStore(); | ||||||
|   const locationStore = useLocationStore(); |  | ||||||
|   const memoStore = useMemoStore(); |   const memoStore = useMemoStore(); | ||||||
|   const userStore = useUserStore(); |   const userStore = useUserStore(); | ||||||
|   const [state, setState] = useState<State>({ |   const [state, setState] = useState<State>({ | ||||||
| @@ -29,7 +29,6 @@ const MemoDetail = () => { | |||||||
|   const loadingState = useLoading(); |   const loadingState = useLoading(); | ||||||
|   const customizedProfile = globalStore.state.systemStatus.customizedProfile; |   const customizedProfile = globalStore.state.systemStatus.customizedProfile; | ||||||
|   const user = userStore.state.user; |   const user = userStore.state.user; | ||||||
|   const location = locationStore.state; |  | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const memoId = Number(params.memoId); |     const memoId = Number(params.memoId); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import userReducer from "./reducer/user"; | |||||||
| import memoReducer from "./reducer/memo"; | import memoReducer from "./reducer/memo"; | ||||||
| import editorReducer from "./reducer/editor"; | import editorReducer from "./reducer/editor"; | ||||||
| import shortcutReducer from "./reducer/shortcut"; | import shortcutReducer from "./reducer/shortcut"; | ||||||
| import locationReducer from "./reducer/location"; | import filterReducer from "./reducer/filter"; | ||||||
| import resourceReducer from "./reducer/resource"; | import resourceReducer from "./reducer/resource"; | ||||||
| import dialogReducer from "./reducer/dialog"; | import dialogReducer from "./reducer/dialog"; | ||||||
| import tagReducer from "./reducer/tag"; | import tagReducer from "./reducer/tag"; | ||||||
| @@ -19,7 +19,7 @@ const store = configureStore({ | |||||||
|     tag: tagReducer, |     tag: tagReducer, | ||||||
|     editor: editorReducer, |     editor: editorReducer, | ||||||
|     shortcut: shortcutReducer, |     shortcut: shortcutReducer, | ||||||
|     location: locationReducer, |     filter: filterReducer, | ||||||
|     resource: resourceReducer, |     resource: resourceReducer, | ||||||
|     dialog: dialogReducer, |     dialog: dialogReducer, | ||||||
|     layout: layoutReducer, |     layout: layoutReducer, | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								web/src/store/module/filter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								web/src/store/module/filter.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | import store, { useAppSelector } from ".."; | ||||||
|  | import { setFilter, Filter } from "../reducer/filter"; | ||||||
|  |  | ||||||
|  | export const useFilterStore = () => { | ||||||
|  |   const state = useAppSelector((state) => state.filter); | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     state, | ||||||
|  |     getState: () => { | ||||||
|  |       return store.getState().filter; | ||||||
|  |     }, | ||||||
|  |     setFilter: (filter: Filter) => { | ||||||
|  |       store.dispatch(setFilter(filter)); | ||||||
|  |     }, | ||||||
|  |     clearFilter: () => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           tag: undefined, | ||||||
|  |           type: undefined, | ||||||
|  |           duration: undefined, | ||||||
|  |           text: undefined, | ||||||
|  |           shortcutId: undefined, | ||||||
|  |           visibility: undefined, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setMemoTypeFilter: (type?: MemoSpecType) => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           type: type, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setMemoShortcut: (shortcutId?: ShortcutId) => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           shortcutId: shortcutId, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setTextFilter: (text?: string) => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           text: text, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setTagFilter: (tag?: string) => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           tag: tag, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setFromAndToFilter: (from?: number, to?: number) => { | ||||||
|  |       let duration = undefined; | ||||||
|  |       if (from && to && from < to) { | ||||||
|  |         duration = { | ||||||
|  |           from, | ||||||
|  |           to, | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           duration, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |     setMemoVisibilityFilter: (visibility?: Visibility) => { | ||||||
|  |       store.dispatch( | ||||||
|  |         setFilter({ | ||||||
|  |           visibility: visibility, | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| export * from "./editor"; | export * from "./editor"; | ||||||
| export * from "./global"; | export * from "./global"; | ||||||
| export * from "./location"; | export * from "./filter"; | ||||||
| export * from "./memo"; | export * from "./memo"; | ||||||
| export * from "./tag"; | export * from "./tag"; | ||||||
| export * from "./resource"; | export * from "./resource"; | ||||||
|   | |||||||
| @@ -1,122 +0,0 @@ | |||||||
| import { stringify } from "qs"; |  | ||||||
| import store, { useAppSelector } from "../"; |  | ||||||
| import { setQuery, setPathname, Query, updateStateWithLocation, updatePathnameStateWithLocation } from "../reducer/location"; |  | ||||||
|  |  | ||||||
| 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); |  | ||||||
|  |  | ||||||
|   return { |  | ||||||
|     state, |  | ||||||
|     getState: () => { |  | ||||||
|       return store.getState().location; |  | ||||||
|     }, |  | ||||||
|     updateStateWithLocation: () => { |  | ||||||
|       store.dispatch(updateStateWithLocation()); |  | ||||||
|     }, |  | ||||||
|     setPathname: (pathname: string) => { |  | ||||||
|       store.dispatch(setPathname(pathname)); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     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: () => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           tag: undefined, |  | ||||||
|           type: undefined, |  | ||||||
|           duration: undefined, |  | ||||||
|           text: undefined, |  | ||||||
|           shortcutId: undefined, |  | ||||||
|           visibility: undefined, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setMemoTypeQuery: (type?: MemoSpecType) => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           type: type, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setMemoShortcut: (shortcutId?: ShortcutId) => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           shortcutId: shortcutId, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setTextQuery: (text?: string) => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           text: text, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setTagQuery: (tag?: string) => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           tag: tag, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setFromAndToQuery: (from?: number, to?: number) => { |  | ||||||
|       let duration = undefined; |  | ||||||
|       if (from && to && from < to) { |  | ||||||
|         duration = { |  | ||||||
|           from, |  | ||||||
|           to, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           duration, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|     setMemoVisibilityQuery: (visibility?: Visibility) => { |  | ||||||
|       store.dispatch( |  | ||||||
|         setQuery({ |  | ||||||
|           visibility: visibility, |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       updateLocationUrl(); |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| @@ -64,7 +64,7 @@ export const initialUserState = async () => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const getUserIdFromPath = () => { | const getUserIdFromPath = () => { | ||||||
|   const pathname = location.pathname; |   const pathname = window.location.pathname; | ||||||
|   const userIdRegex = /^\/u\/(\d+).*/; |   const userIdRegex = /^\/u\/(\d+).*/; | ||||||
|   const result = pathname.match(userIdRegex); |   const result = pathname.match(userIdRegex); | ||||||
|   if (result && result.length === 2) { |   if (result && result.length === 2) { | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								web/src/store/reducer/filter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								web/src/store/reducer/filter.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | 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; | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| 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; |  | ||||||
							
								
								
									
										0
									
								
								web/src/types/location.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								web/src/types/location.d.ts
									
									
									
									
										vendored
									
									
								
							
		Reference in New Issue
	
	Block a user