Compare commits

...

26 Commits

Author SHA1 Message Date
Elian Doran
f8bf301d12 feat(client/bundle): use new toast for script errors with known note ID 2025-12-20 23:34:36 +02:00
Elian Doran
2c25786fa2 feat(client/bundle): expose Trilium hooks 2025-12-20 23:26:10 +02:00
Elian Doran
1093acfe45 feat(client/bundle): make Preact custom widgets content-sized by default 2025-12-20 23:17:30 +02:00
Elian Doran
76f054bbd5 feat(client/bundle): support rendering in other places 2025-12-20 23:16:19 +02:00
Elian Doran
c558255450 feat(client/bundle): add button to open script note 2025-12-20 22:51:04 +02:00
Elian Doran
1e94125133 feat(client/bundle): display toast when parent is missing 2025-12-20 22:45:58 +02:00
Elian Doran
64a770175f refactor(client/bundle): use type for parent name 2025-12-20 22:40:03 +02:00
Elian Doran
e0416097e1 feat(script/jsx): support import syntax for api 2025-12-20 22:23:25 +02:00
Elian Doran
6c1b327f5f feat(script/jsx): support import syntax for preact 2025-12-20 22:14:45 +02:00
Elian Doran
284b66acd2 feat(script/jsx): support export default syntax 2025-12-20 21:59:03 +02:00
Elian Doran
dcd73ff9f9 test(script/jsx): JSX fragment 2025-12-20 21:37:41 +02:00
Elian Doran
645557b505 test(script/jsx): basic JSX processing 2025-12-20 21:35:52 +02:00
Elian Doran
22a83d9f82 refactor(script/jsx): "react-widget" -> "preact-widget" 2025-12-20 21:26:01 +02:00
Elian Doran
f64de3acca chore(script/jsx): move defineWidget into Preact API 2025-12-20 21:25:36 +02:00
Elian Doran
34d5793888 chore(script/jsx): expose RightPanelWidget 2025-12-20 21:19:53 +02:00
Elian Doran
44ca9f457c feat(script/jsx): add support for React hooks 2025-12-20 20:29:03 +02:00
Elian Doran
4d7e5bc8f6 chore(script/jsx): move Preact API in dedicated object 2025-12-20 20:10:19 +02:00
Elian Doran
644ff07a50 feat(script/jsx): get right panel widgets to actually render 2025-12-20 19:49:24 +02:00
Elian Doran
41220a9d1d fix(script/jsx): cannot find preact hydration function 2025-12-20 19:45:44 +02:00
Elian Doran
88945788d6 fix(script/jsx): critical crash if widget fails to render 2025-12-20 19:41:48 +02:00
Elian Doran
fe8f033409 chore(script/jsx): get widgets to be interpreted 2025-12-20 19:36:02 +02:00
Elian Doran
eee7c49f6e fix(script/jsx): module not defined 2025-12-20 19:28:26 +02:00
Elian Doran
d036bf0870 fix(client): full crash if server fails to obtain list of widgets 2025-12-20 19:18:50 +02:00
Elian Doran
fa8ff4bfbf chore(script/jsx): basic client-side logic to render bundles 2025-12-20 19:01:29 +02:00
Elian Doran
3619c0c3e4 feat(script/jsx): compile JSX on server side 2025-12-20 18:46:15 +02:00
Elian Doran
883e32f5c9 chore(script): install sucrase 2025-12-20 18:03:45 +02:00
15 changed files with 493 additions and 162 deletions

View File

@@ -62,6 +62,7 @@
"preact": "10.28.0", "preact": "10.28.0",
"react-i18next": "16.5.0", "react-i18next": "16.5.0",
"reveal.js": "5.2.1", "reveal.js": "5.2.1",
"sucrase": "3.35.1",
"svg-pan-zoom": "3.6.2", "svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1", "tabulator-tables": "6.3.1",
"vanilla-js-wheel-zoom": "9.0.4" "vanilla-js-wheel-zoom": "9.0.4"

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

