feat(script/jsx): add support for React hooks

This commit is contained in:
Elian Doran 2025-12-20 20:29:03 +02:00
parent 4d7e5bc8f6
commit 44ca9f457c
No known key found for this signature in database
4 changed files with 35 additions and 32 deletions

View File

@ -184,7 +184,7 @@ export default class DesktopLayout {
.child(new HighlightsListWidget()) .child(new HighlightsListWidget())
.child(...this.customWidgets.get("right-pane")) .child(...this.customWidgets.get("right-pane"))
) )
.optChild(isNewLayout, <RightPanelContainer customWidgets={this.customWidgets.get("right-pane")} />) .optChild(isNewLayout, <RightPanelContainer widgetsByParent={this.customWidgets} />)
) )
.optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />) .optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
) )

View File

@ -5,7 +5,6 @@ import type { Entity, WidgetDefinition } from "./frontend_script_api.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import ScriptContext from "./script_context.js"; import ScriptContext from "./script_context.js";
import server from "./server.js"; import server from "./server.js";
import toast from "./toast.js";
import toastService from "./toast.js"; import toastService from "./toast.js";
import utils, { getErrorMessage } from "./utils.js"; import utils, { getErrorMessage } from "./utils.js";
@ -66,50 +65,44 @@ async function executeStartupBundles() {
} }
export class WidgetsByParent { export class WidgetsByParent {
private byParent: Record<string, Widget[]>; private legacyWidgets: Record<string, LegacyWidget[]>;
private preactWidgets: Record<string, WidgetDefinition[]>;
constructor() { constructor() {
this.byParent = {}; this.legacyWidgets = {};
this.preactWidgets = {};
} }
add(widget: Widget) { add(widget: Widget) {
if ("type" in widget && widget.type === "react-widget") { if ("type" in widget && widget.type === "react-widget") {
// React-based script. // React-based script.
const reactWidget = widget as WidgetDefinition; const reactWidget = widget as WidgetDefinition;
this.byParent[reactWidget.parent] = this.byParent[reactWidget.parent] || []; this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || [];
this.byParent[reactWidget.parent].push(widget); this.preactWidgets[reactWidget.parent].push(reactWidget);
} else if ("parentWidget" in widget && widget.parentWidget) { } else if ("parentWidget" in widget && widget.parentWidget) {
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || []; this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || [];
this.byParent[widget.parentWidget].push(widget); this.legacyWidgets[widget.parentWidget].push(widget);
} else { } else {
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`); console.log(`Custom widget does not have mandatory 'parentWidget' property defined`);
} }
} }
get(parentName: string) { get(parentName: string) {
if (!this.byParent[parentName]) { if (!this.legacyWidgets[parentName]) {
return []; return [];
} }
return ( return (
this.byParent[parentName] this.legacyWidgets[parentName]
// previously, custom widgets were provided as a single instance, but that has the disadvantage // 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 // for splits where we actually need multiple instaces and thus having a class to instantiate is better
// https://github.com/zadam/trilium/issues/4274 // https://github.com/zadam/trilium/issues/4274
.map((w: any) => { .map((w: any) => (w.prototype ? new w() : w))
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); getPreactWidgets(parentName: string) {
}) return this.preactWidgets[parentName] ?? [];
.filter(Boolean)
);
} }
} }

View File

@ -1,7 +1,10 @@
import { Fragment, h } from "preact"; import { Fragment, h } from "preact";
import * as hooks from "preact/hooks";
export const preactAPI = Object.freeze({ export const preactAPI = Object.freeze({
// Core // Core
h, h,
Fragment Fragment,
...hooks
}); });

View File

@ -6,7 +6,7 @@ import { isValidElement, VNode } from "preact";
import { useEffect, useRef } from "preact/hooks"; import { useEffect, useRef } from "preact/hooks";
import appContext from "../../components/app_context"; import appContext from "../../components/app_context";
import { Widget } from "../../services/bundle"; import { WidgetsByParent } from "../../services/bundle";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import options from "../../services/options"; import options from "../../services/options";
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer"; import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
@ -27,9 +27,9 @@ interface RightPanelWidgetDefinition {
position: number; position: number;
} }
export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) { export default function RightPanelContainer({ widgetsByParent }: { widgetsByParent: WidgetsByParent }) {
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible"); const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
const items = useItems(rightPaneVisible, customWidgets); const items = useItems(rightPaneVisible, widgetsByParent);
useSplit(rightPaneVisible); useSplit(rightPaneVisible);
return ( 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 { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type"); const noteType = useNoteProperty(note, "type");
const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList"); const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList");
@ -69,11 +69,18 @@ function useItems(rightPaneVisible: boolean, customWidgets: Widget[]) {
enabled: noteType === "text" && highlightsList.length > 0, enabled: noteType === "text" && highlightsList.length > 0,
position: 20, position: 20,
}, },
...customWidgets.map((w, i) => ({ ...widgetsByParent.get("right-pane").map((widget, i) => ({
el: isValidElement(w) ? w : <CustomLegacyWidget key={w._noteId} originalWidget={w as LegacyRightPanelWidget} />, el: <CustomLegacyWidget key={widget._noteId} originalWidget={widget as LegacyRightPanelWidget} />,
enabled: true, 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: <El />,
enabled: true
};
})
]; ];
return definitions return definitions