mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: implement memo filters
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useFilterStore } from "@/store/module";
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
import { RendererContext } from "./types";
|
import { RendererContext } from "./types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -9,18 +9,21 @@ interface Props {
|
|||||||
|
|
||||||
const Tag: React.FC<Props> = ({ content }: Props) => {
|
const Tag: React.FC<Props> = ({ content }: Props) => {
|
||||||
const context = useContext(RendererContext);
|
const context = useContext(RendererContext);
|
||||||
const filterStore = useFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
|
|
||||||
const handleTagClick = () => {
|
const handleTagClick = () => {
|
||||||
if (context.disableFilter) {
|
if (context.disableFilter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currTagQuery = filterStore.getState().tag;
|
const isActive = memoFilterStore.getFiltersByFactor("tag").some((filter) => filter.value === content);
|
||||||
if (currTagQuery === content) {
|
if (isActive) {
|
||||||
filterStore.setTagFilter(undefined);
|
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === content);
|
||||||
} else {
|
} else {
|
||||||
filterStore.setTagFilter(content);
|
memoFilterStore.addFilter({
|
||||||
|
factor: "tag",
|
||||||
|
value: content,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
40
web/src/components/MemoFilters.tsx
Normal file
40
web/src/components/MemoFilters.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { isEqual } from "lodash-es";
|
||||||
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
|
import Icon from "./Icon";
|
||||||
|
|
||||||
|
const MemoFilters = () => {
|
||||||
|
const memoFilterStore = useMemoFilterStore();
|
||||||
|
const filters = memoFilterStore.filters;
|
||||||
|
|
||||||
|
if (filters.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full mb-2 flex flex-row justify-start items-start gap-2">
|
||||||
|
<span className="flex flex-row items-center gap-0.5 text-gray-500 text-sm leading-6">
|
||||||
|
<Icon.Filter className="w-4 h-auto opacity-60 inline" />
|
||||||
|
Filters
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-row justify-start items-center flex-wrap gap-2 leading-6">
|
||||||
|
{filters.map((filter) => (
|
||||||
|
<div
|
||||||
|
key={filter.factor}
|
||||||
|
className="flex flex-row items-center gap-1 bg-gray-100 dark:bg-zinc-800 border dark:border-zinc-700 px-1 rounded-md"
|
||||||
|
>
|
||||||
|
<span className="text-gray-600 dark:text-gray-500 text-sm">{filter.factor}</span>
|
||||||
|
{filter.value && <span className="text-gray-500 dark:text-gray-400 text-sm max-w-12 truncate">{filter.value}</span>}
|
||||||
|
<button
|
||||||
|
onClick={() => memoFilterStore.removeFilter((f) => isEqual(f, filter))}
|
||||||
|
className="text-gray-500 dark:text-gray-300 opacity-60 hover:opacity-100"
|
||||||
|
>
|
||||||
|
<Icon.X className="w-3 h-auto" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemoFilters;
|
@ -4,7 +4,6 @@ import { toast } from "react-hot-toast";
|
|||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useFilterStore } from "@/store/module";
|
|
||||||
import { useTagStore } from "@/store/v1";
|
import { useTagStore } from "@/store/v1";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -18,7 +17,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const { tag, destroy } = props;
|
const { tag, destroy } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const tagStore = useTagStore();
|
const tagStore = useTagStore();
|
||||||
const filterStore = useFilterStore();
|
|
||||||
const [newName, setNewName] = useState(tag);
|
const [newName, setNewName] = useState(tag);
|
||||||
const requestState = useLoading(false);
|
const requestState = useLoading(false);
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
@ -44,7 +42,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
|||||||
newTag: newName,
|
newTag: newName,
|
||||||
});
|
});
|
||||||
toast.success("Rename tag successfully");
|
toast.success("Rename tag successfully");
|
||||||
filterStore.setTagFilter(newName);
|
|
||||||
tagStore.fetchTags({ user }, { skipCache: true });
|
tagStore.fetchTags({ user }, { skipCache: true });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
import { useFilterStore } from "@/store/module";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const filterStore = useFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [queryText, setQueryText] = useState("");
|
const [queryText, setQueryText] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
const onTextChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||||
const text = filterStore.getState().text;
|
|
||||||
setQueryText(text === undefined ? "" : text);
|
|
||||||
}, [filterStore.state.text]);
|
|
||||||
|
|
||||||
useDebounce(
|
|
||||||
() => {
|
|
||||||
filterStore.setTextFilter(queryText.length === 0 ? undefined : queryText);
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
[queryText],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleTextQueryInput = (event: React.FormEvent<HTMLInputElement>) => {
|
|
||||||
setQueryText(event.currentTarget.value);
|
setQueryText(event.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (queryText !== "") {
|
||||||
|
memoFilterStore.removeFilter((f) => f.factor === "contentSearch");
|
||||||
|
memoFilterStore.addFilter({
|
||||||
|
factor: "contentSearch",
|
||||||
|
value: queryText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
||||||
<Icon.Search className="absolute left-3 w-4 h-auto opacity-30" />
|
<Icon.Search className="absolute left-3 w-4 h-auto opacity-30" />
|
||||||
@ -33,7 +32,8 @@ const SearchBar = () => {
|
|||||||
className="w-full text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm leading-7 rounded-lg p-1 pl-8 outline-none"
|
className="w-full text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm leading-7 rounded-lg p-1 pl-8 outline-none"
|
||||||
placeholder={t("memo.search-placeholder")}
|
placeholder={t("memo.search-placeholder")}
|
||||||
value={queryText}
|
value={queryText}
|
||||||
onChange={handleTextQueryInput}
|
onChange={onTextChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useToggle from "react-use/lib/useToggle";
|
import useToggle from "react-use/lib/useToggle";
|
||||||
import { useFilterStore } from "@/store/module";
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
interface Tag {
|
interface Tag {
|
||||||
@ -14,8 +14,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TagTree = ({ tags: rawTags }: Props) => {
|
const TagTree = ({ tags: rawTags }: Props) => {
|
||||||
const filterStore = useFilterStore();
|
|
||||||
const filter = filterStore.state;
|
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -67,7 +65,7 @@ const TagTree = ({ tags: rawTags }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap gap-2 mt-1">
|
<div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap gap-2 mt-1">
|
||||||
{tags.map((t, idx) => (
|
{tags.map((t, idx) => (
|
||||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={filter.tag} />
|
<TagItemContainer key={t.text + "-" + idx} tag={t} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -75,21 +73,24 @@ const TagTree = ({ tags: rawTags }: Props) => {
|
|||||||
|
|
||||||
interface TagItemContainerProps {
|
interface TagItemContainerProps {
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
tagQuery?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContainerProps) => {
|
||||||
const filterStore = useFilterStore();
|
const { tag } = props;
|
||||||
const { tag, tagQuery } = props;
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const isActive = tagQuery === tag.text;
|
const tagFilters = memoFilterStore.getFiltersByFactor("tag");
|
||||||
|
const isActive = tagFilters.some((f) => f.value === tag.text);
|
||||||
const hasSubTags = tag.subTags.length > 0;
|
const hasSubTags = tag.subTags.length > 0;
|
||||||
const [showSubTags, toggleSubTags] = useToggle(false);
|
const [showSubTags, toggleSubTags] = useToggle(false);
|
||||||
|
|
||||||
const handleTagClick = () => {
|
const handleTagClick = () => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
filterStore.setTagFilter(undefined);
|
memoFilterStore.removeFilter((f) => f.factor === "tag" && f.value === tag.text);
|
||||||
} else {
|
} else {
|
||||||
filterStore.setTagFilter(tag.text);
|
memoFilterStore.addFilter({
|
||||||
|
factor: "tag",
|
||||||
|
value: tag.text,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tag.subTags.map((st, idx) => (
|
{tag.subTags.map((st, idx) => (
|
||||||
<TagItemContainer key={st.text + "-" + idx} tag={st} tagQuery={tagQuery} />
|
<TagItemContainer key={st.text + "-" + idx} tag={st} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -7,8 +7,7 @@ import { memoServiceClient } from "@/grpcweb";
|
|||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import { useFilterStore } from "@/store/module";
|
import { useMemoFilterStore, useMemoStore } from "@/store/v1";
|
||||||
import { useMemoStore } from "@/store/v1";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import ActivityCalendar from "./ActivityCalendar";
|
import ActivityCalendar from "./ActivityCalendar";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -25,14 +24,13 @@ const UserStatisticsView = () => {
|
|||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const filterStore = useFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [memoAmount, setMemoAmount] = useState(0);
|
const [memoAmount, setMemoAmount] = useState(0);
|
||||||
const [memoStats, setMemoStats] = useState<UserMemoStats>({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 });
|
const [memoStats, setMemoStats] = useState<UserMemoStats>({ link: 0, taskList: 0, code: 0, incompleteTasks: 0 });
|
||||||
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
||||||
const [selectedDate] = useState(new Date());
|
const [selectedDate] = useState(new Date());
|
||||||
const [monthString, setMonthString] = useState(dayjs(selectedDate.toDateString()).format("YYYY-MM"));
|
const [monthString, setMonthString] = useState(dayjs(selectedDate.toDateString()).format("YYYY-MM"));
|
||||||
const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
|
const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
|
||||||
const filter = filterStore.state;
|
|
||||||
|
|
||||||
useAsyncEffect(async () => {
|
useAsyncEffect(async () => {
|
||||||
const { properties } = await memoServiceClient.listMemoProperties({
|
const { properties } = await memoServiceClient.listMemoProperties({
|
||||||
@ -120,9 +118,8 @@ const UserStatisticsView = () => {
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
||||||
filter.memoPropertyFilter?.hasLink ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
|
|
||||||
)}
|
)}
|
||||||
onClick={() => filterStore.setMemoPropertyFilter({ hasLink: !filter.memoPropertyFilter?.hasLink })}
|
onClick={() => memoFilterStore.addFilter({ factor: "property.hasLink", value: "" })}
|
||||||
>
|
>
|
||||||
<div className="w-auto flex justify-start items-center mr-1">
|
<div className="w-auto flex justify-start items-center mr-1">
|
||||||
<Icon.Link className="w-4 h-auto mr-1" />
|
<Icon.Link className="w-4 h-auto mr-1" />
|
||||||
@ -133,9 +130,8 @@ const UserStatisticsView = () => {
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
||||||
filter.memoPropertyFilter?.hasTaskList ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
|
|
||||||
)}
|
)}
|
||||||
onClick={() => filterStore.setMemoPropertyFilter({ hasTaskList: !filter.memoPropertyFilter?.hasTaskList })}
|
onClick={() => memoFilterStore.addFilter({ factor: "property.hasTaskList", value: "" })}
|
||||||
>
|
>
|
||||||
<div className="w-auto flex justify-start items-center mr-1">
|
<div className="w-auto flex justify-start items-center mr-1">
|
||||||
{memoStats.incompleteTasks > 0 ? (
|
{memoStats.incompleteTasks > 0 ? (
|
||||||
@ -160,9 +156,8 @@ const UserStatisticsView = () => {
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
"w-auto border dark:border-zinc-800 pl-1 pr-1.5 rounded-md flex justify-between items-center cursor-pointer hover:shadow",
|
||||||
filter.memoPropertyFilter?.hasCode ? "bg-blue-50 dark:bg-blue-900 shadow" : "",
|
|
||||||
)}
|
)}
|
||||||
onClick={() => filterStore.setMemoPropertyFilter({ hasCode: !filter.memoPropertyFilter?.hasCode })}
|
onClick={() => memoFilterStore.addFilter({ factor: "property.hasCode", value: "" })}
|
||||||
>
|
>
|
||||||
<div className="w-auto flex justify-start items-center mr-1">
|
<div className="w-auto flex justify-start items-center mr-1">
|
||||||
<Icon.Code2 className="w-4 h-auto mr-1" />
|
<Icon.Code2 className="w-4 h-auto mr-1" />
|
||||||
|
@ -2,5 +2,4 @@ export * from "./useLoading";
|
|||||||
export * from "./useCurrentUser";
|
export * from "./useCurrentUser";
|
||||||
export * from "./useNavigateTo";
|
export * from "./useNavigateTo";
|
||||||
export * from "./useAsyncEffect";
|
export * from "./useAsyncEffect";
|
||||||
export * from "./useFilterWithUrlParams";
|
|
||||||
export * from "./useResponsiveWidth";
|
export * from "./useResponsiveWidth";
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import { useFilterStore } from "@/store/module";
|
|
||||||
|
|
||||||
const useFilterWithUrlParams = () => {
|
|
||||||
const location = useLocation();
|
|
||||||
const filterStore = useFilterStore();
|
|
||||||
const { tag, text, memoPropertyFilter } = filterStore.state;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const urlParams = new URLSearchParams(location.search);
|
|
||||||
const tag = urlParams.get("tag");
|
|
||||||
const text = urlParams.get("text");
|
|
||||||
if (tag) {
|
|
||||||
filterStore.setTagFilter(tag);
|
|
||||||
}
|
|
||||||
if (text) {
|
|
||||||
filterStore.setTextFilter(text);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const urlParams = new URLSearchParams(location.search);
|
|
||||||
if (tag) {
|
|
||||||
urlParams.set("tag", tag);
|
|
||||||
} else {
|
|
||||||
urlParams.delete("tag");
|
|
||||||
}
|
|
||||||
if (text) {
|
|
||||||
urlParams.set("text", text);
|
|
||||||
} else {
|
|
||||||
urlParams.delete("text");
|
|
||||||
}
|
|
||||||
const params = urlParams.toString();
|
|
||||||
window.history.replaceState({}, "", `${location.pathname}${params?.length > 0 ? `?${params}` : ""}`);
|
|
||||||
}, [tag, text]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
tag,
|
|
||||||
text,
|
|
||||||
memoPropertyFilter,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFilterWithUrlParams;
|
|
@ -5,13 +5,13 @@ import toast from "react-hot-toast";
|
|||||||
import Empty from "@/components/Empty";
|
import Empty from "@/components/Empty";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
import MemoContent from "@/components/MemoContent";
|
import MemoContent from "@/components/MemoContent";
|
||||||
|
import MemoFilters from "@/components/MemoFilters";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
|
import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1";
|
||||||
import { useMemoList, useMemoStore } from "@/store/v1";
|
|
||||||
import { RowStatus } from "@/types/proto/api/v1/common";
|
import { RowStatus } from "@/types/proto/api/v1/common";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
@ -21,9 +21,9 @@ const Archived = () => {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [isRequesting, setIsRequesting] = useState(true);
|
const [isRequesting, setIsRequesting] = useState(true);
|
||||||
const [nextPageToken, setNextPageToken] = useState<string>("");
|
const [nextPageToken, setNextPageToken] = useState<string>("");
|
||||||
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
|
|
||||||
const sortedMemos = memoList.value
|
const sortedMemos = memoList.value
|
||||||
.filter((memo) => memo.rowStatus === RowStatus.ARCHIVED)
|
.filter((memo) => memo.rowStatus === RowStatus.ARCHIVED)
|
||||||
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
||||||
@ -31,21 +31,22 @@ const Archived = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoList.reset();
|
memoList.reset();
|
||||||
fetchMemos("");
|
fetchMemos("");
|
||||||
}, [tagQuery, textQuery]);
|
}, [memoFilterStore.filters]);
|
||||||
|
|
||||||
const fetchMemos = async (nextPageToken: string) => {
|
const fetchMemos = async (nextPageToken: string) => {
|
||||||
setIsRequesting(true);
|
setIsRequesting(true);
|
||||||
const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`];
|
const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`];
|
||||||
const contentSearch: string[] = [];
|
const contentSearch: string[] = [];
|
||||||
if (textQuery) {
|
for (const filter of memoFilterStore.filters) {
|
||||||
contentSearch.push(JSON.stringify(textQuery));
|
if (filter.factor === "contentSearch") {
|
||||||
|
contentSearch.push(`"${filter.value}"`);
|
||||||
|
} else if (filter.factor === "tag") {
|
||||||
|
filters.push(`tag == "${filter.value}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (contentSearch.length > 0) {
|
if (contentSearch.length > 0) {
|
||||||
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
||||||
}
|
}
|
||||||
if (tagQuery) {
|
|
||||||
filters.push(`tag == "${tagQuery}"`);
|
|
||||||
}
|
|
||||||
const response = await memoStore.fetchMemos({
|
const response = await memoStore.fetchMemos({
|
||||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||||
filter: filters.join(" && "),
|
filter: filters.join(" && "),
|
||||||
@ -92,6 +93,7 @@ const Archived = () => {
|
|||||||
<SearchBar />
|
<SearchBar />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<MemoFilters />
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
<div
|
<div
|
||||||
key={memo.name}
|
key={memo.name}
|
||||||
|
@ -4,14 +4,14 @@ import { useEffect, useState } from "react";
|
|||||||
import Empty from "@/components/Empty";
|
import Empty from "@/components/Empty";
|
||||||
import { ExploreSidebar, ExploreSidebarDrawer } from "@/components/ExploreSidebar";
|
import { ExploreSidebar, ExploreSidebarDrawer } from "@/components/ExploreSidebar";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
|
import MemoFilters from "@/components/MemoFilters";
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
|
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { useMemoList, useMemoStore } from "@/store/v1";
|
import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
const Explore = () => {
|
const Explore = () => {
|
||||||
@ -20,29 +20,30 @@ const Explore = () => {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [isRequesting, setIsRequesting] = useState(true);
|
const [isRequesting, setIsRequesting] = useState(true);
|
||||||
const [nextPageToken, setNextPageToken] = useState<string>("");
|
const [nextPageToken, setNextPageToken] = useState<string>("");
|
||||||
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
|
|
||||||
const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoList.reset();
|
memoList.reset();
|
||||||
fetchMemos("");
|
fetchMemos("");
|
||||||
}, [tagQuery, textQuery]);
|
}, [memoFilterStore.filters]);
|
||||||
|
|
||||||
const fetchMemos = async (nextPageToken: string) => {
|
const fetchMemos = async (nextPageToken: string) => {
|
||||||
setIsRequesting(true);
|
setIsRequesting(true);
|
||||||
const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`];
|
const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`];
|
||||||
const contentSearch: string[] = [];
|
const contentSearch: string[] = [];
|
||||||
if (textQuery) {
|
for (const filter of memoFilterStore.filters) {
|
||||||
contentSearch.push(JSON.stringify(textQuery));
|
if (filter.factor === "contentSearch") {
|
||||||
|
contentSearch.push(`"${filter.value}"`);
|
||||||
|
} else if (filter.factor === "tag") {
|
||||||
|
filters.push(`tag == "${filter.value}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (contentSearch.length > 0) {
|
if (contentSearch.length > 0) {
|
||||||
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
||||||
}
|
}
|
||||||
if (tagQuery) {
|
|
||||||
filters.push(`tag == "${tagQuery}"`);
|
|
||||||
}
|
|
||||||
const response = await memoStore.fetchMemos({
|
const response = await memoStore.fetchMemos({
|
||||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||||
filter: filters.join(" && "),
|
filter: filters.join(" && "),
|
||||||
@ -61,6 +62,7 @@ const Explore = () => {
|
|||||||
)}
|
)}
|
||||||
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||||
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||||
|
<MemoFilters />
|
||||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility showPinned compact />
|
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility showPinned compact />
|
||||||
|
@ -5,14 +5,14 @@ import Empty from "@/components/Empty";
|
|||||||
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
import MemoEditor from "@/components/MemoEditor";
|
import MemoEditor from "@/components/MemoEditor";
|
||||||
|
import MemoFilters from "@/components/MemoFilters";
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
|
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { useMemoList, useMemoStore } from "@/store/v1";
|
import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1";
|
||||||
import { RowStatus } from "@/types/proto/api/v1/common";
|
import { RowStatus } from "@/types/proto/api/v1/common";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
@ -22,9 +22,9 @@ const Home = () => {
|
|||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [isRequesting, setIsRequesting] = useState(true);
|
const [isRequesting, setIsRequesting] = useState(true);
|
||||||
const [nextPageToken, setNextPageToken] = useState<string>("");
|
const [nextPageToken, setNextPageToken] = useState<string>("");
|
||||||
const filter = useFilterWithUrlParams();
|
|
||||||
const sortedMemos = memoList.value
|
const sortedMemos = memoList.value
|
||||||
.filter((memo) => memo.rowStatus === RowStatus.ACTIVE)
|
.filter((memo) => memo.rowStatus === RowStatus.ACTIVE)
|
||||||
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime))
|
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime))
|
||||||
@ -33,32 +33,28 @@ const Home = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
memoList.reset();
|
memoList.reset();
|
||||||
fetchMemos("");
|
fetchMemos("");
|
||||||
}, [filter.tag, filter.text, filter.memoPropertyFilter]);
|
}, [memoFilterStore.filters]);
|
||||||
|
|
||||||
const fetchMemos = async (nextPageToken: string) => {
|
const fetchMemos = async (nextPageToken: string) => {
|
||||||
setIsRequesting(true);
|
setIsRequesting(true);
|
||||||
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
|
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
|
||||||
const contentSearch: string[] = [];
|
const contentSearch: string[] = [];
|
||||||
if (filter.text) {
|
for (const filter of memoFilterStore.filters) {
|
||||||
contentSearch.push(JSON.stringify(filter.text));
|
if (filter.factor === "contentSearch") {
|
||||||
|
contentSearch.push(`"${filter.value}"`);
|
||||||
|
} else if (filter.factor === "tag") {
|
||||||
|
filters.push(`tag == "${filter.value}"`);
|
||||||
|
} else if (filter.factor === "property.hasLink") {
|
||||||
|
filters.push(`has_link == true`);
|
||||||
|
} else if (filter.factor === "property.hasTaskList") {
|
||||||
|
filters.push(`has_task_list == true`);
|
||||||
|
} else if (filter.factor === "property.hasCode") {
|
||||||
|
filters.push(`has_code == true`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (contentSearch.length > 0) {
|
if (contentSearch.length > 0) {
|
||||||
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
||||||
}
|
}
|
||||||
if (filter.tag) {
|
|
||||||
filters.push(`tag == "${filter.tag}"`);
|
|
||||||
}
|
|
||||||
if (filter.memoPropertyFilter) {
|
|
||||||
if (filter.memoPropertyFilter.hasLink) {
|
|
||||||
filters.push(`has_link == true`);
|
|
||||||
}
|
|
||||||
if (filter.memoPropertyFilter.hasTaskList) {
|
|
||||||
filters.push(`has_task_list == true`);
|
|
||||||
}
|
|
||||||
if (filter.memoPropertyFilter.hasCode) {
|
|
||||||
filters.push(`has_code == true`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await memoStore.fetchMemos({
|
const response = await memoStore.fetchMemos({
|
||||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||||
filter: filters.join(" && "),
|
filter: filters.join(" && "),
|
||||||
@ -78,6 +74,7 @@ const Home = () => {
|
|||||||
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||||
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||||
<MemoEditor className="mb-2" cacheKey="home-memo-editor" />
|
<MemoEditor className="mb-2" cacheKey="home-memo-editor" />
|
||||||
|
<MemoFilters />
|
||||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showVisibility showPinned compact />
|
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showVisibility showPinned compact />
|
||||||
|
@ -5,14 +5,14 @@ import { toast } from "react-hot-toast";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import Empty from "@/components/Empty";
|
import Empty from "@/components/Empty";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
|
import MemoFilters from "@/components/MemoFilters";
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import UserAvatar from "@/components/UserAvatar";
|
import UserAvatar from "@/components/UserAvatar";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||||
import useFilterWithUrlParams from "@/hooks/useFilterWithUrlParams";
|
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useMemoList, useMemoStore, useUserStore } from "@/store/v1";
|
import { useMemoFilterStore, useMemoList, useMemoStore, useUserStore } from "@/store/v1";
|
||||||
import { User } from "@/types/proto/api/v1/user_service";
|
import { User } from "@/types/proto/api/v1/user_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
@ -24,9 +24,9 @@ const UserProfile = () => {
|
|||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const [isRequesting, setIsRequesting] = useState(true);
|
const [isRequesting, setIsRequesting] = useState(true);
|
||||||
const [nextPageToken, setNextPageToken] = useState<string>("");
|
const [nextPageToken, setNextPageToken] = useState<string>("");
|
||||||
const { tag: tagQuery, text: textQuery } = useFilterWithUrlParams();
|
|
||||||
const sortedMemos = memoList.value
|
const sortedMemos = memoList.value
|
||||||
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime))
|
.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime))
|
||||||
.sort((a, b) => Number(b.pinned) - Number(a.pinned));
|
.sort((a, b) => Number(b.pinned) - Number(a.pinned));
|
||||||
@ -60,7 +60,7 @@ const UserProfile = () => {
|
|||||||
|
|
||||||
memoList.reset();
|
memoList.reset();
|
||||||
fetchMemos("");
|
fetchMemos("");
|
||||||
}, [user, tagQuery, textQuery]);
|
}, [user, memoFilterStore.filters]);
|
||||||
|
|
||||||
const fetchMemos = async (nextPageToken: string) => {
|
const fetchMemos = async (nextPageToken: string) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -70,15 +70,16 @@ const UserProfile = () => {
|
|||||||
setIsRequesting(true);
|
setIsRequesting(true);
|
||||||
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
|
const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`];
|
||||||
const contentSearch: string[] = [];
|
const contentSearch: string[] = [];
|
||||||
if (textQuery) {
|
for (const filter of memoFilterStore.filters) {
|
||||||
contentSearch.push(JSON.stringify(textQuery));
|
if (filter.factor === "contentSearch") {
|
||||||
|
contentSearch.push(`"${filter.value}"`);
|
||||||
|
} else if (filter.factor === "tag") {
|
||||||
|
filters.push(`tag == "${filter.value}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (contentSearch.length > 0) {
|
if (contentSearch.length > 0) {
|
||||||
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
filters.push(`content_search == [${contentSearch.join(", ")}]`);
|
||||||
}
|
}
|
||||||
if (tagQuery) {
|
|
||||||
filters.push(`tag == "${tagQuery}"`);
|
|
||||||
}
|
|
||||||
const response = await memoStore.fetchMemos({
|
const response = await memoStore.fetchMemos({
|
||||||
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
|
||||||
filter: filters.join(" && "),
|
filter: filters.join(" && "),
|
||||||
@ -125,6 +126,7 @@ const UserProfile = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<MemoFilters />
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
<MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />
|
<MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />
|
||||||
))}
|
))}
|
||||||
|
@ -5,3 +5,4 @@ export * from "./resourceName";
|
|||||||
export * from "./resource";
|
export * from "./resource";
|
||||||
export * from "./workspaceSetting";
|
export * from "./workspaceSetting";
|
||||||
export * from "./tag";
|
export * from "./tag";
|
||||||
|
export * from "./memoFilter";
|
||||||
|
40
web/src/store/v1/memoFilter.ts
Normal file
40
web/src/store/v1/memoFilter.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { uniq } from "lodash-es";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { combine, persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
type FilterFactor =
|
||||||
|
| "tag"
|
||||||
|
| "visibility"
|
||||||
|
| "contentSearch"
|
||||||
|
| "displayTime"
|
||||||
|
| "property.hasLink"
|
||||||
|
| "property.hasTaskList"
|
||||||
|
| "property.hasCode";
|
||||||
|
|
||||||
|
export interface MemoFilter {
|
||||||
|
factor: FilterFactor;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
filters: MemoFilter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultState = (): State => ({
|
||||||
|
filters: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useMemoFilterStore = create(
|
||||||
|
persist(
|
||||||
|
combine(getDefaultState(), (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: uniq([...state.filters, filter]) })),
|
||||||
|
removeFilter: (filterFn: (f: MemoFilter) => boolean) => set((state) => ({ filters: state.filters.filter((f) => !filterFn(f)) })),
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
name: "memo-filter",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
Reference in New Issue
Block a user