From 0d6bcba0233f3faf09ff82a6a0de27f076449f6a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 16:33:58 +0200 Subject: [PATCH] chore(react/launch_bar): port note launcher --- .../src/widgets/containers/launcher.tsx | 5 ++-- .../widgets/launch_bar/BookmarkButtons.tsx | 4 +-- .../src/widgets/launch_bar/GenericButtons.tsx | 28 +++++++++++++++++-- packages/commons/src/lib/attribute_names.ts | 5 +++- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/containers/launcher.tsx b/apps/client/src/widgets/containers/launcher.tsx index 6fd39eac1..cc6f793f1 100644 --- a/apps/client/src/widgets/containers/launcher.tsx +++ b/apps/client/src/widgets/containers/launcher.tsx @@ -1,7 +1,6 @@ import CalendarWidget from "../buttons/calendar.js"; import SyncStatusWidget from "../sync_status.js"; import BasicWidget, { wrapReactWidgets } from "../basic_widget.js"; -import NoteLauncher from "../buttons/launcher/note_launcher.js"; import ScriptLauncher from "../buttons/launcher/script_launcher.js"; import utils from "../../services/utils.js"; import TodayLauncher from "../buttons/launcher/today_launcher.js"; @@ -13,7 +12,7 @@ import HistoryNavigationButton from "../launch_bar/HistoryNavigation.jsx"; import AiChatButton from "../launch_bar/AiChatButton.jsx"; import ProtectedSessionStatusWidget from "../launch_bar/ProtectedSessionStatusWidget.jsx"; import { VNode } from "preact"; -import { CommandButton } from "../launch_bar/GenericButtons.jsx"; +import { CommandButton, NoteLauncher } from "../launch_bar/GenericButtons.jsx"; interface InnerWidget extends BasicWidget { settings?: { @@ -58,7 +57,7 @@ export default class LauncherWidget extends BasicWidget { if (launcherType === "command") { widget = wrapReactWidgets([ ])[0]; } else if (launcherType === "note") { - widget = new NoteLauncher(note).class("launcher-button"); + widget = wrapReactWidgets([ ])[0]; } else if (launcherType === "script") { widget = new ScriptLauncher(note).class("launcher-button"); } else if (launcherType === "customWidget") { diff --git a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx index 17bf1f614..a165ce278 100644 --- a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx +++ b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx @@ -5,7 +5,7 @@ import type FNote from "../../entities/fnote"; import { useChildNotes, useNoteLabelBoolean } from "../react/hooks"; import "./BookmarkButtons.css"; import NoteLink from "../react/NoteLink"; -import { NoteLauncher } from "./GenericButtons"; +import { CustomNoteLauncher } from "./GenericButtons"; const PARENT_NOTE_ID = "_lbBookmarks"; @@ -28,7 +28,7 @@ function SingleBookmark({ note }: { note: FNote }) { const [ bookmarkFolder ] = useNoteLabelBoolean(note, "bookmarkFolder"); return bookmarkFolder ? - : + : } function BookmarkFolder({ note }: { note: FNote }) { diff --git a/apps/client/src/widgets/launch_bar/GenericButtons.tsx b/apps/client/src/widgets/launch_bar/GenericButtons.tsx index 4e09a835c..303475214 100644 --- a/apps/client/src/widgets/launch_bar/GenericButtons.tsx +++ b/apps/client/src/widgets/launch_bar/GenericButtons.tsx @@ -2,8 +2,10 @@ import appContext, { CommandNames } from "../../components/app_context"; import FNote from "../../entities/fnote"; import link_context_menu from "../../menus/link_context_menu"; import { escapeHtml, isCtrlKey } from "../../services/utils"; -import { useNoteLabel, useNoteProperty } from "../react/hooks"; +import { useNoteLabel, useNoteProperty, useNoteRelation } from "../react/hooks"; import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets"; +import dialog from "../../services/dialog"; +import { t } from "../../services/i18n"; export function CommandButton({ launcherNote }: { launcherNote: FNote }) { const { icon, title } = useLauncherIconAndTitle(launcherNote); @@ -18,13 +20,19 @@ export function CommandButton({ launcherNote }: { launcherNote: FNote }) { ) } -export function NoteLauncher({ launcherNote, targetNoteId, hoistedNoteId }: { launcherNote: FNote, targetNoteId: string, hoistedNoteId?: string }) { +export function CustomNoteLauncher({ launcherNote, targetNoteId, hoistedNoteId }: { launcherNote: FNote, targetNoteId: string | null, hoistedNoteId?: string }) { const { icon, title } = useLauncherIconAndTitle(launcherNote); async function launch(evt: MouseEvent) { if (evt.which === 3) { return; } + + if (!targetNoteId) { + dialog.info(t("note_launcher.this_launcher_doesnt_define_target_note")); + return; + } + const hoistedNoteIdWithDefault = hoistedNoteId || launcherNote.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId; const ctrlKey = isCtrlKey(evt); @@ -44,8 +52,22 @@ export function NoteLauncher({ launcherNote, targetNoteId, hoistedNoteId }: { la onAuxClick={launch} onContextMenu={evt => { evt.preventDefault(); - link_context_menu.openContextMenu(targetNoteId, evt); + if (targetNoteId) { + link_context_menu.openContextMenu(targetNoteId, evt); + } }} /> ) } + +// we're intentionally displaying the launcher title and icon instead of the target, +// e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok), +// but on the launchpad you want them distinguishable. +// for titles, the note titles may follow a different scheme than maybe desirable on the launchpad +// another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons). +// The only downside is more work in setting up the typical case +// where you actually want to have both title and icon in sync, but for those cases there are bookmarks +export function NoteLauncher({ launcherNote, ...restProps }: { launcherNote: FNote, hoistedNoteId?: string }) { + const [ targetNote ] = useNoteRelation(launcherNote, "target"); + return +} diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts index 963fba0eb..04d365f1a 100644 --- a/packages/commons/src/lib/attribute_names.ts +++ b/packages/commons/src/lib/attribute_names.ts @@ -59,7 +59,10 @@ type Labels = { */ type Relations = [ "searchScript", - "ancestor" + "ancestor", + + // Launcher-specific + "target" ]; export type LabelNames = keyof Labels;