diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index 518635094..f1dc9a336 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -1,5 +1,5 @@ import { useQueryClient } from "@tanstack/react-query"; -import { ArrowUpIcon } from "lucide-react"; +import { ArrowUpIcon, ChevronDownIcon, ChevronRightIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { matchPath } from "react-router-dom"; import { Button } from "@/components/ui/button"; @@ -28,6 +28,7 @@ interface Props { pageSize?: number; showCreator?: boolean; enabled?: boolean; + collapsiblePinned?: boolean; } function useAutoFetchWhenNotScrollable({ @@ -104,6 +105,29 @@ const PagedMemoList = (props: Props) => { // Apply custom sorting if provided, otherwise use memos directly const sortedMemoList = useMemo(() => (props.listSort ? props.listSort(memos) : memos), [memos, props.listSort]); + const enablePinnedSection = props.collapsiblePinned === true; + const pinnedStorageKey = "memos.ui.pinsCollapsed"; + + const [isPinnedCollapsed, setIsPinnedCollapsed] = useState(() => { + if (!enablePinnedSection) return false; + if (typeof window === "undefined") return false; + return window.localStorage.getItem(pinnedStorageKey) === "true"; + }); + + const pinnedMemos = useMemo(() => { + if (!enablePinnedSection) return []; + return sortedMemoList.filter((memo) => memo.pinned); + }, [enablePinnedSection, sortedMemoList]); + + const unpinnedMemos = useMemo(() => { + if (!enablePinnedSection) return sortedMemoList; + return sortedMemoList.filter((memo) => !memo.pinned); + }, [enablePinnedSection, sortedMemoList]); + + useEffect(() => { + if (!enablePinnedSection || typeof window === "undefined") return; + window.localStorage.setItem(pinnedStorageKey, String(isPinnedCollapsed)); + }, [enablePinnedSection, isPinnedCollapsed]); // Prefetch creators when new data arrives to improve performance useEffect(() => { @@ -155,19 +179,71 @@ const PagedMemoList = (props: Props) => { ) : ( <> - { + const hasPinned = pinnedMemos.length > 0; + const pinnedToggle = enablePinnedSection && hasPinned && ( +
+ + + ); + + const prefixElement = ( <> {showMemoEditor ? ( ) : undefined} + {pinnedToggle} + ); + + if (!enablePinnedSection) { + return ; } - listMode={layout === "LIST"} - /> + + if (layout === "LIST") { + const listMemoList = isPinnedCollapsed ? unpinnedMemos : sortedMemoList; + const lastPinnedName = !isPinnedCollapsed && hasPinned ? pinnedMemos[pinnedMemos.length - 1]?.name : undefined; + const listRenderer = lastPinnedName + ? (memo: Memo, context?: MemoRenderContext) => ( + <> + {props.renderer(memo, context)} + {memo.name === lastPinnedName && ( +