chore(react/type_widget): classic editor & inspector

This commit is contained in:
Elian Doran 2025-09-22 13:19:13 +03:00
parent 2eab8b92d5
commit efaa1815ec
No known key found for this signature in database
3 changed files with 72 additions and 60 deletions

View File

@ -11,9 +11,11 @@ interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "cla
onNotificationWarning?: (evt: any, data: any) => void;
onWatchdogStateChange?: (watchdog: EditorWatchdog<any>) => 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<HTMLDivElement>(null);
const watchdogRef = useRef<EditorWatchdog>(null);
const [ editor, setEditor ] = useState<CKTextEditor>();
@ -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;
});

View File

@ -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<string>();
const watchdogRef = useRef<EditorWatchdog>(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);
}
}}
/>}
</div>
)
@ -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<HTMLElement> {
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");
}
}

View File

@ -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<HTMLElement> {
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;