mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 15:49:56 +01:00
Compare commits
34 Commits
main
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f55395a9 | ||
|
|
444c0c6107 | ||
|
|
4da5cb43fc | ||
|
|
e6b79e83c4 | ||
|
|
6e67da7b1f | ||
|
|
9071e54bfe | ||
|
|
783b5ac8e3 | ||
|
|
f3f491d141 | ||
|
|
f8bf301d12 | ||
|
|
2c25786fa2 | ||
|
|
1093acfe45 | ||
|
|
76f054bbd5 | ||
|
|
c558255450 | ||
|
|
1e94125133 | ||
|
|
64a770175f | ||
|
|
e0416097e1 | ||
|
|
6c1b327f5f | ||
|
|
284b66acd2 | ||
|
|
dcd73ff9f9 | ||
|
|
645557b505 | ||
|
|
22a83d9f82 | ||
|
|
f64de3acca | ||
|
|
34d5793888 | ||
|
|
44ca9f457c | ||
|
|
4d7e5bc8f6 | ||
|
|
644ff07a50 | ||
|
|
41220a9d1d | ||
|
|
88945788d6 | ||
|
|
fe8f033409 | ||
|
|
eee7c49f6e | ||
|
|
d036bf0870 | ||
|
|
fa8ff4bfbf | ||
|
|
3619c0c3e4 | ||
|
|
883e32f5c9 |
@@ -989,6 +989,10 @@ export default class FNote {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
||||||
@@ -996,7 +1000,7 @@ export default class FNote {
|
|||||||
|
|
||||||
/** @returns JS script environment - either "frontend" or "backend" */
|
/** @returns JS script environment - either "frontend" or "backend" */
|
||||||
getScriptEnv() {
|
getScriptEnv() {
|
||||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
|
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
|
||||||
return "frontend";
|
return "frontend";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1018,7 +1022,7 @@ export default class FNote {
|
|||||||
* @returns a promise that resolves when the script has been run. Additionally, for front-end notes, the promise will contain the value that is returned by the script.
|
* @returns a promise that resolves when the script has been run. Additionally, for front-end notes, the promise will contain the value that is returned by the script.
|
||||||
*/
|
*/
|
||||||
async executeScript() {
|
async executeScript() {
|
||||||
if (!this.isJavaScript()) {
|
if (!(this.isJavaScript() || this.isJsx())) {
|
||||||
throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
|
throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 />)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,108 @@ 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();
|
||||||
|
if (preactWidget.position) {
|
||||||
|
widget.position = preactWidget.position;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
101
apps/client/src/services/frontend_script_api_preact.ts
Normal file
101
apps/client/src/services/frontend_script_api_preact.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import * as hooks from "preact/hooks";
|
||||||
|
|
||||||
|
import ActionButton from "../widgets/react/ActionButton";
|
||||||
|
import Admonition from "../widgets/react/Admonition";
|
||||||
|
import Button from "../widgets/react/Button";
|
||||||
|
import CKEditor from "../widgets/react/CKEditor";
|
||||||
|
import Collapsible from "../widgets/react/Collapsible";
|
||||||
|
import Dropdown from "../widgets/react/Dropdown";
|
||||||
|
import FormCheckbox from "../widgets/react/FormCheckbox";
|
||||||
|
import FormDropdownList from "../widgets/react/FormDropdownList";
|
||||||
|
import { FormFileUploadActionButton,FormFileUploadButton } from "../widgets/react/FormFileUpload";
|
||||||
|
import FormGroup from "../widgets/react/FormGroup";
|
||||||
|
import { FormDropdownDivider, FormDropdownSubmenu,FormListItem } from "../widgets/react/FormList";
|
||||||
|
import FormRadioGroup from "../widgets/react/FormRadioGroup";
|
||||||
|
import FormText from "../widgets/react/FormText";
|
||||||
|
import FormTextArea from "../widgets/react/FormTextArea";
|
||||||
|
import FormTextBox from "../widgets/react/FormTextBox";
|
||||||
|
import FormToggle from "../widgets/react/FormToggle";
|
||||||
|
import * as triliumHooks from "../widgets/react/hooks";
|
||||||
|
import Icon from "../widgets/react/Icon";
|
||||||
|
import LinkButton from "../widgets/react/LinkButton";
|
||||||
|
import LoadingSpinner from "../widgets/react/LoadingSpinner";
|
||||||
|
import Modal from "../widgets/react/Modal";
|
||||||
|
import NoteAutocomplete from "../widgets/react/NoteAutocomplete";
|
||||||
|
import NoteLink from "../widgets/react/NoteLink";
|
||||||
|
import RawHtml from "../widgets/react/RawHtml";
|
||||||
|
import Slider from "../widgets/react/Slider";
|
||||||
|
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget";
|
||||||
|
|
||||||
|
export interface WidgetDefinition {
|
||||||
|
parent: "right-pane",
|
||||||
|
render: () => VNode,
|
||||||
|
position?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetDefinitionWithType extends WidgetDefinition {
|
||||||
|
type: "preact-widget"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LauncherWidgetDefinitionWithType {
|
||||||
|
type: "preact-launcher-widget"
|
||||||
|
render: () => VNode
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
defineLauncherWidget(definition: Omit<LauncherWidgetDefinitionWithType, "type">) {
|
||||||
|
return {
|
||||||
|
type: "preact-launcher-widget",
|
||||||
|
...definition
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Basic widgets
|
||||||
|
ActionButton,
|
||||||
|
Admonition,
|
||||||
|
Button,
|
||||||
|
CKEditor,
|
||||||
|
Collapsible,
|
||||||
|
Dropdown,
|
||||||
|
FormCheckbox,
|
||||||
|
FormDropdownList,
|
||||||
|
FormFileUploadButton, FormFileUploadActionButton,
|
||||||
|
FormGroup,
|
||||||
|
FormListItem, FormDropdownDivider, FormDropdownSubmenu,
|
||||||
|
FormRadioGroup,
|
||||||
|
FormText,
|
||||||
|
FormTextArea,
|
||||||
|
FormTextBox,
|
||||||
|
FormToggle,
|
||||||
|
Icon,
|
||||||
|
LinkButton,
|
||||||
|
LoadingSpinner,
|
||||||
|
Modal,
|
||||||
|
NoteAutocomplete,
|
||||||
|
NoteLink,
|
||||||
|
RawHtml,
|
||||||
|
Slider,
|
||||||
|
|
||||||
|
// Specialized widgets
|
||||||
|
RightPanelWidget,
|
||||||
|
|
||||||
|
...hooks,
|
||||||
|
...triliumHooks
|
||||||
|
});
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import server from "./server.js";
|
import { h, VNode } from "preact";
|
||||||
import bundleService, { type Bundle } from "./bundle.js";
|
|
||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
|
import { renderReactWidgetAtElement } from "../widgets/react/react_utils.jsx";
|
||||||
|
import bundleService, { type Bundle } from "./bundle.js";
|
||||||
|
import froca from "./froca.js";
|
||||||
|
import server from "./server.js";
|
||||||
|
|
||||||
async function render(note: FNote, $el: JQuery<HTMLElement>) {
|
async function render(note: FNote, $el: JQuery<HTMLElement>) {
|
||||||
const relations = note.getRelations("renderNote");
|
const relations = note.getRelations("renderNote");
|
||||||
@@ -17,12 +21,34 @@ async function render(note: FNote, $el: JQuery<HTMLElement>) {
|
|||||||
$scriptContainer.append(bundle.html);
|
$scriptContainer.append(bundle.html);
|
||||||
|
|
||||||
// async so that scripts cannot block trilium execution
|
// async so that scripts cannot block trilium execution
|
||||||
bundleService.executeBundle(bundle, note, $scriptContainer);
|
bundleService.executeBundle(bundle, note, $scriptContainer).then(result => {
|
||||||
|
// Render JSX
|
||||||
|
if (bundle.html === "") {
|
||||||
|
renderIfJsx(bundle, result, $el);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderNoteIds.length > 0;
|
return renderNoteIds.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery<HTMLElement>) {
|
||||||
|
// Ensure the root script note is actually a JSX.
|
||||||
|
const rootScriptNoteId = await froca.getNote(bundle.noteId);
|
||||||
|
if (rootScriptNoteId?.mime !== "text/jsx") return;
|
||||||
|
|
||||||
|
// Ensure the output is a valid el.
|
||||||
|
if (typeof result !== "function") return;
|
||||||
|
|
||||||
|
// Obtain the parent component.
|
||||||
|
const closestComponent = glob.getComponentByEl($el.closest(".component")[0]);
|
||||||
|
if (!closestComponent) return;
|
||||||
|
|
||||||
|
// Render the element.
|
||||||
|
const el = h(result as () => VNode, {});
|
||||||
|
renderReactWidgetAtElement(closestComponent, el, $el[0]);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
render
|
render
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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() {}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks";
|
import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import appContext, { CommandNames } from "../../components/app_context";
|
||||||
|
import FNote from "../../entities/fnote";
|
||||||
|
import date_notes from "../../services/date_notes";
|
||||||
|
import dialog from "../../services/dialog";
|
||||||
|
import { LauncherWidgetDefinitionWithType } from "../../services/frontend_script_api_preact";
|
||||||
|
import { t } from "../../services/i18n";
|
||||||
|
import toast from "../../services/toast";
|
||||||
|
import { getErrorMessage, isMobile } from "../../services/utils";
|
||||||
|
import BasicWidget from "../basic_widget";
|
||||||
|
import NoteContextAwareWidget from "../note_context_aware_widget";
|
||||||
|
import QuickSearchWidget from "../quick_search";
|
||||||
import { useGlobalShortcut, useLegacyWidget, useNoteLabel, useNoteRelationTarget, useTriliumOptionBool } from "../react/hooks";
|
import { useGlobalShortcut, useLegacyWidget, useNoteLabel, useNoteRelationTarget, useTriliumOptionBool } from "../react/hooks";
|
||||||
import { ParentComponent } from "../react/react_utils";
|
import { ParentComponent } from "../react/react_utils";
|
||||||
import BasicWidget from "../basic_widget";
|
|
||||||
import FNote from "../../entities/fnote";
|
|
||||||
import QuickSearchWidget from "../quick_search";
|
|
||||||
import { getErrorMessage, isMobile } from "../../services/utils";
|
|
||||||
import date_notes from "../../services/date_notes";
|
|
||||||
import { CustomNoteLauncher } from "./GenericButtons";
|
import { CustomNoteLauncher } from "./GenericButtons";
|
||||||
import { LaunchBarActionButton, LaunchBarContext, LauncherNoteProps, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
import { LaunchBarActionButton, LaunchBarContext, LauncherNoteProps, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||||
import dialog from "../../services/dialog";
|
|
||||||
import { t } from "../../services/i18n";
|
|
||||||
import appContext, { CommandNames } from "../../components/app_context";
|
|
||||||
import toast from "../../services/toast";
|
|
||||||
|
|
||||||
export function CommandButton({ launcherNote }: LauncherNoteProps) {
|
export function CommandButton({ launcherNote }: LauncherNoteProps) {
|
||||||
const { icon, title } = useLauncherIconAndTitle(launcherNote);
|
const { icon, title } = useLauncherIconAndTitle(launcherNote);
|
||||||
@@ -23,7 +26,7 @@ export function CommandButton({ launcherNote }: LauncherNoteProps) {
|
|||||||
text={title}
|
text={title}
|
||||||
triggerCommand={command as CommandNames}
|
triggerCommand={command as CommandNames}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're intentionally displaying the launcher title and icon instead of the target,
|
// we're intentionally displaying the launcher title and icon instead of the target,
|
||||||
@@ -75,7 +78,7 @@ export function ScriptLauncher({ launcherNote }: LauncherNoteProps) {
|
|||||||
text={title}
|
text={title}
|
||||||
onClick={launch}
|
onClick={launch}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AiChatButton({ launcherNote }: LauncherNoteProps) {
|
export function AiChatButton({ launcherNote }: LauncherNoteProps) {
|
||||||
@@ -88,7 +91,7 @@ export function AiChatButton({ launcherNote }: LauncherNoteProps) {
|
|||||||
text={title}
|
text={title}
|
||||||
triggerCommand="createAiChat"
|
triggerCommand="createAiChat"
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TodayLauncher({ launcherNote }: LauncherNoteProps) {
|
export function TodayLauncher({ launcherNote }: LauncherNoteProps) {
|
||||||
@@ -114,12 +117,13 @@ export function QuickSearchLauncherWidget() {
|
|||||||
<div>
|
<div>
|
||||||
{isEnabled && <LegacyWidgetRenderer widget={widget} />}
|
{isEnabled && <LegacyWidgetRenderer widget={widget} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CustomWidget({ launcherNote }: LauncherNoteProps) {
|
export function CustomWidget({ launcherNote }: LauncherNoteProps) {
|
||||||
const [ widgetNote ] = useNoteRelationTarget(launcherNote, "widget");
|
const [ widgetNote ] = useNoteRelationTarget(launcherNote, "widget");
|
||||||
const [ widget, setWidget ] = useState<BasicWidget>();
|
const [ widget, setWidget ] = useState<BasicWidget | NoteContextAwareWidget | LauncherWidgetDefinitionWithType>();
|
||||||
|
|
||||||
const parentComponent = useContext(ParentComponent) as BasicWidget | null;
|
const parentComponent = useContext(ParentComponent) as BasicWidget | null;
|
||||||
parentComponent?.contentSized();
|
parentComponent?.contentSized();
|
||||||
|
|
||||||
@@ -146,9 +150,13 @@ export function CustomWidget({ launcherNote }: LauncherNoteProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{widget && <LegacyWidgetRenderer widget={widget} />}
|
{widget && (
|
||||||
|
("type" in widget && widget.type === "preact-launcher-widget")
|
||||||
|
? <ReactWidgetRenderer widget={widget as LauncherWidgetDefinitionWithType} />
|
||||||
|
: <LegacyWidgetRenderer widget={widget as BasicWidget} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
|
export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
|
||||||
@@ -158,3 +166,8 @@ export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
|
|||||||
|
|
||||||
return widgetEl;
|
return widgetEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ReactWidgetRenderer({ widget }: { widget: LauncherWidgetDefinitionWithType }) {
|
||||||
|
const El = widget.render;
|
||||||
|
return <El />;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Tooltip } from "bootstrap";
|
import { Tooltip } from "bootstrap";
|
||||||
import { useEffect, useRef, useMemo, useCallback } from "preact/hooks";
|
|
||||||
import { escapeQuotes } from "../../services/utils";
|
|
||||||
import { ComponentChildren } from "preact";
|
import { ComponentChildren } from "preact";
|
||||||
import { CSSProperties, memo } from "preact/compat";
|
import { CSSProperties, memo } from "preact/compat";
|
||||||
|
import { useCallback,useEffect, useMemo, useRef } from "preact/hooks";
|
||||||
|
|
||||||
|
import { escapeQuotes } from "../../services/utils";
|
||||||
import { useUniqueName } from "./hooks";
|
import { useUniqueName } from "./hooks";
|
||||||
|
|
||||||
interface FormCheckboxProps {
|
interface FormCheckboxProps {
|
||||||
@@ -18,7 +19,7 @@ interface FormCheckboxProps {
|
|||||||
containerStyle?: CSSProperties;
|
containerStyle?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => {
|
export default function FormCheckbox({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) {
|
||||||
const labelRef = useRef<HTMLLabelElement>(null);
|
const labelRef = useRef<HTMLLabelElement>(null);
|
||||||
const id = useUniqueName(name);
|
const id = useUniqueName(name);
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint
|
|||||||
|
|
||||||
const labelStyle = useMemo(() =>
|
const labelStyle = useMemo(() =>
|
||||||
hint ? { textDecoration: "underline dotted var(--main-text-color)" } : undefined,
|
hint ? { textDecoration: "underline dotted var(--main-text-color)" } : undefined,
|
||||||
[hint]
|
[hint]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback((e: Event) => {
|
const handleChange = useCallback((e: Event) => {
|
||||||
@@ -65,6 +66,4 @@ const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
export default FormCheckbox;
|
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -23,12 +24,12 @@ const MIN_WIDTH_PERCENT = 5;
|
|||||||
interface RightPanelWidgetDefinition {
|
interface RightPanelWidgetDefinition {
|
||||||
el: VNode;
|
el: VNode;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
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");
|
||||||
@@ -61,23 +62,38 @@ function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) {
|
|||||||
{
|
{
|
||||||
el: <TableOfContents />,
|
el: <TableOfContents />,
|
||||||
enabled: (noteType === "text" || noteType === "doc"),
|
enabled: (noteType === "text" || noteType === "doc"),
|
||||||
position: 10,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
el: <HighlightsList />,
|
el: <HighlightsList />,
|
||||||
enabled: noteType === "text" && highlightsList.length > 0,
|
enabled: noteType === "text" && highlightsList.length > 0,
|
||||||
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
|
||||||
}))
|
})),
|
||||||
|
...widgetsByParent.getPreactWidgets("right-pane").map((widget) => {
|
||||||
|
const El = widget.render;
|
||||||
|
return {
|
||||||
|
el: <El />,
|
||||||
|
enabled: true,
|
||||||
|
position: widget.position
|
||||||
|
};
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Assign a position to items that don't have one yet.
|
||||||
|
let pos = 10;
|
||||||
|
for (const definition of definitions) {
|
||||||
|
if (!definition.position) {
|
||||||
|
definition.position = pos;
|
||||||
|
pos += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return definitions
|
return definitions
|
||||||
.filter(e => e.enabled)
|
.filter(e => e.enabled)
|
||||||
.toSorted((a, b) => a.position - b.position)
|
.toSorted((a, b) => (a.position ?? 10) - (b.position ?? 10))
|
||||||
.map(e => e.el);
|
.map(e => e.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +115,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 (
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.`);
|
||||||
|
|||||||
@@ -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";function MyComponent() {
|
||||||
|
return api.preact.h('p', null, "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";function MyComponent() {
|
||||||
|
return api.preact.h(api.preact.Fragment, null
|
||||||
|
, api.preact.h('p', null, "Hi")
|
||||||
|
, api.preact.h('p', null, "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 _triliumpreact = api.preact;
|
||||||
|
_triliumpreact.defineWidget.call(void 0, {
|
||||||
|
render() {
|
||||||
|
return api.preact.h(_triliumpreact.RightPanelWidget, null );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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,39 @@ 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",
|
||||||
|
production: true
|
||||||
|
});
|
||||||
|
|
||||||
|
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, "");
|
||||||
}
|
}
|
||||||
|
|||||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -494,6 +494,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 +6258,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 +6957,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 +10628,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 +11251,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 +13153,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 +13301,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 +13459,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'}
|
||||||
@@ -15290,8 +15322,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:
|
||||||
@@ -15797,8 +15827,6 @@ snapshots:
|
|||||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||||
'@ckeditor/ckeditor5-widget': 47.3.0
|
'@ckeditor/ckeditor5-widget': 47.3.0
|
||||||
ckeditor5: 47.3.0
|
ckeditor5: 47.3.0
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@ckeditor/ckeditor5-mention@47.3.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)':
|
'@ckeditor/ckeditor5-mention@47.3.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -21149,6 +21177,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 +22127,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 +26848,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 +27511,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 +29754,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 +30000,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 +30143,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
|
||||||
|
|||||||
Reference in New Issue
Block a user