mirror of https://github.com/usememos/memos.git
refactor: memo filter store
This commit is contained in:
parent
f12d7ae8bc
commit
c23aebd648
|
|
@ -12,10 +12,10 @@
|
|||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@github/relative-time-element": "^4.4.5",
|
||||
"@github/relative-time-element": "^4.4.8",
|
||||
"@matejmazur/react-katex": "^3.1.3",
|
||||
"@mui/joy": "5.0.0-beta.51",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-popover": "^1.1.14",
|
||||
"@usememos/mui": "0.1.0-20250515140125",
|
||||
"clsx": "^2.1.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
|
|
@ -23,60 +23,59 @@
|
|||
"fuse.js": "^7.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"i18next": "^24.2.3",
|
||||
"katex": "^0.16.21",
|
||||
"katex": "^0.16.22",
|
||||
"leaflet": "^1.9.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.486.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"mermaid": "^11.6.0",
|
||||
"mobx": "^6.13.7",
|
||||
"mobx-react-lite": "^4.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-datepicker": "^8.2.1",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-force-graph-2d": "^1.27.0",
|
||||
"react-force-graph-2d": "^1.27.1",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"react-router-dom": "^7.6.1",
|
||||
"react-simple-pull-to-refresh": "^1.3.3",
|
||||
"react-use": "^17.6.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"uuid": "^11.1.0",
|
||||
"zustand": "^5.0.3"
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@bufbuild/protobuf": "^2.5.0",
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/leaflet": "^1.9.16",
|
||||
"@types/leaflet": "^1.9.18",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/qs": "^6.9.18",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/textarea-caret": "^3.0.3",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/qs": "^6.14.0",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/textarea-caret": "^3.0.4",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-legacy": "^6.1.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"code-inspector-plugin": "^0.18.3",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"long": "^5.3.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"long": "^5.3.2",
|
||||
"nice-grpc-web": "^3.3.7",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"terser": "^5.39.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
"vite": "^6.2.1"
|
||||
"terser": "^5.39.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
|
|
|
|||
2805
web/pnpm-lock.yaml
2805
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -4,8 +4,8 @@ import { observer } from "mobx-react-lite";
|
|||
import { shortcutServiceClient } from "@/grpcweb";
|
||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { userStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||
import { cn } from "@/utils";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
|
@ -16,7 +16,6 @@ const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
|
|||
const ShortcutsSection = observer(() => {
|
||||
const t = useTranslate();
|
||||
const user = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const shortcuts = userStore.state.shortcuts;
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { observer } from "mobx-react-lite";
|
|||
import toast from "react-hot-toast";
|
||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||
import { memoServiceClient } from "@/grpcweb";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { userStore } from "@/store/v2";
|
||||
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
|
||||
import { cn } from "@/utils";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import showRenameTagDialog from "../RenameTagDialog";
|
||||
|
|
@ -18,16 +18,15 @@ interface Props {
|
|||
|
||||
const TagsSection = observer((props: Props) => {
|
||||
const t = useTranslate();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const [treeMode, setTreeMode] = useLocalStorage<boolean>("tag-view-as-tree", false);
|
||||
const tags = Object.entries(userStore.state.tagCount)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
const handleTagClick = (tag: string) => {
|
||||
const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === tag);
|
||||
const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter: MemoFilter) => filter.value === tag);
|
||||
if (isActive) {
|
||||
memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag);
|
||||
memoFilterStore.removeFilter((f: MemoFilter) => f.factor === "tagSearch" && f.value === tag);
|
||||
} else {
|
||||
memoFilterStore.addFilter({
|
||||
factor: "tagSearch",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { useContext } from "react";
|
|||
import { useLocation } from "react-router-dom";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import { Routes } from "@/router";
|
||||
import { stringifyFilters, useMemoFilterStore } from "@/store/v1";
|
||||
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
|
||||
import { stringifyFilters } from "@/store/v2/memoFilter";
|
||||
import { cn } from "@/utils";
|
||||
import { RendererContext } from "./types";
|
||||
|
||||
|
|
@ -12,7 +13,6 @@ interface Props {
|
|||
|
||||
const Tag: React.FC<Props> = ({ content }: Props) => {
|
||||
const context = useContext(RendererContext);
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const location = useLocation();
|
||||
const navigateTo = useNavigateTo();
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ const Tag: React.FC<Props> = ({ content }: Props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter) => filter.value === content);
|
||||
const isActive = memoFilterStore.getFiltersByFactor("tagSearch").some((filter: MemoFilter) => filter.value === content);
|
||||
if (isActive) {
|
||||
memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === content);
|
||||
memoFilterStore.removeFilter((f: MemoFilter) => f.factor === "tagSearch" && f.value === content);
|
||||
} else {
|
||||
memoFilterStore.addFilter({
|
||||
factor: "tagSearch",
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { isEqual } from "lodash-es";
|
|||
import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, HashIcon, LinkIcon, BookmarkIcon, SearchIcon, XIcon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { FilterFactor, getMemoFilterKey, MemoFilter, stringifyFilters, useMemoFilterStore } from "@/store/v1";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { FilterFactor, getMemoFilterKey, MemoFilter, stringifyFilters } from "@/store/v2/memoFilter";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const MemoFilters = () => {
|
||||
const t = useTranslate();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const filters = memoFilterStore.filters;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -45,11 +45,11 @@ const MemoFilters = () => {
|
|||
|
||||
return (
|
||||
<div className="w-full mt-2 flex flex-row justify-start items-center flex-wrap gap-x-2 gap-y-1">
|
||||
{filters.map((filter) => (
|
||||
{filters.map((filter: MemoFilter) => (
|
||||
<div
|
||||
key={getMemoFilterKey(filter)}
|
||||
className="w-auto leading-7 h-7 shrink-0 flex flex-row items-center gap-1 bg-white dark:bg-zinc-800 border dark:border-zinc-700 pl-1.5 pr-1 rounded-md hover:line-through cursor-pointer"
|
||||
onClick={() => memoFilterStore.removeFilter((f) => isEqual(f, filter))}
|
||||
onClick={() => memoFilterStore.removeFilter((f: MemoFilter) => isEqual(f, filter))}
|
||||
>
|
||||
<FactorIcon className="w-4 h-auto text-gray-500 dark:text-gray-400 opacity-60" factor={filter.factor} />
|
||||
<span className="text-gray-500 dark:text-gray-400 text-sm max-w-32 truncate">{getFilterDisplayText(filter)}</span>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { SearchIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
|
||||
|
||||
const SearchBar = () => {
|
||||
const t = useTranslate();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const [queryText, setQueryText] = useState("");
|
||||
|
||||
const onTextChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
|
|||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import i18n from "@/i18n";
|
||||
import { Routes } from "@/router";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { userStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { UserStats_MemoTypeStats } from "@/types/proto/api/v1/user_service";
|
||||
import { cn } from "@/utils";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
|
@ -21,7 +21,6 @@ import "react-datepicker/dist/react-datepicker.css";
|
|||
const StatisticsView = observer(() => {
|
||||
const t = useTranslate();
|
||||
const location = useLocation();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const currentUser = useCurrentUser();
|
||||
const [memoTypeStats, setMemoTypeStats] = useState<UserStats_MemoTypeStats>(UserStats_MemoTypeStats.fromPartial({}));
|
||||
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ChevronRightIcon, HashIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import useToggle from "react-use/lib/useToggle";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
|
||||
|
||||
interface Tag {
|
||||
key: string;
|
||||
|
|
@ -85,15 +85,14 @@ interface TagItemContainerProps {
|
|||
|
||||
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
||||
const { tag } = props;
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const tagFilters = memoFilterStore.getFiltersByFactor("tagSearch");
|
||||
const isActive = tagFilters.some((f) => f.value === tag.text);
|
||||
const isActive = tagFilters.some((f: MemoFilter) => f.value === tag.text);
|
||||
const hasSubTags = tag.subTags.length > 0;
|
||||
const [showSubTags, toggleSubTags] = useToggle(false);
|
||||
|
||||
const handleTagClick = () => {
|
||||
if (isActive) {
|
||||
memoFilterStore.removeFilter((f) => f.factor === "tagSearch" && f.value === tag.text);
|
||||
memoFilterStore.removeFilter((f: MemoFilter) => f.factor === "tagSearch" && f.value === tag.text);
|
||||
} else {
|
||||
memoFilterStore.addFilter({
|
||||
factor: "tagSearch",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import useCurrentUser from "@/hooks/useCurrentUser";
|
|||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import Loading from "@/pages/Loading";
|
||||
import { Routes } from "@/router";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { workspaceStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { cn } from "@/utils";
|
||||
|
||||
const RootLayout = observer(() => {
|
||||
|
|
@ -16,7 +16,6 @@ const RootLayout = observer(() => {
|
|||
const [searchParams] = useSearchParams();
|
||||
const { sm } = useResponsiveWidth();
|
||||
const currentUser = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const pathname = useMemo(() => location.pathname, [location.pathname]);
|
||||
const prevPathname = usePrevious(pathname);
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import { useMemo } from "react";
|
|||
import MemoView from "@/components/MemoView";
|
||||
import PagedMemoList from "@/components/PagedMemoList";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { viewStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { Direction, State } from "@/types/proto/api/v1/common";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
|
||||
const Archived = () => {
|
||||
const user = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
|
||||
const memoListFilter = useMemo(() => {
|
||||
const conditions = [];
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import { useMemo } from "react";
|
|||
import MemoView from "@/components/MemoView";
|
||||
import PagedMemoList from "@/components/PagedMemoList";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { viewStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { Direction, State } from "@/types/proto/api/v1/common";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
|
||||
const Explore = () => {
|
||||
const user = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
|
||||
const memoListFilter = useMemo(() => {
|
||||
const conditions = [];
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@ import { useMemo } from "react";
|
|||
import MemoView from "@/components/MemoView";
|
||||
import PagedMemoList from "@/components/PagedMemoList";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { viewStore, userStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { Direction, State } from "@/types/proto/api/v1/common";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
|
||||
const Home = observer(() => {
|
||||
const user = useCurrentUser();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const selectedShortcut = userStore.state.shortcuts.find((shortcut) => shortcut.id === memoFilterStore.shortcut);
|
||||
|
||||
const memoListFilter = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import MemoView from "@/components/MemoView";
|
|||
import PagedMemoList from "@/components/PagedMemoList";
|
||||
import UserAvatar from "@/components/UserAvatar";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useMemoFilterStore } from "@/store/v1";
|
||||
import { viewStore, userStore } from "@/store/v2";
|
||||
import memoFilterStore from "@/store/v2/memoFilter";
|
||||
import { Direction, State } from "@/types/proto/api/v1/common";
|
||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||
import { User } from "@/types/proto/api/v1/user_service";
|
||||
|
|
@ -22,7 +22,6 @@ const UserProfile = observer(() => {
|
|||
const params = useParams();
|
||||
const loadingState = useLoading();
|
||||
const [user, setUser] = useState<User>();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
|
||||
useEffect(() => {
|
||||
const username = params.username;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from "./memoFilter";
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import { uniqBy } from "lodash-es";
|
||||
import { create } from "zustand";
|
||||
import { combine } from "zustand/middleware";
|
||||
|
||||
export type FilterFactor =
|
||||
| "tagSearch"
|
||||
| "visibility"
|
||||
| "contentSearch"
|
||||
| "displayTime"
|
||||
| "pinned"
|
||||
| "property.hasLink"
|
||||
| "property.hasTaskList"
|
||||
| "property.hasCode";
|
||||
|
||||
export interface MemoFilter {
|
||||
factor: FilterFactor;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const getMemoFilterKey = (filter: MemoFilter) => `${filter.factor}:${filter.value}`;
|
||||
|
||||
export const parseFilterQuery = (query: string | null): MemoFilter[] => {
|
||||
if (!query) return [];
|
||||
try {
|
||||
return query.split(",").map((filterStr) => {
|
||||
const [factor, value] = filterStr.split(":");
|
||||
return {
|
||||
factor: factor as FilterFactor,
|
||||
value: decodeURIComponent(value),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to parse filter query:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const stringifyFilters = (filters: MemoFilter[]): string => {
|
||||
return filters.map((filter) => `${filter.factor}:${encodeURIComponent(filter.value)}`).join(",");
|
||||
};
|
||||
|
||||
interface State {
|
||||
filters: MemoFilter[];
|
||||
// The id of selected shortcut.
|
||||
shortcut?: string;
|
||||
}
|
||||
|
||||
const getInitialState = (): State => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
return {
|
||||
filters: parseFilterQuery(searchParams.get("filter")),
|
||||
};
|
||||
};
|
||||
|
||||
export const useMemoFilterStore = create(
|
||||
combine(getInitialState(), (set, get) => ({
|
||||
setState: (state: State) => set(state),
|
||||
getState: () => get(),
|
||||
getFiltersByFactor: (factor: FilterFactor) => get().filters.filter((f) => f.factor === factor),
|
||||
addFilter: (filter: MemoFilter) => set((state) => ({ filters: uniqBy([...state.filters, filter], getMemoFilterKey) })),
|
||||
removeFilter: (filterFn: (f: MemoFilter) => boolean) => set((state) => ({ filters: state.filters.filter((f) => !filterFn(f)) })),
|
||||
setShortcut: (shortcut?: string) => set({ shortcut }),
|
||||
})),
|
||||
);
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import memoStore from "./memo";
|
||||
import memoFilterStore from "./memoFilter";
|
||||
import resourceStore from "./resource";
|
||||
import userStore from "./user";
|
||||
import viewStore from "./view";
|
||||
import workspaceStore from "./workspace";
|
||||
|
||||
export { memoStore, resourceStore, workspaceStore, userStore, viewStore };
|
||||
export { memoFilterStore, memoStore, resourceStore, workspaceStore, userStore, viewStore };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
import { uniqBy } from "lodash-es";
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
export type FilterFactor =
|
||||
| "tagSearch"
|
||||
| "visibility"
|
||||
| "contentSearch"
|
||||
| "displayTime"
|
||||
| "pinned"
|
||||
| "property.hasLink"
|
||||
| "property.hasTaskList"
|
||||
| "property.hasCode";
|
||||
|
||||
export interface MemoFilter {
|
||||
factor: FilterFactor;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const getMemoFilterKey = (filter: MemoFilter) => `${filter.factor}:${filter.value}`;
|
||||
|
||||
export const parseFilterQuery = (query: string | null): MemoFilter[] => {
|
||||
if (!query) return [];
|
||||
try {
|
||||
return query.split(",").map((filterStr) => {
|
||||
const [factor, value] = filterStr.split(":");
|
||||
return {
|
||||
factor: factor as FilterFactor,
|
||||
value: decodeURIComponent(value),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to parse filter query:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const stringifyFilters = (filters: MemoFilter[]): string => {
|
||||
return filters.map((filter) => `${filter.factor}:${encodeURIComponent(filter.value)}`).join(",");
|
||||
};
|
||||
|
||||
class MemoFilterState {
|
||||
filters: MemoFilter[] = [];
|
||||
shortcut?: string = undefined;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
this.filters = parseFilterQuery(searchParams.get("filter"));
|
||||
}
|
||||
|
||||
setState(state: Partial<MemoFilterState>) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
|
||||
getFiltersByFactor(factor: FilterFactor) {
|
||||
return this.filters.filter((f) => f.factor === factor);
|
||||
}
|
||||
|
||||
addFilter(filter: MemoFilter) {
|
||||
this.filters = uniqBy([...this.filters, filter], getMemoFilterKey);
|
||||
}
|
||||
|
||||
removeFilter(filterFn: (f: MemoFilter) => boolean) {
|
||||
this.filters = this.filters.filter((f) => !filterFn(f));
|
||||
}
|
||||
|
||||
setShortcut(shortcut?: string) {
|
||||
this.shortcut = shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
const memoFilterStore = (() => {
|
||||
const state = new MemoFilterState();
|
||||
|
||||
return {
|
||||
get filters() {
|
||||
return state.filters;
|
||||
},
|
||||
get shortcut() {
|
||||
return state.shortcut;
|
||||
},
|
||||
getFiltersByFactor: (factor: FilterFactor) => state.getFiltersByFactor(factor),
|
||||
addFilter: (filter: MemoFilter) => state.addFilter(filter),
|
||||
removeFilter: (filterFn: (f: MemoFilter) => boolean) => state.removeFilter(filterFn),
|
||||
setShortcut: (shortcut?: string) => state.setShortcut(shortcut),
|
||||
};
|
||||
})();
|
||||
|
||||
export default memoFilterStore;
|
||||
Loading…
Reference in New Issue