mirror of https://github.com/usememos/memos.git
Merge 2b78dc1dc7 into 81022123a1
This commit is contained in:
commit
665ab485b7
|
|
@ -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) => {
|
|||
<Skeleton showCreator={props.showCreator} count={4} />
|
||||
) : (
|
||||
<>
|
||||
<MasonryView
|
||||
memoList={sortedMemoList}
|
||||
renderer={props.renderer}
|
||||
prefixElement={
|
||||
{(() => {
|
||||
const hasPinned = pinnedMemos.length > 0;
|
||||
const pinnedToggle = enablePinnedSection && hasPinned && (
|
||||
<div className="w-full mt-1 mb-2 flex items-center gap-3 text-sm text-muted-foreground">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsPinnedCollapsed((prev) => !prev)}
|
||||
className="flex items-center gap-2 hover:text-foreground transition-colors"
|
||||
aria-expanded={!isPinnedCollapsed}
|
||||
>
|
||||
{isPinnedCollapsed ? <ChevronRightIcon className="w-4 h-4" /> : <ChevronDownIcon className="w-4 h-4" />}
|
||||
<span className="font-medium">
|
||||
{t("common.pinned")} ({pinnedMemos.length})
|
||||
</span>
|
||||
<span className="text-xs opacity-70">{isPinnedCollapsed ? t("common.expand") : t("common.collapse")}</span>
|
||||
</button>
|
||||
<div className="h-px flex-1 bg-border/60" aria-hidden="true" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const prefixElement = (
|
||||
<>
|
||||
{showMemoEditor ? (
|
||||
<MemoEditor className="mb-2" cacheKey="home-memo-editor" placeholder={t("editor.any-thoughts")} />
|
||||
) : undefined}
|
||||
<MemoFilters />
|
||||
{pinnedToggle}
|
||||
</>
|
||||
);
|
||||
|
||||
if (!enablePinnedSection) {
|
||||
return <MasonryView memoList={sortedMemoList} renderer={props.renderer} prefixElement={prefixElement} listMode={layout === "LIST"} />;
|
||||
}
|
||||
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 && (
|
||||
<div className="w-full max-w-2xl mx-auto my-2 h-px bg-border/60" aria-hidden="true" />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
: props.renderer;
|
||||
|
||||
return <MasonryView memoList={listMemoList} renderer={listRenderer} prefixElement={prefixElement} listMode />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<MasonryView
|
||||
memoList={isPinnedCollapsed ? [] : pinnedMemos}
|
||||
renderer={props.renderer}
|
||||
prefixElement={prefixElement}
|
||||
listMode={false}
|
||||
/>
|
||||
{hasPinned && !isPinnedCollapsed && <div className="w-full max-w-2xl mx-auto my-2 h-px bg-border/60" aria-hidden="true" />}
|
||||
<div className={hasPinned ? "mt-2" : ""}>
|
||||
<MasonryView memoList={unpinnedMemos} renderer={props.renderer} listMode={false} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Loading indicator for pagination */}
|
||||
{isFetchingNextPage && <Skeleton showCreator={props.showCreator} count={2} />}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const Home = () => {
|
|||
listSort={listSort}
|
||||
orderBy={orderBy}
|
||||
filter={memoFilter}
|
||||
collapsiblePinned
|
||||
enabled={isInitialized && !!user} // Wait for contexts to stabilize before fetching
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue