mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 23:59:59 +01:00
Compare commits
3 Commits
feature/pr
...
fix/implem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f45920e506 | ||
|
|
6fdd418edd | ||
|
|
409ecb84a8 |
@@ -989,10 +989,6 @@ export default class FNote {
|
||||
);
|
||||
}
|
||||
|
||||
isJsx() {
|
||||
return (this.type === "code" && this.mime === "text/jsx");
|
||||
}
|
||||
|
||||
/** @returns true if this note is HTML */
|
||||
isHtml() {
|
||||
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
|
||||
@@ -1000,7 +996,7 @@ export default class FNote {
|
||||
|
||||
/** @returns JS script environment - either "frontend" or "backend" */
|
||||
getScriptEnv() {
|
||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
|
||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
|
||||
return "frontend";
|
||||
}
|
||||
|
||||
@@ -1022,7 +1018,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.
|
||||
*/
|
||||
async executeScript() {
|
||||
if (!(this.isJavaScript() || this.isJsx())) {
|
||||
if (!this.isJavaScript()) {
|
||||
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(...this.customWidgets.get("right-pane"))
|
||||
)
|
||||
.optChild(isNewLayout, <RightPanelContainer widgetsByParent={this.customWidgets} />)
|
||||
.optChild(isNewLayout, <RightPanelContainer customWidgets={this.customWidgets.get("right-pane")} />)
|
||||
)
|
||||
.optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
|
||||
)
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
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 server from "./server.js";
|
||||
import toastService, { showErrorForScriptNote } from "./toast.js";
|
||||
import utils, { getErrorMessage } from "./utils.js";
|
||||
import toastService, { showError } from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import { t } from "./i18n.js";
|
||||
import type { Entity } from "./frontend_script_api.js";
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
export interface Bundle {
|
||||
@@ -20,13 +14,9 @@ export interface Bundle {
|
||||
allNoteIds: string[];
|
||||
}
|
||||
|
||||
type LegacyWidget = (BasicWidget | RightPanelWidget) & {
|
||||
interface Widget {
|
||||
parentWidget?: string;
|
||||
};
|
||||
type WithNoteId<T> = T & {
|
||||
_noteId: string;
|
||||
};
|
||||
export type Widget = WithNoteId<(LegacyWidget | WidgetDefinitionWithType)>;
|
||||
}
|
||||
|
||||
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
|
||||
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
|
||||
@@ -37,8 +27,6 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
|
||||
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>) {
|
||||
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
|
||||
|
||||
@@ -64,7 +52,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
|
||||
|
||||
async function executeStartupBundles() {
|
||||
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) {
|
||||
await executeBundle(bundle);
|
||||
@@ -72,75 +60,42 @@ async function executeStartupBundles() {
|
||||
}
|
||||
|
||||
export class WidgetsByParent {
|
||||
private legacyWidgets: Record<string, WithNoteId<LegacyWidget>[]>;
|
||||
private preactWidgets: Record<string, WithNoteId<WidgetDefinitionWithType>[]>;
|
||||
private byParent: Record<string, Widget[]>;
|
||||
|
||||
constructor() {
|
||||
this.legacyWidgets = {};
|
||||
this.preactWidgets = {};
|
||||
this.byParent = {};
|
||||
}
|
||||
|
||||
add(widget: Widget) {
|
||||
let hasParentWidget = false;
|
||||
let isPreact = false;
|
||||
if ("type" in widget && widget.type === "preact-widget") {
|
||||
// React-based script.
|
||||
const reactWidget = widget as WithNoteId<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;
|
||||
if (!widget.parentWidget) {
|
||||
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasParentWidget) {
|
||||
showErrorForScriptNote(widget._noteId, t("toast.widget-missing-parent", {
|
||||
property: isPreact ? "parent" : "parentWidget"
|
||||
}));
|
||||
}
|
||||
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || [];
|
||||
this.byParent[widget.parentWidget].push(widget);
|
||||
}
|
||||
|
||||
get(parentName: ParentName) {
|
||||
const widgets: (BasicWidget | VNode)[] = this.getLegacyWidgets(parentName);
|
||||
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;
|
||||
get(parentName: string) {
|
||||
if (!this.byParent[parentName]) {
|
||||
return [];
|
||||
}
|
||||
widgets.push(widget);
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
getLegacyWidgets(parentName: ParentName): (BasicWidget | RightPanelWidget)[] {
|
||||
if (!this.legacyWidgets[parentName]) return [];
|
||||
|
||||
return (
|
||||
this.legacyWidgets[parentName]
|
||||
this.byParent[parentName]
|
||||
// previously, custom widgets were provided as a single instance, but that has the disadvantage
|
||||
// for splits where we actually need multiple instaces and thus having a class to instantiate is better
|
||||
// https://github.com/zadam/trilium/issues/4274
|
||||
.map((w: any) => (w.prototype ? new w() : w))
|
||||
);
|
||||
}
|
||||
|
||||
getPreactWidgets(parentName: ParentName) {
|
||||
return this.preactWidgets[parentName] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getWidgetBundlesByParent() {
|
||||
const widgetsByParent = new WidgetsByParent();
|
||||
|
||||
try {
|
||||
const scriptBundles = await server.get<Bundle[]>("script/widgets");
|
||||
|
||||
const widgetsByParent = new WidgetsByParent();
|
||||
|
||||
for (const bundle of scriptBundles) {
|
||||
let widget;
|
||||
|
||||
@@ -168,14 +123,6 @@ async function getWidgetBundlesByParent() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
toastService.showPersistent({
|
||||
id: `custom-widget-list-failure`,
|
||||
title: t("toast.widget-list-error.title"),
|
||||
message: getErrorMessage(e),
|
||||
icon: "bx bx-error-circle"
|
||||
});
|
||||
}
|
||||
|
||||
return widgetsByParent;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { dayjs, formatLogMessage } from "@triliumnext/commons";
|
||||
|
||||
import appContext from "../components/app_context.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 { preactAPI } from "./frontend_script_api_preact.js";
|
||||
import { t } from "./i18n.js";
|
||||
import server from "./server.js";
|
||||
import utils from "./utils.js";
|
||||
import toastService from "./toast.js";
|
||||
import linkService from "./link.js";
|
||||
import froca from "./froca.js";
|
||||
import noteTooltipService from "./note_tooltip.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import dateNotesService from "./date_notes.js";
|
||||
import searchService from "./search.js";
|
||||
import server from "./server.js";
|
||||
import shortcutService from "./shortcuts.js";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
import toastService from "./toast.js";
|
||||
import utils from "./utils.js";
|
||||
import RightPanelWidget from "../widgets/right_panel_widget.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 dialogService from "./dialog.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
import type Component from "../components/component.js";
|
||||
import { formatLogMessage } from "@triliumnext/commons";
|
||||
|
||||
/**
|
||||
* A whole number
|
||||
@@ -465,8 +464,6 @@ export interface Api {
|
||||
* Log given message to the log pane in UI
|
||||
*/
|
||||
log(message: string | object): void;
|
||||
|
||||
preact: typeof preactAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,9 +533,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
return params.map((p) => {
|
||||
if (typeof p === "function") {
|
||||
return `!@#Function: ${p.toString()}`;
|
||||
}
|
||||
} else {
|
||||
return p;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -565,9 +562,9 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return ret.executionResult;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`server error: ${ret.error}`);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.runOnBackend = async (func, params = []) => {
|
||||
@@ -724,8 +721,6 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
||||
this.logMessages[noteId].push(message);
|
||||
this.logSpacedUpdates[noteId].scheduleUpdate();
|
||||
};
|
||||
|
||||
this.preact = preactAPI;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi as any as {
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
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,10 +1,6 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
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";
|
||||
import bundleService, { type Bundle } from "./bundle.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
async function render(note: FNote, $el: JQuery<HTMLElement>) {
|
||||
const relations = note.getRelations("renderNote");
|
||||
@@ -21,34 +17,12 @@ async function render(note: FNote, $el: JQuery<HTMLElement>) {
|
||||
$scriptContainer.append(bundle.html);
|
||||
|
||||
// async so that scripts cannot block trilium execution
|
||||
bundleService.executeBundle(bundle, note, $scriptContainer).then(result => {
|
||||
// Render JSX
|
||||
if (bundle.html === "") {
|
||||
renderIfJsx(bundle, result, $el);
|
||||
}
|
||||
});
|
||||
bundleService.executeBundle(bundle, note, $scriptContainer);
|
||||
}
|
||||
|
||||
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 {
|
||||
render
|
||||
};
|
||||
|
||||
@@ -133,11 +133,11 @@ async function call<T>(method: string, url: string, componentId?: string, option
|
||||
};
|
||||
|
||||
ipc.send("server-request", {
|
||||
requestId,
|
||||
headers,
|
||||
method,
|
||||
requestId: requestId,
|
||||
headers: headers,
|
||||
method: method,
|
||||
url: `/${window.glob.baseApiUrl}${url}`,
|
||||
data
|
||||
data: data
|
||||
});
|
||||
})) as any;
|
||||
} else {
|
||||
@@ -161,7 +161,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
|
||||
const options: JQueryAjaxSettings = {
|
||||
url: window.glob.baseApiUrl + url,
|
||||
type: method,
|
||||
headers,
|
||||
headers: headers,
|
||||
timeout: 60000,
|
||||
success: (body, textStatus, jqXhr) => {
|
||||
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 }),
|
||||
15_000);
|
||||
}
|
||||
const { logError } = await import("./ws.js");
|
||||
logError(`${statusCode} ${method} ${url} - ${message}`);
|
||||
const { throwError } = await import("./ws.js");
|
||||
throwError(`${statusCode} ${method} ${url} - ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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";
|
||||
|
||||
export interface ToastOptions {
|
||||
@@ -64,24 +61,6 @@ 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
|
||||
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
|
||||
|
||||
@@ -95,7 +74,7 @@ function addToast(opts: ToastOptions) {
|
||||
function updateToast(id: string, partial: Partial<ToastOptions>) {
|
||||
toasts.value = toasts.value.map(toast => {
|
||||
if (toast.id === id) {
|
||||
return { ...toast, ...partial };
|
||||
return { ...toast, ...partial }
|
||||
}
|
||||
return toast;
|
||||
});
|
||||
|
||||
@@ -22,15 +22,7 @@
|
||||
"bundle-error": {
|
||||
"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}}"
|
||||
},
|
||||
"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",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { isValidElement, VNode } from "preact";
|
||||
|
||||
import Component, { TypedComponent } from "../components/component.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import toastService, { showErrorForScriptNote } from "../services/toast.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import { renderReactWidget } from "./react/react_utils.jsx";
|
||||
|
||||
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
|
||||
@@ -57,9 +56,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
optChild(condition: boolean, ...components: (T | VNode)[]) {
|
||||
if (condition) {
|
||||
return this.child(...components);
|
||||
}
|
||||
} else {
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
id(id: string) {
|
||||
@@ -173,11 +172,16 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
const noteId = this._noteId;
|
||||
if (this._noteId) {
|
||||
froca.getNote(noteId, true).then((note) => {
|
||||
showErrorForScriptNote(noteId, t("toast.widget-error.message-custom", {
|
||||
toastService.showPersistent({
|
||||
id: `custom-widget-failure-${noteId}`,
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.widget-error.message-custom", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message || e.toString()
|
||||
}));
|
||||
})
|
||||
});
|
||||
});
|
||||
} else {
|
||||
toastService.showPersistent({
|
||||
@@ -246,9 +250,9 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
getClosestNtxId() {
|
||||
if (this.$widget) {
|
||||
return this.$widget.closest("[data-ntx-id]").attr("data-ntx-id");
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
import splitService from "../../services/resizer.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import splitService from "../../services/resizer.js";
|
||||
import type RightPanelWidget from "../right_panel_widget.js";
|
||||
import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
|
||||
export default class RightPaneContainer extends FlexContainer<BasicWidget> {
|
||||
export default class RightPaneContainer extends FlexContainer<RightPanelWidget> {
|
||||
private rightPaneHidden: boolean;
|
||||
private firstRender: boolean;
|
||||
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
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 { 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 { 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) {
|
||||
const { icon, title } = useLauncherIconAndTitle(launcherNote);
|
||||
@@ -26,7 +23,7 @@ export function CommandButton({ launcherNote }: LauncherNoteProps) {
|
||||
text={title}
|
||||
triggerCommand={command as CommandNames}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// we're intentionally displaying the launcher title and icon instead of the target,
|
||||
@@ -78,7 +75,7 @@ export function ScriptLauncher({ launcherNote }: LauncherNoteProps) {
|
||||
text={title}
|
||||
onClick={launch}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function AiChatButton({ launcherNote }: LauncherNoteProps) {
|
||||
@@ -91,7 +88,7 @@ export function AiChatButton({ launcherNote }: LauncherNoteProps) {
|
||||
text={title}
|
||||
triggerCommand="createAiChat"
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function TodayLauncher({ launcherNote }: LauncherNoteProps) {
|
||||
@@ -117,13 +114,12 @@ export function QuickSearchLauncherWidget() {
|
||||
<div>
|
||||
{isEnabled && <LegacyWidgetRenderer widget={widget} />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function CustomWidget({ launcherNote }: LauncherNoteProps) {
|
||||
const [ widgetNote ] = useNoteRelationTarget(launcherNote, "widget");
|
||||
const [ widget, setWidget ] = useState<BasicWidget | NoteContextAwareWidget | LauncherWidgetDefinitionWithType>();
|
||||
|
||||
const [ widget, setWidget ] = useState<BasicWidget>();
|
||||
const parentComponent = useContext(ParentComponent) as BasicWidget | null;
|
||||
parentComponent?.contentSized();
|
||||
|
||||
@@ -150,13 +146,9 @@ export function CustomWidget({ launcherNote }: LauncherNoteProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{widget && (
|
||||
("type" in widget && widget.type === "preact-launcher-widget")
|
||||
? <ReactWidgetRenderer widget={widget as LauncherWidgetDefinitionWithType} />
|
||||
: <LegacyWidgetRenderer widget={widget as BasicWidget} />
|
||||
)}
|
||||
{widget && <LegacyWidgetRenderer widget={widget} />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
|
||||
@@ -166,8 +158,3 @@ export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
|
||||
|
||||
return widgetEl;
|
||||
}
|
||||
|
||||
function ReactWidgetRenderer({ widget }: { widget: LauncherWidgetDefinitionWithType }) {
|
||||
const El = widget.render;
|
||||
return <El />;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Tooltip } from "bootstrap";
|
||||
import { useEffect, useRef, useMemo, useCallback } from "preact/hooks";
|
||||
import { escapeQuotes } from "../../services/utils";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { CSSProperties, memo } from "preact/compat";
|
||||
import { useCallback,useEffect, useMemo, useRef } from "preact/hooks";
|
||||
|
||||
import { escapeQuotes } from "../../services/utils";
|
||||
import { useUniqueName } from "./hooks";
|
||||
|
||||
interface FormCheckboxProps {
|
||||
@@ -19,7 +18,7 @@ interface FormCheckboxProps {
|
||||
containerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export default function FormCheckbox({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) {
|
||||
const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => {
|
||||
const labelRef = useRef<HTMLLabelElement>(null);
|
||||
const id = useUniqueName(name);
|
||||
|
||||
@@ -66,4 +65,6 @@ export default function FormCheckbox({ name, disabled, label, currentValue, onCh
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default FormCheckbox;
|
||||
@@ -2,11 +2,10 @@
|
||||
import "./RightPanelContainer.css";
|
||||
|
||||
import Split from "@triliumnext/split.js";
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import { VNode } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import { WidgetsByParent } from "../../services/bundle";
|
||||
import { t } from "../../services/i18n";
|
||||
import options from "../../services/options";
|
||||
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
|
||||
@@ -24,12 +23,12 @@ const MIN_WIDTH_PERCENT = 5;
|
||||
interface RightPanelWidgetDefinition {
|
||||
el: VNode;
|
||||
enabled: boolean;
|
||||
position?: number;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export default function RightPanelContainer({ widgetsByParent }: { widgetsByParent: WidgetsByParent }) {
|
||||
export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) {
|
||||
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
|
||||
const items = useItems(rightPaneVisible, widgetsByParent);
|
||||
const items = useItems(rightPaneVisible, customWidgets);
|
||||
useSplit(rightPaneVisible);
|
||||
|
||||
return (
|
||||
@@ -52,7 +51,7 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
|
||||
);
|
||||
}
|
||||
|
||||
function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
|
||||
function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) {
|
||||
const { note } = useActiveNoteContext();
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList");
|
||||
@@ -62,38 +61,23 @@ function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
|
||||
{
|
||||
el: <TableOfContents />,
|
||||
enabled: (noteType === "text" || noteType === "doc"),
|
||||
position: 10,
|
||||
},
|
||||
{
|
||||
el: <HighlightsList />,
|
||||
enabled: noteType === "text" && highlightsList.length > 0,
|
||||
position: 20,
|
||||
},
|
||||
...widgetsByParent.getLegacyWidgets("right-pane").map((widget, i) => ({
|
||||
el: <CustomLegacyWidget key={widget._noteId} originalWidget={widget as LegacyRightPanelWidget} />,
|
||||
...customWidgets.map((w, i) => ({
|
||||
el: <CustomWidget key={w._noteId} originalWidget={w as LegacyRightPanelWidget} />,
|
||||
enabled: true,
|
||||
position: widget.position
|
||||
})),
|
||||
...widgetsByParent.getPreactWidgets("right-pane").map((widget) => {
|
||||
const El = widget.render;
|
||||
return {
|
||||
el: <El />,
|
||||
enabled: true,
|
||||
position: widget.position
|
||||
};
|
||||
})
|
||||
position: w.position ?? 30 + i * 10
|
||||
}))
|
||||
];
|
||||
|
||||
// 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
|
||||
.filter(e => e.enabled)
|
||||
.toSorted((a, b) => (a.position ?? 10) - (b.position ?? 10))
|
||||
.toSorted((a, b) => a.position - b.position)
|
||||
.map(e => e.el);
|
||||
}
|
||||
|
||||
@@ -115,7 +99,7 @@ function useSplit(visible: boolean) {
|
||||
}, [ visible ]);
|
||||
}
|
||||
|
||||
function CustomLegacyWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) {
|
||||
function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.5.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.0.1",
|
||||
"sucrase": "3.35.1"
|
||||
"node-html-parser": "7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.71.2",
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
File diff suppressed because one or more lines are too long
@@ -9,8 +9,8 @@
|
||||
feel free to report them either via a ticket or via the Matrix.</p>
|
||||
<h2>Downloading the nightly release manually</h2>
|
||||
<p>Go to <a href="https://github.com/TriliumNext/Trilium/releases/tag/nightly">github.com/TriliumNext/Trilium/releases/tag/nightly</a> and
|
||||
look for the artifacts starting with <code>TriliumNotes-main</code>. Choose
|
||||
the appropriate one for your platform (e.g. <code>windows-x64.zip</code>).</p>
|
||||
look for the artifacts starting with <code spellcheck="false">TriliumNotes-main</code>.
|
||||
Choose the appropriate one for your platform (e.g. <code spellcheck="false">windows-x64.zip</code>).</p>
|
||||
<p>Depending on your use case, you can either test the portable version or
|
||||
even use the installer.</p>
|
||||
<aside class="admonition note">
|
||||
|
||||
@@ -15,7 +15,6 @@ class="image">
|
||||
<img style="aspect-ratio:1150/27;" src="4_New Layout_image.png"
|
||||
width="1150" height="27">
|
||||
</figure>
|
||||
|
||||
<h3>Inline title</h3>
|
||||
<p>In previous versions of Trilium, the title bar was fixed at all times.
|
||||
In the new layout, there is both a fixed title bar and one that scrolls
|
||||
@@ -42,7 +41,6 @@ class="image">
|
||||
width="910" height="104">
|
||||
<figcaption>The fixed title bar. The title only appears after scrolling past the <em>Inline title</em>.</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3>New note type switcher</h3>
|
||||
<p>When a new <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> or
|
||||
<a
|
||||
@@ -50,9 +48,10 @@ class="image">
|
||||
the <em>Inline title</em>. Apart from changing the note type, it's also
|
||||
possible to apply a <a href="#root/_help_KC1HB96bqqHX">template</a>.</p>
|
||||
<p>The switcher will disappear as soon as a text is entered.</p>
|
||||
<img src="5_New Layout_image.png"
|
||||
width="735" height="143">
|
||||
|
||||
<p>
|
||||
<img src="5_New Layout_image.png" width="735"
|
||||
height="143">
|
||||
</p>
|
||||
<h3>Note badges</h3>
|
||||
<p>Note badges appear near the fixed note title and indicate important information
|
||||
about the note such as whether it is read-only. Some of the badges are
|
||||
@@ -63,18 +62,19 @@ class="image">
|
||||
</figure>
|
||||
<p>The following badges are available:</p>
|
||||
<ul>
|
||||
<li><strong>Read-only badge</strong>, which will be shown if the note is not
|
||||
<li data-list-item-id="e92dd6f75d874ac793f37db886606439d"><strong>Read-only badge</strong>, which will be shown if the note is not
|
||||
editable due to either automatic read-only or manual read-only. Clicking
|
||||
on the badge will temporarily edit the note (similar to the Edit <a href="#root/_help_XpOYSgsLkTJy">floating button</a>).</li>
|
||||
<li><strong>Share badge</strong>, which will indicate that the current note
|
||||
<li
|
||||
data-list-item-id="e7bbe2a9a3ddc469e9fe1c31ec7cf1a2f"><strong>Share badge</strong>, which will indicate that the current note
|
||||
is shared. The badge will also indicate if the share is on the local network
|
||||
(for the desktop application without <a class="reference-link" href="#root/_help_cbkrhQjrkKrh">Synchronization</a> set
|
||||
up) or publicly accessible (for the server).</li>
|
||||
<li><strong>Web clip badge</strong>, which will indicate if the note was clipped
|
||||
<li data-list-item-id="e2f51260db16ace77feb9eddcf987cc52"><strong>Web clip badge</strong>, which will indicate if the note was clipped
|
||||
using the <a class="reference-link" href="#root/_help_MtPxeAWVAzMg">Web Clipper</a>.
|
||||
The badge acts as a link, so it can be clicked on to navigate to the page
|
||||
or right clicked for more options.</li>
|
||||
<li><strong>Execute badge</strong>, for <a href="#root/_help_CdNpE2pqjmI6">scripts</a> or
|
||||
<li data-list-item-id="ef6bec8ce5a4f3fd249bf57a16e1e235e"><strong>Execute badge</strong>, for <a href="#root/_help_CdNpE2pqjmI6">scripts</a> or
|
||||
<a
|
||||
href="#root/_help_YKWqdJhzi2VY">saved SQL queries</a>which have an execute button or a description.</li>
|
||||
</ul>
|
||||
@@ -86,25 +86,26 @@ class="image">
|
||||
</figure>
|
||||
<p>The following sections have been made collapsible:</p>
|
||||
<ul>
|
||||
<li><em>Promoted Attributes</em>
|
||||
<li class="ck-list-marker-italic" data-list-item-id="e8add36768a67f7f5af68545beb18037f"><em>Promoted Attributes</em>
|
||||
<ul>
|
||||
<li>For full-height notes such as <a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>,
|
||||
<li data-list-item-id="e633becb9bcfb1796d6d8203ffd2ac660">For full-height notes such as <a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>,
|
||||
the promoted attributes are collapsed by default to make room.</li>
|
||||
<li>The keyboard shortcut previously used to trigger the promoted attributes
|
||||
<li
|
||||
data-list-item-id="ee7cbce0e436fd7a00a176df51f5570a4">The keyboard shortcut previously used to trigger the promoted attributes
|
||||
ribbon tab (which was no longer working) has been repurposed to toggle
|
||||
the promoted attributes instead.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><em>Edited Notes</em>, which appears for <a class="reference-link"
|
||||
<li data-list-item-id="ece11f5c363adc3938abfd8d21b2f655d"><em>Edited Notes</em>, which appears for <a class="reference-link"
|
||||
href="#root/_help_l0tKav7yLHGF">Day Notes</a> is now shown underneath the
|
||||
title.
|
||||
<ul>
|
||||
<li>Whether the section is collapsed or not depends on the choice in
|
||||
<li data-list-item-id="ef3f8aac2e5f49b4188acd62868b32020">Whether the section is collapsed or not depends on the choice in
|
||||
<a
|
||||
class="reference-link" href="#root/_help_4TIF1oA4VQRO">Options</a> → Appearance.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><em>Search Properties</em>, which appears for the full <a class="reference-link"
|
||||
<li data-list-item-id="e42393439936bb76c397de5ae6e5ecd80"><em>Search Properties</em>, which appears for the full <a class="reference-link"
|
||||
href="#root/_help_eIg8jdvaoNNd">Search</a> and <a class="reference-link"
|
||||
href="#root/_help_m523cpzocqaD">Saved Search</a>.</li>
|
||||
</ul>
|
||||
@@ -115,75 +116,78 @@ class="image">
|
||||
<p>Here's how all the different tabs that were once part of the ribbon are
|
||||
now available in the new layout:</p>
|
||||
<ul>
|
||||
<li>“Formatting toolbar” was relocated to the top of the page.
|
||||
<li data-list-item-id="e758293ceb33f07215fcf5fc4700a4e5f">“Formatting toolbar” was relocated to the top of the page.
|
||||
<ul>
|
||||
<li>Instead of having one per split, now there is a single formatting toolbar
|
||||
<li data-list-item-id="e8dded8673adf680dde8615934b3f857b">Instead of having one per split, now there is a single formatting toolbar
|
||||
per tab. This allows more space for the toolbar items.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>“Owned attributes” and “Inherited attributes” were merged and moved to
|
||||
<li data-list-item-id="e2c510fd7f19155ed0e05946acf7a4cd0">“Owned attributes” and “Inherited attributes” were merged and moved to
|
||||
the status bar region (displayed one above the other).</li>
|
||||
<li>“Basic Properties” were integrated in the <a class="reference-link"
|
||||
<li data-list-item-id="ec032e05420954455db0884651e2d1c4e">“Basic Properties” were integrated in the <a class="reference-link"
|
||||
href="#root/_help_8YBEPzcpUgxw">Note buttons</a> menu.
|
||||
<ul>
|
||||
<li>The only exception here is the Language combo box which can now be found
|
||||
<li data-list-item-id="ebcff28c9a8d07526187559ebd5faa211">The only exception here is the Language combo box which can now be found
|
||||
in the status bar (top-right of the screen).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>“File” and “Image” tabs
|
||||
<li data-list-item-id="e0b76ace8a0be9d7bb6870398b87338ab">“File” and “Image” tabs
|
||||
<ul>
|
||||
<li>The buttons were moved to the right of the note title, as dedicated entries
|
||||
<li data-list-item-id="e55913c9bba25585fbdcb2098a2977e0e">The buttons were moved to the right of the note title, as dedicated entries
|
||||
in <a class="reference-link" href="#root/_help_8YBEPzcpUgxw">Note buttons</a>.</li>
|
||||
<li>The info section has been merged into the <em>Note info</em> section of
|
||||
<li
|
||||
data-list-item-id="e749af557c7ec8307b65cebfc0b375508">The info section has been merged into the <em>Note info</em> section of
|
||||
the status bar.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Edited notes
|
||||
<li data-list-item-id="ee3556b093a5a07fd6d239c33d8cf2611">Edited notes
|
||||
<ul>
|
||||
<li>Moved underneath the title, displayed under a collapsible area and the
|
||||
<li data-list-item-id="e541470be93aa45c6495efb6cd8063e47">Moved underneath the title, displayed under a collapsible area and the
|
||||
notes are represented as badges/chips.</li>
|
||||
<li>Whether the section is expanded or collapsed depends on the “Edited Notes
|
||||
<li data-list-item-id="e16f53a81cda714958d00ca53dbb1ffa9">Whether the section is expanded or collapsed depends on the “Edited Notes
|
||||
ribbon tab will automatically open on day notes” setting from Options →
|
||||
Appearance.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Search definition tab
|
||||
<li data-list-item-id="e0fc642280dfcbc6ca7cdc995c57941f8">Search definition tab
|
||||
<ul>
|
||||
<li>Moved underneath the title under a collapsible area.</li>
|
||||
<li>Expanded by default for new searches, collapsed for saved searches.</li>
|
||||
<li data-list-item-id="e616bd30c16829e863accfcab35c41874">Moved underneath the title under a collapsible area.</li>
|
||||
<li data-list-item-id="e1c2b80776b0ffe26ae69410131c1e0f2">Expanded by default for new searches, collapsed for saved searches.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The Note map is now available in the Note actions menu.
|
||||
<li data-list-item-id="ec4b6bd664511175e82281caf113b13b8">The Note map is now available in the Note actions menu.
|
||||
<ul>
|
||||
<li>Instead of opening into a panel in the ribbon, the note map now opens
|
||||
<li data-list-item-id="ecd6bd6144cea4bc2148e6baaa940f9b0">Instead of opening into a panel in the ribbon, the note map now opens
|
||||
in a side split (similar to the in-app help).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>“Note info” tab was moved to a small (i) icon in the status bar.</li>
|
||||
<li>“Similar notes” tab
|
||||
<li data-list-item-id="ea0cd465194ced54082c799b53a1839c0">“Note info” tab was moved to a small (i) icon in the status bar.</li>
|
||||
<li
|
||||
data-list-item-id="eeb0fd706272546ff31b629917b8fcb0a">“Similar notes” tab
|
||||
<ul>
|
||||
<li>Moved to the status bar, by going to the “Note info” section and pressing
|
||||
<li data-list-item-id="eb01edf7d21f73998b99516cd46e0e321">Moved to the status bar, by going to the “Note info” section and pressing
|
||||
the button to show similar notes.</li>
|
||||
<li>Displayed as a fixed panel, similar to the attributes.</li>
|
||||
<li data-list-item-id="e3aa6b54672681d8d90795c57372f2595">Displayed as a fixed panel, similar to the attributes.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The Collection properties tab were relocated under the note title and
|
||||
<li data-list-item-id="e791313bf732b70bbf4d6f91755db2494">The Collection properties tab were relocated under the note title and
|
||||
grouped into:
|
||||
<ul>
|
||||
<li>A combo box to quickly switch between views.</li>
|
||||
<li>Individual settings for the current view in a submenu.</li>
|
||||
<li data-list-item-id="e712f0f389eab1a329a4df3ed6d2b49aa">A combo box to quickly switch between views.</li>
|
||||
<li data-list-item-id="edaba6f82803ed727489575b9c46d667c">Individual settings for the current view in a submenu.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Some smaller ribbon tabs were converted to badges that appear near the
|
||||
<li data-list-item-id="e26e827650e594f717a90c0cd94c1c3a1">Some smaller ribbon tabs were converted to badges that appear near the
|
||||
note title in the breadcrumb section:
|
||||
<ul>
|
||||
<li>Original URL indicator for clipped web pages (<code>#pageUrl</code>).</li>
|
||||
<li>SQL and script execute buttons.</li>
|
||||
<li data-list-item-id="eaf6b3ff2c30e7167a29cb58c160c55f8">Original URL indicator for clipped web pages (<code spellcheck="false">#pageUrl</code>).</li>
|
||||
<li
|
||||
data-list-item-id="ee7fc72c13a93f9e5d0edc0bba6bc0b34">SQL and script execute buttons.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<aside class="admonition note">
|
||||
<p>The ribbon keyboard shortcuts (e.g. <code>toggleRibbonTabClassicEditor</code>)
|
||||
<p>The ribbon keyboard shortcuts (e.g. <code spellcheck="false">toggleRibbonTabClassicEditor</code>)
|
||||
have been repurposed to work on the new layout, where they will toggle
|
||||
the appropriate panel.</p>
|
||||
</aside>
|
||||
@@ -192,11 +196,13 @@ class="image">
|
||||
the <a class="reference-link" href="#root/_help_8YBEPzcpUgxw">Note buttons</a> area,
|
||||
with the exception of:</p>
|
||||
<ul>
|
||||
<li>The Edit button is displayed near the note title, as a badge.</li>
|
||||
<li><em>Backlinks</em> is displayed in the status bar. When clicked, the same
|
||||
<li data-list-item-id="e68c4ff8e52782bbef0104bca4b3f972a">The Edit button is displayed near the note title, as a badge.</li>
|
||||
<li
|
||||
data-list-item-id="e45b03477a6cb8c98327562abd551967c"><em>Backlinks</em> is displayed in the status bar. When clicked, the same
|
||||
list of backlinks is displayed.</li>
|
||||
<li>Relation map zoom buttons are now part of the relation map itself.</li>
|
||||
<li>Export image to PNG/SVG are now in the Note actions menu, in the <em>Export as image</em> option.</li>
|
||||
<li data-list-item-id="e7241aa3a6ec8dd92a3debade61439d81">Relation map zoom buttons are now part of the relation map itself.</li>
|
||||
<li
|
||||
data-list-item-id="e2d44d21ae8b0d77bef0327f2aaf4e050">Export image to PNG/SVG are now in the Note actions menu, in the <em>Export as image</em> option.</li>
|
||||
</ul>
|
||||
<h3>Changes to the sidebar</h3>
|
||||
<p>The sidebar (also known as the right pane) also received some important
|
||||
@@ -204,7 +210,7 @@ class="image">
|
||||
<p>The previous iteration of the sidebar would appear contextually, depending
|
||||
on whether there are any items to be displayed. This caused occasional
|
||||
content shifts when moving between two panes in a split view. In the new
|
||||
layout, the sidebar acts more like the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> pane,
|
||||
layout, the sidebar acts more like the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> pane,
|
||||
remaining visible even if there is nothing to display.</p>
|
||||
<p>In order to toggle the sidebar, there is a new button on the top-right
|
||||
side of the screen, near the window buttons (on Windows and Linux).</p>
|
||||
|
||||
@@ -8,48 +8,48 @@
|
||||
displayed in the bottom-left of the screen.</p>
|
||||
<h2>Layout and Interaction</h2>
|
||||
<ul>
|
||||
<li>If a note or workspace is hoisted, a badge will appear on the left-most
|
||||
<li data-list-item-id="e235d35dec66a5a6e191ec5cd8af5038a">If a note or workspace is hoisted, a badge will appear on the left-most
|
||||
side.
|
||||
<ul>
|
||||
<li>Clicking on the badge will un-hoist the note/workspace.</li>
|
||||
<li data-list-item-id="e456ddbf22db13b73ffd4915431ef4edb">Clicking on the badge will un-hoist the note/workspace.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The left-most icon represents the root note, or the hoisted note or workspace.
|
||||
<li data-list-item-id="e1f0000e6ce3c61b41a8d9ee6ec991686">The left-most icon represents the root note, or the hoisted note or workspace.
|
||||
<ul>
|
||||
<li>Clicking the icon will jump to the root note.</li>
|
||||
<li>Right clicking the icon will display a menu that allows opening the note
|
||||
<li data-list-item-id="e224bf09301a82f6a58038c55bb4769e4">Clicking the icon will jump to the root note.</li>
|
||||
<li data-list-item-id="e5e1de710e5d166aa47ba54ddd46657cb">Right clicking the icon will display a menu that allows opening the note
|
||||
in a new tab, split, etc.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Each segment shows the title of a note in the current note hierarchy.
|
||||
<li data-list-item-id="e62deaf618aa52bb7062b1801cdaf5bf5">Each segment shows the title of a note in the current note hierarchy.
|
||||
<ul>
|
||||
<li>Clicking the icon will jump to that note.</li>
|
||||
<li>Right clicking will open a menu with multiple options such as opening
|
||||
<li data-list-item-id="e03cfdc5ebec9d3dce4503960e24fc693">Clicking the icon will jump to that note.</li>
|
||||
<li data-list-item-id="e2b084f07f11586bbcfb0ec85d1fe52c2">Right clicking will open a menu with multiple options such as opening
|
||||
the note in a different tab/split/window, hoisting, moving/cloning the
|
||||
note, duplicating as well as changing the color of the note.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Clicking the arrow next to each segment will reveal the child notes of
|
||||
<li data-list-item-id="ed8b04db71853f4c0453d321de96acfdf">Clicking the arrow next to each segment will reveal the child notes of
|
||||
the segment on the left.
|
||||
<ul>
|
||||
<li>Clicking on an icon will navigate to that particular note.</li>
|
||||
<li>It's also possible to create a new child note from here.</li>
|
||||
<li>The menu can optionally hide the archived notes.</li>
|
||||
<li data-list-item-id="e0c88db9aafa125a06b81d9e4ab40bf8e">Clicking on an icon will navigate to that particular note.</li>
|
||||
<li data-list-item-id="e04d705da35d36f06aa734b35e60e454a">It's also possible to create a new child note from here.</li>
|
||||
<li data-list-item-id="e07c9f1f7bd7ea872181c7c005513ed68">The menu can optionally hide the archived notes.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>If the current note is deep within a hierarchy, the segments will collapse
|
||||
<li data-list-item-id="ee49ef8ad47fbb227dc69ca8f976872aa">If the current note is deep within a hierarchy, the segments will collapse
|
||||
into a […] button in order not to occupy too much space.
|
||||
<ul>
|
||||
<li>Clicking this button will display each collapsed entry as a menu item.
|
||||
<li data-list-item-id="e1869d9d1b5a2f0c5def31172891a9b14">Clicking this button will display each collapsed entry as a menu item.
|
||||
Clicking on it will navigate to that particular note.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Right clicking on an empty space to the right of the breadcrumb (before
|
||||
<li data-list-item-id="e8d793fcdf6ddc0a15f422d5ec12b05c4">Right clicking on an empty space to the right of the breadcrumb (before
|
||||
the other status bar items) will reveal another menu that allows:
|
||||
<ul>
|
||||
<li>Toggling whether archived notes are displayed in the breadcrumb and in
|
||||
<li data-list-item-id="e4d96585f45337c4bac4182db334343eb">Toggling whether archived notes are displayed in the breadcrumb and in
|
||||
the note tree.</li>
|
||||
<li>Copying the current note path to clipboard.</li>
|
||||
<li data-list-item-id="e37105f037c1fe10fe48e428db4c7c0e7">Copying the current note path to clipboard.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -7,31 +7,32 @@
|
||||
<p>On the right side, specific sections will show depending on the type of
|
||||
the current note.</p>
|
||||
<ol>
|
||||
<li>For code notes, the language mode of the note is indicated (e.g. JavaScript,
|
||||
<li data-list-item-id="e7409c70ee461800fea112abc0cf9ae25">For code notes, the language mode of the note is indicated (e.g. JavaScript,
|
||||
plain text), as well as allowing easy switching to another mode.</li>
|
||||
<li>For text notes, the content language is displayed and can be changed,
|
||||
<li
|
||||
data-list-item-id="e02ade3f598cdd3dfda1abbcf9d55060b">For text notes, the content language is displayed and can be changed,
|
||||
thus configuring the spell-check and the right-to-left support.
|
||||
<ol>
|
||||
<li>Note that this applies to the entire note and not the selection, unlike
|
||||
<li data-list-item-id="e0e530605f6c359593162f582fc9113a1">Note that this applies to the entire note and not the selection, unlike
|
||||
some text editors.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>If a note is placed in multiple places in the tree (cloned), the number
|
||||
<li data-list-item-id="eb55ddf60b5526101fb30234178ccd4b1">If a note is placed in multiple places in the tree (cloned), the number
|
||||
of the note paths will be displayed.
|
||||
<ol>
|
||||
<li>Clicking it will reveal the full list of note paths and a button to place
|
||||
<li data-list-item-id="e9fa65cb2d50422d4d715a29108268c57">Clicking it will reveal the full list of note paths and a button to place
|
||||
it somewhere else.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>If a note has attachments, their number will be displayed.
|
||||
<li data-list-item-id="e353272924ea7c0a8817b75978e74e2f4">If a note has attachments, their number will be displayed.
|
||||
<ol>
|
||||
<li>Clicking on it will reveal the list of attachments in a new tab.</li>
|
||||
<li data-list-item-id="e36057cb3080443d03fc54f373c4811f7">Clicking on it will reveal the list of attachments in a new tab.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>If a note is linked from other text notes (backlinks), the number of backlinks
|
||||
<li data-list-item-id="ebed069f4938bd155c5e436fe92e44cea">If a note is linked from other text notes (backlinks), the number of backlinks
|
||||
will be displayed.
|
||||
<ol>
|
||||
<li>Clicking on it will show the list of notes that link to this note, as
|
||||
<li data-list-item-id="e1a3f17eeda7672abaf283f7283dd462c">Clicking on it will show the list of notes that link to this note, as
|
||||
well as an excerpt of where the note is referenced.</li>
|
||||
</ol>
|
||||
</li>
|
||||
@@ -39,13 +40,14 @@
|
||||
<p>Regardless of note type, the following items will always be displayed
|
||||
if there is a note:</p>
|
||||
<ol>
|
||||
<li>Note info, which displays:
|
||||
<li data-list-item-id="ecbd6632bd798107863b39fc23e251ae2">Note info, which displays:
|
||||
<ol>
|
||||
<li>The creation/modification date of the note.</li>
|
||||
<li>The type and MIME of the note.</li>
|
||||
<li>The note ID.</li>
|
||||
<li>An estimation of the note size of the note itself and its children.</li>
|
||||
<li>A button to show Similar notes.</li>
|
||||
<li data-list-item-id="e56c0d5f618b79a64326bc2ab739c2df4">The creation/modification date of the note.</li>
|
||||
<li data-list-item-id="e7843741f9e99763fcf5ded821f09d789">The type and MIME of the note.</li>
|
||||
<li data-list-item-id="eaf479da2bbf044be78676f7a68ded903">The note ID.</li>
|
||||
<li data-list-item-id="ebbeb7fde731354c1b92951e4d45be579">An estimation of the note size of the note itself and its children.</li>
|
||||
<li
|
||||
data-list-item-id="e0e10095682b51783b11592065b7e28a7">A button to show Similar notes.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
45
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Render Note.html
generated
vendored
45
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Render Note.html
generated
vendored
@@ -7,16 +7,13 @@
|
||||
via an attribute.</p>
|
||||
<h2>Creating a render note</h2>
|
||||
<ol>
|
||||
<li data-list-item-id="e6c172eb567596820dd7f444924e568b3">Create a <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> note
|
||||
with the HTML language, with what needs to be displayed (for example
|
||||
<code
|
||||
spellcheck="false"><p>Hello world.</p></code>).</li>
|
||||
<li data-list-item-id="e06560e53dd0ab195c0e89dc8db7aec1f">Create a <a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</li>
|
||||
<li
|
||||
data-list-item-id="e9db87d13293c732ac62d6f92d2997cb3">Assign the <code spellcheck="false">renderNote</code> <a href="#root/_help_zEY4DaJG4YT5">relation</a> to
|
||||
<li>Create a <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> note
|
||||
with the HTML language, with what needs to be displayed (for example <code><p>Hello world.</p></code>).</li>
|
||||
<li>Create a <a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</li>
|
||||
<li>Assign the <code>renderNote</code> <a href="#root/_help_zEY4DaJG4YT5">relation</a> to
|
||||
point at the previously created code note.</li>
|
||||
</ol>
|
||||
<h2>Legacy scripting using jQuery</h2>
|
||||
<h2>Dynamic content</h2>
|
||||
<p>A static HTML is generally not enough for <a class="reference-link"
|
||||
href="#root/_help_CdNpE2pqjmI6">Scripting</a>. The next step is to automatically
|
||||
change parts of the note using JavaScript.</p>
|
||||
@@ -29,46 +26,22 @@ The current date & time is <span class="date"></span></code></pr
|
||||
language. Make sure the newly created note is a direct child of the HTML
|
||||
note created previously; with the following content:</p><pre><code class="language-text-x-trilium-auto">const $dateEl = api.$container.find(".date");
|
||||
$dateEl.text(new Date());</code></pre>
|
||||
<p>Now create a render note at any place and set its <code spellcheck="false">~renderNote</code> relation
|
||||
<p>Now create a render note at any place and set its <code>~renderNote</code> relation
|
||||
to point to the HTML note. When the render note is accessed it will display:</p>
|
||||
<blockquote>
|
||||
<p><strong>Current date & time</strong>
|
||||
<br>The current date & time is Sun Apr 06 2025 15:26:29 GMT+0300 (Eastern
|
||||
European Summer Time)</p>
|
||||
</blockquote>
|
||||
<h2>Dynamic content using Preact & JSX</h2>
|
||||
<p>As a more modern alternative to jQuery, it's possible to use Preact &
|
||||
JSX to render pages. Since JSX is a superset of JavaScript, there's no
|
||||
need to provide a HTML anymore.</p>
|
||||
<p>Here are the steps to creating a simple render note:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="ee6c6868efde1b910dac6807580e2a8e2">Create a note of type <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>.</li>
|
||||
<li
|
||||
data-list-item-id="eae9b5f496cba824738734828c11448d5">
|
||||
<p>Create a child <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> note
|
||||
with JSX as the language.
|
||||
<br>As an example, use the following content:</p><pre><code class="language-text-jsx">export default function() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello world.</p>
|
||||
</>
|
||||
);
|
||||
}</code></pre>
|
||||
</li>
|
||||
<li data-list-item-id="e28812b27d5227675943ef98ab9477e67">In the parent render note, define a <code spellcheck="false">~renderNote</code> relation
|
||||
pointing to the newly created child.</li>
|
||||
<li data-list-item-id="e24cc71d55c7c8ec61014ac0d800653da">Refresh the render note and it should display a “Hello world” message.</li>
|
||||
</ol>
|
||||
<h2>Refreshing the note</h2>
|
||||
<p>It's possible to refresh the note via:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e01c4ae9c0999b2879e914c5b641215e3">The corresponding button in <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
||||
<li
|
||||
data-list-item-id="e871da2cff905b2daa180fcae27aee43b">The “Render active note” <a href="#root/_help_A9Oc6YKKc65v">keyboard shortcut</a> (not
|
||||
<li>The corresponding button in <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
||||
<li>The “Render active note” <a href="#root/_help_A9Oc6YKKc65v">keyboard shortcut</a> (not
|
||||
assigned by default).</li>
|
||||
</ul>
|
||||
<h2>Examples</h2>
|
||||
<ul>
|
||||
<li data-list-item-id="ec01930f20370b46f519bd844b67623ee"><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a> which
|
||||
<li><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a> which
|
||||
is present in the <a class="reference-link" href="#root/_help_6tZeKvSHEUiB">Demo Notes</a>.</li>
|
||||
</ul>
|
||||
@@ -1,46 +0,0 @@
|
||||
<p>For both <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a> and
|
||||
more complicated scripts, it's generally useful to split the code into
|
||||
multiple <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> notes.</p>
|
||||
<p>When a script is run, the sub-children of the script being run (or the
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>) are checked for children. If the children are Code notes
|
||||
of the corresponding type (front-end or backend) as the code being run,
|
||||
they will be evaluated as well.</p>
|
||||
<p>The collection of a script and its child notes is called a <em>bundle</em>.
|
||||
A child note inside a bundle is called a <em>module</em>.</p>
|
||||
<p>As a basic example of dependencies, consider the following note structure:</p>
|
||||
<ul>
|
||||
<li class="ck-list-marker-italic" data-list-item-id="e4c343ba6ed99b3a943049b6f0af99dd6">
|
||||
<p><em>Script with dependency</em>
|
||||
</p><pre><code class="language-application-javascript-env-frontend">api.log(MyMath.sum(2, 2));</code></pre>
|
||||
<ul>
|
||||
<li class="ck-list-marker-italic" data-list-item-id="ea27318ca64cb71cfa5eceb15f9acba75">
|
||||
<p><em>MyMath</em>
|
||||
</p><pre><code class="language-application-javascript-env-frontend">module.exports = {
|
||||
sum(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
};</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>When <em>Script with dependency</em> is run, it will detect <em>MyMath</em> as
|
||||
a submodule and provide the result of its <code spellcheck="false">module.exports</code> object
|
||||
into a global object with the same name as the note.</p>
|
||||
<aside class="admonition note">
|
||||
<p>If the note contains spaces or special characters, they will be stripped.
|
||||
For example <code spellcheck="false">My Nice Note!</code> becomes <code spellcheck="false">MyNiceNote</code>.</p>
|
||||
</aside>
|
||||
<h2>Alternative syntax</h2>
|
||||
<p>Instead of providing an object to <code spellcheck="false">module.exports</code>,
|
||||
it's also possible to add fields individually:</p><pre><code class="language-application-javascript-env-frontend">module.exports.sum = (a, b) => a + b;
|
||||
module.exports.subtract = (a, b) => a - b;</code></pre>
|
||||
<h2>Ignoring a code script from a bundle</h2>
|
||||
<p>To ignore a script from being included in a bundle (e.g. if it's unrelated
|
||||
to the parent script note), apply the <code spellcheck="false">#disableInclusion</code> label.</p>
|
||||
<h2>Sharing a module across multiple bundles</h2>
|
||||
<p>Modules can be reused across multiple scripts by simply cloning the shared
|
||||
module between two modules (see <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_IakOLONlIfGI">Cloning Notes</a>).</p>
|
||||
<p>Optionally, a separate note can be used to contain all the different reusable
|
||||
modules for an easy way to discover them.</p>
|
||||
@@ -1,167 +1,13 @@
|
||||
<p>Custom widgets are a special subset of scripts that render graphical elements
|
||||
in certain parts of the application. These can be used to add new functionality
|
||||
to the Trilium application.</p>
|
||||
<h2>Preact with JSX vs. vanilla jQuery</h2>
|
||||
<p>In older versions of Trilium, custom widgets were exclusively written
|
||||
in a combination of jQuery with Trilium's internal widget architecture
|
||||
(e.g., <code spellcheck="false">BasicWidget</code>, <code spellcheck="false">NoteContextAwareWidget</code>).</p>
|
||||
<p>Starting with v0.101.0, custom widgets can also be written in JSX using
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_KLsqhjaqh1QW">Preact</a> framework.
|
||||
Both legacy and Preact widgets have the same capabilities, with a single
|
||||
difference:</p>
|
||||
<p>It's possible to create custom widget in three possible locations where
|
||||
you can display your custom content.</p>
|
||||
<p>Positions are:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e2a6a28bcf69987db6b33b4902b913282">Preact widgets are content-sized by default whereas legacy widgets need
|
||||
<code
|
||||
spellcheck="false">this.contentSized()</code>applied in the constructor. For more information,
|
||||
see the corresponding section in <a class="reference-link" href="#root/MgibgPcfeuGz/_help_gMkgcLJ6jBkg">Troubleshooting</a>.</li>
|
||||
</ul>
|
||||
<p>Wherever possible, widget examples will be both in the legacy and Preact
|
||||
format.</p>
|
||||
<h2>Creating a custom widget</h2>
|
||||
<ol>
|
||||
<li data-list-item-id="efdec30e3ba380f9691c2955248e662aa">Create a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> note.</li>
|
||||
<li
|
||||
data-list-item-id="e6ac401b11f885fbf6ee2eb486e2fb4db">Set the language to:
|
||||
<ol>
|
||||
<li data-list-item-id="ea981b36d647bb2020c63b6eb8888ffcb">JavaScript (frontend) for legacy widgets using jQuery.</li>
|
||||
<li data-list-item-id="e27097e820e8b18d5539035afc4f91cf6">JSX for Preact widgets. You might need to go to Options → Code to enable
|
||||
the language first.</li>
|
||||
</ol>
|
||||
<li><code>left-pane</code>
|
||||
</li>
|
||||
<li><code>center-pane</code>
|
||||
</li>
|
||||
<li><code>note-detail-pane</code> - located within <code>center-pane</code>,
|
||||
but specific to note (split)</li>
|
||||
<li><code>right-pane</code>
|
||||
</li>
|
||||
<li data-list-item-id="e94ba32049422e310a2322f85bb37ab85">Apply the <code spellcheck="false">#widget</code> <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_HI6GBBIduIgv">label</a>.</li>
|
||||
</ol>
|
||||
<h2>Getting started with a simple example</h2>
|
||||
<p>Let's start by creating a widget that shows a message near the content
|
||||
area. Follow the previous section to create a code note, and use the following
|
||||
content.</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Legacy</th>
|
||||
<th style="width:50%;">Preact (v0.101.0+)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<span>Center pane</span>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new HelloNoteDetail();</code></pre>
|
||||
</td>
|
||||
<td style="vertical-align:top;"><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "center-pane",
|
||||
render: () => <span>Center pane from Preact.</span>
|
||||
});</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p><a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_s8alTXmpFR61">Refresh the application</a> and
|
||||
the widget should appear underneath the content area.</p>
|
||||
<h2>Widget location (parent widget)</h2>
|
||||
<p>A widget can be placed in one of the following sections of the applications:</p>
|
||||
<figure
|
||||
class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:15.59%;">
|
||||
<col style="width:30.42%;">
|
||||
<col style="width:16.68%;">
|
||||
<col style="width:37.31%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Value for <code spellcheck="false">parentWidget</code>
|
||||
</th>
|
||||
<th>Description</th>
|
||||
<th>Sample widget</th>
|
||||
<th>Special requirements</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><code spellcheck="false">left-pane</code>
|
||||
</th>
|
||||
<td>Appears within the same pane that holds the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</td>
|
||||
<td>Same as above, with only a different <code spellcheck="false">parentWidget</code>.</td>
|
||||
<td>None.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><code spellcheck="false">center-pane</code>
|
||||
</th>
|
||||
<td>In the content area. If a split is open, the widget will span all of the
|
||||
splits.</td>
|
||||
<td>See example above.</td>
|
||||
<td>None.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><code spellcheck="false">note-detail-pane</code>
|
||||
</th>
|
||||
<td>
|
||||
<p>In the content area, inside the note detail area. If a split is open,
|
||||
the widget will be contained inside the split.</p>
|
||||
<p>This is ideal if the widget is note-specific.</p>
|
||||
</td>
|
||||
<td><a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/MgibgPcfeuGz/_help_GhurYZjh8e1V">Note context aware widget</a>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li data-list-item-id="ec06332efcc3039721606c052f0d913fa">The widget must export a <code spellcheck="false">class</code> and not an
|
||||
instance of the class (e.g. <code spellcheck="false">no new</code>) because
|
||||
it needs to be multiplied for each note, so that splits work correctly.</li>
|
||||
<li
|
||||
data-list-item-id="e8da690a2a8df148f6b5fc04ba1611688">Since the <code spellcheck="false">class</code> is exported instead of an
|
||||
instance, the <code spellcheck="false">parentWidget</code> getter must be
|
||||
<code
|
||||
spellcheck="false">static</code>, otherwise the widget is ignored.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><code spellcheck="false">right-pane</code>
|
||||
</th>
|
||||
<td>In the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_RnaPdbciOfeq">Right Sidebar</a>,
|
||||
as a dedicated section.</td>
|
||||
<td><a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/MgibgPcfeuGz/_help_M8IppdwVHSjG">Right pane widget</a>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li data-list-item-id="efe008d361e224f422582552648e1afe7">Although not mandatory, it's best to use a <code spellcheck="false">RightPanelWidget</code> instead
|
||||
of a <code spellcheck="false">BasicWidget</code> or a <code spellcheck="false">NoteContextAwareWidget</code>.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>To position the widget somewhere else, just change the value passed to
|
||||
<code
|
||||
spellcheck="false">get parentWidget()</code>for legacy widgets or the <code spellcheck="false">parent</code> field
|
||||
for Preact. Do note that some positions such as <code spellcheck="false">note-detail-pane</code> and
|
||||
<code
|
||||
spellcheck="false">right-pane</code>have special requirements that need to be accounted for
|
||||
(see the table above).</p>
|
||||
<h2>Launch bar widgets</h2>
|
||||
<p>Launch bar widgets are similar to <em>Custom widgets</em> but are specific
|
||||
to the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.
|
||||
See <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_4Gn3psZKsfSm">Launch Bar Widgets</a> for
|
||||
more information.</p>
|
||||
<h2>Custom position</h2>
|
||||
<p> </p>
|
||||
@@ -1,22 +1,9 @@
|
||||
<h2>Classic widgets</h2>
|
||||
<p>In <code spellcheck="false">doRender()</code>:<span class="footnote-reference"
|
||||
data-footnote-reference="" data-footnote-index="1" data-footnote-id="1saoftmefpp"
|
||||
role="doc-noteref" id="fnref1saoftmefpp"><sup><a href="#fn1saoftmefpp">[1]</a></sup></span>
|
||||
</p><pre><code class="language-text-x-trilium-auto">this.cssBlock(`#my-widget {
|
||||
<p>In <code>doRender()</code>:</p><pre><code class="language-text-x-trilium-auto">this.cssBlock(`#my-widget {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 60px;
|
||||
z-index: 1;
|
||||
}`);</code></pre>
|
||||
<h2>Preact widgets</h2>
|
||||
<p>See the dedicated page: <a class="reference-link" href="#root/KLsqhjaqh1QW/_help_Sg9GrCtyftZf">CSS</a>.</p>
|
||||
<ol
|
||||
class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
|
||||
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
|
||||
data-footnote-id="1saoftmefpp" role="doc-endnote" id="fn1saoftmefpp"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="1saoftmefpp"><sup><strong><a href="#fnref1saoftmefpp">^</a></strong></sup></span>
|
||||
<div
|
||||
class="footnote-content" data-footnote-content="">
|
||||
<p>Reference: <a href="https://trilium.rocks/X7pxYpiu0lgU">https://trilium.rocks/X7pxYpiu0lgU</a> </p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
}`)</code></pre>
|
||||
<hr>
|
||||
<p>Reference: <a href="https://trilium.rocks/X7pxYpiu0lgU">https://trilium.rocks/X7pxYpiu0lgU</a>
|
||||
</p>
|
||||
@@ -1,48 +0,0 @@
|
||||
<p>Note context-aware widgets are a different type of widget which automatically
|
||||
react to changes in the current note.</p>
|
||||
<p>Important aspects:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ebe74655683475b6a16e53832e6b2c47a">The widget must export a <code spellcheck="false">class</code> and not an
|
||||
instance of the class (e.g. <code spellcheck="false">no new</code>) because
|
||||
it needs to be multiplied for each note, so that splits work correctly.</li>
|
||||
<li
|
||||
data-list-item-id="edf648bc7ea3325255223d28f0543e5b9">Since the <code spellcheck="false">class</code> is exported instead of an
|
||||
instance, the <code spellcheck="false">parentWidget</code> getter must be
|
||||
<code
|
||||
spellcheck="false">static</code>, otherwise the widget is ignored.</li>
|
||||
</ul>
|
||||
<h2>Example displaying the current note title</h2>
|
||||
<p>This is a note context-aware widget that simply displays the name the
|
||||
current note. </p>
|
||||
<h3>Classic example</h3><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.NoteContextAwareWidget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<div>");
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$widget.text("Current note: " + note.title);
|
||||
}
|
||||
|
||||
static get parentWidget() { return "note-detail-pane" }
|
||||
get position() { return 10 }
|
||||
|
||||
}
|
||||
|
||||
module.exports = HelloNoteDetail;</code></pre>
|
||||
<h3>Preact (v0.101.0+)</h3><pre><code class="language-text-x-trilium-auto">import { defineWidget, useNoteContext, useNoteProperty } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "note-detail-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const { note } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title");
|
||||
return <span>Current note JSX: {title}</span>;
|
||||
}
|
||||
});</code></pre>
|
||||
@@ -1,23 +1,23 @@
|
||||
<h2>Key highlights</h2>
|
||||
<ul>
|
||||
<li data-list-item-id="e05167a939f3b6c8083cf62d4f8108b4b"><code spellcheck="false">doRender</code> must not be overridden, instead
|
||||
<li data-list-item-id="e08ebed8d78d0359c71a72be31cd95074"><code spellcheck="false">doRender</code> must not be overridden, instead
|
||||
<code
|
||||
spellcheck="false">doRenderBody()</code>has to be overridden.
|
||||
<ul>
|
||||
<li data-list-item-id="eee44709b64b220f4af124ff1458a61e8"><code spellcheck="false">doRenderBody</code> can optionally be <code spellcheck="false">async</code>.</li>
|
||||
<li data-list-item-id="e34a4e9826ab89e0ce8ded8786a803f3a"><code spellcheck="false">doRenderBody</code> can optionally be <code spellcheck="false">async</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e94b7be0f1d366c502703ff3a5fc3bfb1"><code spellcheck="false">parentWidget()</code> must be set to <code spellcheck="false">“rightPane”</code>.</li>
|
||||
<li data-list-item-id="e24bc184054a9c87a6c7695bb65c8d30b"><code spellcheck="false">parentWidget()</code> must be set to <code spellcheck="false">“rightPane”</code>.</li>
|
||||
<li
|
||||
data-list-item-id="ea56b04dbcf63573acbde90d990137829"><code spellcheck="false">widgetTitle()</code> getter can optionally be
|
||||
data-list-item-id="e581cbed615c9be4f249274c5c51c6a18"><code spellcheck="false">widgetTitle()</code> getter can optionally be
|
||||
overriden, otherwise the widget will be displayed as “Untitled widget”.</li>
|
||||
</ul>
|
||||
<h2>Example for new layout</h2>
|
||||
<aside class="admonition important">
|
||||
<p>This section addresses example that are tailored for the <a class="reference-link"
|
||||
href="#root/_help_IjZS7iK5EXtb">New Layout</a> (available starting with
|
||||
v0.101.0) where the right pane widget/sidebar is no longer shown or hidden
|
||||
based on the widgets it has. </p>
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a> (available
|
||||
starting with v0.101.0) where the right pane widget/sidebar is no longer
|
||||
shown or hidden based on the widgets it has. </p>
|
||||
</aside>
|
||||
<h3>Title widget</h3>
|
||||
<p>This is an example of a context-aware widget which displays the title
|
||||
@@ -41,8 +41,7 @@
|
||||
module.exports = new NoteTitleWidget();</code></pre>
|
||||
<h3>Clock</h3>
|
||||
<p>A simple widget which will show the current time, as an example on how
|
||||
to dynamically change the content of the widget periodically.</p>
|
||||
<h3>Legacy widget</h3><pre><code class="language-text-x-trilium-auto">const template = `<div></div>`;
|
||||
to dynamically change the content of the widget periodically.</p><pre><code class="language-text-x-trilium-auto">const template = `<div></div>`;
|
||||
|
||||
class ToDoListWidget extends api.RightPanelWidget {
|
||||
|
||||
@@ -61,26 +60,6 @@ class ToDoListWidget extends api.RightPanelWidget {
|
||||
}
|
||||
|
||||
module.exports = new ToDoListWidget();</code></pre>
|
||||
<h3>Preact widget</h3><pre><code class="language-text-x-trilium-auto">import { defineWidget, RightPanelWidget, useEffect, useState } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "right-pane",
|
||||
position: 1,
|
||||
render() {
|
||||
const [ time, setTime ] = useState();
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(new Date().toLocaleString());
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
return (
|
||||
<RightPanelWidget id="clock-jsx" title="Clock (JSX)">
|
||||
<p>The time is: {time}</p>
|
||||
</RightPanelWidget>
|
||||
);
|
||||
}
|
||||
});</code></pre>
|
||||
<h2>Example for old layout</h2>
|
||||
<p>Here's a widget that displays a basic message ("Hi"):</p><pre><code class="language-text-x-trilium-auto">const template = `<div>Hi</div>`;
|
||||
|
||||
@@ -110,7 +89,7 @@ this.triggerCommand("reEvaluateRightPaneVisibility");</code></pre>
|
||||
<p>By default, the sidebar items are displayed in the order they are found
|
||||
by the application when searching for <code spellcheck="false">#widget</code> notes.</p>
|
||||
<p>It is possible to make a widget appear higher or lower up, by adjusting
|
||||
its <code spellcheck="false">position</code> property:</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.RightPanelWidget {
|
||||
its <code spellcheck="false">position</code> property:</p><pre><code class="language-text-x-diff">class MyWidget extends api.RightPanelWidget {
|
||||
|
||||
+ get position() { return 20 };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<h2>Why is my widget clipped by other UI elements</h2>
|
||||
<p>For performance and layout reasons, the size of widgets in Trilium is
|
||||
independent from its children. At CSS level, this means that the widget
|
||||
container has <code>contain: size</code> applied to it.</p>
|
||||
container has <code spellcheck="false">contain: size</code> applied to it.</p>
|
||||
<p>This works well if the widget has a fixed size (or based on its parent
|
||||
container), however to make a widget resize to fit its content, apply the
|
||||
following change:</p><pre><code class="language-text-x-diff">class MyWidget extends api.RightPanelWidget {
|
||||
@@ -12,4 +12,5 @@
|
||||
+ }
|
||||
|
||||
}</code></pre>
|
||||
<p>Alternatively apply <code>contain: none</code> to its CSS.</p>
|
||||
<p>Alternatively apply <code spellcheck="false">contain: none</code> to its
|
||||
CSS.</p>
|
||||
@@ -16,17 +16,17 @@
|
||||
module.exports = new MyWidget();</code></pre>
|
||||
<p>To implement this widget:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="e9927fa1c4b393854487094afd914ece9">Create a new <code spellcheck="false">JS Frontend</code> note in Trilium
|
||||
and paste in the code above.</li>
|
||||
<li data-list-item-id="e3741fe167b7dccae0081cc0d1a326a5d">Assign the <code spellcheck="false">#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
|
||||
<li>Create a new <code>JS Frontend</code> note in Trilium and paste in the code
|
||||
above.</li>
|
||||
<li>Assign the <code>#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
|
||||
the <a href="#root/_help_BFs8mudNFgCS">note</a>.</li>
|
||||
<li data-list-item-id="e300febf71f84c3be97fc8556182ea124">Restart Trilium or reload the window.</li>
|
||||
<li>Restart Trilium or reload the window.</li>
|
||||
</ol>
|
||||
<p>To verify that the widget is working, open the developer tools (<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd>)
|
||||
and run <code spellcheck="false">document.querySelector("#my-widget")</code>.
|
||||
If the element is found, the widget is functioning correctly. If <code spellcheck="false">undefined</code> is
|
||||
and run <code>document.querySelector("#my-widget")</code>. If the element
|
||||
is found, the widget is functioning correctly. If <code>undefined</code> is
|
||||
returned, double-check that the <a href="#root/_help_BFs8mudNFgCS">note</a> has
|
||||
the <code spellcheck="false">#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a>.</p>
|
||||
the <code>#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a>.</p>
|
||||
<h3>Step 2: Adding an UI Element</h3>
|
||||
<p>Next, let's improve the widget by adding a button to it.</p><pre><code class="language-text-x-trilium-auto">const template = `<div id="my-widget"><button>Click Me!</button></div>`;
|
||||
|
||||
@@ -46,9 +46,7 @@ module.exports = new MyWidget();</code></pre>
|
||||
<h3>Step 3: Styling the Widget</h3>
|
||||
<p>To make the button more visually appealing and position it correctly,
|
||||
we'll apply some custom styling. Trilium includes <a href="https://boxicons.com">Box Icons</a>,
|
||||
which we'll use to replace the button text with an icon. For example the
|
||||
<code
|
||||
spellcheck="false">bx bxs-magic-wand</code>icon.</p>
|
||||
which we'll use to replace the button text with an icon. For example the <code>bx bxs-magic-wand</code> icon.</p>
|
||||
<p>Here's the updated template:</p><pre><code class="language-text-x-trilium-auto">const template = `<div id="my-widget"><button class="tree-floating-button bx bxs-magic-wand tree-settings-button"></button></div>`;</code></pre>
|
||||
<p>Next, we'll adjust the button's position using CSS:</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.BasicWidget {
|
||||
get position() { return 1; }
|
||||
@@ -71,8 +69,7 @@ module.exports = new MyWidget();</code></pre>
|
||||
of the left pane, alongside other action buttons.</p>
|
||||
<h3>Step 4: Adding User Interaction</h3>
|
||||
<p>Let’s make the button interactive by showing a message when it’s clicked.
|
||||
We'll use the <code spellcheck="false">api.showMessage</code> method from
|
||||
the <a href="#root/_help_GLks18SNjxmC">Script API</a>.</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.BasicWidget {
|
||||
We'll use the <code>api.showMessage</code> method from the <a href="#root/_help_GLks18SNjxmC">Script API</a>.</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.BasicWidget {
|
||||
get position() { return 1; }
|
||||
get parentWidget() { return "left-pane"; }
|
||||
|
||||
@@ -90,8 +87,17 @@ module.exports = new MyWidget();</code></pre>
|
||||
}
|
||||
|
||||
module.exports = new MyWidget();</code></pre>
|
||||
<p>For the list of possible values for <code spellcheck="false">parentWidget()</code>,
|
||||
see <a class="reference-link" href="#root/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>. </p>
|
||||
<p><code>parentWidget()</code> can be given the following values:</p>
|
||||
<ul>
|
||||
<li><code>left-pane</code> - This renders the widget on the left side of the
|
||||
screen where the note tree lives.</li>
|
||||
<li><code>center-pane</code> - This renders the widget in the center of the
|
||||
layout in the same location that notes and splits appear.</li>
|
||||
<li><code>note-detail-pane</code> - This renders the widget <em>with</em> the
|
||||
note in the center pane. This means it can appear multiple times with splits.</li>
|
||||
<li><code>right-pane</code> - This renders the widget to the right of any opened
|
||||
notes.</li>
|
||||
</ul>
|
||||
<p><a href="#root/_help_s8alTXmpFR61">Reload</a> the application one last time.
|
||||
When you click the button, a "Hello World!" message should appear, confirming
|
||||
that your widget is fully functional.</p>
|
||||
@@ -5,7 +5,6 @@
|
||||
<p>This is an example of a note context-aware widget, which reacts to the
|
||||
currently opened note and refreshes automatically as the user navigates
|
||||
through the notes.</p>
|
||||
<h2>Legacy widget</h2>
|
||||
<p>In this example, the title of the note is displayed. It works best on
|
||||
the <a href="#root/_help_x0JgW8UqGXvq">horizontal layout</a>.</p><pre><code class="language-application-javascript-env-backend">const TPL = `\
|
||||
<div style="
|
||||
@@ -30,18 +29,3 @@ class NoteTitleWidget extends api.NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
module.exports = new NoteTitleWidget();</code></pre>
|
||||
<h2>Preact widget (v0.101.0+)</h2><pre><code class="language-text-jsx">import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
|
||||
|
||||
export default defineLauncherWidget({
|
||||
render: () => {
|
||||
const { note } = useActiveNoteContext();
|
||||
return <div style={{
|
||||
display: "flex",
|
||||
height: "53px",
|
||||
width: "fit-content",
|
||||
fontSize: "0.75em",
|
||||
alignItems: "center",
|
||||
flexShrink: 0
|
||||
}}>{note?.title}</div>;
|
||||
}
|
||||
});</code></pre>
|
||||
@@ -1,64 +0,0 @@
|
||||
<p>Since v0.101.0, Trilium integrates Preact for front-end scripting, including
|
||||
support for JSX.</p>
|
||||
<p>Preact can be used for:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ecaa7ce1d629d04e22a9c9e5de7bbba9d"><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>,
|
||||
where a JSX code note is used instead of a HTML one.</li>
|
||||
<li data-list-item-id="ebec1c35ba18bb42ce91497eae106fad6"><a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
where JSX can be used to replace the old, jQuery-based mechanism.</li>
|
||||
</ul>
|
||||
<p>To get started, the first step is to enable JSX in the list of Code languages.
|
||||
Go to Options → Code Notes and check the “JSX” language. Afterwards, proceed
|
||||
with the documentation in either <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a> or
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>, which will both have a section on how to use the new
|
||||
Preact integration.</p>
|
||||
<aside class="admonition important">
|
||||
<p>The documentation assumes prior knowledge with React or Preact. As a starting
|
||||
point, consider the <a href="https://www.freecodecamp.org/learn/front-end-development-libraries-v9/">FreeCodeCamp course on Front End Development Libraries</a> or
|
||||
the <a href="https://preactjs.com/tutorial/">Preact Tutorial</a>.</p>
|
||||
</aside>
|
||||
<h2>Import/exports</h2>
|
||||
<p>When using Preact with JSX, there is a special syntax which provides ES-like
|
||||
imports. This <code spellcheck="false">import</code> syntax makes way for
|
||||
a more intuitive that doesn't make use of global objects and paves the
|
||||
way for better auto-completion support that might be introduced in the
|
||||
future. </p>
|
||||
<h3>API imports</h3>
|
||||
<p>Instead of:</p><pre><code class="language-text-jsx">api.showMessage("Hello");</code></pre>
|
||||
<p>the JSX version looks like this:</p><pre><code class="language-text-jsx">import { showMessage } from "trilium:api";
|
||||
showMessage("hello");</code></pre>
|
||||
<h3>Preact API imports (hooks, components)</h3>
|
||||
<p>There's a new <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/_help_GLks18SNjxmC">Script API</a> dedicated
|
||||
to Preact, which provides shared components that are also used by Trilium
|
||||
internally as well as hooks, for example.</p><pre><code class="language-text-jsx">import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");</code></pre>
|
||||
<h3>Exporting</h3>
|
||||
<p>JSX notes can export a component for use in <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a> or
|
||||
for <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/KLsqhjaqh1QW/_help_Bqde6BvPo05g">Component libraries</a>: </p><pre><code class="language-text-jsx">export default function() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello world.</p>
|
||||
</>
|
||||
);
|
||||
}</code></pre>
|
||||
<h3>Import/export are not required</h3>
|
||||
<p>These imports are syntactic sugar meant to replace the usage for the
|
||||
<code
|
||||
spellcheck="false">api</code>global object (see <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/_help_GLks18SNjxmC">Script API</a>). </p>
|
||||
<aside
|
||||
class="admonition note">
|
||||
<p>The <code spellcheck="false">import</code> and <code spellcheck="false">export</code> syntax
|
||||
work only for JSX notes. Standard/jQuery code notes still need to use the
|
||||
<code
|
||||
spellcheck="false">api</code>global and <code spellcheck="false">module.exports</code>.</p>
|
||||
</aside>
|
||||
<h2>Under the hood</h2>
|
||||
<p>Unlike JavaScript, JSX requires pre-processing to turn it into JavaScript
|
||||
(just like TypeScript). To do so, Trilium uses <a href="https://github.com/alangpierce/sucrase">Sucrase</a>,
|
||||
a JavaScript library which processes the JSX to pure JavaScript. The processing
|
||||
is done each time a script is run (for widgets this happens at every program
|
||||
startup). If you notice any performance degradation due to long compilation,
|
||||
consider <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">reporting the issue</a> to
|
||||
us.</p>
|
||||
@@ -1,44 +0,0 @@
|
||||
<figure class="image image_resized" style="width:54.58%;">
|
||||
<img style="aspect-ratio:896/712;" src="Built-in components_image.png"
|
||||
width="896" height="712">
|
||||
<figcaption>A partial screenshot from the Widget showcase example (see below).</figcaption>
|
||||
</figure>
|
||||
<p>Trilium comes with its own set of Preact components, some of which are
|
||||
also available to <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a> and
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>.</p>
|
||||
<p>To use these components, simply import them from <code spellcheck="false">trilium:preact</code>:</p><pre><code class="language-text-jsx">import { ActionButton, Button, LinkButton } from "trilium:preact";</code></pre>
|
||||
<p>and then use them:</p><pre><code class="language-text-jsx">export default function MyRenderNote() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Buttons</h2>
|
||||
<div style={{ display: "flex", gap: "1em", alignItems: "center" }}>
|
||||
<ActionButton icon="bx bx-rocket" text="Action button" onClick={onClick} />
|
||||
<Button icon="bx bx-rocket" text="Simple button" onClick={onClick} />
|
||||
<LinkButton text="Link button" onClick={onClick} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}</code></pre>
|
||||
<h2>Widget showcase</h2>
|
||||
<aside class="admonition tip">
|
||||
<p>Starting with v0.101.0, the widget showcase is also available in the
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/wX4HbRucYSDD/_help_6tZeKvSHEUiB">Demo Notes</a>.</p>
|
||||
</aside>
|
||||
<p>This is a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a> example
|
||||
with JSX that shows most of the built-in components that are accessible
|
||||
to custom widgets and JSX render notes.</p>
|
||||
<p>To use it, simply:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="ef2da471f61429e4755fdfee1301299d8">Create a render note.</li>
|
||||
<li data-list-item-id="e2e1475eb5099d3df20de96997712a9dc">Create a child code note of JSX type with the content of this file:
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/KLsqhjaqh1QW/RSssb9S3xgSr/_help_i9B4IW7b6V6z">Widget showcase</a>
|
||||
</li>
|
||||
<li data-list-item-id="ed0b442c37c1db5287d95d8d982f025d9">Set the <code spellcheck="false">~renderNote</code> relation of the parent
|
||||
note to the child note.</li>
|
||||
<li data-list-item-id="e62386a552bb441bcb830285f5487868b">Refresh the render note to see the results.</li>
|
||||
</ol>
|
||||
@@ -1,189 +0,0 @@
|
||||
import {
|
||||
ActionButton, Button, LinkButton,
|
||||
Admonition, Collapsible,
|
||||
FormCheckbox, FormDropdownList, FormFileUploadButton, FormGroup, FormRadioGroup, FormTextArea,
|
||||
FormTextBox, FormToggle, Slider, RawHtml, LoadingSpinner, Icon,
|
||||
Dropdown, FormListItem, FormDropdownDivider, FormDropdownSubmenu,
|
||||
NoteAutocomplete, NoteLink, Modal,
|
||||
CKEditor,
|
||||
useEffect, useState
|
||||
} from "trilium:preact";
|
||||
import { showMessage } from "trilium:api";
|
||||
|
||||
export default function() {
|
||||
const [ time, setTime ] = useState();
|
||||
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam accumsan eu odio non gravida. Pellentesque ornare, arcu condimentum molestie dignissim, nibh turpis ultrices elit, eget elementum nunc erat at erat. Maecenas vehicula consectetur elit, nec fermentum elit venenatis eu.";
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setTime(new Date().toLocaleString()), 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20, display: "flex", flexDirection: "column", gap: "1em" }}>
|
||||
<h1>Widget showcase</h1>
|
||||
|
||||
<Buttons />
|
||||
<Admonition type="note">
|
||||
<strong>Admonition</strong><br />
|
||||
{lorem}
|
||||
</Admonition>
|
||||
|
||||
<Collapsible title="Collapsible" initiallyExpanded>
|
||||
{lorem}
|
||||
</Collapsible>
|
||||
|
||||
<FormElements />
|
||||
<NoteElements />
|
||||
<ModalSample />
|
||||
<DropdownSample />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Buttons() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Buttons</h2>
|
||||
<div style={{ display: "flex", gap: "1em", alignItems: "center" }}>
|
||||
<ActionButton icon="bx bx-rocket" text="Action button" onClick={onClick} />
|
||||
<Button icon="bx bx-rocket" text="Simple button" onClick={onClick} />
|
||||
<LinkButton text="Link button" onClick={onClick} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FormElements() {
|
||||
const [ checkboxChecked, setCheckboxChecked ] = useState(false);
|
||||
const [ dropdownValue, setDropdownValue ] = useState("key-1");
|
||||
const [ radioGroupValue, setRadioGroupValue ] = useState("key-1");
|
||||
const [ sliderValue, setSliderValue ] = useState(50);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Form elements</h2>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1em" }}>
|
||||
<FormGroup name="checkbox" label="Checkbox">
|
||||
<FormCheckbox label="Checkbox" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
|
||||
</FormGroup>
|
||||
<FormGroup name="toggle" label="Toggle">
|
||||
<FormToggle switchOnName="Off" switchOffName="On" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
|
||||
</FormGroup>
|
||||
<FormGroup name="dropdown" label="Dropdown">
|
||||
<FormDropdownList
|
||||
values={[
|
||||
{ key: "key-1", name: "First item" },
|
||||
{ key: "key-2", name: "Second item" },
|
||||
{ key: "key-3", name: "Third item" },
|
||||
]}
|
||||
currentValue={dropdownValue} onChange={setDropdownValue}
|
||||
keyProperty="key" titleProperty="name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="radio-group" label="Radio group">
|
||||
<FormRadioGroup
|
||||
values={[
|
||||
{ value: "key-1", label: "First item" },
|
||||
{ value: "key-2", label: "Second item" },
|
||||
{ value: "key-3", label: "Third item" },
|
||||
]}
|
||||
currentValue={radioGroupValue} onChange={setRadioGroupValue}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="text-box" label="Text box">
|
||||
<FormTextBox
|
||||
placeholder="Type something..."
|
||||
currentValue="" onChange={(newValue) => {}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="text-area" label="Text area">
|
||||
<FormTextArea
|
||||
placeholder="Type something bigger..."
|
||||
currentValue="" onChange={(newValue) => {}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="slider" label="Slider">
|
||||
<Slider
|
||||
min={1} max={100}
|
||||
value={sliderValue} onChange={setSliderValue}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="file-upload" label="File upload">
|
||||
<FormFileUploadButton
|
||||
text="Upload"
|
||||
onChange={(files) => {
|
||||
const file = files?.[0];
|
||||
if (!file) return;
|
||||
showMessage(`Got file "${file.name}" of size ${file.size} B and type ${file.type}.`);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="icon" label="Icon">
|
||||
<Icon icon="bx bx-smile" />
|
||||
</FormGroup>
|
||||
<FormGroup name="loading-spinner" label="Loading spinner">
|
||||
<LoadingSpinner />
|
||||
</FormGroup>
|
||||
<FormGroup name="raw-html" label="Raw HTML">
|
||||
<RawHtml html="<strong>Hi</strong> <em>there</em>" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NoteElements() {
|
||||
const [ noteId, setNoteId ] = useState("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Note elements</h2>
|
||||
|
||||
<FormGroup name="note-autocomplete" label="Note autocomplete">
|
||||
<NoteAutocomplete
|
||||
placeholder="Select a note"
|
||||
noteId={noteId} noteIdChanged={setNoteId}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup name="note-link" label="Note link">
|
||||
{noteId
|
||||
? <NoteLink notePath={noteId} showNoteIcon />
|
||||
: <span>Select a note first</span>}
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalSample() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Modal</h2>
|
||||
<Button text="Open modal" onClick={() => setShown(true)} />
|
||||
<Modal title="Modal title" size="md" show={shown} onHidden={() => setShown(false)}>
|
||||
Modal goes here.
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownSample() {
|
||||
return (
|
||||
<>
|
||||
<h2>Dropdown menu</h2>
|
||||
<Dropdown text="Dropdown" hideToggleArrow>
|
||||
<FormListItem icon="bx bx-cut">Cut</FormListItem>
|
||||
<FormListItem icon="bx bx-copy">Copy</FormListItem>
|
||||
<FormListItem icon="bx bx-paste">Paste</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
<FormDropdownSubmenu title="Submenu">
|
||||
<FormListItem>More items</FormListItem>
|
||||
</FormDropdownSubmenu>
|
||||
</Dropdown>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
@@ -1,19 +0,0 @@
|
||||
<h2>Inline styles</h2><pre><code class="language-text-jsx"><div style={{
|
||||
|
||||
display: "flex",
|
||||
|
||||
height: "53px",
|
||||
|
||||
width: "fit-content",
|
||||
|
||||
fontSize: "0.75em",
|
||||
|
||||
alignItems: "center",
|
||||
|
||||
flexShrink: 0
|
||||
|
||||
}}>/* [...] */</div></code></pre>
|
||||
<h2>Custom CSS file</h2>
|
||||
<p>Simply create a <a class="reference-link" href="#root/pOsGYCXsbNQG/pKK96zzmvBGf/_help_AlhDUqhENtH7">Custom app-wide CSS</a>.
|
||||
Make sure the class names are unique enough to not intersect with other
|
||||
UI elements, consider adding a prefix (e.g. <code spellcheck="false">x-mywidget-</code>).</p>
|
||||
@@ -1,55 +0,0 @@
|
||||
<p>Using the concept of <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/wqXwKJl6VpNk/_help_hA834UaHhSNn">Script bundles</a>,
|
||||
it's possible to create components that are shared for multiple widgets
|
||||
or <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>.</p>
|
||||
<h2>Exporting a single component</h2>
|
||||
<p>This is generally useful for big components.</p>
|
||||
<p>Here's an example child hierarchy using <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>: </p>
|
||||
<ul>
|
||||
<li data-list-item-id="e3f972228e04ea1f4801d76cb1145fe54"><em>My render note</em>
|
||||
<br>Note type: Render Note
|
||||
<br>Link <code spellcheck="false">~renderNote</code> to the child note (<em>Render note with subcomponents</em>)
|
||||
<ul>
|
||||
<li data-list-item-id="eed4ff5d1824e4f8cf1a05feef1117b91">
|
||||
<p><em>Render note with subcomponents</em>
|
||||
<br>Type: JSX</p><pre><code class="language-application-javascript-env-frontend">export default function() {
|
||||
return (
|
||||
<MyComponent />
|
||||
);
|
||||
}</code></pre>
|
||||
<ul>
|
||||
<li data-list-item-id="e9fdf8ed08c3577977dc48be1bc075f0e">
|
||||
<p><em>MyComponent</em>
|
||||
<br>Type: Code / JSX</p><pre><code class="language-application-javascript-env-frontend">export default function MyComponent() {
|
||||
return <p>Hi</p>;
|
||||
}</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Multiple components per note</h2>
|
||||
<p>To export multiple components, use the <code spellcheck="false">export</code> keyword
|
||||
next to each of the function components.</p>
|
||||
<p>Here's how a sub-note called <code spellcheck="false">MyComponents</code> would
|
||||
look like:</p><pre><code class="language-application-javascript-env-frontend">export function MyFirstComponent() {
|
||||
return <p>First</p>;
|
||||
}
|
||||
|
||||
export function MySecondComponent() {
|
||||
return <p>Bar</p>;
|
||||
}</code></pre>
|
||||
<p>Then in its parent note:</p><pre><code class="language-application-javascript-env-frontend">const { MyFirstComponent, MySecondComponent } = MyComponents;
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
<MyFirstComponent />
|
||||
<MySecondComponent />
|
||||
</>
|
||||
);
|
||||
}</code></pre>
|
||||
<p>Alternatively, it's also possible to use the components directly without
|
||||
assigning them to a <code spellcheck="false">const</code> first:</p><pre><code class="language-application-javascript-env-frontend"><MyComponents.MyFirstComponent />
|
||||
<MyComponents.MySecondComponent /></code></pre>
|
||||
@@ -1,35 +0,0 @@
|
||||
<h2>Standard Preact hooks</h2>
|
||||
<p>All standard Preact hooks are available as an import in <code spellcheck="false">trilium:api</code>.</p>
|
||||
<p>For example:</p><pre><code class="language-text-x-trilium-auto">import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");</code></pre>
|
||||
<h2>Custom hooks</h2>
|
||||
<p>Trilium comes with a large set of custom hooks for Preact, all of which
|
||||
are also available to custom widgets and <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_HcABDtFCkbFN">Render Note</a>.</p>
|
||||
<h3><code spellcheck="false">useNoteContext</code></h3>
|
||||
<p>As a replacement to <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/MgibgPcfeuGz/_help_GhurYZjh8e1V">Note context aware widget</a>,
|
||||
Preact exposes the current note context in the <code spellcheck="false">useNoteContext</code> hook:</p><pre><code class="language-text-x-trilium-auto">import { defineWidget, useNoteContext, useNoteProperty } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "note-detail-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const { note } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title");
|
||||
return <span>Current note JSX: {title}</span>;
|
||||
}
|
||||
});</code></pre>
|
||||
<p>Note that the custom widget must be inside the content area (so note detail
|
||||
widget) for this to work properly, especially when dealing with splits.</p>
|
||||
<h3><code spellcheck="false">useActiveNoteContext</code></h3>
|
||||
<p><code spellcheck="false">useActiveNoteContext</code> is an alternative
|
||||
to <code spellcheck="false">useNoteContext</code> which works even if the
|
||||
widget is not within the note detail section and it automatically switches
|
||||
the note context as the user is navigating around between tabs and splits.</p>
|
||||
<h3><code spellcheck="false">useNoteProperty</code></h3>
|
||||
<p>This hook allows “listening” for changes to a particular property of a
|
||||
<code
|
||||
spellcheck="false">FNote</code>, such as the <code spellcheck="false">title</code> or
|
||||
<code
|
||||
spellcheck="false">type</code>of a note. The benefit from using the hook is that it actually
|
||||
reacts to changes, for example if the note title or type is changed.</p>
|
||||
@@ -1,25 +1,26 @@
|
||||
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
"use strict";
|
||||
|
||||
import cloningService from "../../services/cloning.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 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 log from "../../services/log.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import TaskContext from "../../services/task_context.js";
|
||||
import optionService from "../../services/options.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import type { NotePojo } from "../becca-interface.js";
|
||||
import dateUtils from "../../services/date_utils.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";
|
||||
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 BBranch from "./bbranch.js";
|
||||
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 noteService from "../../services/notes.js";
|
||||
import handlers from "../../services/handlers.js";
|
||||
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
@@ -295,10 +296,6 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
);
|
||||
}
|
||||
|
||||
isJsx() {
|
||||
return (this.type === "code" && this.mime === "text/jsx");
|
||||
}
|
||||
|
||||
/** @returns true if this note is HTML */
|
||||
isHtml() {
|
||||
return ["code", "file", "render"].includes(this.type) && this.mime === "text/html";
|
||||
@@ -321,7 +318,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
/** @returns JS script environment - either "frontend" or "backend" */
|
||||
getScriptEnv() {
|
||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
|
||||
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
|
||||
return "frontend";
|
||||
}
|
||||
|
||||
@@ -358,9 +355,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return this.__attributeCache.filter((attr) => attr.type === type);
|
||||
} else if (name) {
|
||||
return this.__attributeCache.filter((attr) => attr.name === name);
|
||||
}
|
||||
} else {
|
||||
return this.__attributeCache;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private __ensureAttributeCacheIsAvailable() {
|
||||
@@ -695,9 +692,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return this.ownedAttributes.filter((attr) => attr.type === type);
|
||||
} else if (name) {
|
||||
return this.ownedAttributes.filter((attr) => attr.name === name);
|
||||
}
|
||||
} else {
|
||||
return this.ownedAttributes;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,9 +745,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return 1;
|
||||
} else if (a.parentNote?.isHiddenCompletely()) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.parents = this.parentBranches.map((branch) => branch.parentNote).filter((note) => !!note) as BNote[];
|
||||
@@ -1181,9 +1178,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
return a.isArchived ? 1 : -1;
|
||||
} else if (a.isHidden !== b.isHidden) {
|
||||
return a.isHidden ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
return a.notePath.length - b.notePath.length;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return notePaths;
|
||||
@@ -1260,9 +1257,9 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
} else {
|
||||
new BAttribute({
|
||||
noteId: this.noteId,
|
||||
type,
|
||||
name,
|
||||
value
|
||||
type: type,
|
||||
name: name,
|
||||
value: value
|
||||
}).save();
|
||||
}
|
||||
}
|
||||
@@ -1295,11 +1292,11 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
addAttribute(type: AttributeType, name: string, value: string = "", isInheritable: boolean = false, position: number | null = null): BAttribute {
|
||||
return new BAttribute({
|
||||
noteId: this.noteId,
|
||||
type,
|
||||
name,
|
||||
value,
|
||||
isInheritable,
|
||||
position
|
||||
type: type,
|
||||
name: name,
|
||||
value: value,
|
||||
isInheritable: isInheritable,
|
||||
position: position
|
||||
}).save();
|
||||
}
|
||||
|
||||
@@ -1473,10 +1470,10 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
role: "image",
|
||||
mime: this.mime,
|
||||
title: this.title,
|
||||
content
|
||||
content: content
|
||||
});
|
||||
|
||||
const parentContent = parentNote.getContent();
|
||||
let parentContent = parentNote.getContent();
|
||||
|
||||
const oldNoteUrl = `api/images/${this.noteId}/`;
|
||||
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
|
||||
@@ -1715,14 +1712,14 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
} else {
|
||||
return "bx bx-note";
|
||||
|
||||
}
|
||||
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
|
||||
return "bx bx-data";
|
||||
}
|
||||
} else {
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
@@ -1732,7 +1729,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
getFilteredChildBranches() {
|
||||
const childBranches = this.getChildBranches();
|
||||
let childBranches = this.getChildBranches();
|
||||
|
||||
if (!childBranches) {
|
||||
console.error(`No children for '${this.noteId}'. This shouldn't happen.`);
|
||||
|
||||
@@ -108,7 +108,7 @@ function loginSync(req: Request) {
|
||||
|
||||
const givenHash = req.body.hash;
|
||||
|
||||
if (expectedHash !== givenHash) {
|
||||
if (!utils.constantTimeCompare(expectedHash, givenHash)) {
|
||||
return [400, { message: "Sync login credentials are incorrect. It looks like you're trying to sync two different initialized documents which is not possible." }];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from "crypto";
|
||||
import utils from "../services/utils.js";
|
||||
import optionService from "../services/options.js";
|
||||
import myScryptService from "../services/encryption/my_scrypt.js";
|
||||
@@ -160,7 +161,11 @@ function verifyPassword(submittedPassword: string) {
|
||||
|
||||
const guess_hashed = myScryptService.getVerificationHash(submittedPassword);
|
||||
|
||||
return guess_hashed.equals(hashed_password);
|
||||
// Use constant-time comparison to prevent timing attacks
|
||||
if (hashed_password.length !== guess_hashed.length) {
|
||||
return false;
|
||||
}
|
||||
return crypto.timingSafeEqual(guess_hashed, hashed_password);
|
||||
}
|
||||
|
||||
function sendLoginError(req: Request, res: Response, errorType: 'password' | 'totp' = 'password') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import utils from "../utils.js";
|
||||
import utils, { constantTimeCompare } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import sql from "../sql.js";
|
||||
import sqlInit from "../sql_init.js";
|
||||
@@ -87,8 +87,7 @@ function verifyOpenIDSubjectIdentifier(subjectIdentifier: string) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
console.log("Matches: " + givenHash === savedHash);
|
||||
return givenHash === savedHash;
|
||||
return constantTimeCompare(givenHash, savedHash as string);
|
||||
}
|
||||
|
||||
function setDataKey(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { toBase64 } from "../utils.js";
|
||||
import { toBase64, constantTimeCompare } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
|
||||
function verifyPassword(password: string) {
|
||||
@@ -12,7 +12,7 @@ function verifyPassword(password: string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return givenPasswordHash === dbPasswordHash;
|
||||
return constantTimeCompare(givenPasswordHash, dbPasswordHash);
|
||||
}
|
||||
|
||||
function setDataKey(password: string, plainTextDataKey: string | Buffer) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from 'crypto';
|
||||
import optionService from '../options.js';
|
||||
import sql from '../sql.js';
|
||||
import { constantTimeCompare } from '../utils.js';
|
||||
|
||||
function isRecoveryCodeSet() {
|
||||
return optionService.getOptionBool('encryptedRecoveryCodes');
|
||||
@@ -55,13 +56,22 @@ function verifyRecoveryCode(recoveryCodeGuess: string) {
|
||||
|
||||
const recoveryCodes = getRecoveryCodes();
|
||||
let loginSuccess = false;
|
||||
recoveryCodes.forEach((recoveryCode) => {
|
||||
if (recoveryCodeGuess === recoveryCode) {
|
||||
removeRecoveryCode(recoveryCode);
|
||||
let matchedCode: string | null = null;
|
||||
|
||||
// Check ALL codes to prevent timing attacks - do not short-circuit
|
||||
for (const recoveryCode of recoveryCodes) {
|
||||
if (constantTimeCompare(recoveryCodeGuess, recoveryCode)) {
|
||||
matchedCode = recoveryCode;
|
||||
loginSuccess = true;
|
||||
return;
|
||||
// Continue checking all codes to maintain constant time
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the matched code only after checking all codes
|
||||
if (matchedCode) {
|
||||
removeRecoveryCode(matchedCode);
|
||||
}
|
||||
|
||||
return loginSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { randomSecureToken, toBase64 } from "../utils.js";
|
||||
import { randomSecureToken, toBase64, constantTimeCompare } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import type { OptionNames } from "@triliumnext/commons";
|
||||
|
||||
@@ -18,7 +18,7 @@ function verifyTotpSecret(secret: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
return givenSecretHash === dbSecretHash;
|
||||
return constantTimeCompare(givenSecretHash, dbSecretHash);
|
||||
}
|
||||
|
||||
function setTotpSecret(secret: string) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import becca from "../becca/becca.js";
|
||||
import { fromBase64, randomSecureToken } from "./utils.js";
|
||||
import { fromBase64, randomSecureToken, constantTimeCompare } from "./utils.js";
|
||||
import BEtapiToken from "../becca/entities/betapi_token.js";
|
||||
import crypto from "crypto";
|
||||
|
||||
@@ -83,15 +83,16 @@ function isValidAuthHeader(auth: string | undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return etapiToken.tokenHash === authTokenHash;
|
||||
return constantTimeCompare(etapiToken.tokenHash, authTokenHash);
|
||||
} else {
|
||||
// Check ALL tokens to prevent timing attacks - do not short-circuit
|
||||
let isValid = false;
|
||||
for (const etapiToken of becca.getEtapiTokens()) {
|
||||
if (etapiToken.tokenHash === authTokenHash) {
|
||||
return true;
|
||||
if (constantTimeCompare(etapiToken.tokenHash, authTokenHash)) {
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { trimIndentation } from "@triliumnext/commons";
|
||||
|
||||
import becca from "../becca/becca.js";
|
||||
import BBranch from "../becca/entities/bbranch.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";
|
||||
import { executeBundle, getScriptBundle } from "./script.js";
|
||||
import BBranch from "../becca/entities/bbranch.js";
|
||||
import BNote from "../becca/entities/bnote.js";
|
||||
|
||||
|
||||
describe("Script", () => {
|
||||
@@ -86,95 +84,3 @@ 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");
|
||||
`;
|
||||
expect(buildJsx(script).code).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { transform } from "sucrase";
|
||||
|
||||
import ScriptContext from "./script_context.js";
|
||||
import cls from "./cls.js";
|
||||
import log from "./log.js";
|
||||
import becca from "../becca/becca.js";
|
||||
import type BNote from "../becca/entities/bnote.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 {
|
||||
note?: BNote;
|
||||
@@ -112,9 +110,9 @@ function getParams(params?: ScriptParams) {
|
||||
.map((p) => {
|
||||
if (typeof p === "string" && p.startsWith("!@#Function: ")) {
|
||||
return p.substr(13);
|
||||
}
|
||||
} else {
|
||||
return JSON.stringify(p);
|
||||
|
||||
}
|
||||
})
|
||||
.join(",");
|
||||
}
|
||||
@@ -147,7 +145,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(note.isJavaScript() || note.isHtml() || note.isJsx())) {
|
||||
if (!note.isJavaScript() && !note.isHtml()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +158,7 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
}
|
||||
|
||||
const bundle: Bundle = {
|
||||
note,
|
||||
note: note,
|
||||
script: "",
|
||||
html: "",
|
||||
allNotes: [note]
|
||||
@@ -194,18 +192,12 @@ export function getScriptBundle(note: BNote, root: boolean = true, scriptEnv: st
|
||||
// only frontend scripts are async. Backend cannot be async because of transaction management.
|
||||
const isFrontend = scriptEnv === "frontend";
|
||||
|
||||
if (note.isJsx() || note.isJavaScript()) {
|
||||
let scriptContent = note.getContent();
|
||||
|
||||
if (note.isJsx()) {
|
||||
scriptContent = buildJsx(scriptContent).code;
|
||||
}
|
||||
|
||||
if (note.isJavaScript()) {
|
||||
bundle.script += `
|
||||
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(", ")}) {
|
||||
try {
|
||||
${overrideContent || scriptContent};
|
||||
${overrideContent || note.getContent()};
|
||||
} 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];
|
||||
return module.exports;
|
||||
@@ -218,39 +210,6 @@ return module.exports;
|
||||
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|let|const)\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|let|const)\s+(\w+)\s*=\s*require\(['"]trilium:api['"]\);?/g,
|
||||
'const $1 = api;'
|
||||
);
|
||||
|
||||
output.code = code;
|
||||
return output;
|
||||
}
|
||||
|
||||
function sanitizeVariableName(str: string) {
|
||||
return str.replace(/[^a-z0-9_]/gim, "");
|
||||
}
|
||||
|
||||
@@ -74,6 +74,36 @@ export function hmac(secret: any, value: any) {
|
||||
return hmac.digest("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant-time string comparison to prevent timing attacks.
|
||||
* Uses crypto.timingSafeEqual to ensure comparison time is independent
|
||||
* of how many characters match.
|
||||
*
|
||||
* @param a First string to compare
|
||||
* @param b Second string to compare
|
||||
* @returns true if strings are equal, false otherwise
|
||||
* @note Returns false for null/undefined/non-string inputs. Empty strings are considered equal.
|
||||
*/
|
||||
export function constantTimeCompare(a: string | null | undefined, b: string | null | undefined): boolean {
|
||||
// Handle null/undefined/non-string cases safely
|
||||
if (typeof a !== "string" || typeof b !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bufA = Buffer.from(a, "utf-8");
|
||||
const bufB = Buffer.from(b, "utf-8");
|
||||
|
||||
// If lengths differ, we still do a constant-time comparison
|
||||
// to avoid leaking length information through timing
|
||||
if (bufA.length !== bufB.length) {
|
||||
// Compare bufA against itself to maintain constant time behavior
|
||||
crypto.timingSafeEqual(bufA, bufA);
|
||||
return false;
|
||||
}
|
||||
|
||||
return crypto.timingSafeEqual(bufA, bufB);
|
||||
}
|
||||
|
||||
export function hash(text: string) {
|
||||
text = text.normalize();
|
||||
|
||||
@@ -486,6 +516,7 @@ function slugify(text: string) {
|
||||
|
||||
export default {
|
||||
compareVersions,
|
||||
constantTimeCompare,
|
||||
crash,
|
||||
envToBoolean,
|
||||
escapeHtml,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/nvEEQ1vvJMmS/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/8sStlTtSCgN7/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
700
docs/User Guide/!!!meta.json
vendored
700
docs/User Guide/!!!meta.json
vendored
@@ -3600,13 +3600,6 @@
|
||||
"isInheritable": false,
|
||||
"position": 170
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 180
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
@@ -3614,6 +3607,13 @@
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 180
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
@@ -15631,83 +15631,6 @@
|
||||
"value": "bx bxs-widget",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "RnaPdbciOfeq",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "xYmIYSP6wE3F",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "4Gn3psZKsfSm",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "KLsqhjaqh1QW",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 90
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HI6GBBIduIgv",
|
||||
"isInheritable": false,
|
||||
"position": 100
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "s8alTXmpFR61",
|
||||
"isInheritable": false,
|
||||
"position": 110
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GhurYZjh8e1V",
|
||||
"isInheritable": false,
|
||||
"position": 120
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "M8IppdwVHSjG",
|
||||
"isInheritable": false,
|
||||
"position": 130
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "gMkgcLJ6jBkg",
|
||||
"isInheritable": false,
|
||||
"position": 140
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@@ -15715,127 +15638,6 @@
|
||||
"attachments": [],
|
||||
"dirFileName": "Custom Widgets",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "SynTBQiBsdYJ",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"MgibgPcfeuGz",
|
||||
"SynTBQiBsdYJ"
|
||||
],
|
||||
"title": "Widget Basics",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/markdown",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "zEY4DaJG4YT5",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "BFs8mudNFgCS",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GLks18SNjxmC",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "s8alTXmpFR61",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "widget-basics",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "MgibgPcfeuGz",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Widget Basics.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "GhurYZjh8e1V",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"MgibgPcfeuGz",
|
||||
"GhurYZjh8e1V"
|
||||
],
|
||||
"title": "Note context aware widget",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Note context aware widget.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "M8IppdwVHSjG",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"MgibgPcfeuGz",
|
||||
"M8IppdwVHSjG"
|
||||
],
|
||||
"title": "Right pane widget",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IjZS7iK5EXtb",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "right-pane-widget",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Right pane widget.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "YNxAqkI5Kg1M",
|
||||
@@ -15847,7 +15649,7 @@
|
||||
"YNxAqkI5Kg1M"
|
||||
],
|
||||
"title": "Word count widget",
|
||||
"notePosition": 40,
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -15895,6 +15697,99 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "SynTBQiBsdYJ",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"MgibgPcfeuGz",
|
||||
"SynTBQiBsdYJ"
|
||||
],
|
||||
"title": "Widget Basics",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/markdown",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "zEY4DaJG4YT5",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "BFs8mudNFgCS",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GLks18SNjxmC",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "s8alTXmpFR61",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "widget-basics",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Widget Basics.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "M8IppdwVHSjG",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"MgibgPcfeuGz",
|
||||
"M8IppdwVHSjG"
|
||||
],
|
||||
"title": "Right pane widget",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "right-pane-widget",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IjZS7iK5EXtb",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Right pane widget.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "VqGQnnPGnqAU",
|
||||
@@ -15906,7 +15801,7 @@
|
||||
"VqGQnnPGnqAU"
|
||||
],
|
||||
"title": "CSS",
|
||||
"notePosition": 70,
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -15918,13 +15813,6 @@
|
||||
"value": "css",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "Sg9GrCtyftZf",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@@ -15942,7 +15830,7 @@
|
||||
"gMkgcLJ6jBkg"
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
"notePosition": 80,
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -16329,292 +16217,6 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "KLsqhjaqh1QW",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW"
|
||||
],
|
||||
"title": "Preact",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxl-react",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "MgibgPcfeuGz",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HcABDtFCkbFN",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "wy8So3yZZlH9",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GLks18SNjxmC",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "Bqde6BvPo05g",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Preact.md",
|
||||
"attachments": [],
|
||||
"dirFileName": "Preact",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "Bqde6BvPo05g",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW",
|
||||
"Bqde6BvPo05g"
|
||||
],
|
||||
"title": "Component libraries",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "hA834UaHhSNn",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HcABDtFCkbFN",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxs-component",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Component libraries.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "ykYtbM9k3a7B",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW",
|
||||
"ykYtbM9k3a7B"
|
||||
],
|
||||
"title": "Hooks",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HcABDtFCkbFN",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GhurYZjh8e1V",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-question-mark",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Hooks.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "Sg9GrCtyftZf",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW",
|
||||
"Sg9GrCtyftZf"
|
||||
],
|
||||
"title": "CSS",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "AlhDUqhENtH7",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxs-file-css",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "CSS.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "RSssb9S3xgSr",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW",
|
||||
"RSssb9S3xgSr"
|
||||
],
|
||||
"title": "Built-in components",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxs-component",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "MgibgPcfeuGz",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HcABDtFCkbFN",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "i9B4IW7b6V6z",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6tZeKvSHEUiB",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Built-in components.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "KtDChJYITDxC",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Built-in components_image.png"
|
||||
}
|
||||
],
|
||||
"dirFileName": "Built-in components",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "i9B4IW7b6V6z",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"yIhgI5H7A2Sm",
|
||||
"KLsqhjaqh1QW",
|
||||
"RSssb9S3xgSr",
|
||||
"i9B4IW7b6V6z"
|
||||
],
|
||||
"title": "Widget showcase",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "code",
|
||||
"mime": "text/jsx",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "widget-showcase",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
}
|
||||
],
|
||||
"dataFileName": "Widget showcase.jsx",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -16738,98 +16340,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "wqXwKJl6VpNk",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"wqXwKJl6VpNk"
|
||||
],
|
||||
"title": "Common concepts",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxl-nodejs",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "common-concepts",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"attachments": [],
|
||||
"dirFileName": "Common concepts",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "hA834UaHhSNn",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"wqXwKJl6VpNk",
|
||||
"hA834UaHhSNn"
|
||||
],
|
||||
"title": "Script bundles",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-package",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "HcABDtFCkbFN",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "bundles",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IakOLONlIfGI",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Script bundles.md",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "GLks18SNjxmC",
|
||||
@@ -16839,7 +16349,7 @@
|
||||
"GLks18SNjxmC"
|
||||
],
|
||||
"title": "Script API",
|
||||
"notePosition": 110,
|
||||
"notePosition": 100,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -17037,7 +16547,7 @@
|
||||
"vElnKeDNPSVl"
|
||||
],
|
||||
"title": "Logging",
|
||||
"notePosition": 120,
|
||||
"notePosition": 110,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
|
||||
@@ -9,7 +9,7 @@ Render Note is used in <a class="reference-link" href="../Scripting.md">Scripti
|
||||
2. Create a <a class="reference-link" href="Render%20Note.md">Render Note</a>.
|
||||
3. Assign the `renderNote` [relation](../Advanced%20Usage/Attributes.md) to point at the previously created code note.
|
||||
|
||||
## Legacy scripting using jQuery
|
||||
## Dynamic content
|
||||
|
||||
A static HTML is generally not enough for <a class="reference-link" href="../Scripting.md">Scripting</a>. The next step is to automatically change parts of the note using JavaScript.
|
||||
|
||||
@@ -34,28 +34,6 @@ Now create a render note at any place and set its `~renderNote` relation to poin
|
||||
> **Current date & time**
|
||||
> The current date & time is Sun Apr 06 2025 15:26:29 GMT+0300 (Eastern European Summer Time)
|
||||
|
||||
## Dynamic content using Preact & JSX
|
||||
|
||||
As a more modern alternative to jQuery, it's possible to use Preact & JSX to render pages. Since JSX is a superset of JavaScript, there's no need to provide a HTML anymore.
|
||||
|
||||
Here are the steps to creating a simple render note:
|
||||
|
||||
1. Create a note of type <a class="reference-link" href="Render%20Note.md">Render Note</a>.
|
||||
2. Create a child <a class="reference-link" href="Code.md">Code</a> note with JSX as the language.
|
||||
As an example, use the following content:
|
||||
|
||||
```jsx
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello world.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. In the parent render note, define a `~renderNote` relation pointing to the newly created child.
|
||||
4. Refresh the render note and it should display a “Hello world” message.
|
||||
|
||||
## Refreshing the note
|
||||
|
||||
It's possible to refresh the note via:
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Script bundles
|
||||
For both <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a> and more complicated scripts, it's generally useful to split the code into multiple <a class="reference-link" href="../../Note%20Types/Code.md">Code</a> notes.
|
||||
|
||||
When a script is run, the sub-children of the script being run (or the <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a>) are checked for children. If the children are Code notes of the corresponding type (front-end or backend) as the code being run, they will be evaluated as well.
|
||||
|
||||
The collection of a script and its child notes is called a _bundle_. A child note inside a bundle is called a _module_.
|
||||
|
||||
As a basic example of dependencies, consider the following note structure:
|
||||
|
||||
* _Script with dependency_
|
||||
|
||||
```javascript
|
||||
api.log(MyMath.sum(2, 2));
|
||||
```
|
||||
|
||||
* _MyMath_
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
sum(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When _Script with dependency_ is run, it will detect _MyMath_ as a submodule and provide the result of its `module.exports` object into a global object with the same name as the note.
|
||||
|
||||
> [!NOTE]
|
||||
> If the note contains spaces or special characters, they will be stripped. For example `My Nice Note!` becomes `MyNiceNote`.
|
||||
|
||||
## Alternative syntax
|
||||
|
||||
Instead of providing an object to `module.exports`, it's also possible to add fields individually:
|
||||
|
||||
```javascript
|
||||
module.exports.sum = (a, b) => a + b;
|
||||
module.exports.subtract = (a, b) => a - b;
|
||||
```
|
||||
|
||||
## Ignoring a code script from a bundle
|
||||
|
||||
To ignore a script from being included in a bundle (e.g. if it's unrelated to the parent script note), apply the `#disableInclusion` label.
|
||||
|
||||
## Sharing a module across multiple bundles
|
||||
|
||||
Modules can be reused across multiple scripts by simply cloning the shared module between two modules (see <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/Notes/Cloning%20Notes.md">Cloning Notes</a>).
|
||||
|
||||
Optionally, a separate note can be used to contain all the different reusable modules for an easy way to discover them.
|
||||
@@ -1,62 +1,9 @@
|
||||
# Custom Widgets
|
||||
Custom widgets are a special subset of scripts that render graphical elements in certain parts of the application. These can be used to add new functionality to the Trilium application.
|
||||
It's possible to create custom widget in three possible locations where you can display your custom content.
|
||||
|
||||
## Preact with JSX vs. vanilla jQuery
|
||||
Positions are:
|
||||
|
||||
In older versions of Trilium, custom widgets were exclusively written in a combination of jQuery with Trilium's internal widget architecture (e.g., `BasicWidget`, `NoteContextAwareWidget`).
|
||||
|
||||
Starting with v0.101.0, custom widgets can also be written in JSX using the <a class="reference-link" href="Preact.md">Preact</a> framework. Both legacy and Preact widgets have the same capabilities, with a single difference:
|
||||
|
||||
* Preact widgets are content-sized by default whereas legacy widgets need `this.contentSized()` applied in the constructor. For more information, see the corresponding section in <a class="reference-link" href="Custom%20Widgets/Troubleshooting.md">Troubleshooting</a>.
|
||||
|
||||
Wherever possible, widget examples will be both in the legacy and Preact format.
|
||||
|
||||
## Creating a custom widget
|
||||
|
||||
1. Create a <a class="reference-link" href="../../Note%20Types/Code.md">Code</a> note.
|
||||
2. Set the language to:
|
||||
1. JavaScript (frontend) for legacy widgets using jQuery.
|
||||
2. JSX for Preact widgets. You might need to go to Options → Code to enable the language first.
|
||||
3. Apply the `#widget` [label](../../Advanced%20Usage/Attributes/Labels.md).
|
||||
|
||||
## Getting started with a simple example
|
||||
|
||||
Let's start by creating a widget that shows a message near the content area. Follow the previous section to create a code note, and use the following content.
|
||||
|
||||
<table><thead><tr><th>Legacy</th><th style="width:50%;">Preact (v0.101.0+)</th></tr></thead><tbody><tr><td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<span>Center pane</span>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new HelloNoteDetail();</code></pre></td><td style="vertical-align:top;"><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "center-pane",
|
||||
render: () => <span>Center pane from Preact.</span>
|
||||
});</code></pre></td></tr></tbody></table>
|
||||
|
||||
[Refresh the application](../../Troubleshooting/Refreshing%20the%20application.md) and the widget should appear underneath the content area.
|
||||
|
||||
## Widget location (parent widget)
|
||||
|
||||
A widget can be placed in one of the following sections of the applications:
|
||||
|
||||
<table class="ck-table-resized"><colgroup><col style="width:15.59%;"><col style="width:30.42%;"><col style="width:16.68%;"><col style="width:37.31%;"></colgroup><thead><tr><th>Value for <code spellcheck="false">parentWidget</code></th><th>Description</th><th>Sample widget</th><th>Special requirements</th></tr></thead><tbody><tr><th><code spellcheck="false">left-pane</code></th><td>Appears within the same pane that holds the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree.md">Note Tree</a>.</td><td>Same as above, with only a different <code spellcheck="false">parentWidget</code>.</td><td>None.</td></tr><tr><th><code spellcheck="false">center-pane</code></th><td>In the content area. If a split is open, the widget will span all of the splits.</td><td>See example above.</td><td>None.</td></tr><tr><th><code spellcheck="false">note-detail-pane</code></th><td><p>In the content area, inside the note detail area. If a split is open, the widget will be contained inside the split.</p><p>This is ideal if the widget is note-specific.</p></td><td><a class="reference-link" href="Custom%20Widgets/Note%20context%20aware%20widget.md">Note context aware widget</a></td><td><ul><li data-list-item-id="ec06332efcc3039721606c052f0d913fa">The widget must export a <code spellcheck="false">class</code> and not an instance of the class (e.g. <code spellcheck="false">no new</code>) because it needs to be multiplied for each note, so that splits work correctly.</li><li data-list-item-id="e8da690a2a8df148f6b5fc04ba1611688">Since the <code spellcheck="false">class</code> is exported instead of an instance, the <code spellcheck="false">parentWidget</code> getter must be <code spellcheck="false">static</code>, otherwise the widget is ignored.</li></ul></td></tr><tr><th><code spellcheck="false">right-pane</code></th><td>In the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/Right%20Sidebar.md">Right Sidebar</a>, as a dedicated section.</td><td><a class="reference-link" href="Custom%20Widgets/Right%20pane%20widget.md">Right pane widget</a></td><td><ul><li data-list-item-id="efe008d361e224f422582552648e1afe7">Although not mandatory, it's best to use a <code spellcheck="false">RightPanelWidget</code> instead of a <code spellcheck="false">BasicWidget</code> or a <code spellcheck="false">NoteContextAwareWidget</code>.</li></ul></td></tr></tbody></table>
|
||||
|
||||
To position the widget somewhere else, just change the value passed to `get parentWidget()` for legacy widgets or the `parent` field for Preact. Do note that some positions such as `note-detail-pane` and `right-pane` have special requirements that need to be accounted for (see the table above).
|
||||
|
||||
## Launch bar widgets
|
||||
|
||||
Launch bar widgets are similar to _Custom widgets_ but are specific to the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/Launch%20Bar.md">Launch Bar</a>. See <a class="reference-link" href="Launch%20Bar%20Widgets.md">Launch Bar Widgets</a> for more information.
|
||||
|
||||
## Custom position
|
||||
* `left-pane`
|
||||
* `center-pane`
|
||||
* `note-detail-pane` - located within `center-pane`, but specific to note (split)
|
||||
* `right-pane`
|
||||
@@ -1,7 +1,5 @@
|
||||
# CSS
|
||||
## Classic widgets
|
||||
|
||||
In `doRender()`:<sup><a href="#fn1saoftmefpp">[1]</a></sup>
|
||||
In `doRender()`:
|
||||
|
||||
```
|
||||
this.cssBlock(`#my-widget {
|
||||
@@ -9,13 +7,9 @@ this.cssBlock(`#my-widget {
|
||||
bottom: 40px;
|
||||
left: 60px;
|
||||
z-index: 1;
|
||||
}`);
|
||||
}`)
|
||||
```
|
||||
|
||||
## Preact widgets
|
||||
|
||||
See the dedicated page: <a class="reference-link" href="../Preact/CSS.md">CSS</a>.
|
||||
|
||||
1. <sup><strong><a href="#fnref1saoftmefpp">^</a></strong></sup>
|
||||
* * *
|
||||
|
||||
Reference: [https://trilium.rocks/X7pxYpiu0lgU](https://trilium.rocks/X7pxYpiu0lgU)
|
||||
@@ -1,53 +0,0 @@
|
||||
# Note context aware widget
|
||||
Note context-aware widgets are a different type of widget which automatically react to changes in the current note.
|
||||
|
||||
Important aspects:
|
||||
|
||||
* The widget must export a `class` and not an instance of the class (e.g. `no new`) because it needs to be multiplied for each note, so that splits work correctly.
|
||||
* Since the `class` is exported instead of an instance, the `parentWidget` getter must be `static`, otherwise the widget is ignored.
|
||||
|
||||
## Example displaying the current note title
|
||||
|
||||
This is a note context-aware widget that simply displays the name the current note.
|
||||
|
||||
### Classic example
|
||||
|
||||
```
|
||||
class HelloNoteDetail extends api.NoteContextAwareWidget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<div>");
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$widget.text("Current note: " + note.title);
|
||||
}
|
||||
|
||||
static get parentWidget() { return "note-detail-pane" }
|
||||
get position() { return 10 }
|
||||
|
||||
}
|
||||
|
||||
module.exports = HelloNoteDetail;
|
||||
```
|
||||
|
||||
### Preact (v0.101.0+)
|
||||
|
||||
```
|
||||
import { defineWidget, useNoteContext, useNoteProperty } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "note-detail-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const { note } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title");
|
||||
return <span>Current note JSX: {title}</span>;
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -40,8 +40,6 @@ module.exports = new NoteTitleWidget();
|
||||
|
||||
A simple widget which will show the current time, as an example on how to dynamically change the content of the widget periodically.
|
||||
|
||||
### Legacy widget
|
||||
|
||||
```
|
||||
const template = `<div></div>`;
|
||||
|
||||
@@ -64,31 +62,6 @@ class ToDoListWidget extends api.RightPanelWidget {
|
||||
module.exports = new ToDoListWidget();
|
||||
```
|
||||
|
||||
### Preact widget
|
||||
|
||||
```
|
||||
import { defineWidget, RightPanelWidget, useEffect, useState } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "right-pane",
|
||||
position: 1,
|
||||
render() {
|
||||
const [ time, setTime ] = useState();
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(new Date().toLocaleString());
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
return (
|
||||
<RightPanelWidget id="clock-jsx" title="Clock (JSX)">
|
||||
<p>The time is: {time}</p>
|
||||
</RightPanelWidget>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Example for old layout
|
||||
|
||||
Here's a widget that displays a basic message ("Hi"):
|
||||
@@ -132,7 +105,7 @@ By default, the sidebar items are displayed in the order they are found by the a
|
||||
|
||||
It is possible to make a widget appear higher or lower up, by adjusting its `position` property:
|
||||
|
||||
```
|
||||
```diff
|
||||
class MyWidget extends api.RightPanelWidget {
|
||||
|
||||
+ get position() { return 20 };
|
||||
|
||||
@@ -108,6 +108,11 @@ class MyWidget extends api.BasicWidget {
|
||||
module.exports = new MyWidget();
|
||||
```
|
||||
|
||||
For the list of possible values for `parentWidget()`, see <a class="reference-link" href="../Custom%20Widgets.md">Custom Widgets</a>.
|
||||
`parentWidget()` can be given the following values:
|
||||
|
||||
* `left-pane` - This renders the widget on the left side of the screen where the note tree lives.
|
||||
* `center-pane` - This renders the widget in the center of the layout in the same location that notes and splits appear.
|
||||
* `note-detail-pane` - This renders the widget _with_ the note in the center pane. This means it can appear multiple times with splits.
|
||||
* `right-pane` - This renders the widget to the right of any opened notes.
|
||||
|
||||
[Reload](../../../Troubleshooting/Refreshing%20the%20application.md) the application one last time. When you click the button, a "Hello World!" message should appear, confirming that your widget is fully functional.
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
This is an example of a note context-aware widget, which reacts to the currently opened note and refreshes automatically as the user navigates through the notes.
|
||||
|
||||
## Legacy widget
|
||||
|
||||
In this example, the title of the note is displayed. It works best on the [horizontal layout](../../../Basic%20Concepts%20and%20Features/UI%20Elements/Vertical%20and%20horizontal%20layout.md).
|
||||
|
||||
```javascript
|
||||
@@ -32,23 +30,3 @@ class NoteTitleWidget extends api.NoteContextAwareWidget {
|
||||
|
||||
module.exports = new NoteTitleWidget();
|
||||
```
|
||||
|
||||
## Preact widget (v0.101.0+)
|
||||
|
||||
```jsx
|
||||
import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
|
||||
|
||||
export default defineLauncherWidget({
|
||||
render: () => {
|
||||
const { note } = useActiveNoteContext();
|
||||
return <div style={{
|
||||
display: "flex",
|
||||
height: "53px",
|
||||
width: "fit-content",
|
||||
fontSize: "0.75em",
|
||||
alignItems: "center",
|
||||
flexShrink: 0
|
||||
}}>{note?.title}</div>;
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
# Preact
|
||||
Since v0.101.0, Trilium integrates Preact for front-end scripting, including support for JSX.
|
||||
|
||||
Preact can be used for:
|
||||
|
||||
* <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a>, where a JSX code note is used instead of a HTML one.
|
||||
* <a class="reference-link" href="Custom%20Widgets.md">Custom Widgets</a>, where JSX can be used to replace the old, jQuery-based mechanism.
|
||||
|
||||
To get started, the first step is to enable JSX in the list of Code languages. Go to Options → Code Notes and check the “JSX” language. Afterwards, proceed with the documentation in either <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a> or <a class="reference-link" href="Custom%20Widgets.md">Custom Widgets</a>, which will both have a section on how to use the new Preact integration.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The documentation assumes prior knowledge with React or Preact. As a starting point, consider the [FreeCodeCamp course on Front End Development Libraries](https://www.freecodecamp.org/learn/front-end-development-libraries-v9/) or the [Preact Tutorial](https://preactjs.com/tutorial/).
|
||||
|
||||
## Import/exports
|
||||
|
||||
When using Preact with JSX, there is a special syntax which provides ES-like imports. This `import` syntax makes way for a more intuitive that doesn't make use of global objects and paves the way for better auto-completion support that might be introduced in the future.
|
||||
|
||||
### API imports
|
||||
|
||||
Instead of:
|
||||
|
||||
```jsx
|
||||
api.showMessage("Hello");
|
||||
```
|
||||
|
||||
the JSX version looks like this:
|
||||
|
||||
```jsx
|
||||
import { showMessage } from "trilium:api";
|
||||
showMessage("hello");
|
||||
```
|
||||
|
||||
### Preact API imports (hooks, components)
|
||||
|
||||
There's a new <a class="reference-link" href="../Script%20API.md">Script API</a> dedicated to Preact, which provides shared components that are also used by Trilium internally as well as hooks, for example.
|
||||
|
||||
```jsx
|
||||
import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");
|
||||
```
|
||||
|
||||
### Exporting
|
||||
|
||||
JSX notes can export a component for use in <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a> or for <a class="reference-link" href="Preact/Component%20libraries.md">Component libraries</a>:
|
||||
|
||||
```jsx
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello world.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Import/export are not required
|
||||
|
||||
These imports are syntactic sugar meant to replace the usage for the `api` global object (see <a class="reference-link" href="../Script%20API.md">Script API</a>).
|
||||
|
||||
> [!NOTE]
|
||||
> The `import` and `export` syntax work only for JSX notes. Standard/jQuery code notes still need to use the `api` global and `module.exports`.
|
||||
|
||||
## Under the hood
|
||||
|
||||
Unlike JavaScript, JSX requires pre-processing to turn it into JavaScript (just like TypeScript). To do so, Trilium uses [Sucrase](https://github.com/alangpierce/sucrase), a JavaScript library which processes the JSX to pure JavaScript. The processing is done each time a script is run (for widgets this happens at every program startup). If you notice any performance degradation due to long compilation, consider [reporting the issue](../../Troubleshooting/Reporting%20issues.md) to us.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Built-in components
|
||||
<figure class="image image_resized" style="width:54.58%;"><img style="aspect-ratio:896/712;" src="Built-in components_image.png" width="896" height="712"><figcaption>A partial screenshot from the Widget showcase example (see below).</figcaption></figure>
|
||||
|
||||
Trilium comes with its own set of Preact components, some of which are also available to <a class="reference-link" href="../Custom%20Widgets.md">Custom Widgets</a> and <a class="reference-link" href="../../../Note%20Types/Render%20Note.md">Render Note</a>.
|
||||
|
||||
To use these components, simply import them from `trilium:preact`:
|
||||
|
||||
```jsx
|
||||
import { ActionButton, Button, LinkButton } from "trilium:preact";
|
||||
```
|
||||
|
||||
and then use them:
|
||||
|
||||
```jsx
|
||||
export default function MyRenderNote() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Buttons</h2>
|
||||
<div style={{ display: "flex", gap: "1em", alignItems: "center" }}>
|
||||
<ActionButton icon="bx bx-rocket" text="Action button" onClick={onClick} />
|
||||
<Button icon="bx bx-rocket" text="Simple button" onClick={onClick} />
|
||||
<LinkButton text="Link button" onClick={onClick} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Widget showcase
|
||||
|
||||
> [!TIP]
|
||||
> Starting with v0.101.0, the widget showcase is also available in the <a class="reference-link" href="../../../Advanced%20Usage/Database/Demo%20Notes.md">Demo Notes</a>.
|
||||
|
||||
This is a <a class="reference-link" href="../../../Note%20Types/Render%20Note.md">Render Note</a> example with JSX that shows most of the built-in components that are accessible to custom widgets and JSX render notes.
|
||||
|
||||
To use it, simply:
|
||||
|
||||
1. Create a render note.
|
||||
2. Create a child code note of JSX type with the content of this file: <a class="reference-link" href="Built-in%20components/Widget%20showcase.jsx">Widget showcase</a>
|
||||
3. Set the `~renderNote` relation of the parent note to the child note.
|
||||
4. Refresh the render note to see the results.
|
||||
@@ -1,189 +0,0 @@
|
||||
import {
|
||||
ActionButton, Button, LinkButton,
|
||||
Admonition, Collapsible,
|
||||
FormCheckbox, FormDropdownList, FormFileUploadButton, FormGroup, FormRadioGroup, FormTextArea,
|
||||
FormTextBox, FormToggle, Slider, RawHtml, LoadingSpinner, Icon,
|
||||
Dropdown, FormListItem, FormDropdownDivider, FormDropdownSubmenu,
|
||||
NoteAutocomplete, NoteLink, Modal,
|
||||
CKEditor,
|
||||
useEffect, useState
|
||||
} from "trilium:preact";
|
||||
import { showMessage } from "trilium:api";
|
||||
|
||||
export default function() {
|
||||
const [ time, setTime ] = useState();
|
||||
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam accumsan eu odio non gravida. Pellentesque ornare, arcu condimentum molestie dignissim, nibh turpis ultrices elit, eget elementum nunc erat at erat. Maecenas vehicula consectetur elit, nec fermentum elit venenatis eu.";
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setTime(new Date().toLocaleString()), 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20, display: "flex", flexDirection: "column", gap: "1em" }}>
|
||||
<h1>Widget showcase</h1>
|
||||
|
||||
<Buttons />
|
||||
<Admonition type="note">
|
||||
<strong>Admonition</strong><br />
|
||||
{lorem}
|
||||
</Admonition>
|
||||
|
||||
<Collapsible title="Collapsible" initiallyExpanded>
|
||||
{lorem}
|
||||
</Collapsible>
|
||||
|
||||
<FormElements />
|
||||
<NoteElements />
|
||||
<ModalSample />
|
||||
<DropdownSample />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Buttons() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Buttons</h2>
|
||||
<div style={{ display: "flex", gap: "1em", alignItems: "center" }}>
|
||||
<ActionButton icon="bx bx-rocket" text="Action button" onClick={onClick} />
|
||||
<Button icon="bx bx-rocket" text="Simple button" onClick={onClick} />
|
||||
<LinkButton text="Link button" onClick={onClick} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FormElements() {
|
||||
const [ checkboxChecked, setCheckboxChecked ] = useState(false);
|
||||
const [ dropdownValue, setDropdownValue ] = useState("key-1");
|
||||
const [ radioGroupValue, setRadioGroupValue ] = useState("key-1");
|
||||
const [ sliderValue, setSliderValue ] = useState(50);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Form elements</h2>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1em" }}>
|
||||
<FormGroup name="checkbox" label="Checkbox">
|
||||
<FormCheckbox label="Checkbox" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
|
||||
</FormGroup>
|
||||
<FormGroup name="toggle" label="Toggle">
|
||||
<FormToggle switchOnName="Off" switchOffName="On" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
|
||||
</FormGroup>
|
||||
<FormGroup name="dropdown" label="Dropdown">
|
||||
<FormDropdownList
|
||||
values={[
|
||||
{ key: "key-1", name: "First item" },
|
||||
{ key: "key-2", name: "Second item" },
|
||||
{ key: "key-3", name: "Third item" },
|
||||
]}
|
||||
currentValue={dropdownValue} onChange={setDropdownValue}
|
||||
keyProperty="key" titleProperty="name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="radio-group" label="Radio group">
|
||||
<FormRadioGroup
|
||||
values={[
|
||||
{ value: "key-1", label: "First item" },
|
||||
{ value: "key-2", label: "Second item" },
|
||||
{ value: "key-3", label: "Third item" },
|
||||
]}
|
||||
currentValue={radioGroupValue} onChange={setRadioGroupValue}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="text-box" label="Text box">
|
||||
<FormTextBox
|
||||
placeholder="Type something..."
|
||||
currentValue="" onChange={(newValue) => {}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="text-area" label="Text area">
|
||||
<FormTextArea
|
||||
placeholder="Type something bigger..."
|
||||
currentValue="" onChange={(newValue) => {}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="slider" label="Slider">
|
||||
<Slider
|
||||
min={1} max={100}
|
||||
value={sliderValue} onChange={setSliderValue}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="file-upload" label="File upload">
|
||||
<FormFileUploadButton
|
||||
text="Upload"
|
||||
onChange={(files) => {
|
||||
const file = files?.[0];
|
||||
if (!file) return;
|
||||
showMessage(`Got file "${file.name}" of size ${file.size} B and type ${file.type}.`);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup name="icon" label="Icon">
|
||||
<Icon icon="bx bx-smile" />
|
||||
</FormGroup>
|
||||
<FormGroup name="loading-spinner" label="Loading spinner">
|
||||
<LoadingSpinner />
|
||||
</FormGroup>
|
||||
<FormGroup name="raw-html" label="Raw HTML">
|
||||
<RawHtml html="<strong>Hi</strong> <em>there</em>" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NoteElements() {
|
||||
const [ noteId, setNoteId ] = useState("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Note elements</h2>
|
||||
|
||||
<FormGroup name="note-autocomplete" label="Note autocomplete">
|
||||
<NoteAutocomplete
|
||||
placeholder="Select a note"
|
||||
noteId={noteId} noteIdChanged={setNoteId}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup name="note-link" label="Note link">
|
||||
{noteId
|
||||
? <NoteLink notePath={noteId} showNoteIcon />
|
||||
: <span>Select a note first</span>}
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalSample() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Modal</h2>
|
||||
<Button text="Open modal" onClick={() => setShown(true)} />
|
||||
<Modal title="Modal title" size="md" show={shown} onHidden={() => setShown(false)}>
|
||||
Modal goes here.
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownSample() {
|
||||
return (
|
||||
<>
|
||||
<h2>Dropdown menu</h2>
|
||||
<Dropdown text="Dropdown" hideToggleArrow>
|
||||
<FormListItem icon="bx bx-cut">Cut</FormListItem>
|
||||
<FormListItem icon="bx bx-copy">Copy</FormListItem>
|
||||
<FormListItem icon="bx bx-paste">Paste</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
<FormDropdownSubmenu title="Submenu">
|
||||
<FormListItem>More items</FormListItem>
|
||||
</FormDropdownSubmenu>
|
||||
</Dropdown>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
@@ -1,17 +0,0 @@
|
||||
# CSS
|
||||
## Inline styles
|
||||
|
||||
```jsx
|
||||
<div style={{
|
||||
display: "flex",
|
||||
height: "53px",
|
||||
width: "fit-content",
|
||||
fontSize: "0.75em",
|
||||
alignItems: "center",
|
||||
flexShrink: 0
|
||||
}}>/* [...] */</div>
|
||||
```
|
||||
|
||||
## Custom CSS file
|
||||
|
||||
Simply create a <a class="reference-link" href="../../../Theme%20development/Custom%20app-wide%20CSS.md">Custom app-wide CSS</a>. Make sure the class names are unique enough to not intersect with other UI elements, consider adding a prefix (e.g. `x-mywidget-`).
|
||||
@@ -1,69 +0,0 @@
|
||||
# Component libraries
|
||||
Using the concept of <a class="reference-link" href="../../Common%20concepts/Script%20bundles.md">Script bundles</a>, it's possible to create components that are shared for multiple widgets or <a class="reference-link" href="../../../Note%20Types/Render%20Note.md">Render Note</a>.
|
||||
|
||||
## Exporting a single component
|
||||
|
||||
This is generally useful for big components.
|
||||
|
||||
Here's an example child hierarchy using <a class="reference-link" href="../../../Note%20Types/Render%20Note.md">Render Note</a>:
|
||||
|
||||
* _My render note_
|
||||
Note type: Render Note
|
||||
Link `~renderNote` to the child note (_Render note with subcomponents_)
|
||||
* _Render note with subcomponents_
|
||||
Type: JSX
|
||||
|
||||
```javascript
|
||||
export default function() {
|
||||
return (
|
||||
<MyComponent />
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
* _MyComponent_
|
||||
Type: Code / JSX
|
||||
|
||||
```javascript
|
||||
export default function MyComponent() {
|
||||
return <p>Hi</p>;
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple components per note
|
||||
|
||||
To export multiple components, use the `export` keyword next to each of the function components.
|
||||
|
||||
Here's how a sub-note called `MyComponents` would look like:
|
||||
|
||||
```javascript
|
||||
export function MyFirstComponent() {
|
||||
return <p>First</p>;
|
||||
}
|
||||
|
||||
export function MySecondComponent() {
|
||||
return <p>Bar</p>;
|
||||
}
|
||||
```
|
||||
|
||||
Then in its parent note:
|
||||
|
||||
```javascript
|
||||
const { MyFirstComponent, MySecondComponent } = MyComponents;
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
<MyFirstComponent />
|
||||
<MySecondComponent />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, it's also possible to use the components directly without assigning them to a `const` first:
|
||||
|
||||
```javascript
|
||||
<MyComponents.MyFirstComponent />
|
||||
<MyComponents.MySecondComponent />
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
# Hooks
|
||||
## Standard Preact hooks
|
||||
|
||||
All standard Preact hooks are available as an import in `trilium:api`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");
|
||||
```
|
||||
|
||||
## Custom hooks
|
||||
|
||||
Trilium comes with a large set of custom hooks for Preact, all of which are also available to custom widgets and <a class="reference-link" href="../../../Note%20Types/Render%20Note.md">Render Note</a>.
|
||||
|
||||
### `useNoteContext`
|
||||
|
||||
As a replacement to <a class="reference-link" href="../Custom%20Widgets/Note%20context%20aware%20widget.md">Note context aware widget</a>, Preact exposes the current note context in the `useNoteContext` hook:
|
||||
|
||||
```
|
||||
import { defineWidget, useNoteContext, useNoteProperty } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "note-detail-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const { note } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title");
|
||||
return <span>Current note JSX: {title}</span>;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that the custom widget must be inside the content area (so note detail widget) for this to work properly, especially when dealing with splits.
|
||||
|
||||
### `useActiveNoteContext`
|
||||
|
||||
`useActiveNoteContext` is an alternative to `useNoteContext` which works even if the widget is not within the note detail section and it automatically switches the note context as the user is navigating around between tabs and splits.
|
||||
|
||||
### `useNoteProperty`
|
||||
|
||||
This hook allows “listening” for changes to a particular property of a `FNote`, such as the `title` or `type` of a note. The benefit from using the hook is that it actually reacts to changes, for example if the note title or type is changed.
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -494,9 +494,6 @@ importers:
|
||||
node-html-parser:
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1
|
||||
sucrase:
|
||||
specifier: 3.35.1
|
||||
version: 3.35.1
|
||||
devDependencies:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: 0.71.2
|
||||
@@ -6258,9 +6255,6 @@ packages:
|
||||
any-base@1.1.0:
|
||||
resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==}
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -6957,10 +6951,6 @@ packages:
|
||||
commander@2.20.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -10628,9 +10618,6 @@ packages:
|
||||
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
nan@2.22.2:
|
||||
resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==}
|
||||
|
||||
@@ -11251,10 +11238,6 @@ packages:
|
||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pixelmatch@5.3.0:
|
||||
resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==}
|
||||
hasBin: true
|
||||
@@ -13153,11 +13136,6 @@ packages:
|
||||
stylis@4.3.6:
|
||||
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:
|
||||
resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==}
|
||||
engines: {node: '>=12.0'}
|
||||
@@ -13301,13 +13279,6 @@ packages:
|
||||
text-decoder@1.2.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==}
|
||||
engines: {node: '>=10.18'}
|
||||
@@ -13459,9 +13430,6 @@ packages:
|
||||
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
|
||||
engines: {node: '>=6.10'}
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
ts-loader@9.5.4:
|
||||
resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -15322,6 +15290,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
'@ckeditor/ckeditor5-watchdog': 47.3.0
|
||||
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)':
|
||||
dependencies:
|
||||
@@ -15827,6 +15797,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
'@ckeditor/ckeditor5-widget': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-mention@47.3.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)':
|
||||
dependencies:
|
||||
@@ -21177,8 +21149,6 @@ snapshots:
|
||||
|
||||
any-base@1.1.0: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
@@ -22127,8 +22097,6 @@ snapshots:
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
|
||||
commander@6.2.0: {}
|
||||
@@ -26848,12 +26816,6 @@ snapshots:
|
||||
mute-stream@2.0.0:
|
||||
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:
|
||||
optional: true
|
||||
|
||||
@@ -27511,8 +27473,6 @@ snapshots:
|
||||
pify@4.0.1:
|
||||
optional: true
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pixelmatch@5.3.0:
|
||||
dependencies:
|
||||
pngjs: 6.0.0
|
||||
@@ -29754,16 +29714,6 @@ snapshots:
|
||||
|
||||
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):
|
||||
dependencies:
|
||||
postcss: 8.5.6
|
||||
@@ -30000,14 +29950,6 @@ snapshots:
|
||||
dependencies:
|
||||
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):
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -30143,8 +30085,6 @@ snapshots:
|
||||
|
||||
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)):
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
|
||||
Reference in New Issue
Block a user