diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx index 0e3d7d5add..211c83cffa 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx @@ -207,19 +207,22 @@ function NoteAttributes({ note }: { note: FNote }) { return ; } -export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes, showTextRepresentation }: { +export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes, showTextRepresentation, onReady }: { note: FNote; trim?: boolean; noChildrenList?: boolean; highlightedTokens: string[] | null | undefined; includeArchivedNotes: boolean; showTextRepresentation?: boolean; + onReady?: () => void; }) { const contentRef = useRef(null); const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens); const [ready, setReady] = useState(false); const [noteType, setNoteType] = useState("none"); + const onReadyRef = useRef(onReady); + onReadyRef.current = onReady; useEffect(() => { const contentElement = contentRef.current; @@ -233,6 +236,7 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc }, []); useEffect(() => { + setReady(false); content_renderer.getRenderedContent(note, { trim, noChildrenList, @@ -250,12 +254,14 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc highlightSearch(contentRef.current); setNoteType(type); setReady(true); + onReadyRef.current?.(); }) .catch(e => { console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`); console.error(e); contentRef.current?.replaceChildren(t("collections.rendering_error")); setReady(true); + onReadyRef.current?.(); }); }, [ note, highlightedTokens ]); diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css index e080455f21..7feb203a85 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css @@ -25,6 +25,7 @@ } .mobile-navigator-scroll { + position: relative; flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; @@ -34,6 +35,22 @@ padding-bottom: env(safe-area-inset-bottom, 0); } +.mobile-navigator-scroll.is-pending > .mobile-navigator-current-tile, +.mobile-navigator-scroll.is-pending > .mobile-navigator-list { + visibility: hidden; +} + +.mobile-navigator-placeholder { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.8em; + color: var(--muted-text-color, var(--main-text-color)); + pointer-events: none; +} + .mobile-navigator-current-tile { display: flex; flex-direction: column; diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx index 659c94fcb9..3d52003187 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx @@ -79,6 +79,16 @@ export default function MobileNoteNavigator() { const children = useNavigatorChildren(parentNote, hideArchived, isLoaded); const canGoBack = stack.length > 1; + // Gate the visible body on the content preview being ready to avoid a layout shift + // when the preview finishes rendering. Tied to the parent's noteId so a stale "ready" + // flag from the previous column can't leak into the new one. + const [readyForNoteId, setReadyForNoteId] = useState(null); + const previewReady = !!parentNote && readyForNoteId === parentNote.noteId; + const bodyVisible = previewReady && isLoaded; + const onPreviewReady = useCallback(() => { + if (parentNote) setReadyForNoteId(parentNote.noteId); + }, [parentNote]); + const goBack = useCallback(() => { manualStackRef.current = true; setStack((prev) => (prev.length > 1 ? prev.slice(0, -1) : prev)); @@ -117,40 +127,38 @@ export default function MobileNoteNavigator() { /> -
-
-
- - - {parentNote?.title ?? t("mobile_note_navigator.loading")} - - -
- {parentNote && ( +
+ {parentNote && ( +
+
+ + {parentNote.title} + +
- )} -
+
+ )}
- {!isLoaded ? ( - - ) : children.length === 0 ? ( + {!isLoaded ? null : children.length === 0 ? ( ) : ( children.map((child) => ( @@ -166,6 +174,12 @@ export default function MobileNoteNavigator() { )) )}
+ + {!bodyVisible && ( +
+ +
+ )}
);