From afe3904ea3647010b66415fe778082a57746d4d2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 20 Aug 2025 21:50:06 +0300 Subject: [PATCH] feat(react): render raw react components --- apps/client/src/desktop.ts | 1 - .../{layout_commons.ts => layout_commons.tsx} | 4 +- apps/client/src/widgets/basic_widget.ts | 36 +++++++++++++++- .../widgets/{note_title.ts => note_title.bak} | 0 apps/client/src/widgets/note_title.tsx | 7 ++++ apps/client/src/widgets/react/Modal.tsx | 2 +- .../src/widgets/react/ReactBasicWidget.tsx | 31 +------------- apps/client/src/widgets/react/hooks.tsx | 2 +- apps/client/src/widgets/react/react_utils.ts | 15 ------- apps/client/src/widgets/react/react_utils.tsx | 42 +++++++++++++++++++ .../ribbon_widgets/search_definition.ts | 2 +- .../client/src/widgets/type_widgets/canvas.ts | 2 +- .../widgets/type_widgets/content_widget.tsx | 2 +- 13 files changed, 92 insertions(+), 54 deletions(-) rename apps/client/src/layouts/{layout_commons.ts => layout_commons.tsx} (97%) rename apps/client/src/widgets/{note_title.ts => note_title.bak} (100%) create mode 100644 apps/client/src/widgets/note_title.tsx delete mode 100644 apps/client/src/widgets/react/react_utils.ts create mode 100644 apps/client/src/widgets/react/react_utils.tsx diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 2791f0577..57ff4084e 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -8,7 +8,6 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; import options from "./services/options.js"; -import server from "./services/server.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; diff --git a/apps/client/src/layouts/layout_commons.ts b/apps/client/src/layouts/layout_commons.tsx similarity index 97% rename from apps/client/src/layouts/layout_commons.ts rename to apps/client/src/layouts/layout_commons.tsx index 5ee261317..e3be51a3a 100644 --- a/apps/client/src/layouts/layout_commons.ts +++ b/apps/client/src/layouts/layout_commons.tsx @@ -25,12 +25,12 @@ import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js"; import PopupEditorDialog from "../widgets/dialogs/popup_editor.js"; import FlexContainer from "../widgets/containers/flex_container.js"; import NoteIconWidget from "../widgets/note_icon.js"; -import NoteTitleWidget from "../widgets/note_title.js"; import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; import NoteDetailWidget from "../widgets/note_detail.js"; import NoteListWidget from "../widgets/note_list.js"; import { CallToActionDialog } from "../widgets/dialogs/call_to_action.jsx"; +import NoteTitleWidget from "../widgets/note_title.jsx"; export function applyModals(rootContainer: RootContainer) { rootContainer @@ -62,7 +62,7 @@ export function applyModals(rootContainer: RootContainer) { .css("align-items", "center") .cssBlock(".title-row > * { margin: 5px; }") .child(new NoteIconWidget()) - .child(new NoteTitleWidget())) + .child()) .child(new ClassicEditorToolbar()) .child(new PromotedAttributesWidget()) .child(new NoteDetailWidget()) diff --git a/apps/client/src/widgets/basic_widget.ts b/apps/client/src/widgets/basic_widget.ts index be7b0bbd7..7b5c8e29c 100644 --- a/apps/client/src/widgets/basic_widget.ts +++ b/apps/client/src/widgets/basic_widget.ts @@ -1,7 +1,9 @@ +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 from "../services/toast.js"; +import { renderReactWidget } from "./react/react_utils.jsx"; export class TypedBasicWidget> extends TypedComponent { protected attrs: Record; @@ -22,11 +24,14 @@ export class TypedBasicWidget> extends TypedCompon this.childPositionCounter = 10; } - child(...components: T[]) { - if (!components) { + child(..._components: (T | VNode)[]) { + if (!_components) { return this; } + // Convert any React components to legacy wrapped components. + const components = wrapReactWidgets(_components); + super.child(...components); for (const component of components) { @@ -258,3 +263,30 @@ export class TypedBasicWidget> extends TypedCompon * For information on using widgets, see the tutorial {@tutorial widget_basics}. */ export default class BasicWidget extends TypedBasicWidget {} + +export function wrapReactWidgets>(components: (T | VNode)[]) { + const wrappedResult: T[] = []; + for (const component of components) { + if (isValidElement(component)) { + wrappedResult.push(new ReactWrappedWidget(component) as unknown as T); + } else { + wrappedResult.push(component); + } + } + return wrappedResult; +} + +class ReactWrappedWidget extends BasicWidget { + + private el: VNode; + + constructor(el: VNode) { + super(); + this.el = el; + } + + doRender() { + this.$widget = renderReactWidget(this, this.el); + } + +} diff --git a/apps/client/src/widgets/note_title.ts b/apps/client/src/widgets/note_title.bak similarity index 100% rename from apps/client/src/widgets/note_title.ts rename to apps/client/src/widgets/note_title.bak diff --git a/apps/client/src/widgets/note_title.tsx b/apps/client/src/widgets/note_title.tsx new file mode 100644 index 000000000..b1409d5e8 --- /dev/null +++ b/apps/client/src/widgets/note_title.tsx @@ -0,0 +1,7 @@ +export default function NoteTitleWidget() { + return ( + <> +

Hi

+ + ); +} diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx index 362d3ab38..02aeedd9b 100644 --- a/apps/client/src/widgets/react/Modal.tsx +++ b/apps/client/src/widgets/react/Modal.tsx @@ -3,7 +3,7 @@ import { t } from "../../services/i18n"; import { ComponentChildren } from "preact"; import type { CSSProperties, RefObject } from "preact/compat"; import { openDialog } from "../../services/dialog"; -import { ParentComponent } from "./ReactBasicWidget"; +import { ParentComponent } from "./react_utils"; import { Modal as BootstrapModal } from "bootstrap"; import { memo } from "preact/compat"; diff --git a/apps/client/src/widgets/react/ReactBasicWidget.tsx b/apps/client/src/widgets/react/ReactBasicWidget.tsx index b813be3bd..744d06736 100644 --- a/apps/client/src/widgets/react/ReactBasicWidget.tsx +++ b/apps/client/src/widgets/react/ReactBasicWidget.tsx @@ -1,9 +1,6 @@ -import { createContext, JSX, render } from "preact"; +import { JSX } from "preact"; import BasicWidget from "../basic_widget.js"; -import Component from "../../components/component.js"; - -export const ParentComponent = createContext(null); - +import { renderReactWidget } from "./react_utils.jsx"; export default abstract class ReactBasicWidget extends BasicWidget { abstract get component(): JSX.Element; @@ -13,27 +10,3 @@ export default abstract class ReactBasicWidget extends BasicWidget { } } - -/** - * Renders a React component and returns the corresponding DOM element wrapped in JQuery. - * - * @param parentComponent the parent Trilium component for the component to be able to handle events. - * @param el the JSX element to render. - * @returns the rendered wrapped DOM element. - */ -export function renderReactWidget(parentComponent: Component, el: JSX.Element) { - return renderReactWidgetAtElement(parentComponent, el, new DocumentFragment()).children(); -} - -export function renderReactWidgetAtElement(parentComponent: Component, el: JSX.Element, container: Element | DocumentFragment) { - render(( - - {el} - - ), container); - return $(container) as JQuery; -} - -export function disposeReactWidget(container: Element) { - render(null, container); -} \ No newline at end of file diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index d254b930c..adf6a8576 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { EventData, EventNames } from "../../components/app_context"; -import { ParentComponent } from "./ReactBasicWidget"; +import { ParentComponent } from "./react_utils"; import SpacedUpdate from "../../services/spaced_update"; import { OptionNames } from "@triliumnext/commons"; import options, { type OptionValue } from "../../services/options"; diff --git a/apps/client/src/widgets/react/react_utils.ts b/apps/client/src/widgets/react/react_utils.ts deleted file mode 100644 index 14357e124..000000000 --- a/apps/client/src/widgets/react/react_utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { RefObject } from "preact"; - -/** - * Takes in a React ref and returns a corresponding JQuery selector. - * - * @param ref the React ref from which to obtain the jQuery selector. - * @returns the corresponding jQuery selector. - */ -export function refToJQuerySelector(ref: RefObject | null): JQuery { - if (ref?.current) { - return $(ref.current); - } else { - return $(); - } -} diff --git a/apps/client/src/widgets/react/react_utils.tsx b/apps/client/src/widgets/react/react_utils.tsx new file mode 100644 index 000000000..e8b0752b4 --- /dev/null +++ b/apps/client/src/widgets/react/react_utils.tsx @@ -0,0 +1,42 @@ +import { createContext, render, type JSX, type RefObject } from "preact"; +import Component from "../../components/component"; + +export const ParentComponent = createContext(null); + +/** + * Takes in a React ref and returns a corresponding JQuery selector. + * + * @param ref the React ref from which to obtain the jQuery selector. + * @returns the corresponding jQuery selector. + */ +export function refToJQuerySelector(ref: RefObject | null): JQuery { + if (ref?.current) { + return $(ref.current); + } else { + return $(); + } +} + +/** + * Renders a React component and returns the corresponding DOM element wrapped in JQuery. + * + * @param parentComponent the parent Trilium component for the component to be able to handle events. + * @param el the JSX element to render. + * @returns the rendered wrapped DOM element. + */ +export function renderReactWidget(parentComponent: Component, el: JSX.Element) { + return renderReactWidgetAtElement(parentComponent, el, new DocumentFragment()).children(); +} + +export function renderReactWidgetAtElement(parentComponent: Component, el: JSX.Element, container: Element | DocumentFragment) { + render(( + + {el} + + ), container); + return $(container) as JQuery; +} + +export function disposeReactWidget(container: Element) { + render(null, container); +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/search_definition.ts b/apps/client/src/widgets/ribbon_widgets/search_definition.ts index 2439b590f..99ac91c50 100644 --- a/apps/client/src/widgets/ribbon_widgets/search_definition.ts +++ b/apps/client/src/widgets/ribbon_widgets/search_definition.ts @@ -19,7 +19,7 @@ import bulkActionService from "../../services/bulk_action.js"; import { Dropdown } from "bootstrap"; import type FNote from "../../entities/fnote.js"; import type { AttributeType } from "../../entities/fattribute.js"; -import { renderReactWidget } from "../react/ReactBasicWidget.jsx"; +import { renderReactWidget } from "../react/react_utils.jsx"; const TPL = /*html*/`
diff --git a/apps/client/src/widgets/type_widgets/canvas.ts b/apps/client/src/widgets/type_widgets/canvas.ts index e81e8a36f..42314f055 100644 --- a/apps/client/src/widgets/type_widgets/canvas.ts +++ b/apps/client/src/widgets/type_widgets/canvas.ts @@ -6,7 +6,7 @@ import type { LibraryItem } from "@excalidraw/excalidraw/types"; import type { Theme } from "@excalidraw/excalidraw/element/types"; import type Canvas from "./canvas_el.js"; import { CanvasContent } from "./canvas_el.js"; -import { renderReactWidget } from "../react/ReactBasicWidget.jsx"; +import { renderReactWidget } from "../react/react_utils.jsx"; const TPL = /*html*/`
diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index be803018f..98175a0c8 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -5,7 +5,7 @@ import { t } from "../../services/i18n.js"; import type BasicWidget from "../basic_widget.js"; import type { JSX } from "preact/jsx-runtime"; import AppearanceSettings from "./options/appearance.jsx"; -import { disposeReactWidget, renderReactWidget, renderReactWidgetAtElement } from "../react/ReactBasicWidget.jsx"; +import { disposeReactWidget, renderReactWidgetAtElement } from "../react/react_utils.jsx"; import ImageSettings from "./options/images.jsx"; import AdvancedSettings from "./options/advanced.jsx"; import InternationalizationOptions from "./options/i18n.jsx";