diff --git a/apps/client/src/components/component.ts b/apps/client/src/components/component.ts index 8686a7bb9..9a59b96be 100644 --- a/apps/client/src/components/component.ts +++ b/apps/client/src/components/component.ts @@ -1,6 +1,8 @@ import utils from "../services/utils.js"; import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.js"; +type EventHandler = ((data: any) => void); + /** * Abstract class for all components in the Trilium's frontend. * @@ -19,6 +21,7 @@ export class TypedComponent> { initialized: Promise | null; parent?: TypedComponent; _position!: number; + private listeners: Record | null = {}; constructor() { this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; @@ -76,6 +79,14 @@ export class TypedComponent> { handleEventInChildren(name: T, data: EventData): Promise | null { const promises: Promise[] = []; + // Handle React children. + if (this.listeners?.[name]) { + for (const listener of this.listeners[name]) { + listener(data); + } + } + + // Handle legacy children. for (const child of this.children) { const ret = child.handleEvent(name, data) as Promise; @@ -120,6 +131,35 @@ export class TypedComponent> { return promise; } + + registerHandler(name: T, handler: EventHandler) { + if (!this.listeners) { + this.listeners = {}; + } + + if (!this.listeners[name]) { + this.listeners[name] = []; + } + + if (this.listeners[name].includes(handler)) { + return; + } + + this.listeners[name].push(handler); + } + + removeHandler(name: T, handler: EventHandler) { + if (!this.listeners?.[name]?.includes(handler)) { + return; + } + + this.listeners[name] = this.listeners[name] + .filter(listener => listener !== handler); + + if (!this.listeners[name].length) { + delete this.listeners[name]; + } + } } export default class Component extends TypedComponent {} diff --git a/apps/client/src/widgets/basic_widget.ts b/apps/client/src/widgets/basic_widget.ts index b783e7a21..f49f2382c 100644 --- a/apps/client/src/widgets/basic_widget.ts +++ b/apps/client/src/widgets/basic_widget.ts @@ -278,12 +278,9 @@ export function wrapReactWidgets>(components: (T | return wrappedResult; } -type EventHandler = ((data: any) => void); - export class ReactWrappedWidget extends BasicWidget { private el: VNode; - private listeners: Record = {}; constructor(el: VNode) { super(); @@ -294,41 +291,4 @@ export class ReactWrappedWidget extends BasicWidget { this.$widget = renderReactWidget(this, this.el); } - handleEvent(name: T, data: EventData): Promise | null | undefined { - if (!this.listeners[name]) { - return; - } - - for (const listener of this.listeners[name]) { - listener(data); - } - - super.handleEvent(name, data); - } - - registerHandler(name: T, handler: EventHandler) { - if (!this.listeners[name]) { - this.listeners[name] = []; - } - - if (this.listeners[name].includes(handler)) { - return; - } - - this.listeners[name].push(handler); - } - - removeHandler(name: T, handler: EventHandler) { - if (!this.listeners[name]?.includes(handler)) { - return; - } - - this.listeners[name] = this.listeners[name] - .filter(listener => listener !== handler); - - if (!this.listeners[name].length) { - delete this.listeners[name]; - } - } - } diff --git a/apps/client/src/widgets/dialogs/about.tsx b/apps/client/src/widgets/dialogs/about.tsx index ec12de3ab..2d0d67316 100644 --- a/apps/client/src/widgets/dialogs/about.tsx +++ b/apps/client/src/widgets/dialogs/about.tsx @@ -7,14 +7,14 @@ import openService from "../../services/open.js"; import { useState } from "preact/hooks"; import type { CSSProperties } from "preact/compat"; import type { AppInfo } from "@triliumnext/commons"; -import { useTriliumEventBeta } from "../react/hooks.jsx"; +import { useTriliumEvent } from "../react/hooks.jsx"; export default function AboutDialog() { let [appInfo, setAppInfo] = useState(null); let [shown, setShown] = useState(false); const forceWordBreak: CSSProperties = { wordBreak: "break-all" }; - useTriliumEventBeta("openAboutDialog", () => setShown(true)); + useTriliumEvent("openAboutDialog", () => setShown(true)); return ( (null); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showAddLinkDialog", ( { textTypeWidget, text }) => { + useTriliumEvent("showAddLinkDialog", ( { textTypeWidget, text }) => { setTextTypeWidget(textTypeWidget); initialText.current = text; setShown(true); diff --git a/apps/client/src/widgets/dialogs/branch_prefix.tsx b/apps/client/src/widgets/dialogs/branch_prefix.tsx index 849988ad8..f04280748 100644 --- a/apps/client/src/widgets/dialogs/branch_prefix.tsx +++ b/apps/client/src/widgets/dialogs/branch_prefix.tsx @@ -8,7 +8,7 @@ import froca from "../../services/froca.js"; import tree from "../../services/tree.js"; import Button from "../react/Button.jsx"; import FormGroup from "../react/FormGroup.js"; -import { useTriliumEventBeta } from "../react/hooks.jsx"; +import { useTriliumEvent } from "../react/hooks.jsx"; import FBranch from "../../entities/fbranch.js"; export default function BranchPrefixDialog() { @@ -17,7 +17,7 @@ export default function BranchPrefixDialog() { const [ prefix, setPrefix ] = useState(branch?.prefix ?? ""); const branchInput = useRef(null); - useTriliumEventBeta("editBranchPrefix", async () => { + useTriliumEvent("editBranchPrefix", async () => { const notePath = appContext.tabManager.getActiveContextNotePath(); if (!notePath) { return; diff --git a/apps/client/src/widgets/dialogs/bulk_actions.tsx b/apps/client/src/widgets/dialogs/bulk_actions.tsx index 25b490218..1d0f5a779 100644 --- a/apps/client/src/widgets/dialogs/bulk_actions.tsx +++ b/apps/client/src/widgets/dialogs/bulk_actions.tsx @@ -12,7 +12,7 @@ import toast from "../../services/toast"; import RenameNoteBulkAction from "../bulk_actions/note/rename_note"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function BulkActionsDialog() { const [ selectedOrActiveNoteIds, setSelectedOrActiveNoteIds ] = useState(); @@ -22,7 +22,7 @@ export default function BulkActionsDialog() { const [ existingActions, setExistingActions ] = useState([]); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("openBulkActionsDialog", async ({ selectedOrActiveNoteIds }) => { + useTriliumEvent("openBulkActionsDialog", async ({ selectedOrActiveNoteIds }) => { setSelectedOrActiveNoteIds(selectedOrActiveNoteIds); setBulkActionNote(await froca.getNote("_bulkAction")); setShown(true); @@ -46,7 +46,7 @@ export default function BulkActionsDialog() { refreshExistingActions(); }, [refreshExistingActions]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows().find((row) => row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) { refreshExistingActions(); diff --git a/apps/client/src/widgets/dialogs/clone_to.tsx b/apps/client/src/widgets/dialogs/clone_to.tsx index f6e538b0b..568dec63e 100644 --- a/apps/client/src/widgets/dialogs/clone_to.tsx +++ b/apps/client/src/widgets/dialogs/clone_to.tsx @@ -13,7 +13,7 @@ import tree from "../../services/tree"; import branches from "../../services/branches"; import toast from "../../services/toast"; import NoteList from "../react/NoteList"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function CloneToDialog() { const [ clonedNoteIds, setClonedNoteIds ] = useState(); @@ -22,7 +22,7 @@ export default function CloneToDialog() { const [ shown, setShown ] = useState(false); const autoCompleteRef = useRef(null); - useTriliumEventBeta("cloneNoteIdsTo", ({ noteIds }) => { + useTriliumEvent("cloneNoteIdsTo", ({ noteIds }) => { if (!noteIds || noteIds.length === 0) { noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""]; } diff --git a/apps/client/src/widgets/dialogs/confirm.tsx b/apps/client/src/widgets/dialogs/confirm.tsx index 6e83bebff..456f11ad2 100644 --- a/apps/client/src/widgets/dialogs/confirm.tsx +++ b/apps/client/src/widgets/dialogs/confirm.tsx @@ -3,7 +3,7 @@ import Button from "../react/Button"; import { t } from "../../services/i18n"; import { useState } from "preact/hooks"; import FormCheckbox from "../react/FormCheckbox"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; interface ConfirmDialogProps { title?: string; @@ -27,8 +27,8 @@ export default function ConfirmDialog() { setShown(true); } - useTriliumEventBeta("showConfirmDialog", ({ message, callback }) => showDialog(null, message, callback, false)); - useTriliumEventBeta("showConfirmDeleteNoteBoxWithNoteDialog", ({ title, callback }) => showDialog(title, t("confirm.are_you_sure_remove_note", { title: title }), callback, true)); + useTriliumEvent("showConfirmDialog", ({ message, callback }) => showDialog(null, message, callback, false)); + useTriliumEvent("showConfirmDeleteNoteBoxWithNoteDialog", ({ title, callback }) => showDialog(title, t("confirm.are_you_sure_remove_note", { title: title }), callback, true)); return ( (null); - useTriliumEventBeta("showDeleteNotesDialog", (opts) => { + useTriliumEvent("showDeleteNotesDialog", (opts) => { setOpts(opts); setShown(true); }) diff --git a/apps/client/src/widgets/dialogs/export.tsx b/apps/client/src/widgets/dialogs/export.tsx index 26ca4d537..b13622d67 100644 --- a/apps/client/src/widgets/dialogs/export.tsx +++ b/apps/client/src/widgets/dialogs/export.tsx @@ -11,7 +11,7 @@ import toastService, { ToastOptions } from "../../services/toast"; import utils from "../../services/utils"; import open from "../../services/open"; import froca from "../../services/froca"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; interface ExportDialogProps { branchId?: string | null; @@ -27,7 +27,7 @@ export default function ExportDialog() { const [ opmlVersion, setOpmlVersion ] = useState("2.0"); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showExportDialog", async ({ notePath, defaultType }) => { + useTriliumEvent("showExportDialog", async ({ notePath, defaultType }) => { const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath); if (!parentNoteId) { return; diff --git a/apps/client/src/widgets/dialogs/help.tsx b/apps/client/src/widgets/dialogs/help.tsx index 474b70745..d5c2f695d 100644 --- a/apps/client/src/widgets/dialogs/help.tsx +++ b/apps/client/src/widgets/dialogs/help.tsx @@ -5,11 +5,11 @@ import { CommandNames } from "../../components/app_context.js"; import RawHtml from "../react/RawHtml.jsx"; import { useEffect, useState } from "preact/hooks"; import keyboard_actions from "../../services/keyboard_actions.js"; -import { useTriliumEventBeta } from "../react/hooks.jsx"; +import { useTriliumEvent } from "../react/hooks.jsx"; export default function HelpDialog() { const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showCheatsheet", () => setShown(true)); + useTriliumEvent("showCheatsheet", () => setShown(true)); return ( (); @@ -23,7 +23,7 @@ export default function ImportDialog() { const [ replaceUnderscoresWithSpaces, setReplaceUnderscoresWithSpaces ] = useState(true); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showImportDialog", ({ noteId }) => { + useTriliumEvent("showImportDialog", ({ noteId }) => { setParentNoteId(noteId); tree.getNoteTitle(noteId).then(setNoteTitle); setShown(true); diff --git a/apps/client/src/widgets/dialogs/include_note.tsx b/apps/client/src/widgets/dialogs/include_note.tsx index 49c650521..757d23829 100644 --- a/apps/client/src/widgets/dialogs/include_note.tsx +++ b/apps/client/src/widgets/dialogs/include_note.tsx @@ -9,7 +9,7 @@ import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete import tree from "../../services/tree"; import froca from "../../services/froca"; import EditableTextTypeWidget from "../type_widgets/editable_text"; -import useTriliumEvent from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function IncludeNoteDialog() { const [textTypeWidget, setTextTypeWidget] = useState(); diff --git a/apps/client/src/widgets/dialogs/incorrect_cpu_arch.tsx b/apps/client/src/widgets/dialogs/incorrect_cpu_arch.tsx index 112d7efc8..438cff1f3 100644 --- a/apps/client/src/widgets/dialogs/incorrect_cpu_arch.tsx +++ b/apps/client/src/widgets/dialogs/incorrect_cpu_arch.tsx @@ -4,12 +4,12 @@ import utils from "../../services/utils.js"; import Button from "../react/Button.js"; import Modal from "../react/Modal.js"; import { useState } from "preact/hooks"; -import { useTriliumEventBeta } from "../react/hooks.jsx"; +import { useTriliumEvent } from "../react/hooks.jsx"; export default function IncorrectCpuArchDialogComponent() { const [ shown, setShown ] = useState(false); const downloadButtonRef = useRef(null); - useTriliumEventBeta("showCpuArchWarning", () => setShown(true)); + useTriliumEvent("showCpuArchWarning", () => setShown(true)); return ( >(); const [ shown, setShown ] = useState(false); const okButtonRef = useRef(null); - useTriliumEventBeta("showInfoDialog", (opts) => { + useTriliumEvent("showInfoDialog", (opts) => { setOpts(opts); setShown(true); }); diff --git a/apps/client/src/widgets/dialogs/jump_to_note.tsx b/apps/client/src/widgets/dialogs/jump_to_note.tsx index 20d0c3e2e..6bb9c81a5 100644 --- a/apps/client/src/widgets/dialogs/jump_to_note.tsx +++ b/apps/client/src/widgets/dialogs/jump_to_note.tsx @@ -7,7 +7,7 @@ import note_autocomplete, { Suggestion } from "../../services/note_autocomplete" import appContext from "../../components/app_context"; import commandRegistry from "../../services/command_registry"; import { refToJQuerySelector } from "../react/react_utils"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120; @@ -50,8 +50,8 @@ export default function JumpToNoteDialogComponent() { setLastOpenedTs(Date.now()); } - useTriliumEventBeta("jumpToNote", () => openDialog(false)); - useTriliumEventBeta("commandPalette", () => openDialog(true)); + useTriliumEvent("jumpToNote", () => openDialog(false)); + useTriliumEvent("commandPalette", () => openDialog(true)); async function onItemSelected(suggestion?: Suggestion | null) { if (!suggestion) { diff --git a/apps/client/src/widgets/dialogs/markdown_import.tsx b/apps/client/src/widgets/dialogs/markdown_import.tsx index 8049e98da..5fc8584ad 100644 --- a/apps/client/src/widgets/dialogs/markdown_import.tsx +++ b/apps/client/src/widgets/dialogs/markdown_import.tsx @@ -6,7 +6,7 @@ import toast from "../../services/toast"; import utils from "../../services/utils"; import Modal from "../react/Modal"; import Button from "../react/Button"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; interface RenderMarkdownResponse { htmlContent: string; @@ -32,8 +32,8 @@ export default function MarkdownImportDialog() { } }, []); - useTriliumEventBeta("importMarkdownInline", triggerImport); - useTriliumEventBeta("pasteMarkdownIntoText", triggerImport); + useTriliumEvent("importMarkdownInline", triggerImport); + useTriliumEvent("pasteMarkdownIntoText", triggerImport); async function sendForm() { await convertMarkdownToHtml(text); diff --git a/apps/client/src/widgets/dialogs/move_to.tsx b/apps/client/src/widgets/dialogs/move_to.tsx index 92cd20c37..d0d3f913b 100644 --- a/apps/client/src/widgets/dialogs/move_to.tsx +++ b/apps/client/src/widgets/dialogs/move_to.tsx @@ -11,7 +11,7 @@ import tree from "../../services/tree"; import froca from "../../services/froca"; import branches from "../../services/branches"; import toast from "../../services/toast"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function MoveToDialog() { const [ movedBranchIds, setMovedBranchIds ] = useState(); @@ -19,7 +19,7 @@ export default function MoveToDialog() { const [ shown, setShown ] = useState(false); const autoCompleteRef = useRef(null); - useTriliumEventBeta("moveBranchIdsTo", ({ branchIds }) => { + useTriliumEvent("moveBranchIdsTo", ({ branchIds }) => { setMovedBranchIds(branchIds); setShown(true); }); diff --git a/apps/client/src/widgets/dialogs/note_type_chooser.tsx b/apps/client/src/widgets/dialogs/note_type_chooser.tsx index 70cf9a7d8..19430b2cb 100644 --- a/apps/client/src/widgets/dialogs/note_type_chooser.tsx +++ b/apps/client/src/widgets/dialogs/note_type_chooser.tsx @@ -9,7 +9,7 @@ import { MenuCommandItem, MenuItem } from "../../menus/context_menu"; import { TreeCommandNames } from "../../menus/tree_context_menu"; import { Suggestion } from "../../services/note_autocomplete"; import Badge from "../react/Badge"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export interface ChooseNoteTypeResponse { success: boolean; @@ -31,7 +31,7 @@ export default function NoteTypeChooserDialogComponent() { const [ parentNote, setParentNote ] = useState(); const [ noteTypes, setNoteTypes ] = useState[]>([]); - useTriliumEventBeta("chooseNoteType", ({ callback }) => { + useTriliumEvent("chooseNoteType", ({ callback }) => { setCallback(() => callback); setShown(true); }); diff --git a/apps/client/src/widgets/dialogs/password_not_set.tsx b/apps/client/src/widgets/dialogs/password_not_set.tsx index bc225f798..5d0b92e5c 100644 --- a/apps/client/src/widgets/dialogs/password_not_set.tsx +++ b/apps/client/src/widgets/dialogs/password_not_set.tsx @@ -3,11 +3,11 @@ import { t } from "../../services/i18n"; import Button from "../react/Button"; import appContext from "../../components/app_context"; import { useState } from "preact/hooks"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function PasswordNotSetDialog() { const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showPasswordNotSet", () => setShown(true)); + useTriliumEvent("showPasswordNotSet", () => setShown(true)); return ( (null); - useTriliumEventBeta("showPromptDialog", (newOpts) => { + useTriliumEvent("showPromptDialog", (newOpts) => { opts.current = newOpts; setValue(newOpts.defaultValue ?? ""); setShown(true); diff --git a/apps/client/src/widgets/dialogs/protected_session_password.tsx b/apps/client/src/widgets/dialogs/protected_session_password.tsx index afbd4fdff..1584a1ae1 100644 --- a/apps/client/src/widgets/dialogs/protected_session_password.tsx +++ b/apps/client/src/widgets/dialogs/protected_session_password.tsx @@ -4,15 +4,15 @@ import Button from "../react/Button"; import FormTextBox from "../react/FormTextBox"; import Modal from "../react/Modal"; import protected_session from "../../services/protected_session"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function ProtectedSessionPasswordDialog() { const [ shown, setShown ] = useState(false); const [ password, setPassword ] = useState(""); const inputRef = useRef(null); - useTriliumEventBeta("showProtectedSessionPasswordDialog", () => setShown(true)); - useTriliumEventBeta("closeProtectedSessionPasswordDialog", () => setShown(false)); + useTriliumEvent("showProtectedSessionPasswordDialog", () => setShown(true)); + useTriliumEvent("closeProtectedSessionPasswordDialog", () => setShown(false)); return ( (); @@ -21,7 +21,7 @@ export default function RecentChangesDialog() { const [ needsRefresh, setNeedsRefresh ] = useState(false); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showRecentChanges", ({ ancestorNoteId }) => { + useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => { setNeedsRefresh(true); setAncestorNoteId(ancestorNoteId ?? hoisted_note.getHoistedNoteId()); setShown(true); diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index 77d019814..fd94ec130 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -8,7 +8,6 @@ import server from "../../services/server"; import toast from "../../services/toast"; import Button from "../react/Button"; import Modal from "../react/Modal"; -import ReactBasicWidget from "../react/ReactBasicWidget"; import FormList, { FormListItem } from "../react/FormList"; import utils from "../../services/utils"; import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; @@ -18,7 +17,7 @@ import type { CSSProperties } from "preact/compat"; import open from "../../services/open"; import ActionButton from "../react/ActionButton"; import options from "../../services/options"; -import useTriliumEvent from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function RevisionsDialog() { const [ note, setNote ] = useState(); diff --git a/apps/client/src/widgets/dialogs/sort_child_notes.tsx b/apps/client/src/widgets/dialogs/sort_child_notes.tsx index 522096a12..10b69806c 100644 --- a/apps/client/src/widgets/dialogs/sort_child_notes.tsx +++ b/apps/client/src/widgets/dialogs/sort_child_notes.tsx @@ -7,7 +7,7 @@ import FormTextBox from "../react/FormTextBox"; import Modal from "../react/Modal"; import server from "../../services/server"; import FormGroup from "../react/FormGroup"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function SortChildNotesDialog() { const [ parentNoteId, setParentNoteId ] = useState(); @@ -18,7 +18,7 @@ export default function SortChildNotesDialog() { const [ sortLocale, setSortLocale ] = useState(""); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("sortChildNotes", ({ node }) => { + useTriliumEvent("sortChildNotes", ({ node }) => { setParentNoteId(node.data.noteId); setShown(true); }); diff --git a/apps/client/src/widgets/dialogs/upload_attachments.tsx b/apps/client/src/widgets/dialogs/upload_attachments.tsx index fb18efa36..6af9bd35b 100644 --- a/apps/client/src/widgets/dialogs/upload_attachments.tsx +++ b/apps/client/src/widgets/dialogs/upload_attachments.tsx @@ -9,7 +9,7 @@ import ReactBasicWidget from "../react/ReactBasicWidget"; import options from "../../services/options"; import importService from "../../services/import.js"; import tree from "../../services/tree"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function UploadAttachmentsDialog() { const [ parentNoteId, setParentNoteId ] = useState(); @@ -19,7 +19,7 @@ export default function UploadAttachmentsDialog() { const [ description, setDescription ] = useState(undefined); const [ shown, setShown ] = useState(false); - useTriliumEventBeta("showUploadAttachmentsDialog", ({ noteId }) => { + useTriliumEvent("showUploadAttachmentsDialog", ({ noteId }) => { setParentNoteId(noteId); setShown(true); }); diff --git a/apps/client/src/widgets/note_title.tsx b/apps/client/src/widgets/note_title.tsx index adef54bc9..e6b43051b 100644 --- a/apps/client/src/widgets/note_title.tsx +++ b/apps/client/src/widgets/note_title.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { t } from "../services/i18n"; import FormTextBox from "./react/FormTextBox"; -import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEventBeta } from "./react/hooks"; +import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent } from "./react/hooks"; import protected_session_holder from "../services/protected_session_holder"; import server from "../services/server"; import "./note_title.css"; @@ -48,12 +48,12 @@ export default function NoteTitleWidget() { useEffect(() => { appContext.addBeforeUnloadListener(() => spacedUpdate.isAllSavedAndTriggerUpdate()); }, []); - useTriliumEventBeta([ "beforeNoteSwitch", "beforeNoteContextRemove" ], () => spacedUpdate.updateNowIfNecessary()); + useTriliumEvent([ "beforeNoteSwitch", "beforeNoteContextRemove" ], () => spacedUpdate.updateNowIfNecessary()); // Manage focus. const textBoxRef = useRef(null); const isNewNote = useRef(); - useTriliumEventBeta([ "focusOnTitle", "focusAndSelectTitle" ], (e) => { + useTriliumEvent([ "focusOnTitle", "focusAndSelectTitle" ], (e) => { if (noteContext?.isActive() && textBoxRef.current) { textBoxRef.current.focus(); isNewNote.current = ("isNewNote" in e ? e.isNewNote : false); diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 042163531..3d62b3985 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -5,94 +5,25 @@ import SpacedUpdate from "../../services/spaced_update"; import { OptionNames } from "@triliumnext/commons"; import options, { type OptionValue } from "../../services/options"; import utils, { reloadFrontendApp } from "../../services/utils"; -import Component from "../../components/component"; import NoteContext from "../../components/note_context"; import BasicWidget, { ReactWrappedWidget } from "../basic_widget"; import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; import FBlob from "../../entities/fblob"; import NoteContextAwareWidget from "../note_context_aware_widget"; -import { Ref, RefObject, VNode } from "preact"; +import { RefObject, VNode } from "preact"; import { Tooltip } from "bootstrap"; import { CSSProperties } from "preact/compat"; type TriliumEventHandler = (data: EventData) => void; -const registeredHandlers: Map[]>> = new Map(); -/** - * Allows a React component to react to Trilium events (e.g. `entitiesReloaded`). When the desired event is triggered, the handler is invoked with the event parameters. - * - * Under the hood, it works by altering the parent (Trilium) component of the React element to introduce the corresponding event. - * - * @param eventName the name of the Trilium event to listen for. - * @param handler the handler to be invoked when the event is triggered. - * @param enabled determines whether the event should be listened to or not. Useful to conditionally limit the listener based on a state (e.g. a modal being displayed). - */ -export default function useTriliumEvent(eventName: T, handler: TriliumEventHandler, enabled = true) { - const parentWidget = useContext(ParentComponent); - if (!parentWidget) { +export function useTriliumEvent(eventName: T | T[], handler: TriliumEventHandler) { + const parentComponent = useContext(ParentComponent); + + if (!parentComponent) { + console.error("React widget has no legacy parent component. Event handling will not work.", new Error().stack); return; } - - const handlerName = `${eventName}Event`; - const customHandler = useMemo(() => { - return async (data: EventData) => { - // Inform the attached event listeners. - const eventHandlers = registeredHandlers.get(parentWidget)?.get(eventName) ?? []; - for (const eventHandler of eventHandlers) { - eventHandler(data); - } - } - }, [ eventName, parentWidget ]); - - useEffect(() => { - // Attach to the list of handlers. - let handlersByWidget = registeredHandlers.get(parentWidget); - if (!handlersByWidget) { - handlersByWidget = new Map(); - registeredHandlers.set(parentWidget, handlersByWidget); - } - - let handlersByWidgetAndEventName = handlersByWidget.get(eventName); - if (!handlersByWidgetAndEventName) { - handlersByWidgetAndEventName = []; - handlersByWidget.set(eventName, handlersByWidgetAndEventName); - } - - if (!handlersByWidgetAndEventName.includes(handler)) { - handlersByWidgetAndEventName.push(handler); - } - - // Apply the custom event handler. - if (parentWidget[handlerName] && parentWidget[handlerName] !== customHandler) { - console.warn(`Widget ${parentWidget.componentId} already had an event listener and it was replaced by the React one.`); - } - - parentWidget[handlerName] = customHandler; - - return () => { - const eventHandlers = registeredHandlers.get(parentWidget)?.get(eventName); - if (!eventHandlers || !eventHandlers.includes(handler)) { - return; - } - - // Remove the event handler from the array. - const newEventHandlers = eventHandlers.filter(e => e !== handler); - if (newEventHandlers.length) { - registeredHandlers.get(parentWidget)?.set(eventName, newEventHandlers); - } else { - registeredHandlers.get(parentWidget)?.delete(eventName); - } - - if (!registeredHandlers.get(parentWidget)?.size) { - registeredHandlers.delete(parentWidget); - } - }; - }, [ eventName, parentWidget, handler ]); -} - -export function useTriliumEventBeta(eventName: T | T[], handler: TriliumEventHandler) { - const parentComponent = useContext(ParentComponent) as ReactWrappedWidget; if (Array.isArray(eventName)) { for (const eventSingleName of eventName) { @@ -185,7 +116,7 @@ export function useTriliumOptionBeta(name: OptionNames, needsRefresh?: boolean): } }, [ name, needsRefresh ]); - useTriliumEventBeta("entitiesReloaded", useCallback(({ loadResults }) => { + useTriliumEvent("entitiesReloaded", useCallback(({ loadResults }) => { if (loadResults.getOptionNames().includes(name)) { const newValue = options.get(name); setValue(newValue); @@ -283,20 +214,20 @@ export function useNoteContext() { setNote(noteContext?.note); }, [ notePath ]); - useTriliumEventBeta("activeContextChanged", ({ noteContext }) => { + useTriliumEvent("activeContextChanged", ({ noteContext }) => { setNoteContext(noteContext); setNotePath(noteContext.notePath); }); - useTriliumEventBeta("setNoteContext", ({ noteContext }) => { + useTriliumEvent("setNoteContext", ({ noteContext }) => { setNoteContext(noteContext); }); - useTriliumEventBeta("noteSwitchedAndActivated", ({ noteContext }) => { + useTriliumEvent("noteSwitchedAndActivated", ({ noteContext }) => { setNoteContext(noteContext); }); - useTriliumEventBeta("noteSwitched", ({ noteContext, notePath }) => { + useTriliumEvent("noteSwitched", ({ noteContext, notePath }) => { setNotePath(notePath); }); - useTriliumEventBeta("frocaReloaded", () => { + useTriliumEvent("frocaReloaded", () => { setNote(noteContext?.note); }); @@ -336,7 +267,7 @@ export function useNoteProperty(note: FNote | null | unde useEffect(() => refreshValue(), [ note, note[property] ]); // Watch for external changes. - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.isNoteReloaded(note.noteId, componentId)) { refreshValue(); } @@ -349,7 +280,7 @@ export function useNoteRelation(note: FNote | undefined | null, relationName: st const [ relationValue, setRelationValue ] = useState(note?.getRelationValue(relationName)); useEffect(() => setRelationValue(note?.getRelationValue(relationName) ?? null), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { if (attr.type === "relation" && attr.name === relationName && attributes.isAffecting(attr, note)) { setRelationValue(attr.value ?? null); @@ -380,7 +311,7 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string): const [ labelValue, setLabelValue ] = useState(note?.getLabelValue(labelName)); useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { setLabelValue(attr.value ?? null); @@ -409,7 +340,7 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: s useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { setLabelValue(!attr.isDeleted); @@ -442,7 +373,7 @@ export function useNoteBlob(note: FNote | null | undefined): [ FBlob | null | un } useEffect(refresh, [ note?.noteId ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (note && loadResults.hasRevisionForNote(note.noteId)) { refresh(); } diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index e517f3894..623324d52 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -3,7 +3,7 @@ import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; import { getAvailableLocales, t } from "../../services/i18n"; -import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionBeta, useTriliumOptionJson } from "../react/hooks"; +import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption, useTriliumOptionBeta, useTriliumOptionJson } from "../react/hooks"; import mime_types from "../../services/mime_types"; import { Locale, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; import server from "../../services/server"; @@ -179,7 +179,7 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { }, [ note ]); useEffect(() => refreshState(), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { refreshState(); } @@ -228,7 +228,7 @@ function SharedSwitch({ note }: { note?: FNote | null }) { }, [ note ]); useEffect(() => refreshState(), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { refreshState(); } diff --git a/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx b/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx index ddd3d87e9..d9eb32b7f 100644 --- a/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx +++ b/apps/client/src/widgets/ribbon/InheritedAttributesTab.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "preact/hooks"; import { TabContext } from "./ribbon-interface"; import FAttribute from "../../entities/fattribute"; -import { useLegacyWidget, useTriliumEventBeta } from "../react/hooks"; +import { useLegacyWidget, useTriliumEvent } from "../react/hooks"; import attributes from "../../services/attributes"; import { t } from "../../services/i18n"; import attribute_renderer from "../../services/attribute_renderer"; @@ -29,7 +29,7 @@ export default function InheritedAttributesTab({ note, componentId }: TabContext } useEffect(refresh, [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { refresh(); } diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx index 889b28b21..b31bb2672 100644 --- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -7,7 +7,7 @@ import Button from "../react/Button"; import { formatDateTime } from "../../utils/formatters"; import { formatSize } from "../../services/utils"; import LoadingSpinner from "../react/LoadingSpinner"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; export default function NoteInfoTab({ note }: TabContext) { const [ metadata, setMetadata ] = useState(); @@ -26,7 +26,7 @@ export default function NoteInfoTab({ note }: TabContext) { } useEffect(refresh, [ note?.noteId ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { const noteId = note?.noteId; if (noteId && (loadResults.isNoteReloaded(noteId) || loadResults.isNoteContentReloaded(noteId))) { refresh(); diff --git a/apps/client/src/widgets/ribbon/NotePathsTab.tsx b/apps/client/src/widgets/ribbon/NotePathsTab.tsx index dfbe38079..bde553eaf 100644 --- a/apps/client/src/widgets/ribbon/NotePathsTab.tsx +++ b/apps/client/src/widgets/ribbon/NotePathsTab.tsx @@ -1,7 +1,7 @@ import { TabContext } from "./ribbon-interface"; import { t } from "../../services/i18n"; import Button from "../react/Button"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; import { useEffect, useMemo, useState } from "preact/hooks"; import { NotePathRecord } from "../../entities/fnote"; import NoteLink from "../react/NoteLink"; @@ -18,7 +18,7 @@ export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabConte } useEffect(refresh, [ note?.noteId ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { const noteId = note?.noteId; if (!noteId) return; if (loadResults.getBranchRows().find((branch) => branch.noteId === noteId) diff --git a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx index 10da53a4a..1a934eb57 100644 --- a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx +++ b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx @@ -8,7 +8,7 @@ import toast from "../../services/toast"; import froca from "../../services/froca"; import { useContext, useEffect, useState } from "preact/hooks"; import { ParentComponent } from "../react/react_utils"; -import { useTriliumEventBeta } from "../react/hooks"; +import { useTriliumEvent } from "../react/hooks"; import appContext from "../../components/app_context"; import server from "../../services/server"; import ws from "../../services/ws"; @@ -65,7 +65,7 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) { // Refresh the list of available and active options. useEffect(refreshOptions, [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows().find((attrRow) => attributes.isAffecting(attrRow, note))) { refreshOptions(); } @@ -166,7 +166,7 @@ function BulkActionsList({ note }: { note: FNote }) { // React to changes. useEffect(refreshBulkActions, [ note ]); - useTriliumEventBeta("entitiesReloaded", ({loadResults}) => { + useTriliumEvent("entitiesReloaded", ({loadResults}) => { if (loadResults.getAttributeRows().find(attr => attr.type === "label" && attr.name === "action" && attributes.isAffecting(attr, note))) { refreshBulkActions(); } diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index d01216022..b88c24b83 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -4,7 +4,7 @@ import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import CKEditor, { CKEditorApi } from "../../react/CKEditor"; -import { useLegacyImperativeHandlers, useLegacyWidget, useTooltip, useTriliumEventBeta } from "../../react/hooks"; +import { useLegacyImperativeHandlers, useLegacyWidget, useTooltip, useTriliumEvent } from "../../react/hooks"; import FAttribute from "../../../entities/fattribute"; import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; @@ -215,7 +215,7 @@ export default function AttributeEditor({ note, componentId, notePath, ntxId }: } useEffect(() => refresh(), [ note ]); - useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { console.log("Trigger due to entities reloaded"); refresh(); @@ -257,11 +257,11 @@ export default function AttributeEditor({ note, componentId, notePath, ntxId }: }), [])); // Keyboard shortcuts - useTriliumEventBeta("addNewLabel", ({ ntxId: eventNtxId }) => { + useTriliumEvent("addNewLabel", ({ ntxId: eventNtxId }) => { if (eventNtxId !== ntxId) return; handleAddNewAttributeCommand("addNewLabel"); }); - useTriliumEventBeta("addNewRelation", ({ ntxId: eventNtxId }) => { + useTriliumEvent("addNewRelation", ({ ntxId: eventNtxId }) => { if (eventNtxId !== ntxId) return; handleAddNewAttributeCommand("addNewRelation"); }); diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index 98175a0c8..0ea49105c 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -76,7 +76,6 @@ const CONTENT_WIDGETS: Record; - private widget?: BasicWidget; static getType() { return "contentWidget"; @@ -113,7 +112,6 @@ export default class ContentWidgetTypeWidget extends TypeWidget { this.child(widget); this.$content.append(widget.render()); - this.widget = widget; await widget.refresh(); } return; diff --git a/apps/client/src/widgets/type_widgets/options/etapi.tsx b/apps/client/src/widgets/type_widgets/options/etapi.tsx index 2309c2c12..67283096d 100644 --- a/apps/client/src/widgets/type_widgets/options/etapi.tsx +++ b/apps/client/src/widgets/type_widgets/options/etapi.tsx @@ -10,7 +10,7 @@ import toast from "../../../services/toast"; import dialog from "../../../services/dialog"; import { formatDateTime } from "../../../utils/formatters"; import ActionButton from "../../react/ActionButton"; -import useTriliumEvent from "../../react/hooks"; +import { useTriliumEvent } from "../../react/hooks"; type RenameTokenCallback = (tokenId: string, oldName: string) => Promise; type DeleteTokenCallback = (tokenId: string, name: string ) => Promise; diff --git a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx index 215505b52..9094d0a00 100644 --- a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx +++ b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx @@ -11,7 +11,7 @@ import { useCallback, useEffect, useState } from "preact/hooks"; import server from "../../../services/server"; import options from "../../../services/options"; import dialog from "../../../services/dialog"; -import useTriliumEvent from "../../react/hooks"; +import { useTriliumEvent } from "../../react/hooks"; export default function ShortcutSettings() { const [ keyboardShortcuts, setKeyboardShortcuts ] = useState([]);