From 720281a8db0dc7ce5d5239ba6db24b9e2fe8027d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 5 Feb 2026 21:41:55 +0200 Subject: [PATCH] feat(bookmarks): support bookmark folders on mobile --- .../widgets/launch_bar/BookmarkButtons.tsx | 48 ++++++++++++++----- .../src/widgets/launch_bar/GenericButtons.tsx | 43 ++++++++++------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx index f7ead77bd7..c02d6cdf87 100644 --- a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx +++ b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx @@ -4,12 +4,12 @@ import { CSSProperties } from "preact"; import { useContext, useMemo } from "preact/hooks"; import type FNote from "../../entities/fnote"; -import froca from "../../services/froca"; import { t } from "../../services/i18n"; -import { useChildNotes, useNoteLabelBoolean } from "../react/hooks"; +import { FormDropdownSubmenu, FormListItem } from "../react/FormList"; +import { useChildNotes, useNote, useNoteIcon, useNoteLabelBoolean } from "../react/hooks"; import NoteLink from "../react/NoteLink"; import ResponsiveContainer from "../react/ResponseContainer"; -import { CustomNoteLauncher } from "./GenericButtons"; +import { CustomNoteLauncher, launchCustomNoteLauncher } from "./GenericButtons"; import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets"; const PARENT_NOTE_ID = "_lbBookmarks"; @@ -35,11 +35,7 @@ export default function BookmarkButtons() { icon="bx bx-bookmark" title={t("bookmark_buttons.bookmarks")} > -
- -
+ {childNotes?.map(childNote => )} } /> @@ -54,14 +50,40 @@ function SingleBookmark({ note }: { note: FNote }) { ? : note.noteId} /> } - mobile={ -
  • - -
  • - } + mobile={} />; } +function MobileBookmarkItem({ noteId, bookmarkFolder }: { noteId: string, bookmarkFolder: boolean }) { + const note = useNote(noteId); + const noteIcon = useNoteIcon(note); + if (!note) return; + + return ( + !bookmarkFolder + ? launchCustomNoteLauncher(e, { launcherNote: note, getTargetNoteId: () => note.noteId })}>{note.title} + : + ); +} + +function MobileBookmarkFolder({ note }: { note: FNote }) { + const childNotes = useChildNotes(note.noteId); + + return ( + + {childNotes.map(childNote => ( + launchCustomNoteLauncher(e, { launcherNote: childNote, getTargetNoteId: () => childNote.noteId })} + > + {childNote.title} + + ))} + + ); +} + function BookmarkFolder({ note }: { note: FNote }) { const { icon, title } = useLauncherIconAndTitle(note); const childNotes = useChildNotes(note.noteId); diff --git a/apps/client/src/widgets/launch_bar/GenericButtons.tsx b/apps/client/src/widgets/launch_bar/GenericButtons.tsx index 70fe0fccef..59c158dec1 100644 --- a/apps/client/src/widgets/launch_bar/GenericButtons.tsx +++ b/apps/client/src/widgets/launch_bar/GenericButtons.tsx @@ -7,32 +7,18 @@ import { isCtrlKey } from "../../services/utils"; import { useGlobalShortcut, useNoteLabel } from "../react/hooks"; import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets"; -export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNoteId }: { +export function CustomNoteLauncher(props: { launcherNote: FNote; getTargetNoteId: (launcherNote: FNote) => string | null | Promise; getHoistedNoteId?: (launcherNote: FNote) => string | null; keyboardShortcut?: string; }) { + const { launcherNote, getTargetNoteId } = props; const { icon, title } = useLauncherIconAndTitle(launcherNote); const launch = useCallback(async (evt: MouseEvent | KeyboardEvent) => { - if (evt.which === 3) { - return; - } - - const targetNoteId = await getTargetNoteId(launcherNote); - if (!targetNoteId) return; - - const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId; - const ctrlKey = isCtrlKey(evt); - - if ((evt.which === 1 && ctrlKey) || evt.which === 2) { - const activate = !!evt.shiftKey; - await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate); - } else { - await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault); - } - }, [ launcherNote, getTargetNoteId, getHoistedNoteId ]); + await launchCustomNoteLauncher(evt, props); + }, [ props ]); // Keyboard shortcut. const [ shortcut ] = useNoteLabel(launcherNote, "keyboardShortcut"); @@ -54,3 +40,24 @@ export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNo /> ); } + +export async function launchCustomNoteLauncher(evt: MouseEvent | KeyboardEvent, { launcherNote, getTargetNoteId, getHoistedNoteId }: { + launcherNote: FNote; + getTargetNoteId: (launcherNote: FNote) => string | null | Promise; + getHoistedNoteId?: (launcherNote: FNote) => string | null; +}) { + if (evt.which === 3) return; + + const targetNoteId = await getTargetNoteId(launcherNote); + if (!targetNoteId) return; + + const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId; + const ctrlKey = isCtrlKey(evt); + + if ((evt.which === 1 && ctrlKey) || evt.which === 2) { + const activate = !!evt.shiftKey; + await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate); + } else { + await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault); + } +}