@@ -1,10 +1,16 @@
import { h, VNode } from "preact";
import Component from "../components/component.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import froca from "./froca.js";
import type { Entity } from "./frontend_script_api.js";
import { WidgetDefinitionWithType } from "./frontend_script_api_preact.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 toastService, { showError } from "./toast.js"; import toastService, { showErrorForScriptNote } from "./toast.js";
import froca from "./froca.js"; import utils, { getErrorMessage } from "./utils.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
import type { Entity } from "./frontend_script_api.js";
// TODO: Deduplicate with server. // TODO: Deduplicate with server.
export interface Bundle { export interface Bundle {
@@ -14,9 +20,12 @@ export interface Bundle {
allNoteIds: string[]; allNoteIds: string[];
} }
interface Widget { type LegacyWidget = (BasicWidget | RightPanelWidget) & {
parentWidget?: string; parentWidget?: string;
} };
export type Widget = (LegacyWidget | WidgetDefinitionWithType) & {
_noteId: string;
};
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) { async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, { const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
@@ -27,6 +36,8 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
return await executeBundle(bundle, originEntity); return await executeBundle(bundle, originEntity);
} }
export type ParentName = "left-pane" | "center-pane" | "note-detail-pane" | "right-pane";
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) { export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container); const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
@@ -52,7 +63,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
async function executeStartupBundles() { async function executeStartupBundles() {
const isMobile = utils.isMobile(); const isMobile = utils.isMobile();
const scriptBundles = await server.get<Bundle[]>("script/startup" + (isMobile ? "?mobile=true" : "")); const scriptBundles = await server.get<Bundle[]>(`script/startup${ isMobile ? "?mobile=true" : ""}`);
for (const bundle of scriptBundles) { for (const bundle of scriptBundles) {
await executeBundle(bundle); await executeBundle(bundle);
@@ -60,68 +71,106 @@ async function executeStartupBundles() {
} }
export class WidgetsByParent { export class WidgetsByParent {
private byParent: Record<string, Widget[]>; private legacyWidgets: Record<string, LegacyWidget[]>;
private preactWidgets: Record<string, WidgetDefinitionWithType[]>;
constructor() { constructor() {
this.byParent = {}; this.legacyWidgets = {};
this.preactWidgets = {};
} }
add(widget: Widget) { add(widget: Widget) {
if (!widget.parentWidget) { let hasParentWidget = false;
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`); let isPreact = false;
return; if ("type" in widget && widget.type === "preact-widget") {
// React-based script.
const reactWidget = widget as WidgetDefinitionWithType;
this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || [];
this.preactWidgets[reactWidget.parent].push(reactWidget);
isPreact = true;
hasParentWidget = !!reactWidget.parent;
} else if ("parentWidget" in widget && widget.parentWidget) {
this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || [];
this.legacyWidgets[widget.parentWidget].push(widget);
hasParentWidget = !!widget.parentWidget;
} }
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || []; if (!hasParentWidget) {
this.byParent[widget.parentWidget].push(widget); showErrorForScriptNote(widget._noteId, t("toast.widget-missing-parent", {
property: isPreact ? "parent" : "parentWidget"
}));
}
} }
get(parentName: string) { get(parentName: ParentName) {
if (!this.byParent[parentName]) { const widgets: (Component | VNode)[] = this.getLegacyWidgets(parentName);
return []; for (const preactWidget of this.getPreactWidgets(parentName)) {
const el = h(preactWidget.render, {});
const widget = new ReactWrappedWidget(el);
widget.contentSized();
// TODO: set position here.
widgets.push(widget);
} }
return widgets;
}
getLegacyWidgets(parentName: ParentName): (BasicWidget | RightPanelWidget)[] {
if (!this.legacyWidgets[parentName]) 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) => (w.prototype ? new w() : w)) .map((w: any) => (w.prototype ? new w() : w))
); );
} }
getPreactWidgets(parentName: ParentName) {
return this.preactWidgets[parentName] ?? [];
}
} }
async function getWidgetBundlesByParent() { async function getWidgetBundlesByParent() {
const scriptBundles = await server.get<Bundle[]>("script/widgets");
const widgetsByParent = new WidgetsByParent(); const widgetsByParent = new WidgetsByParent();
for (const bundle of scriptBundles) { try {
let widget; const scriptBundles = await server.get<Bundle[]>("script/widgets");
try { for (const bundle of scriptBundles) {
widget = await executeBundle(bundle); let widget;
if (widget) {
widget._noteId = bundle.noteId; try {
widgetsByParent.add(widget); widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
}
} catch (e: any) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
id: `custom-script-failure-${noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: noteId,
title: note?.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
} }
} catch (e: any) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
id: `custom-script-failure-${noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: noteId,
title: note?.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
} }
} catch (e) {
toastService.showPersistent({
title: t("toast.widget-list-error.title"),
message: getErrorMessage(e),
icon: "bx bx-error-circle"
});
} }
return widgetsByParent; return widgetsByParent;

View File

@@ -1,26 +1,27 @@
import server from "./server.js"; import { dayjs, formatLogMessage } from "@triliumnext/commons";
import utils from "./utils.js";
import toastService from "./toast.js"; import appContext from "../components/app_context.js";
import linkService from "./link.js"; import type Component from "../components/component.js";
import type NoteContext from "../components/note_context.js";
import type FNote from "../entities/fnote.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import dateNotesService from "./date_notes.js";
import dialogService from "./dialog.js";
import froca from "./froca.js"; import froca from "./froca.js";
import { preactAPI } from "./frontend_script_api_preact.js";
import { t } from "./i18n.js";
import linkService from "./link.js";
import noteTooltipService from "./note_tooltip.js"; import noteTooltipService from "./note_tooltip.js";
import protectedSessionService from "./protected_session.js"; import protectedSessionService from "./protected_session.js";
import dateNotesService from "./date_notes.js";
import searchService from "./search.js"; import searchService from "./search.js";
import RightPanelWidget from "../widgets/right_panel_widget.js"; import server from "./server.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js";
import shortcutService from "./shortcuts.js"; import shortcutService from "./shortcuts.js";
import dialogService from "./dialog.js"; import SpacedUpdate from "./spaced_update.js";
import type FNote from "../entities/fnote.js"; import toastService from "./toast.js";
import { t } from "./i18n.js"; import utils from "./utils.js";
import { dayjs } from "@triliumnext/commons"; import ws from "./ws.js";
import type NoteContext from "../components/note_context.js";
import type Component from "../components/component.js";
import { formatLogMessage } from "@triliumnext/commons";
/** /**
* A whole number * A whole number
@@ -464,6 +465,8 @@ export interface Api {
* Log given message to the log pane in UI * Log given message to the log pane in UI
*/ */
log(message: string | object): void; log(message: string | object): void;
preact: typeof preactAPI;
} }
/** /**
@@ -533,9 +536,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
return params.map((p) => { return params.map((p) => {
if (typeof p === "function") { if (typeof p === "function") {
return `!@#Function: ${p.toString()}`; return `!@#Function: ${p.toString()}`;
} else {
return p;
} }
return p;
}); });
} }
@@ -562,9 +565,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();
return ret.executionResult; return ret.executionResult;
} else {
throw new Error(`server error: ${ret.error}`);
} }
throw new Error(`server error: ${ret.error}`);
}; };
this.runOnBackend = async (func, params = []) => { this.runOnBackend = async (func, params = []) => {
@@ -721,6 +724,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
this.logMessages[noteId].push(message); this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate(); this.logSpacedUpdates[noteId].scheduleUpdate();
}; };
this.preact = preactAPI;
} }
export default FrontendScriptApi as any as { export default FrontendScriptApi as any as {

View File

@@ -0,0 +1,37 @@
import { Fragment, h, VNode } from "preact";
import * as hooks from "preact/hooks";
import * as triliumHooks from "../widgets/react/hooks";
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget";
export interface WidgetDefinition {
parent: "right-pane",
render: () => VNode
}
export interface WidgetDefinitionWithType extends WidgetDefinition {
type: "preact-widget"
}
export const preactAPI = Object.freeze({
// Core
h,
Fragment,
/**
* Method that must be run for widget scripts that run on Preact, using JSX. The method just returns the same definition, reserved for future typechecking and perhaps validation purposes.
*
* @param definition the widget definition.
*/
defineWidget(definition: WidgetDefinition) {
return {
type: "preact-widget",
...definition
};
},
RightPanelWidget,
...hooks,
...triliumHooks
});

View File

@@ -133,11 +133,11 @@ async function call<T>(method: string, url: string, componentId?: string, option
}; };
ipc.send("server-request", { ipc.send("server-request", {
requestId: requestId, requestId,
headers: headers, headers,
method: method, method,
url: `/${window.glob.baseApiUrl}${url}`, url: `/${window.glob.baseApiUrl}${url}`,
data: data data
}); });
})) as any; })) as any;
} else { } else {
@@ -161,7 +161,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
const options: JQueryAjaxSettings = { const options: JQueryAjaxSettings = {
url: window.glob.baseApiUrl + url, url: window.glob.baseApiUrl + url,
type: method, type: method,
headers: headers, headers,
timeout: 60000, timeout: 60000,
success: (body, textStatus, jqXhr) => { success: (body, textStatus, jqXhr) => {
const respHeaders: Headers = {}; const respHeaders: Headers = {};
@@ -288,8 +288,8 @@ async function reportError(method: string, url: string, statusCode: number, resp
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }), t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
15_000); 15_000);
} }
const { throwError } = await import("./ws.js"); const { logError } = await import("./ws.js");
throwError(`${statusCode} ${method} ${url} - ${message}`); logError(`${statusCode} ${method} ${url} - ${message}`);
} }
} }

