fix(ckeditor5): reintroduce block handle for floating editor

This commit is contained in:
Elian Doran 2025-05-05 15:43:14 +03:00
parent 10e5852a67
commit 910b0d280d
No known key found for this signature in database
9 changed files with 54 additions and 125 deletions

View File

@ -26,6 +26,7 @@ import type TypeWidget from "../widgets/type_widgets/type_widget.js";
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js"; import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
import type { NativeImage, TouchBar } from "electron"; import type { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js"; import TouchBarComponent from "./touch_bar.js";
import type { ClassicEditor, PopupEditor } from "@triliumnext/ckeditor5";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@ -187,7 +188,7 @@ export type CommandMappings = {
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void; callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
}; };
executeWithTextEditor: CommandData & executeWithTextEditor: CommandData &
ExecuteCommandData<TextEditor> & { ExecuteCommandData<ClassicEditor | PopupEditor> & {
callback?: GetTextEditorCallback; callback?: GetTextEditorCallback;
}; };
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>; executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>;

View File

@ -10,13 +10,14 @@ import options from "../services/options.js";
import type { ViewScope } from "../services/link.js"; import type { ViewScope } from "../services/link.js";
import type FNote from "../entities/fnote.js"; import type FNote from "../entities/fnote.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js"; import type TypeWidget from "../widgets/type_widgets/type_widget.js";
import type { ClassicEditor, PopupEditor } from "@triliumnext/ckeditor5";
export interface SetNoteOpts { export interface SetNoteOpts {
triggerSwitchEvent?: unknown; triggerSwitchEvent?: unknown;
viewScope?: ViewScope; viewScope?: ViewScope;
} }
export type GetTextEditorCallback = (editor: TextEditor) => void; export type GetTextEditorCallback = (editor: ClassicEditor | PopupEditor) => void;
class NoteContext extends Component implements EventListener<"entitiesReloaded"> { class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
ntxId: string | null; ntxId: string | null;
@ -298,7 +299,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
} }
async getTextEditor(callback?: GetTextEditorCallback) { async getTextEditor(callback?: GetTextEditorCallback) {
return this.timeout<TextEditor>( return this.timeout<ClassicEditor | PopupEditor>(
new Promise((resolve) => new Promise((resolve) =>
appContext.triggerCommand("executeWithTextEditor", { appContext.triggerCommand("executeWithTextEditor", {
callback, callback,

View File

@ -332,109 +332,6 @@ declare global {
}; };
} }
interface TextEditor {
create(el: HTMLElement, config: {
removePlugins?: string[];
toolbar: {
items: any[];
},
placeholder: string;
mention: MentionConfig
});
enableReadOnlyMode(reason: string);
commands: {
get(name: string): {
value: unknown;
on(event: string, callback: () => void): void;
};
}
model: {
document: {
on(event: string, cb: () => void);
getRoot(): CKNode;
registerPostFixer(callback: (writer: Writer) => boolean);
selection: {
getFirstPosition(): undefined | TextPosition;
getLastPosition(): undefined | TextPosition;
getSelectedElement(): CKNode;
hasAttribute(attribute: string): boolean;
getAttribute(attribute: string): string;
getFirstRange(): Range;
isCollapsed: boolean;
};
differ: {
getChanges(): {
type: string;
name: string;
position?: {
nodeAfter?: CKNode;
parent: CKNode;
toJSON(): Object;
}
}[];
}
},
insertContent(modelFragment: any, selection?: any);
change(cb: (writer: Writer) => void)
},
editing: {
view: {
document: {
on(event: string, cb: (event: CKEvent, data: {
preventDefault();
}) => void, opts?: {
priority: "high"
});
getRoot(): CKNode
},
domRoots: {
values: () => {
next: () => {
value: string;
}
};
}
change(cb: (writer: Writer) => void);
scrollToTheSelection(): void;
focus(): void;
}
},
plugins: {
get(command: string)
},
data: {
processor: {
toView(html: string);
};
toModel(viewFeragment: any);
},
ui: {
view: {
toolbar: {
items: any[];
element: HTMLElement;
}
}
}
conversion: {
for(filter: string): {
markerToHighlight(data: {
model: string;
view: (data: {
markerName: string;
}) => void;
})
}
}
getData(): string;
setData(data: string): void;
getSelectedHtml(): string;
removeSelection(): void;
execute<T>(action: string, ...args: unknown[]): T;
focus(): void;
sourceElement: HTMLElement;
}
interface EditingState { interface EditingState {
highlightedResult: string; highlightedResult: string;
results: unknown[]; results: unknown[];

View File

@ -4,7 +4,7 @@ import noteAutocompleteService from "../../services/note_autocomplete.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.js"; import contextMenuService from "../../menus/context_menu.js";
import attributeParser, { type Attribute } from "../../services/attribute_parser.js"; import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
import { BalloonEditor } from "@triliumnext/ckeditor5"; import { AttributeEditor } from "@triliumnext/ckeditor5";
import froca from "../../services/froca.js"; import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js"; import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js"; import noteCreateService from "../../services/note_create.js";
@ -199,7 +199,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
private $saveAttributesButton!: JQuery<HTMLElement>; private $saveAttributesButton!: JQuery<HTMLElement>;
private $errors!: JQuery<HTMLElement>; private $errors!: JQuery<HTMLElement>;
private textEditor!: BalloonEditor; private textEditor!: AttributeEditor;
private lastUpdatedNoteId!: string | undefined; private lastUpdatedNoteId!: string | undefined;
private lastSavedContent!: string; private lastSavedContent!: string;
@ -373,7 +373,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
this.$editor.on("click", (e) => this.handleEditorClick(e)); this.$editor.on("click", (e) => this.handleEditorClick(e));
this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig); this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
this.textEditor.model.document.on("change:data", () => this.dataChanged()); this.textEditor.model.document.on("change:data", () => this.dataChanged());
this.textEditor.editing.view.document.on( this.textEditor.editing.view.document.on(
"enter", "enter",

View File

@ -18,7 +18,7 @@ import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import { getMermaidConfig } from "../../services/mermaid.js"; import { getMermaidConfig } from "../../services/mermaid.js";
import { BalloonEditor, COMMON_PLUGINS, DecoupledEditor, EditorWatchdog } from "@triliumnext/ckeditor5"; import { PopupEditor, ClassicEditor, EditorWatchdog } from "@triliumnext/ckeditor5";
const ENABLE_INSPECTOR = false; const ENABLE_INSPECTOR = false;
@ -128,7 +128,7 @@ function buildListOfLanguages() {
export default class EditableTextTypeWidget extends AbstractTextTypeWidget { export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
private contentLanguage?: string | null; private contentLanguage?: string | null;
private watchdog!: EditorWatchdog<DecoupledEditor | BalloonEditor>; private watchdog!: EditorWatchdog<ClassicEditor | PopupEditor>;
private $editor!: JQuery<HTMLElement>; private $editor!: JQuery<HTMLElement>;
@ -151,14 +151,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
async initEditor() { async initEditor() {
const isClassicEditor = utils.isMobile() || options.get("textNoteEditorType") === "ckeditor-classic"; const isClassicEditor = utils.isMobile() || options.get("textNoteEditorType") === "ckeditor-classic";
const editorClass = isClassicEditor ? DecoupledEditor : BalloonEditor; const editorClass = isClassicEditor ? ClassicEditor : PopupEditor;
// CKEditor since version 12 needs the element to be visible before initialization. At the same time, // CKEditor since version 12 needs the element to be visible before initialization. At the same time,
// we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate // we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate
// display of $widget in both branches. // display of $widget in both branches.
this.$widget.show(); this.$widget.show();
this.watchdog = new EditorWatchdog<DecoupledEditor | BalloonEditor>(editorClass, { this.watchdog = new EditorWatchdog<ClassicEditor | PopupEditor>(editorClass, {
// An average number of milliseconds between the last editor errors (defaults to 5000). // An average number of milliseconds between the last editor errors (defaults to 5000).
// When the period of time between errors is lower than that and the crashNumberLimit // When the period of time between errors is lower than that and the crashNumberLimit
// is also reached, the watchdog changes its state to crashedPermanently, and it stops // is also reached, the watchdog changes its state to crashedPermanently, and it stops
@ -252,7 +252,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
$classicToolbarWidget.empty(); $classicToolbarWidget.empty();
if ($classicToolbarWidget.length) { if ($classicToolbarWidget.length) {
$classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element); const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
if (toolbarView.element) {
$classicToolbarWidget[0].appendChild(toolbarView.element);
}
} }
if (utils.isMobile()) { if (utils.isMobile()) {
@ -260,7 +263,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
// Reposition all dropdowns to point upwards instead of downwards. // 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. // See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
const toolbarView = (editor as DecoupledEditor).ui.view.toolbar; const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
for (const item of toolbarView.items) { for (const item of toolbarView.items) {
if (!("panelView" in item)) { if (!("panelView" in item)) {
continue; continue;
@ -317,8 +320,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
mermaid: { mermaid: {
lazyLoad: async () => (await import("mermaid")).default, // FIXME lazyLoad: async () => (await import("mermaid")).default, // FIXME
config: getMermaidConfig() config: getMermaidConfig()
}, }
plugins: COMMON_PLUGINS
}); });
} }
@ -443,6 +445,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
await this.initialized; await this.initialized;
if (!this.watchdog.editor) {
return;
}
if (callback) { if (callback) {
callback(this.watchdog.editor); callback(this.watchdog.editor);
} }
@ -489,7 +495,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
} }
} }
if (!selection.hasAttribute("linkHref")) { if (!selection?.hasAttribute("linkHref")) {
return; return;
} }

View File

@ -1,3 +1,22 @@
import "ckeditor5/ckeditor5.css"; import "ckeditor5/ckeditor5.css";
export { EditorWatchdog, BalloonEditor, DecoupledEditor } from "ckeditor5"; import { COMMON_PLUGINS, POPUP_EDITOR_PLUGINS } from "./plugins";
export * from "./plugins.js"; import { BalloonEditor, DecoupledEditor } from "ckeditor5";
export { EditorWatchdog } from "ckeditor5";
export class AttributeEditor extends BalloonEditor {
static override get builtinPlugins() {
return [];
}
}
export class ClassicEditor extends DecoupledEditor {
static override get builtinPlugins() {
return COMMON_PLUGINS;
}
}
export class PopupEditor extends BalloonEditor {
static override get builtinPlugins() {
return POPUP_EDITOR_PLUGINS;
}
}

View File

@ -1,4 +1,4 @@
import { Autoformat, AutoLink, BlockQuote, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo } from "ckeditor5"; import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo } from "ckeditor5";
import type { Plugin } from "ckeditor5"; import type { Plugin } from "ckeditor5";
import CutToNotePlugin from "./plugins/cuttonote.js"; import CutToNotePlugin from "./plugins/cuttonote.js";
import UploadimagePlugin from "./plugins/uploadimage.js"; import UploadimagePlugin from "./plugins/uploadimage.js";
@ -110,4 +110,9 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
Style Style
]; ];
export const POPUP_EDITOR_PLUGINS: typeof Plugin[] = [
...COMMON_PLUGINS,
BlockToolbar
];
export const COMMON_SETTINGS = { }; export const COMMON_SETTINGS = { };

View File

@ -4,10 +4,10 @@
"include": [], "include": [],
"references": [ "references": [
{ {
"path": "../ckeditor5-math" "path": "../ckeditor5-footnotes"
}, },
{ {
"path": "../ckeditor5-footnotes" "path": "../ckeditor5-math"
}, },
{ {
"path": "../ckeditor5-admonition" "path": "../ckeditor5-admonition"

View File

@ -20,10 +20,10 @@
], ],
"references": [ "references": [
{ {
"path": "../ckeditor5-math" "path": "../ckeditor5-footnotes"
}, },
{ {
"path": "../ckeditor5-footnotes" "path": "../ckeditor5-math"
}, },
{ {
"path": "../ckeditor5-admonition" "path": "../ckeditor5-admonition"