From 44ca9f457c5980c4bb6d1a441a3e74c035e2b750 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 20 Dec 2025 20:29:03 +0200 Subject: [PATCH] feat(script/jsx): add support for React hooks --- apps/client/src/layouts/desktop_layout.tsx | 2 +- apps/client/src/services/bundle.ts | 37 ++++++++----------- .../services/frontend_script_api_preact.ts | 5 ++- .../widgets/sidebar/RightPanelContainer.tsx | 23 ++++++++---- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 758b2e9c9..21760b9a9 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -184,7 +184,7 @@ export default class DesktopLayout { .child(new HighlightsListWidget()) .child(...this.customWidgets.get("right-pane")) ) - .optChild(isNewLayout, ) + .optChild(isNewLayout, ) ) .optChild(!launcherPaneIsHorizontal && isNewLayout, ) ) diff --git a/apps/client/src/services/bundle.ts b/apps/client/src/services/bundle.ts index 649b9a9fd..5dcdfad05 100644 --- a/apps/client/src/services/bundle.ts +++ b/apps/client/src/services/bundle.ts @@ -5,7 +5,6 @@ import type { Entity, WidgetDefinition } from "./frontend_script_api.js"; import { t } from "./i18n.js"; import ScriptContext from "./script_context.js"; import server from "./server.js"; -import toast from "./toast.js"; import toastService from "./toast.js"; import utils, { getErrorMessage } from "./utils.js"; @@ -66,51 +65,45 @@ async function executeStartupBundles() { } export class WidgetsByParent { - private byParent: Record; + private legacyWidgets: Record; + private preactWidgets: Record; constructor() { - this.byParent = {}; + this.legacyWidgets = {}; + this.preactWidgets = {}; } add(widget: Widget) { if ("type" in widget && widget.type === "react-widget") { // React-based script. const reactWidget = widget as WidgetDefinition; - this.byParent[reactWidget.parent] = this.byParent[reactWidget.parent] || []; - this.byParent[reactWidget.parent].push(widget); + this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || []; + this.preactWidgets[reactWidget.parent].push(reactWidget); } else if ("parentWidget" in widget && widget.parentWidget) { - this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || []; - this.byParent[widget.parentWidget].push(widget); + this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || []; + this.legacyWidgets[widget.parentWidget].push(widget); } else { console.log(`Custom widget does not have mandatory 'parentWidget' property defined`); } } get(parentName: string) { - if (!this.byParent[parentName]) { + if (!this.legacyWidgets[parentName]) { return []; } return ( - this.byParent[parentName] + this.legacyWidgets[parentName] // previously, custom widgets were provided as a single instance, but that has the disadvantage // for splits where we actually need multiple instaces and thus having a class to instantiate is better // https://github.com/zadam/trilium/issues/4274 - .map((w: any) => { - if ("type" in w && w.type === "react-widget") { - try { - return w.render(); - } catch (e: unknown) { - toast.showErrorTitleAndMessage(t("toast.widget-render-error.title"), getErrorMessage(e)); - return null; - } - } - - return (w.prototype ? new w() : w); - }) - .filter(Boolean) + .map((w: any) => (w.prototype ? new w() : w)) ); } + + getPreactWidgets(parentName: string) { + return this.preactWidgets[parentName] ?? []; + } } async function getWidgetBundlesByParent() { diff --git a/apps/client/src/services/frontend_script_api_preact.ts b/apps/client/src/services/frontend_script_api_preact.ts index d728c057f..99132dfb1 100644 --- a/apps/client/src/services/frontend_script_api_preact.ts +++ b/apps/client/src/services/frontend_script_api_preact.ts @@ -1,7 +1,10 @@ import { Fragment, h } from "preact"; +import * as hooks from "preact/hooks"; export const preactAPI = Object.freeze({ // Core h, - Fragment + Fragment, + + ...hooks }); diff --git a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx index f7a9a8f51..62da69571 100644 --- a/apps/client/src/widgets/sidebar/RightPanelContainer.tsx +++ b/apps/client/src/widgets/sidebar/RightPanelContainer.tsx @@ -6,7 +6,7 @@ import { isValidElement, VNode } from "preact"; import { useEffect, useRef } from "preact/hooks"; import appContext from "../../components/app_context"; -import { Widget } from "../../services/bundle"; +import { WidgetsByParent } from "../../services/bundle"; import { t } from "../../services/i18n"; import options from "../../services/options"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; @@ -27,9 +27,9 @@ interface RightPanelWidgetDefinition { position: number; } -export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) { +export default function RightPanelContainer({ widgetsByParent }: { widgetsByParent: WidgetsByParent }) { const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); - const items = useItems(rightPaneVisible, customWidgets); + const items = useItems(rightPaneVisible, widgetsByParent); useSplit(rightPaneVisible); return ( @@ -52,7 +52,7 @@ export default function RightPanelContainer({ customWidgets }: { customWidgets: ); } -function useItems(rightPaneVisible: boolean, customWidgets: Widget[]) { +function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) { const { note } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); const [ highlightsList ] = useTriliumOptionJson("highlightsList"); @@ -69,11 +69,18 @@ function useItems(rightPaneVisible: boolean, customWidgets: Widget[]) { enabled: noteType === "text" && highlightsList.length > 0, position: 20, }, - ...customWidgets.map((w, i) => ({ - el: isValidElement(w) ? w : , + ...widgetsByParent.get("right-pane").map((widget, i) => ({ + el: , enabled: true, - position: w.position ?? 30 + i * 10 - })) + position: widget.position ?? 30 + i * 10 + })), + ...widgetsByParent.getPreactWidgets("right-pane").map((widget) => { + const El = widget.render; + return { + el: , + enabled: true + }; + }) ]; return definitions