From efaa1815ec5b8e985a9e4798faca6bcc43571d36 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 22 Sep 2025 13:19:13 +0300 Subject: [PATCH] chore(react/type_widget): classic editor & inspector --- .../text/CKEditorWithWatchdog.tsx | 12 +++- .../type_widgets/text/EditableText.tsx | 63 ++++++++++++++++++- .../widgets/type_widgets_old/editable_text.ts | 57 ----------------- 3 files changed, 72 insertions(+), 60 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx index 6896de045..5d7df279d 100644 --- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx +++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx @@ -11,9 +11,11 @@ interface CKEditorWithWatchdogProps extends Pick, "cla onNotificationWarning?: (evt: any, data: any) => void; onWatchdogStateChange?: (watchdog: EditorWatchdog) => void; onChange: () => void; + /** Called upon whenever a new CKEditor instance is initialized, whether it's the first initialization, after a crash or after a config change that requires it (e.g. content language). */ + onEditorInitialized?: (editor: CKTextEditor) => void; } -export default function CKEditorWithWatchdog({ content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, buildEditorOpts, onNotificationWarning, onWatchdogStateChange, onChange }: CKEditorWithWatchdogProps) { +export default function CKEditorWithWatchdog({ content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized }: CKEditorWithWatchdogProps) { const containerRef = useRef(null); const watchdogRef = useRef(null); const [ editor, setEditor ] = useState(); @@ -33,6 +35,14 @@ export default function CKEditorWithWatchdog({ content, contentLanguage, classNa setEditor(editor); + // Inspector integration. + if (import.meta.env.VITE_CKEDITOR_ENABLE_INSPECTOR === "true") { + const CKEditorInspector = (await import("@ckeditor/ckeditor5-inspector")).default; + CKEditorInspector.attach(editor); + } + + onEditorInitialized?.(editor); + return editor; }); diff --git a/apps/client/src/widgets/type_widgets/text/EditableText.tsx b/apps/client/src/widgets/type_widgets/text/EditableText.tsx index 1330852a8..425f62ae3 100644 --- a/apps/client/src/widgets/type_widgets/text/EditableText.tsx +++ b/apps/client/src/widgets/type_widgets/text/EditableText.tsx @@ -6,7 +6,8 @@ import { useEditorSpacedUpdate, useNoteLabel, useTriliumOption } from "../../rea import { TypeWidgetProps } from "../type_widget"; import CKEditorWithWatchdog from "./CKEditorWithWatchdog"; import "./EditableText.css"; -import { EditorWatchdog } from "@triliumnext/ckeditor5"; +import { CKTextEditor, ClassicEditor, EditorWatchdog } from "@triliumnext/ckeditor5"; +import Component from "../../../components/component"; /** * The editor can operate into two distinct modes: @@ -14,7 +15,7 @@ import { EditorWatchdog } from "@triliumnext/ckeditor5"; * - Ballon block mode, in which there is a floating toolbar for the selected text, but another floating button for the entire block (i.e. paragraph). * - 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) { +export default function EditableText({ note, parentComponent }: TypeWidgetProps) { const [ content, setContent ] = useState(); const watchdogRef = useRef(null); const [ language ] = useNoteLabel(note, "language"); @@ -61,6 +62,13 @@ export default function EditableText({ note }: TypeWidgetProps) { onNotificationWarning={onNotificationWarning} onWatchdogStateChange={onWatchdogStateChange} onChange={() => spacedUpdate.scheduleUpdate()} + onEditorInitialized={(editor) => { + console.log("Editor has been initialized!", parentComponent, editor); + + if (isClassicEditor) { + setupClassicEditor(editor, parentComponent); + } + }} />} ) @@ -94,3 +102,54 @@ function onNotificationWarning(data, evt) { evt.stop(); } + +function setupClassicEditor(editor: CKTextEditor, parentComponent: Component | undefined) { + if (!parentComponent) return; + const $classicToolbarWidget = findClassicToolbar(parentComponent); + console.log("Found ", $classicToolbarWidget); + + $classicToolbarWidget.empty(); + if ($classicToolbarWidget.length) { + const toolbarView = (editor as ClassicEditor).ui.view.toolbar; + if (toolbarView.element) { + $classicToolbarWidget[0].appendChild(toolbarView.element); + } + } + + if (utils.isMobile()) { + $classicToolbarWidget.addClass("visible"); + + // Reposition all dropdowns to point upwards instead of downwards. + // See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info. + const toolbarView = (editor as ClassicEditor).ui.view.toolbar; + for (const item of toolbarView.items) { + if (!("panelView" in item)) continue; + + item.on("change:isOpen", () => { + if (!("isOpen" in item) || !item.isOpen) return; + + // @ts-ignore + item.panelView.position = item.panelView.position.replace("s", "n"); + }); + } + } +} + +function findClassicToolbar(parentComponent: Component): JQuery { + const $widget = $(parentComponent.$widget); + + if (!utils.isMobile()) { + const $parentSplit = $widget.parents(".note-split.type-text"); + console.log("Got split ", $parentSplit) + + if ($parentSplit.length) { + // The editor is in a normal tab. + return $parentSplit.find("> .ribbon-container .classic-toolbar-widget"); + } else { + // The editor is in a popup. + return $widget.closest(".modal-body").find(".classic-toolbar-widget"); + } + } else { + return $("body").find(".classic-toolbar-widget"); + } +} 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 d3f23e0aa..42ae5eef7 100644 --- a/apps/client/src/widgets/type_widgets_old/editable_text.ts +++ b/apps/client/src/widgets/type_widgets_old/editable_text.ts @@ -38,47 +38,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { async initEditor() { this.watchdog.setCreator(async (_, editorConfig) => { - logInfo("Creating new CKEditor"); - - if (isClassicEditor) { - const $classicToolbarWidget = this.findClassicToolbar(); - - $classicToolbarWidget.empty(); - if ($classicToolbarWidget.length) { - const toolbarView = (editor as ClassicEditor).ui.view.toolbar; - if (toolbarView.element) { - $classicToolbarWidget[0].appendChild(toolbarView.element); - } - } - - if (utils.isMobile()) { - $classicToolbarWidget.addClass("visible"); - - // Reposition all dropdowns to point upwards instead of downwards. - // See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info. - const toolbarView = (editor as ClassicEditor).ui.view.toolbar; - for (const item of toolbarView.items) { - if (!("panelView" in item)) { - continue; - } - - item.on("change:isOpen", () => { - if (!("isOpen" in item) || !item.isOpen) { - return; - } - - // @ts-ignore - item.panelView.position = item.panelView.position.replace("s", "n"); - }); - } - } - } - - if (import.meta.env.VITE_CKEDITOR_ENABLE_INSPECTOR === "true") { - const CKEditorInspector = (await import("@ckeditor/ckeditor5-inspector")).default; - CKEditorInspector.attach(editor); - } - // Touch bar integration if (hasTouchBar) { for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) { @@ -320,22 +279,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { } } - findClassicToolbar(): JQuery { - if (!utils.isMobile()) { - const $parentSplit = this.$widget.parents(".note-split.type-text"); - - if ($parentSplit.length) { - // The editor is in a normal tab. - return $parentSplit.find("> .ribbon-container .classic-toolbar-widget"); - } else { - // The editor is in a popup. - return this.$widget.closest(".modal-body").find(".classic-toolbar-widget"); - } - } else { - return $("body").find(".classic-toolbar-widget"); - } - } - buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) { const { TouchBar, buildIcon } = data; const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;