View File

@@ -1,5 +1,8 @@
import { signal } from "@preact/signals"; import { signal } from "@preact/signals";
import appContext from "../components/app_context.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import utils from "./utils.js"; import utils from "./utils.js";
export interface ToastOptions { export interface ToastOptions {
@@ -61,6 +64,24 @@ function showErrorTitleAndMessage(title: string, message: string, timeout = 1000
}); });
} }
export async function showErrorForScriptNote(noteId: string, message: string) {
const note = await froca.getNote(noteId, true);
showPersistent({
id: `custom-widget-failure-${noteId}`,
title: note?.title ?? "",
icon: note?.getIcon() ?? "bx bx-error-circle",
message,
timeout: 15_000,
buttons: [
{
text: t("toast.open-script-note"),
onClick: () => appContext.tabManager.openInNewTab(noteId, null, true)
}
]
});
}
//#region Toast store //#region Toast store
export const toasts = signal<ToastOptionsWithRequiredId[]>([]); export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
@@ -74,7 +95,7 @@ function addToast(opts: ToastOptions) {
function updateToast(id: string, partial: Partial<ToastOptions>) { function updateToast(id: string, partial: Partial<ToastOptions>) {
toasts.value = toasts.value.map(toast => { toasts.value = toasts.value.map(toast => {
if (toast.id === id) { if (toast.id === id) {
return { ...toast, ...partial } return { ...toast, ...partial };
} }
return toast; return toast;
}); });

View File

@@ -22,7 +22,15 @@
"bundle-error": { "bundle-error": {
"title": "Failed to load a custom script", "title": "Failed to load a custom script",
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}" "message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
} },
"widget-list-error": {
"title": "Failed to obtain the list of widgets from the server"
},
"widget-render-error": {
"title": "Failed to render a custom React widget"
},
"widget-missing-parent": "Custom widget does not have mandatory '{{property}}' property defined.",
"open-script-note": "Open script note"
}, },
"add_link": { "add_link": {
"add_link": "Add link", "add_link": "Add link",

View File

@@ -1,8 +1,9 @@
import { isValidElement, VNode } from "preact"; import { isValidElement, VNode } from "preact";
import Component, { TypedComponent } from "../components/component.js"; import Component, { TypedComponent } from "../components/component.js";
import froca from "../services/froca.js"; import froca from "../services/froca.js";
import { t } from "../services/i18n.js"; import { t } from "../services/i18n.js";
import toastService from "../services/toast.js"; import toastService, { showErrorForScriptNote } from "../services/toast.js";
import { renderReactWidget } from "./react/react_utils.jsx"; import { renderReactWidget } from "./react/react_utils.jsx";
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> { export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
@@ -56,9 +57,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
optChild(condition: boolean, ...components: (T | VNode)[]) { optChild(condition: boolean, ...components: (T | VNode)[]) {
if (condition) { if (condition) {
return this.child(...components); return this.child(...components);
} else { }
return this; return this;
}
} }
id(id: string) { id(id: string) {
@@ -172,16 +173,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
const noteId = this._noteId; const noteId = this._noteId;
if (this._noteId) { if (this._noteId) {
froca.getNote(noteId, true).then((note) => { froca.getNote(noteId, true).then((note) => {
toastService.showPersistent({ showErrorForScriptNote(noteId, t("toast.widget-error.message-custom", {
id: `custom-widget-failure-${noteId}`, id: noteId,
title: t("toast.widget-error.title"), title: note?.title,
icon: "bx bx-error-circle", message: e.message || e.toString()
message: t("toast.widget-error.message-custom", { }));
id: noteId,
title: note?.title,
message: e.message || e.toString()
})
});
}); });
} else { } else {
toastService.showPersistent({ toastService.showPersistent({
@@ -213,7 +209,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
toggleInt(show: boolean | null | undefined) { toggleInt(show: boolean | null | undefined) {
this.$widget.toggleClass("hidden-int", !show) this.$widget.toggleClass("hidden-int", !show)
.toggleClass("visible", !!show); .toggleClass("visible", !!show);
} }
isHiddenInt() { isHiddenInt() {
@@ -222,7 +218,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
toggleExt(show: boolean | null | "" | undefined) { toggleExt(show: boolean | null | "" | undefined) {
this.$widget.toggleClass("hidden-ext", !show) this.$widget.toggleClass("hidden-ext", !show)
.toggleClass("visible", !!show); .toggleClass("visible", !!show);
} }
isHiddenExt() { isHiddenExt() {
@@ -250,9 +246,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
getClosestNtxId() { getClosestNtxId() {
if (this.$widget) { if (this.$widget) {
return this.$widget.closest("[data-ntx-id]").attr("data-ntx-id"); return this.$widget.closest("[data-ntx-id]").attr("data-ntx-id");
} else { }
return null; return null;
}
} }
cleanup() {} cleanup() {}

View File

@@ -2,10 +2,11 @@
import "./RightPanelContainer.css"; import "./RightPanelContainer.css";
import Split from "@triliumnext/split.js"; import Split from "@triliumnext/split.js";
import { VNode } from "preact"; 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 { 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";
@@ -26,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 (
@@ -51,7 +52,7 @@ export default function RightPanelContainer({ customWidgets }: { customWidgets:
); );
} }
function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) { 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");
@@ -68,11 +69,18 @@ function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) {
enabled: noteType === "text" && highlightsList.length > 0, enabled: noteType === "text" && highlightsList.length > 0,
position: 20, position: 20,
}, },
...customWidgets.map((w, i) => ({ ...widgetsByParent.getLegacyWidgets("right-pane").map((widget, i) => ({
el: <CustomWidget 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
@@ -99,7 +107,7 @@ function useSplit(visible: boolean) {
}, [ visible ]); }, [ visible ]);
} }
function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) { function CustomLegacyWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
return ( return (

View File

@@ -30,7 +30,8 @@
"dependencies": { "dependencies": {
"better-sqlite3": "12.5.0", "better-sqlite3": "12.5.0",
"html-to-text": "9.0.5", "html-to-text": "9.0.5",
"node-html-parser": "7.0.1" "node-html-parser": "7.0.1",
"sucrase": "3.35.1"
}, },
"devDependencies": { "devDependencies": {
"@anthropic-ai/sdk": "0.71.2", "@anthropic-ai/sdk": "0.71.2",

View File

@@ -1,26 +1,25 @@
"use strict";
import protectedSessionService from "../../services/protected_session.js";
import log from "../../services/log.js";
import sql from "../../services/sql.js";
import optionService from "../../services/options.js";
import eraseService from "../../services/erase.js";
import utils from "../../services/utils.js";
import dateUtils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
import BRevision from "./brevision.js";
import BAttachment from "./battachment.js";
import TaskContext from "../../services/task_context.js";
import { dayjs } from "@triliumnext/commons";
import eventService from "../../services/events.js";
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons"; import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
import type BBranch from "./bbranch.js"; import { dayjs } from "@triliumnext/commons";
import BAttribute from "./battribute.js";
import type { NotePojo } from "../becca-interface.js";
import searchService from "../../services/search/services/search.js";
import cloningService from "../../services/cloning.js"; import cloningService from "../../services/cloning.js";
import noteService from "../../services/notes.js"; import dateUtils from "../../services/date_utils.js";
import eraseService from "../../services/erase.js";
import eventService from "../../services/events.js";
import handlers from "../../services/handlers.js"; import handlers from "../../services/handlers.js";
import log from "../../services/log.js";
import noteService from "../../services/notes.js";
import optionService from "../../services/options.js";
import protectedSessionService from "../../services/protected_session.js";
import searchService from "../../services/search/services/search.js";
import sql from "../../services/sql.js";
import TaskContext from "../../services/task_context.js";
import utils from "../../services/utils.js";
import type { NotePojo } from "../becca-interface.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
import BAttachment from "./battachment.js";
import BAttribute from "./battribute.js";
import type BBranch from "./bbranch.js";
import BRevision from "./brevision.js";
const LABEL = "label"; const LABEL = "label";
const RELATION = "relation"; const RELATION = "relation";
@@ -296,6 +295,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
); );
} }
isJsx() {
return (this.type === "code" && this.mime === "text/jsx");
}
/** @returns true if this note is HTML */ /** @returns true if this note is HTML */
isHtml() { isHtml() {
return ["code", "file", "render"].includes(this.type) && this.mime === "text/html"; return ["code", "file", "render"].includes(this.type) && this.mime === "text/html";
@@ -355,9 +358,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
return this.__attributeCache.filter((attr) => attr.type === type); return this.__attributeCache.filter((attr) => attr.type === type);
} else if (name) { } else if (name) {
return this.__attributeCache.filter((attr) => attr.name === name); return this.__attributeCache.filter((attr) => attr.name === name);
} else {
return this.__attributeCache;
} }
return this.__attributeCache;
} }
private __ensureAttributeCacheIsAvailable() { private __ensureAttributeCacheIsAvailable() {
@@ -692,9 +695,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
return this.ownedAttributes.filter((attr) => attr.type === type); return this.ownedAttributes.filter((attr) => attr.type === type);
} else if (name) { } else if (name) {
return this.ownedAttributes.filter((attr) => attr.name === name); return this.ownedAttributes.filter((attr) => attr.name === name);
} else {
return this.ownedAttributes;
} }
return this.ownedAttributes;
} }
/** /**
@@ -745,9 +748,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
return 1; return 1;
} else if (a.parentNote?.isHiddenCompletely()) { } else if (a.parentNote?.isHiddenCompletely()) {
return 1; return 1;
} else {
return 0;
} }
return 0;
}); });
this.parents = this.parentBranches.map((branch) => branch.parentNote).filter((note) => !!note) as BNote[]; this.parents = this.parentBranches.map((branch) => branch.parentNote).filter((note) => !!note) as BNote[];
@@ -1178,9 +1181,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
return a.isArchived ? 1 : -1; return a.isArchived ? 1 : -1;
} else if (a.isHidden !== b.isHidden) { } else if (a.isHidden !== b.isHidden) {
return a.isHidden ? 1 : -1; return a.isHidden ? 1 : -1;
} else {
return a.notePath.length - b.notePath.length;
} }
return a.notePath.length - b.notePath.length;
}); });
return notePaths; return notePaths;
@@ -1257,9 +1260,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
} else { } else {
new BAttribute({ new BAttribute({
noteId: this.noteId, noteId: this.noteId,
type: type, type,
name: name, name,
value: value value
}).save(); }).save();
} }
} }
@@ -1292,11 +1295,11 @@ class BNote extends AbstractBeccaEntity<BNote> {
addAttribute(type: AttributeType, name: string, value: string = "", isInheritable: boolean = false, position: number | null = null): BAttribute { addAttribute(type: AttributeType, name: string, value: string = "", isInheritable: boolean = false, position: number | null = null): BAttribute {
return new BAttribute({ return new BAttribute({
noteId: this.noteId, noteId: this.noteId,
type: type, type,
name: name, name,
value: value, value,
isInheritable: isInheritable, isInheritable,
position: position position
}).save(); }).save();
} }
@@ -1470,10 +1473,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
role: "image", role: "image",
mime: this.mime, mime: this.mime,
title: this.title, title: this.title,
content: content content
}); });
let parentContent = parentNote.getContent(); const parentContent = parentNote.getContent();
const oldNoteUrl = `api/images/${this.noteId}/`; const oldNoteUrl = `api/images/${this.noteId}/`;
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`; const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
@@ -1712,14 +1715,14 @@ class BNote extends AbstractBeccaEntity<BNote> {
} else if (this.type === "text") { } else if (this.type === "text") {
if (this.isFolder()) { if (this.isFolder()) {
return "bx bx-folder"; return "bx bx-folder";
} else {
return "bx bx-note";
} }
return "bx bx-note";
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) { } else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
return "bx bx-data"; return "bx bx-data";
} else {
return NOTE_TYPE_ICONS[this.type];
} }
return NOTE_TYPE_ICONS[this.type];
} }
// TODO: Deduplicate with fnote // TODO: Deduplicate with fnote
@@ -1729,7 +1732,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
// TODO: Deduplicate with fnote // TODO: Deduplicate with fnote
getFilteredChildBranches() { getFilteredChildBranches() {
let childBranches = this.getChildBranches(); const childBranches = this.getChildBranches();
if (!childBranches) { if (!childBranches) {
console.error(`No children for '${this.noteId}'. This shouldn't happen.`); console.error(`No children for '${this.noteId}'. This shouldn't happen.`);

View File

@@ -1,9 +1,11 @@
import { trimIndentation } from "@triliumnext/commons";
import becca from "../becca/becca.js"; import becca from "../becca/becca.js";
import { note, NoteBuilder } from "../test/becca_mocking.js";
import cls from "./cls.js";
import { executeBundle, getScriptBundle } from "./script.js";
import BBranch from "../becca/entities/bbranch.js"; import BBranch from "../becca/entities/bbranch.js";
import BNote from "../becca/entities/bnote.js"; import BNote from "../becca/entities/bnote.js";
import { note, NoteBuilder } from "../test/becca_mocking.js";
import cls from "./cls.js";
import { buildJsx, executeBundle, getScriptBundle } from "./script.js";
describe("Script", () => { describe("Script", () => {
@@ -84,3 +86,96 @@ describe("Script", () => {
}); });
}); });
}); });
describe("JSX building", () => {
it("processes basic JSX", () => {
const script = trimIndentation`\
function MyComponent() {
return <p>Hello world.</p>;
}
`;
const expected = trimIndentation`\
"use strict";const _jsxFileName = "";function MyComponent() {
return api.preact.h('p', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 2}}, "Hello world." );
}
`;
expect(buildJsx(script).code).toStrictEqual(expected);
});
it("processes fragments", () => {
const script = trimIndentation`\
function MyComponent() {
return <>
<p>Hi</p>
<p>there</p>
</>;
}
`;
const expected = trimIndentation`\
"use strict";const _jsxFileName = "";function MyComponent() {
return api.preact.h(api.preact.Fragment, null
, api.preact.h('p', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 3}}, "Hi")
, api.preact.h('p', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 4}}, "there")
);
}
`;
expect(buildJsx(script).code).toStrictEqual(expected);
});
it("rewrites export", () => {
const script = trimIndentation`\
const { defineWidget } = api.preact;
export default defineWidget({
parent: "right-pane",
render() {
return <></>;
}
});
`;
const expected = trimIndentation`\
"use strict";Object.defineProperty(exports, "__esModule", {value: true});const { defineWidget } = api.preact;
module.exports = defineWidget({
parent: "right-pane",
render() {
return api.preact.h(api.preact.Fragment, null);
}
});
`;
expect(buildJsx(script).code).toStrictEqual(expected);
});
it("rewrites React API imports", () => {
const script = trimIndentation`\
import { defineWidget, RightPanelWidget} from "trilium:preact";
defineWidget({
render() {
return <RightPanelWidget />;
}
});
`;
const expected = trimIndentation`\
"use strict";const _jsxFileName = "";const _triliumpreact = api.preact;
_triliumpreact.defineWidget.call(void 0, {
render() {
return api.preact.h(_triliumpreact.RightPanelWidget, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 4}} );
}
});
`;
expect(buildJsx(script).code).toStrictEqual(expected);
});
it("rewrites internal API imports", () => {
const script = trimIndentation`\
import { log } from "trilium:api";
log("Hi");
`;
const expected = trimIndentation`\
"use strict";const _triliumapi = api;
_triliumapi.log.call(void 0, "Hi");
`;
console.log(buildJsx(script).code);
expect(buildJsx(script).code).toStrictEqual(expected);
});
});

View File

@@ -1,9 +1,11 @@
import ScriptContext from "./script_context.js"; import { transform } from "sucrase";
import cls from "./cls.js";
import log from "./log.js";
import becca from "../becca/becca.js"; import becca from "../becca/becca.js";
import type BNote from "../becca/entities/bnote.js"; import type BNote from "../becca/entities/bnote.js";
import type { ApiParams } from "./backend_script_api_interface.js"; import type { ApiParams } from "./backend_script_api_interface.js";
import cls from "./cls.js";
import log from "./log.js";
import ScriptContext from "./script_context.js";
export interface Bundle { export interface Bundle {
note?: BNote; note?: BNote;
@@ -110,9 +112,9 @@ function getParams(params?: ScriptParams) {
.map((p) => { .map((p) => {
if (typeof p === "string" && p.startsWith("!@#Function: ")) { if (typeof p === "string" && p.startsWith("!@#Function: ")) {
return p.substr(13); return p.substr(13);
} else {
return JSON.stringify(p);
} }
return JSON.stringify(p);
}) })
.join(","); .join(",");
} }
@@ -145,7 +147,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
return; return;
} }
if (!note.isJavaScript() && !note.isHtml()) { if (!(note.isJavaScript() || note.isHtml() || note.isJsx())) {
return; return;
} }
@@ -158,7 +160,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
} }
const bundle: Bundle = { const bundle: Bundle = {
note: note, note,
script: "", script: "",
html: "", html: "",
allNotes: [note] allNotes: [note]
@@ -192,12 +194,18 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
// only frontend scripts are async. Backend cannot be async because of transaction management. // only frontend scripts are async. Backend cannot be async because of transaction management.
const isFrontend = scriptEnv === "frontend"; const isFrontend = scriptEnv === "frontend";
if (note.isJavaScript()) { if (note.isJsx() || note.isJavaScript()) {
let scriptContent = note.getContent();
if (note.isJsx()) {
scriptContent = buildJsx(scriptContent).code;
}
bundle.script += ` bundle.script += `
apiContext.modules['${note.noteId}'] = { exports: {} }; apiContext.modules['${note.noteId}'] = { exports: {} };
${root ? "return " : ""}${isFrontend ? "await" : ""} ((${isFrontend ? "async" : ""} function(exports, module, require, api${modules.length > 0 ? ", " : ""}${modules.map((child) => sanitizeVariableName(child.title)).join(", ")}) { ${root ? "return " : ""}${isFrontend ? "await" : ""} ((${isFrontend ? "async" : ""} function(exports, module, require, api${modules.length > 0 ? ", " : ""}${modules.map((child) => sanitizeVariableName(child.title)).join(", ")}) {
try { try {
${overrideContent || note.getContent()}; ${overrideContent || scriptContent};
} catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); } } catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); }
for (const exportKey in exports) module.exports[exportKey] = exports[exportKey]; for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
return module.exports; return module.exports;
@@ -210,6 +218,38 @@ return module.exports;
return bundle; return bundle;
} }
export function buildJsx(contentRaw: string | Buffer) {
const content = Buffer.isBuffer(contentRaw) ? contentRaw.toString("utf-8") : contentRaw;
const output = transform(content, {
transforms: ["jsx", "imports"],
jsxPragma: "api.preact.h",
jsxFragmentPragma: "api.preact.Fragment",
});
let code = output.code;
// Rewrite ESM-like exports to `module.exports =`.
code = output.code.replaceAll(
/\bexports\s*\.\s*default\s*=\s*/g,
'module.exports = '
);
// Rewrite ESM-like imports to Preact, to `const { foo } = api.preact`
code = code.replaceAll(
/var\s+(\w+)\s*=\s*require\(['"]trilium:preact['"]\);?/g,
'const $1 = api.preact;'
);
// Rewrite ESM-like imports to internal API, to `const { foo } = api.preact`
code = code.replaceAll(
/var\s+(\w+)\s*=\s*require\(['"]trilium:api['"]\);?/g,
'const $1 = api;'
);
output.code = code;
return output;
}
function sanitizeVariableName(str: string) { function sanitizeVariableName(str: string) {
return str.replace(/[^a-z0-9_]/gim, ""); return str.replace(/[^a-z0-9_]/gim, "");
} }

71
pnpm-lock.yaml generated
View File

@@ -295,6 +295,9 @@ importers:
reveal.js: reveal.js:
specifier: 5.2.1 specifier: 5.2.1
version: 5.2.1 version: 5.2.1
sucrase:
specifier: 3.35.1
version: 3.35.1
svg-pan-zoom: svg-pan-zoom:
specifier: 3.6.2 specifier: 3.6.2
version: 3.6.2 version: 3.6.2
@@ -494,6 +497,9 @@ importers:
node-html-parser: node-html-parser:
specifier: 7.0.1 specifier: 7.0.1
version: 7.0.1 version: 7.0.1
sucrase:
specifier: 3.35.1
version: 3.35.1
devDependencies: devDependencies:
'@anthropic-ai/sdk': '@anthropic-ai/sdk':
specifier: 0.71.2 specifier: 0.71.2
@@ -6255,6 +6261,9 @@ packages:
any-base@1.1.0: any-base@1.1.0:
resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==}
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
anymatch@3.1.3: anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -6951,6 +6960,10 @@ packages:
commander@2.20.3: commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@5.1.0: commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -10618,6 +10631,9 @@ packages:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
engines: {node: ^18.17.0 || >=20.5.0} engines: {node: ^18.17.0 || >=20.5.0}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nan@2.22.2: nan@2.22.2:
resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==} resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==}
@@ -11238,6 +11254,10 @@ packages:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'} engines: {node: '>=6'}
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pixelmatch@5.3.0: pixelmatch@5.3.0:
resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==}
hasBin: true hasBin: true
@@ -13136,6 +13156,11 @@ packages:
stylis@4.3.6: stylis@4.3.6:
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
sucrase@3.35.1:
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
sugarss@4.0.1: sugarss@4.0.1:
resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==} resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==}
engines: {node: '>=12.0'} engines: {node: '>=12.0'}
@@ -13279,6 +13304,13 @@ packages:
text-decoder@1.2.3: text-decoder@1.2.3:
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
thingies@2.5.0: thingies@2.5.0:
resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==}
engines: {node: '>=10.18'} engines: {node: '>=10.18'}
@@ -13430,6 +13462,9 @@ packages:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'} engines: {node: '>=6.10'}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
ts-loader@9.5.4: ts-loader@9.5.4:
resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -15225,6 +15260,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0 ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies: dependencies:
@@ -15290,8 +15327,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-watchdog': 47.3.0 '@ckeditor/ckeditor5-watchdog': 47.3.0
es-toolkit: 1.39.5 es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@54.2.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': '@ckeditor/ckeditor5-dev-build-tools@54.2.2(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies: dependencies:
@@ -21149,6 +21184,8 @@ snapshots:
any-base@1.1.0: {} any-base@1.1.0: {}
any-promise@1.3.0: {}
anymatch@3.1.3: anymatch@3.1.3:
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
@@ -22097,6 +22134,8 @@ snapshots:
commander@2.20.3: {} commander@2.20.3: {}
commander@4.1.1: {}
commander@5.1.0: {} commander@5.1.0: {}
commander@6.2.0: {} commander@6.2.0: {}
@@ -26816,6 +26855,12 @@ snapshots:
mute-stream@2.0.0: mute-stream@2.0.0:
optional: true optional: true
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
nan@2.22.2: nan@2.22.2:
optional: true optional: true
@@ -27473,6 +27518,8 @@ snapshots:
pify@4.0.1: pify@4.0.1:
optional: true optional: true
pirates@4.0.7: {}
pixelmatch@5.3.0: pixelmatch@5.3.0:
dependencies: dependencies:
pngjs: 6.0.0 pngjs: 6.0.0
@@ -29714,6 +29761,16 @@ snapshots:
stylis@4.3.6: {} stylis@4.3.6: {}
sucrase@3.35.1:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
commander: 4.1.1
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
tinyglobby: 0.2.15
ts-interface-checker: 0.1.13
sugarss@4.0.1(postcss@8.5.6): sugarss@4.0.1(postcss@8.5.6):
dependencies: dependencies:
postcss: 8.5.6 postcss: 8.5.6
@@ -29950,6 +30007,14 @@ snapshots:
dependencies: dependencies:
b4a: 1.6.7 b4a: 1.6.7
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
thingies@2.5.0(tslib@2.8.1): thingies@2.5.0(tslib@2.8.1):
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@@ -30085,6 +30150,8 @@ snapshots:
ts-dedent@2.2.0: {} ts-dedent@2.2.0: {}
ts-interface-checker@0.1.13: {}
ts-loader@9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)): ts-loader@9.5.4(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)):
dependencies: dependencies:
chalk: 4.1.2 chalk: 4.1.2