mirror of
https://github.com/usememos/memos.git
synced 2024-12-20 01:31:29 +03:00
refactor: filter store (#1331)
This commit is contained in:
parent
f3f0efba1e
commit
a9218ed5f0
@ -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 { useLocationStore, useGlobalStore } 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) => {
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -1,5 +1,5 @@
|
||||
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"
|
||||
}`}
|
||||
>
|
||||
|
@ -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"
|
||||
}`}
|
||||
>
|
||||
|
@ -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, useLocationStore, useMemoStore, useUserStore } from "../store/module";
|
||||
import { useEditorStore, useFilterStore, 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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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);
|
||||
|
@ -1,5 +1,7 @@
|
||||
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={() => {
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
filterStore.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}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
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 * 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 = useLocationStore();
|
||||
const query = locationStore.state.query;
|
||||
const filterStore = useFilterStore();
|
||||
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 {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLayoutStore, useLocationStore, useShortcutStore } from "../store/module";
|
||||
import { useLayoutStore, useFilterStore, 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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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">
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
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 Icon from "./Icon";
|
||||
|
||||
const SearchBar = () => {
|
||||
const locationStore = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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]
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocationStore, useShortcutStore } from "../store/module";
|
||||
import { useFilterStore, 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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
const shortcutStore = useShortcutStore();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleShortcutClick = () => {
|
||||
if (isActive) {
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
filterStore.setMemoShortcut(undefined);
|
||||
} else {
|
||||
locationStore.setMemoShortcut(shortcut.id);
|
||||
filterStore.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
|
||||
locationStore.setMemoShortcut(undefined);
|
||||
filterStore.setMemoShortcut(undefined);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocationStore, useTagStore } from "../store/module";
|
||||
import { useFilterStore, 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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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 = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { getMemoStats } from "../helpers/api";
|
||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||
@ -29,7 +29,7 @@ interface DailyUsageStat {
|
||||
|
||||
const UsageHeatMap = () => {
|
||||
const { t } = useTranslation();
|
||||
const locationStore = useLocationStore();
|
||||
const filterStore = useFilterStore();
|
||||
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);
|
||||
}
|
||||
}, []);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
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 { 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
|
||||
|
@ -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, useLocation, useParams } from "react-router-dom";
|
||||
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 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);
|
||||
|
@ -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: locationReducer,
|
||||
filter: filterReducer,
|
||||
resource: resourceReducer,
|
||||
dialog: dialogReducer,
|
||||
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 "./global";
|
||||
export * from "./location";
|
||||
export * from "./filter";
|
||||
export * from "./memo";
|
||||
export * from "./tag";
|
||||
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 pathname = location.pathname;
|
||||
const pathname = window.location.pathname;
|
||||
const userIdRegex = /^\/u\/(\d+).*/;
|
||||
const result = pathname.match(userIdRegex);
|
||||
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
Loading…
Reference in New Issue
Block a user