Fix scrollbar disappearing on search and All Notes list

This commit is contained in:
chrisf committed 2026-10-29 00:49:47 -06:00
1 parent 67468d9fac
commit 61705e3f0c
2 files changed
+38 -11

No files matched your search

+10 -6
View File
@@ -9,20 +9,24 @@
} }
} }
/* Custom thin scrollbars */ /* Custom scrollbars across all modern browsers */
* {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 7px;
height: 6px; height: 7px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: transparent; background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: rgba(156, 163, 175, 0.45); background: rgba(156, 163, 175, 0.5);
border-radius: 3px; border-radius: 4px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: rgba(107, 114, 128, 0.7); background: rgba(107, 114, 128, 0.8);
} }
/* TipTap / ProseMirror Evernote Editor Styles */ /* TipTap / ProseMirror Evernote Editor Styles */
+28 -5
View File
@@ -412,6 +412,8 @@ export default function EvercloneWorkspace() {
async (preferSelectNoteId?: string) => { async (preferSelectNoteId?: string) => {
const currentReqId = ++listRequestIdRef.current; const currentReqId = ++listRequestIdRef.current;
setLoadingList(true); setLoadingList(true);
setLoadingMore(false);
loadingMoreRef.current = false;
try { try {
const sp = buildNoteQueryParams(0, NOTES_PAGE_SIZE); const sp = buildNoteQueryParams(0, NOTES_PAGE_SIZE);
const res = await fetch(`/api/notes?${sp.toString()}`); const res = await fetch(`/api/notes?${sp.toString()}`);
@@ -423,6 +425,11 @@ export default function EvercloneWorkspace() {
setTotalNotes(data.total || 0); setTotalNotes(data.total || 0);
setHasMoreNotes(loadedNotes.length < (data.total || 0)); setHasMoreNotes(loadedNotes.length < (data.total || 0));
// Always reset list scroll position to 0 when fresh results are loaded
if (listParentRef.current) {
listParentRef.current.scrollTop = 0;
}
if (preferSelectNoteId) { if (preferSelectNoteId) {
setSelectedNoteId(preferSelectNoteId); setSelectedNoteId(preferSelectNoteId);
} else if (loadedNotes.length > 0) { } else if (loadedNotes.length > 0) {
@@ -476,9 +483,7 @@ export default function EvercloneWorkspace() {
} }
} finally { } finally {
loadingMoreRef.current = false; loadingMoreRef.current = false;
if (currentReqId === listRequestIdRef.current) { setLoadingMore(false);
setLoadingMore(false);
}
} }
}, [hasMoreNotes, loadingList, buildNoteQueryParams, notes.length]); }, [hasMoreNotes, loadingList, buildNoteQueryParams, notes.length]);
@@ -1445,14 +1450,32 @@ export default function EvercloneWorkspace() {
if (listParentRef.current) { if (listParentRef.current) {
listParentRef.current.scrollTop = 0; listParentRef.current.scrollTop = 0;
} }
rowVirtualizer.scrollToOffset(0);
rowVirtualizer.measure(); rowVirtualizer.measure();
} }
}, [listMode, rowVirtualizer]); }, [listMode, rowVirtualizer]);
// Whenever view, search query, notebook/tag, or active filters change:
// Reset list scroll position to 0 and remeasure virtualizer so the scrollbar and rows never desync
const prevQueryKeyRef = useRef('');
const currentQueryKey = `${debouncedSearch}|${activeView}|${selectedNotebookId}|${selectedStackId}|${selectedTagId}|${filterNotebookId}|${filterTagIds.join(',')}|${filterUpdatedDays}|${filterHasImages}|${filterHasAttachments}|${filterHasTodo}|${filterHasWebClip}|${filterOnlyPinned}|${filterUntagged}|${sortBy}|${sortDir}`;
useEffect(() => {
if (prevQueryKeyRef.current !== currentQueryKey) {
prevQueryKeyRef.current = currentQueryKey;
if (listParentRef.current) {
listParentRef.current.scrollTop = 0;
}
rowVirtualizer.scrollToOffset(0);
rowVirtualizer.measure();
}
}, [currentQueryKey, rowVirtualizer]);
// User-scroll driven infinite pagination: only triggers when user deliberately scrolls near bottom // User-scroll driven infinite pagination: only triggers when user deliberately scrolls near bottom
const handleListScroll = useCallback( const handleListScroll = useCallback(
(e: React.UIEvent<HTMLDivElement>) => { (e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget; const el = e.currentTarget;
if (el.scrollHeight <= el.clientHeight) return;
const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight; const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (distanceToBottom < 300) { if (distanceToBottom < 300) {
if (!loadingMoreRef.current && hasMoreNotes && !loadingList) { if (!loadingMoreRef.current && hasMoreNotes && !loadingList) {
@@ -2259,7 +2282,7 @@ export default function EvercloneWorkspace() {
listMode === 'cards' listMode === 'cards'
? 'md:w-96 lg:w-[460px] xl:w-[500px]' ? 'md:w-96 lg:w-[460px] xl:w-[500px]'
: 'md:w-80 lg:w-96' : 'md:w-80 lg:w-96'
} border-r border-gray-200 bg-gray-50/50 flex-col shrink-0 transition-all duration-150 relative`} } h-full min-h-0 overflow-hidden border-r border-gray-200 bg-gray-50/50 flex-col shrink-0 transition-all duration-150 relative`}
> >
{/* List Header */} {/* List Header */}
<div className="px-3 sm:px-4 py-2.5 border-b border-gray-200 bg-white space-y-2"> <div className="px-3 sm:px-4 py-2.5 border-b border-gray-200 bg-white space-y-2">
@@ -2796,7 +2819,7 @@ export default function EvercloneWorkspace() {
</div> </div>
{/* Note List Body: Card Grid vs Virtualized Snippet / Compact List */} {/* Note List Body: Card Grid vs Virtualized Snippet / Compact List */}
<div ref={listParentRef} onScroll={handleListScroll} className="flex-1 overflow-y-scroll"> <div ref={listParentRef} onScroll={handleListScroll} className="flex-1 min-h-0 overflow-y-scroll">
{loadingList && displayedNotes.length === 0 ? ( {loadingList && displayedNotes.length === 0 ? (
<div className="p-8 text-center text-xs text-gray-400">Loading notes…</div> <div className="p-8 text-center text-xs text-gray-400">Loading notes…</div>
) : displayedNotes.length === 0 ? ( ) : displayedNotes.length === 0 ? (