refactor: filter store (#1331)

This commit is contained in:
boojack 2023-03-11 09:13:54 +08:00 committed by GitHub
parent f3f0efba1e
commit a9218ed5f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 215 additions and 327 deletions

View File

@ -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) => {

View File

@ -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>
</>

View File

@ -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"
}`}
>

View File

@ -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"
}`}
>

View File

@ -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);
}
};

View File

@ -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);

View File

@ -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}

View File

@ -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 {

View File

@ -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">

View File

@ -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]

View File

@ -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);

View File

@ -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);
}
};

View File

@ -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);
}
}, []);

View File

@ -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

View File

@ -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);

View File

@ -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,

View 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,
})
);
},
};
};

View File

@ -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";

View File

@ -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();
},
};
};

View File

@ -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) {

View 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;

View File

@ -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;

View File