feat(client/jsx): support launcher widgets

This commit is contained in:
Elian Doran 2025-12-21 10:23:34 +02:00
parent f3f491d141
commit 783b5ac8e3
No known key found for this signature in database
2 changed files with 35 additions and 18 deletions

View File

@ -989,6 +989,10 @@ 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";
@ -996,7 +1000,7 @@ export default class FNote {
/** @returns JS script environment - either "frontend" or "backend" */
getScriptEnv() {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
return "frontend";
}
@ -1018,7 +1022,7 @@ export default class FNote {
* @returns a promise that resolves when the script has been run. Additionally, for front-end notes, the promise will contain the value that is returned by the script.
*/
async executeScript() {
if (!this.isJavaScript()) {
if (!(this.isJavaScript() || this.isJsx())) {
throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
}

View File

@ -1,17 +1,20 @@
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 { WidgetDefinition } 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);
@ -23,7 +26,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,
@ -75,7 +78,7 @@ export function ScriptLauncher({ launcherNote }: LauncherNoteProps) {
text={title}
onClick={launch}
/>
)
);
}
export function AiChatButton({ launcherNote }: LauncherNoteProps) {
@ -88,7 +91,7 @@ export function AiChatButton({ launcherNote }: LauncherNoteProps) {
text={title}
triggerCommand="createAiChat"
/>
)
);
}
export function TodayLauncher({ launcherNote }: LauncherNoteProps) {
@ -114,12 +117,13 @@ export function QuickSearchLauncherWidget() {
<div>
{isEnabled && <LegacyWidgetRenderer widget={widget} />}
</div>
)
);
}
export function CustomWidget({ launcherNote }: LauncherNoteProps) {
const [ widgetNote ] = useNoteRelationTarget(launcherNote, "widget");
const [ widget, setWidget ] = useState<BasicWidget>();
const [ widget, setWidget ] = useState<BasicWidget | NoteContextAwareWidget | WidgetDefinition>();
const parentComponent = useContext(ParentComponent) as BasicWidget | null;
parentComponent?.contentSized();
@ -146,9 +150,13 @@ export function CustomWidget({ launcherNote }: LauncherNoteProps) {
return (
<div>
{widget && <LegacyWidgetRenderer widget={widget} />}
{widget && (
("type" in widget && widget.type === "preact-widget")
? <ReactWidgetRenderer widget={widget as WidgetDefinition} />
: <LegacyWidgetRenderer widget={widget as BasicWidget} />
)}
</div>
)
);
}
export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
@ -158,3 +166,8 @@ export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
return widgetEl;
}
function ReactWidgetRenderer({ widget }: { widget: WidgetDefinition }) {
const El = widget.render;
return <El />;
}