From f6631b7b9a6022cb1ed0ea66021dcb2a1c9245b3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 22 Sep 2025 12:41:32 +0300 Subject: [PATCH] chore(react/type_widget): save on change --- .../text/CKEditorWithWatchdog.tsx | 26 +++++++++++-- .../type_widgets/text/EditableText.tsx | 37 ++++++++++++++++--- .../widgets/type_widgets_old/editable_text.ts | 19 ---------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx index 61fcd5d15..6b97fd566 100644 --- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx +++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx @@ -1,22 +1,30 @@ -import { HTMLProps, useEffect, useRef } from "preact/compat"; -import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig } from "@triliumnext/ckeditor5"; +import { HTMLProps, RefObject, useEffect, useRef, useState } from "preact/compat"; +import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor } from "@triliumnext/ckeditor5"; import { buildConfig, BuildEditorOptions } from "./config"; +import { Editor } from "tabulator-tables"; interface CKEditorWithWatchdogProps extends Pick, "className" | "tabIndex"> { + content?: string; isClassicEditor?: boolean; + watchdogRef: RefObject; watchdogConfig?: WatchdogConfig; buildEditorOpts: Omit; onNotificationWarning?: (evt: any, data: any) => void; onWatchdogStateChange?: (watchdog: EditorWatchdog) => void; + onChange: () => void; } -export default function CKEditorWithWatchdog({ className, tabIndex, isClassicEditor, watchdogConfig, buildEditorOpts, onNotificationWarning, onWatchdogStateChange }: CKEditorWithWatchdogProps) { +export default function CKEditorWithWatchdog({ content, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, buildEditorOpts, onNotificationWarning, onWatchdogStateChange, onChange }: CKEditorWithWatchdogProps) { const containerRef = useRef(null); + const watchdogRef = useRef(null); + const [ editor, setEditor ] = useState(); useEffect(() => { const container = containerRef.current; if (!container) return; const watchdog = buildWatchdog(!!isClassicEditor, watchdogConfig); + watchdogRef.current = watchdog; + externalWatchdogRef.current = watchdog; watchdog.setCreator(async () => { const editor = await buildEditor(container, !!isClassicEditor, { ...buildEditorOpts, @@ -27,6 +35,8 @@ export default function CKEditorWithWatchdog({ className, tabIndex, isClassicEdi editor.plugins.get("Notification").on("show:warning", onNotificationWarning); } + setEditor(editor); + return editor; }); @@ -39,6 +49,16 @@ export default function CKEditorWithWatchdog({ className, tabIndex, isClassicEdi return () => watchdog.destroy(); }, []); + // React to content changes. + useEffect(() => editor?.setData(content ?? ""), [ editor, content ]); + + // React to on change listener. + useEffect(() => { + if (!editor) return; + editor.model.document.on("change:data", onChange); + return () => editor.model.document.off("change:data", onChange); + }, [ editor, onChange ]); + return (
diff --git a/apps/client/src/widgets/type_widgets/text/EditableText.tsx b/apps/client/src/widgets/type_widgets/text/EditableText.tsx index e74c265bd..def4f62e9 100644 --- a/apps/client/src/widgets/type_widgets/text/EditableText.tsx +++ b/apps/client/src/widgets/type_widgets/text/EditableText.tsx @@ -1,11 +1,12 @@ +import { useRef, useState } from "preact/hooks"; import dialog from "../../../services/dialog"; import toast from "../../../services/toast"; -import { isMobile } from "../../../services/utils"; -import { useNoteLabel, useTriliumOption } from "../../react/hooks"; +import utils, { isMobile } from "../../../services/utils"; +import { useEditorSpacedUpdate, useNoteLabel, useTriliumOption } from "../../react/hooks"; import { TypeWidgetProps } from "../type_widget"; import CKEditorWithWatchdog from "./CKEditorWithWatchdog"; import "./EditableText.css"; -import type { EditorWatchdog } from "@triliumnext/ckeditor5"; +import { EditorWatchdog } from "@triliumnext/ckeditor5"; /** * The editor can operate into two distinct modes: @@ -14,15 +15,40 @@ import type { EditorWatchdog } from "@triliumnext/ckeditor5"; * - Decoupled mode, in which the editing toolbar is actually added on the client side (in {@link ClassicEditorToolbar}), see https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for an example on how the decoupled editor works. */ export default function EditableText({ note }: TypeWidgetProps) { + const [ content, setContent ] = useState(); + const watchdogRef = useRef(null); const [ language ] = useNoteLabel(note, "language"); const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType"); const isClassicEditor = isMobile() || textNoteEditorType === "ckeditor-classic"; + const spacedUpdate = useEditorSpacedUpdate({ + note, + getData() { + const editor = watchdogRef.current?.editor; + if (!editor) { + // There is nothing to save, most likely a result of the editor crashing and reinitializing. + return; + } + + const content = editor.getData() ?? ""; + + // if content is only tags/whitespace (typically

 

), then just make it empty, + // this is important when setting a new note to code + return { + content: utils.isHtmlEmpty(content) ? "" : content + }; + }, + onContentChange(newContent) { + setContent(newContent); + } + }) return (
- + onChange={() => spacedUpdate.scheduleUpdate()} + />}
) } diff --git a/apps/client/src/widgets/type_widgets_old/editable_text.ts b/apps/client/src/widgets/type_widgets_old/editable_text.ts index 6c910a35a..22117f7c2 100644 --- a/apps/client/src/widgets/type_widgets_old/editable_text.ts +++ b/apps/client/src/widgets/type_widgets_old/editable_text.ts @@ -74,8 +74,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { } } - editor.model.document.on("change:data", () => this.spacedUpdate.scheduleUpdate()); - if (import.meta.env.VITE_CKEDITOR_ENABLE_INSPECTOR === "true") { const CKEditorInspector = (await import("@ckeditor/ckeditor5-inspector")).default; CKEditorInspector.attach(editor); @@ -102,27 +100,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { const newContentLanguage = this.note?.getLabelValue("language"); if (this.contentLanguage !== newContentLanguage) { await this.reinitializeWithData(data); - } else { - this.watchdog.editor?.setData(data); } }); } - getData() { - if (!this.watchdog.editor) { - // There is nothing to save, most likely a result of the editor crashing and reinitializing. - return; - } - - const content = this.watchdog.editor?.getData() ?? ""; - - // if content is only tags/whitespace (typically

 

), then just make it empty, - // this is important when setting a new note to code - return { - content: utils.isHtmlEmpty(content) ? "" : content - }; - } - focus() { const editor = this.watchdog.editor; if (editor) {