From feb3f8d4442cc35893d97dec88f66842ada4cd1d Mon Sep 17 00:00:00 2001 From: Local Admin Date: Sun, 25 Jan 2026 18:59:19 +0000 Subject: [PATCH 1/3] Add collapsible pinned section on Home --- .../PagedMemoList/PagedMemoList.tsx | 70 +++++++++++++++++-- web/src/pages/Home.tsx | 1 + 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index 518635094..5dcc31892 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,51 @@ const PagedMemoList = (props: Props) => { ) : ( <> - { + const hasPinned = pinnedMemos.length > 0; + const pinnedToggle = enablePinnedSection && hasPinned && ( + + ); + + const prefixElement = ( <> {showMemoEditor ? ( ) : undefined} + {pinnedToggle} + ); + + if (!enablePinnedSection) { + return ; } - listMode={layout === "LIST"} - /> + + return ( + <> + +
+ +
+ + ); + })()} {/* Loading indicator for pagination */} {isFetchingNextPage && } diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 417a0cd10..c055b6dd4 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -28,6 +28,7 @@ const Home = () => { listSort={listSort} orderBy={orderBy} filter={memoFilter} + collapsiblePinned enabled={isInitialized && !!user} // Wait for contexts to stabilize before fetching /> From e14f88232ace0af643726dbbf82c3b46dbdf6b77 Mon Sep 17 00:00:00 2001 From: Local Admin Date: Sun, 25 Jan 2026 19:49:31 +0000 Subject: [PATCH 2/3] Add pinned section dividers --- .../PagedMemoList/PagedMemoList.tsx | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index 5dcc31892..25fabd14a 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -182,18 +182,21 @@ const PagedMemoList = (props: Props) => { {(() => { const hasPinned = pinnedMemos.length > 0; const pinnedToggle = enablePinnedSection && hasPinned && ( - +
+ + ); const prefixElement = ( @@ -210,6 +213,22 @@ const PagedMemoList = (props: Props) => { return ; } + 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 && ( +