diff --git a/web/src/components/HomeSidebar/HomeSidebar.tsx b/web/src/components/HomeSidebar/HomeSidebar.tsx index 6849ec61e..83e63bd81 100644 --- a/web/src/components/HomeSidebar/HomeSidebar.tsx +++ b/web/src/components/HomeSidebar/HomeSidebar.tsx @@ -2,7 +2,6 @@ import { observer } from "mobx-react-lite"; import SearchBar from "@/components/SearchBar"; import useCurrentUser from "@/hooks/useCurrentUser"; import { cn } from "@/lib/utils"; -import MemoFilters from "../MemoFilters"; import StatisticsView from "../StatisticsView"; import ShortcutsSection from "./ShortcutsSection"; import TagsSection from "./TagsSection"; @@ -22,7 +21,6 @@ const HomeSidebar = observer((props: Props) => { )} > -
{currentUser && } diff --git a/web/src/components/MemoFilters.tsx b/web/src/components/MemoFilters.tsx index 8699f6d6f..60a4b1b1c 100644 --- a/web/src/components/MemoFilters.tsx +++ b/web/src/components/MemoFilters.tsx @@ -1,5 +1,16 @@ import { isEqual } from "lodash-es"; -import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, HashIcon, LinkIcon, BookmarkIcon, SearchIcon, XIcon } from "lucide-react"; +import { + BookmarkIcon, + CalendarIcon, + CheckCircleIcon, + CodeIcon, + EyeIcon, + HashIcon, + LinkIcon, + LucideIcon, + SearchIcon, + XIcon, +} from "lucide-react"; import { observer } from "mobx-react-lite"; import { useEffect } from "react"; import { useSearchParams } from "react-router-dom"; @@ -7,6 +18,46 @@ import { memoFilterStore } from "@/store"; import { FilterFactor, getMemoFilterKey, MemoFilter, stringifyFilters } from "@/store/memoFilter"; import { useTranslate } from "@/utils/i18n"; +interface FilterConfig { + icon: LucideIcon; + getLabel: (value: string, t: ReturnType) => string; +} + +const FILTER_CONFIGS: Record = { + tagSearch: { + icon: HashIcon, + getLabel: (value) => value, + }, + visibility: { + icon: EyeIcon, + getLabel: (value) => value, + }, + contentSearch: { + icon: SearchIcon, + getLabel: (value) => value, + }, + displayTime: { + icon: CalendarIcon, + getLabel: (value) => value, + }, + pinned: { + icon: BookmarkIcon, + getLabel: (value) => value, + }, + "property.hasLink": { + icon: LinkIcon, + getLabel: (_, t) => t("filters.has-link"), + }, + "property.hasTaskList": { + icon: CheckCircleIcon, + getLabel: (_, t) => t("filters.has-task-list"), + }, + "property.hasCode": { + icon: CodeIcon, + getLabel: (_, t) => t("filters.has-code"), + }, +}; + const MemoFilters = observer(() => { const t = useTranslate(); const [, setSearchParams] = useSearchParams(); @@ -18,63 +69,51 @@ const MemoFilters = observer(() => { searchParams.set("filter", stringifyFilters(filters)); } setSearchParams(searchParams); - }, [filters]); + }, [filters, setSearchParams]); - const getFilterDisplayText = (filter: MemoFilter) => { - if (filter.value) { - return filter.value; - } - if (filter.factor.startsWith("property.")) { - const factorLabel = filter.factor.replace("property.", ""); - switch (factorLabel) { - case "hasLink": - return t("filters.has-link"); - case "hasCode": - return t("filters.has-code"); - case "hasTaskList": - return t("filters.has-task-list"); - default: - return factorLabel; - } + const handleRemoveFilter = (filter: MemoFilter) => { + memoFilterStore.removeFilter((f: MemoFilter) => isEqual(f, filter)); + }; + + const getFilterDisplayText = (filter: MemoFilter): string => { + const config = FILTER_CONFIGS[filter.factor]; + if (!config) { + return filter.value || filter.factor; } - return filter.factor; + return config.getLabel(filter.value, t); }; if (filters.length === 0) { - return undefined; + return null; } return ( -
- {filters.map((filter: MemoFilter) => ( -
memoFilterStore.removeFilter((f: MemoFilter) => isEqual(f, filter))} - > - - {getFilterDisplayText(filter)} - -
- ))} +
+ {filters.map((filter) => { + const config = FILTER_CONFIGS[filter.factor]; + const Icon = config?.icon; + + return ( +
+ {Icon && } + {getFilterDisplayText(filter)} + +
+ ); + })}
); }); -const FactorIcon = ({ factor, className }: { factor: FilterFactor; className?: string }) => { - const iconMap = { - tagSearch: , - visibility: , - contentSearch: , - displayTime: , - pinned: , - "property.hasLink": , - "property.hasTaskList": , - "property.hasCode": , - }; - return iconMap[factor as keyof typeof iconMap] || <>; -}; +MemoFilters.displayName = "MemoFilters"; export default MemoFilters; diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index fafa9ed38..fd76c6c8b 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -14,6 +14,7 @@ import { useTranslate } from "@/utils/i18n"; import Empty from "../Empty"; import MasonryView, { MemoRenderContext } from "../MasonryView"; import MemoEditor from "../MemoEditor"; +import MemoFilters from "../MemoFilters"; import MemoSkeleton from "../MemoSkeleton"; interface Props { @@ -153,7 +154,12 @@ const PagedMemoList = observer((props: Props) => { : undefined} + prefixElement={ + <> + {showMemoEditor ? : undefined} + + + } listMode={viewStore.state.layout === "LIST"} /> diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index 986e0e371..884312f52 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -4,16 +4,17 @@ import { MemoRenderContext } from "@/components/MasonryView"; import MemoView from "@/components/MemoView"; import PagedMemoList from "@/components/PagedMemoList"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { viewStore } from "@/store"; +import { viewStore, workspaceStore } from "@/store"; import { extractUserIdFromName } from "@/store/common"; import memoFilterStore from "@/store/memoFilter"; import { State } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; +import { WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; const Archived = observer(() => { const user = useCurrentUser(); - // Build filter from active filters - no useMemo needed since component is MobX observer + // Build filter from active filters const buildMemoFilter = () => { const conditions = [`creator_id == ${extractUserIdFromName(user.name)}`]; for (const filter of memoFilterStore.filters) { @@ -21,6 +22,20 @@ const Archived = observer(() => { conditions.push(`content.contains("${filter.value}")`); } else if (filter.factor === "tagSearch") { conditions.push(`tag in ["${filter.value}"]`); + } else if (filter.factor === "property.hasLink") { + conditions.push(`has_link`); + } else if (filter.factor === "property.hasTaskList") { + conditions.push(`has_task_list`); + } else if (filter.factor === "property.hasCode") { + conditions.push(`has_code`); + } else if (filter.factor === "displayTime") { + const displayWithUpdateTime = workspaceStore.getWorkspaceSettingByKey(WorkspaceSetting_Key.MEMO_RELATED).memoRelatedSetting + ?.displayWithUpdateTime; + const factor = displayWithUpdateTime ? "updated_ts" : "created_ts"; + const filterDate = new Date(filter.value); + const filterUtcTimestamp = filterDate.getTime() + filterDate.getTimezoneOffset() * 60 * 1000; + const timestampAfter = filterUtcTimestamp / 1000; + conditions.push(`${factor} >= ${timestampAfter} && ${factor} < ${timestampAfter + 60 * 60 * 24}`); } } return conditions.length > 0 ? conditions.join(" && ") : undefined; diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index bf551024b..7aeee0974 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -5,13 +5,44 @@ import MemoView from "@/components/MemoView"; import MobileHeader from "@/components/MobileHeader"; import PagedMemoList from "@/components/PagedMemoList"; import useResponsiveWidth from "@/hooks/useResponsiveWidth"; -import { viewStore } from "@/store"; +import { viewStore, workspaceStore } from "@/store"; +import memoFilterStore from "@/store/memoFilter"; import { State } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; +import { WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; const Explore = observer(() => { const { md } = useResponsiveWidth(); + // Build filter from active filters + const buildMemoFilter = () => { + const conditions: string[] = []; + for (const filter of memoFilterStore.filters) { + if (filter.factor === "contentSearch") { + conditions.push(`content.contains("${filter.value}")`); + } else if (filter.factor === "tagSearch") { + conditions.push(`tag in ["${filter.value}"]`); + } else if (filter.factor === "property.hasLink") { + conditions.push(`has_link`); + } else if (filter.factor === "property.hasTaskList") { + conditions.push(`has_task_list`); + } else if (filter.factor === "property.hasCode") { + conditions.push(`has_code`); + } else if (filter.factor === "displayTime") { + const displayWithUpdateTime = workspaceStore.getWorkspaceSettingByKey(WorkspaceSetting_Key.MEMO_RELATED).memoRelatedSetting + ?.displayWithUpdateTime; + const factor = displayWithUpdateTime ? "updated_ts" : "created_ts"; + const filterDate = new Date(filter.value); + const filterUtcTimestamp = filterDate.getTime() + filterDate.getTimezoneOffset() * 60 * 1000; + const timestampAfter = filterUtcTimestamp / 1000; + conditions.push(`${factor} >= ${timestampAfter} && ${factor} < ${timestampAfter + 60 * 60 * 24}`); + } + } + return conditions.length > 0 ? conditions.join(" && ") : undefined; + }; + + const memoFilter = buildMemoFilter(); + return (
{!md && } @@ -30,6 +61,7 @@ const Explore = observer(() => { ) } orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"} + filter={memoFilter} showCreator />
diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index 557b9d18f..6cea26396 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -11,12 +11,13 @@ import PagedMemoList from "@/components/PagedMemoList"; import UserAvatar from "@/components/UserAvatar"; import { Button } from "@/components/ui/button"; import useLoading from "@/hooks/useLoading"; -import { viewStore, userStore } from "@/store"; +import { viewStore, userStore, workspaceStore } from "@/store"; import { extractUserIdFromName } from "@/store/common"; import memoFilterStore from "@/store/memoFilter"; import { 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"; +import { WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; import { useTranslate } from "@/utils/i18n"; const UserProfile = observer(() => { @@ -43,7 +44,7 @@ const UserProfile = observer(() => { }); }, [params.username]); - // Build filter from active filters - no useMemo needed since component is MobX observer + // Build filter from active filters const buildMemoFilter = () => { if (!user) { return undefined; @@ -55,6 +56,22 @@ const UserProfile = observer(() => { conditions.push(`content.contains("${filter.value}")`); } else if (filter.factor === "tagSearch") { conditions.push(`tag in ["${filter.value}"]`); + } else if (filter.factor === "pinned") { + conditions.push(`pinned`); + } else if (filter.factor === "property.hasLink") { + conditions.push(`has_link`); + } else if (filter.factor === "property.hasTaskList") { + conditions.push(`has_task_list`); + } else if (filter.factor === "property.hasCode") { + conditions.push(`has_code`); + } else if (filter.factor === "displayTime") { + const displayWithUpdateTime = workspaceStore.getWorkspaceSettingByKey(WorkspaceSetting_Key.MEMO_RELATED).memoRelatedSetting + ?.displayWithUpdateTime; + const factor = displayWithUpdateTime ? "updated_ts" : "created_ts"; + const filterDate = new Date(filter.value); + const filterUtcTimestamp = filterDate.getTime() + filterDate.getTimezoneOffset() * 60 * 1000; + const timestampAfter = filterUtcTimestamp / 1000; + conditions.push(`${factor} >= ${timestampAfter} && ${factor} < ${timestampAfter + 60 * 60 * 24}`); } } return conditions.length > 0 ? conditions.join(" && ") : undefined;