From 660db3b3ab4ff97ba173594092a737bab656cdc8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 25 Aug 2025 11:48:56 +0300 Subject: [PATCH] chore(react): prototype for note context --- apps/client/src/components/component.ts | 2 +- apps/client/src/widgets/basic_widget.ts | 33 ++++++++++++++++--- apps/client/src/widgets/note_title.tsx | 12 ++++--- .../src/widgets/react/ReactBasicWidget.tsx | 5 ++- apps/client/src/widgets/react/hooks.tsx | 9 +++-- apps/client/src/widgets/react/react_utils.tsx | 19 +++++++---- 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/apps/client/src/components/component.ts b/apps/client/src/components/component.ts index 9a59b96be..517539d0f 100644 --- a/apps/client/src/components/component.ts +++ b/apps/client/src/components/component.ts @@ -15,7 +15,7 @@ type EventHandler = ((data: any) => void); * event / command is executed in all components - by simply awaiting the `triggerEvent()`. */ export class TypedComponent> { - $widget!: JQuery; + $widget!: JQuery; componentId: string; children: ChildT[]; initialized: Promise | null; diff --git a/apps/client/src/widgets/basic_widget.ts b/apps/client/src/widgets/basic_widget.ts index f49f2382c..409482c5f 100644 --- a/apps/client/src/widgets/basic_widget.ts +++ b/apps/client/src/widgets/basic_widget.ts @@ -3,9 +3,9 @@ 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"; -import { EventNames, EventData } from "../components/app_context.js"; -import { Handler } from "leaflet"; +import { renderReactWidgetAtElement } from "./react/react_utils.jsx"; +import { type default as NoteContextType } from "../../components/note_context"; +import { EventData } from "../components/app_context.js"; export class TypedBasicWidget> extends TypedComponent { protected attrs: Record; @@ -281,6 +281,8 @@ export function wrapReactWidgets>(components: (T | export class ReactWrappedWidget extends BasicWidget { private el: VNode; + private noteContext: NoteContextType | null; + private fragment: DocumentFragment = new DocumentFragment(); constructor(el: VNode) { super(); @@ -288,7 +290,30 @@ export class ReactWrappedWidget extends BasicWidget { } doRender() { - this.$widget = renderReactWidget(this, this.el); + renderReactWidgetAtElement({ + parentComponent: this, + noteContext: this.noteContext + }, this.el, this.fragment); + this.$widget = $(this.fragment); + } + + activeContextChangedEvent({ noteContext }: EventData<"activeContextChanged">) { + this.noteContext = noteContext; + this.doRender(); + } + + setNoteContextEvent({ noteContext }: EventData<"setNoteContext">) { + this.noteContext = noteContext; + this.doRender(); + } + + noteSwitchedAndActivatedEvent({ noteContext }: EventData<"noteSwitchedAndActivated">) { + this.noteContext = noteContext; + this.doRender(); + } + + noteSwitchedEvent({ noteContext }: EventData<"noteSwitched">) { + this.doRender(); } } diff --git a/apps/client/src/widgets/note_title.tsx b/apps/client/src/widgets/note_title.tsx index e6b43051b..fe1cc433b 100644 --- a/apps/client/src/widgets/note_title.tsx +++ b/apps/client/src/widgets/note_title.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "preact/hooks"; +import { useContext, useEffect, useRef, useState } from "preact/hooks"; import { t } from "../services/i18n"; import FormTextBox from "./react/FormTextBox"; import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent } from "./react/hooks"; @@ -8,9 +8,13 @@ import "./note_title.css"; import { isLaunchBarConfig } from "../services/utils"; import appContext from "../components/app_context"; import branches from "../services/branches"; +import { NoteContext, ParentComponent } from "./react/react_utils"; export default function NoteTitleWidget() { - const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext(); + const parentComponent = useContext(ParentComponent); + const noteContext = useContext(NoteContext); + const { noteId, note, componentId, viewScope } = noteContext ?? {}; + const title = useNoteProperty(note, "title", componentId); const isProtected = useNoteProperty(note, "isProtected"); const newTitle = useRef(""); @@ -31,7 +35,7 @@ export default function NoteTitleWidget() { // Manage the title for read-only notes useEffect(() => { if (isReadOnly) { - noteContext?.getNavigationTitle().then(setNavigationTitle); + noteContext?.getNavigationTitle?.().then(setNavigationTitle); } }, [isReadOnly]); @@ -78,7 +82,7 @@ export default function NoteTitleWidget() { // Focus on the note content when pressing enter. if (e.key === "Enter") { e.preventDefault(); - parentComponent.triggerCommand("focusOnDetail", { ntxId: noteContext?.ntxId }); + parentComponent?.triggerCommand("focusOnDetail", { ntxId: noteContext?.ntxId }); return; } diff --git a/apps/client/src/widgets/react/ReactBasicWidget.tsx b/apps/client/src/widgets/react/ReactBasicWidget.tsx index 744d06736..702d55bda 100644 --- a/apps/client/src/widgets/react/ReactBasicWidget.tsx +++ b/apps/client/src/widgets/react/ReactBasicWidget.tsx @@ -6,7 +6,10 @@ export default abstract class ReactBasicWidget extends BasicWidget { abstract get component(): JSX.Element; doRender() { - this.$widget = renderReactWidget(this, this.component); + this.$widget = renderReactWidget({ + parentComponent: this, + noteContext: null + }, this.component); } } diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 483f826dd..f93de9d38 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -17,9 +17,13 @@ import { CSSProperties } from "preact/compat"; export function useTriliumEvent(eventName: T, handler: (data: EventData) => void) { const parentComponent = useContext(ParentComponent)!; - parentComponent.registerHandler(eventName, handler); + + useEffect(() => { + parentComponent.registerHandler(eventName, handler); + return (() => parentComponent.removeHandler(eventName, handler)); + }, [eventName, handler]); + useDebugValue(eventName); - return (() => parentComponent.removeHandler(eventName, handler)); } export function useTriliumEvents(eventNames: T[], handler: (data: EventData, eventName: T) => void) { @@ -199,7 +203,6 @@ export function useNoteContext() { }, [ notePath ]); useTriliumEvent("activeContextChanged", ({ noteContext }) => { - setNoteContext(noteContext); setNotePath(noteContext.notePath); }); useTriliumEvent("setNoteContext", ({ noteContext }) => { diff --git a/apps/client/src/widgets/react/react_utils.tsx b/apps/client/src/widgets/react/react_utils.tsx index ebb4c6156..178a12712 100644 --- a/apps/client/src/widgets/react/react_utils.tsx +++ b/apps/client/src/widgets/react/react_utils.tsx @@ -1,9 +1,14 @@ import { ComponentChild, createContext, render, type JSX, type RefObject } from "preact"; import Component from "../../components/component"; -import { EventData, EventNames } from "../../components/app_context"; -import { useContext } from "preact/hooks"; +import { type default as NoteContextType } from "../../components/note_context"; export const ParentComponent = createContext(null); +export const NoteContext = createContext(null); + +interface ComponentContext { + parentComponent: Component | null; + noteContext: NoteContextType | null; +} /** * Takes in a React ref and returns a corresponding JQuery selector. @@ -26,14 +31,16 @@ export function refToJQuerySelector(ref: RefObject | n * @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 renderReactWidget(context: ComponentContext, el: JSX.Element) { + return renderReactWidgetAtElement(context, el, new DocumentFragment()).children(); } -export function renderReactWidgetAtElement(parentComponent: Component, el: JSX.Element, container: Element | DocumentFragment) { +export function renderReactWidgetAtElement({ parentComponent, noteContext }: ComponentContext, el: JSX.Element, container: Element | DocumentFragment) { render(( - {el} + + {el} + ), container); return $(container) as JQuery;