diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css index 5a866f51ca..e080455f21 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css @@ -6,20 +6,15 @@ contain: content; } -.mobile-navigator-header { +.mobile-navigator-toolbar { display: flex; align-items: center; - gap: 4px; - padding: 6px 8px; + padding: 4px 6px; border-bottom: 1px solid var(--main-border-color); background: var(--main-background-color); - position: sticky; - top: 0; - z-index: 1; } .mobile-navigator-back { - flex: 0 0 auto; font-size: 1.6em; min-width: 40px; min-height: 40px; @@ -29,41 +24,7 @@ visibility: hidden; } -.mobile-navigator-title { - flex: 1 1 auto; - display: flex; - align-items: center; - gap: 8px; - background: none; - border: none; - color: var(--main-text-color); - padding: 6px 8px; - min-height: 40px; - font-size: 1.05em; - font-weight: 600; - text-align: start; - min-width: 0; - cursor: pointer; -} - -.mobile-navigator-title:disabled { - cursor: default; - opacity: 0.7; -} - -.mobile-navigator-title-icon { - font-size: 1.1em; - flex: 0 0 auto; -} - -.mobile-navigator-title-text { - flex: 1 1 auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.mobile-navigator-list { +.mobile-navigator-scroll { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; @@ -73,6 +34,98 @@ padding-bottom: env(safe-area-inset-bottom, 0); } +.mobile-navigator-current-tile { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px; + background: var(--accented-background-color, var(--hover-item-background-color)); + border-bottom: 2px solid var(--main-border-color); + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; + color: var(--main-text-color); +} + +.mobile-navigator-current-tile:active { + filter: brightness(0.95); +} + +.mobile-navigator-current-tile.is-active { + background: var(--active-item-background-color); + color: var(--active-item-text-color); +} + +.mobile-navigator-current-tile.is-archived { + opacity: 0.7; +} + +.mobile-navigator-current-header { + display: flex; + align-items: center; + gap: 10px; + min-height: 32px; +} + +.mobile-navigator-current-icon { + flex: 0 0 auto; + font-size: 1.3em; +} + +.mobile-navigator-current-title { + flex: 1 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 1.15em; + font-weight: 600; +} + +.mobile-navigator-current-open { + flex: 0 0 auto; + font-size: 1.1em; + opacity: 0.6; +} + +.mobile-navigator-current-preview { + position: relative; + max-height: 140px; + overflow: hidden; + font-size: 0.92em; + line-height: 1.35; + color: var(--muted-text-color, var(--main-text-color)); + pointer-events: none; +} + +.mobile-navigator-current-preview::after { + content: ""; + position: absolute; + inset-inline: 0; + bottom: 0; + height: 32px; + background: linear-gradient(to bottom, transparent, var(--accented-background-color, var(--hover-item-background-color))); +} + +.mobile-navigator-current-preview:empty, +.mobile-navigator-current-preview .note-book-content:empty { + display: none; +} + +.mobile-navigator-current-preview .note-book-content { + max-width: 100%; +} + +.mobile-navigator-current-preview img, +.mobile-navigator-current-preview video { + max-width: 100%; + height: auto; +} + +.mobile-navigator-list { + display: flex; + flex-direction: column; +} + .mobile-navigator-row { display: flex; align-items: center; @@ -115,5 +168,5 @@ .mobile-navigator-row-chevron { flex: 0 0 auto; font-size: 1.4em; - opacity: 0.6; + opacity: 0.5; } diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx index 17d629ce1f..a9689b6ffa 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx @@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks" import type FNote from "../../entities/fnote"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; +import { NoteContent } from "../collections/legacy/ListOrGridView"; import ActionButton from "../react/ActionButton"; import { useActiveNoteContext, @@ -19,8 +20,9 @@ import NoItems from "../react/NoItems"; /** * A touch-native replacement for the Fancytree-based note tree on mobile. Shows one - * "column" of children at a time (iOS Files / macOS Finder style), with a back - * chevron to pop up the hierarchy and tappable rows to drill in or open notes. + * "column" of children at a time (iOS Files / macOS Finder style): the column's + * current note is rendered at the top with a content preview and opens on tap, + * while tapping child rows drills deeper without navigating. */ export default function MobileNoteNavigator() { const { notePath: activeNotePath, hoistedNoteId, noteContext, parentComponent } = useActiveNoteContext(); @@ -42,7 +44,6 @@ export default function MobileNoteNavigator() { const parentPath = stack[stack.length - 1]; const parentNoteId = getLastSegment(parentPath); if (loadResults.getBranchRows().some((b) => b.parentNoteId === parentNoteId)) { - // No state change, but force a re-render by resetting the same stack reference. setStack((prev) => [...prev]); } }); @@ -50,6 +51,7 @@ export default function MobileNoteNavigator() { const currentParentPath = stack[stack.length - 1]; const currentParentId = getLastSegment(currentParentPath); const parentNote = useNote(currentParentId); + const parentIcon = useNoteIcon(parentNote); const activeNoteId = activeNotePath ? getLastSegment(activeNotePath) : undefined; // Froca's initial `tree` response only returns the top of the tree; deeper levels are @@ -82,34 +84,23 @@ export default function MobileNoteNavigator() { setStack((prev) => (prev.length > 1 ? prev.slice(0, -1) : prev)); }, []); - const openNote = useCallback( - async (childNotePath: string, closeSidebar: boolean) => { - await noteContext?.setNote(childNotePath); - if (closeSidebar) { - manualStackRef.current = false; - parentComponent?.triggerCommand("setActiveScreen", { screen: "detail" }); - } - }, - [noteContext, parentComponent] - ); - - const drillInto = useCallback( - async (childNotePath: string) => { - manualStackRef.current = true; - setStack((prev) => [...prev, childNotePath]); - await noteContext?.setNote(childNotePath); - }, - [noteContext] - ); - - const openParent = useCallback(() => { + const openCurrent = useCallback(async () => { if (!currentParentPath) return; - openNote(currentParentPath, true); - }, [currentParentPath, openNote]); + await noteContext?.setNote(currentParentPath); + manualStackRef.current = false; + parentComponent?.triggerCommand("setActiveScreen", { screen: "detail" }); + }, [currentParentPath, noteContext, parentComponent]); + + const drillInto = useCallback((childNotePath: string) => { + manualStackRef.current = true; + setStack((prev) => [...prev, childNotePath]); + }, []); + + const isCurrentActive = !!activeNoteId && activeNoteId === currentParentId; return (
-
+
-
-
- {!isLoaded ? ( - - ) : children.length === 0 ? ( - - ) : ( - children.map((child) => ( - - )) - )} +
+
+
+ + + {parentNote?.title ?? t("mobile_note_navigator.loading")} + + +
+ {parentNote && ( +
+ +
+ )} +
+ +
+ {!isLoaded ? ( + + ) : children.length === 0 ? ( + + ) : ( + children.map((child) => ( + + )) + )} +
); @@ -158,11 +168,10 @@ interface NavigatorRowProps { prefix?: string; childNotePath: string; isActive: boolean; - onOpen: (notePath: string, closeSidebar: boolean) => void; onDrill: (notePath: string) => void; } -function NavigatorRow({ note, prefix, childNotePath, isActive, onOpen, onDrill }: NavigatorRowProps) { +function NavigatorRow({ note, prefix, childNotePath, isActive, onDrill }: NavigatorRowProps) { const icon = useNoteIcon(note); const hasChildren = note.hasChildren(); const colorClass = note.getColorClass(); @@ -177,11 +186,11 @@ function NavigatorRow({ note, prefix, childNotePath, isActive, onOpen, onDrill } })} role="button" tabIndex={0} - onClick={() => (hasChildren ? onDrill(childNotePath) : onOpen(childNotePath, true))} + onClick={() => onDrill(childNotePath)} > {title} - {hasChildren && } +
); }