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 { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js";
import type { ClassicEditor, PopupEditor } from "@triliumnext/ckeditor5";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
@ -187,7 +188,7 @@ export type CommandMappings = {
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
};
executeWithTextEditor: CommandData &
ExecuteCommandData<TextEditor> & {
ExecuteCommandData<ClassicEditor | PopupEditor> & {
callback?: GetTextEditorCallback;
};
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 FNote from "../entities/fnote.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
import type { ClassicEditor, PopupEditor } from "@triliumnext/ckeditor5";
export interface SetNoteOpts {
triggerSwitchEvent?: unknown;
viewScope?: ViewScope;
}
export type GetTextEditorCallback = (editor: TextEditor) => void;
export type GetTextEditorCallback = (editor: ClassicEditor | PopupEditor) => void;
class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
ntxId: string | null;
@ -298,7 +299,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
}
async getTextEditor(callback?: GetTextEditorCallback) {
return this.timeout<TextEditor>(
return this.timeout<ClassicEditor | PopupEditor>(
new Promise((resolve) =>
appContext.triggerCommand("executeWithTextEditor", {
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 {
highlightedResult: string;
results: unknown[];

View File

@ -4,7 +4,7 @@ import noteAutocompleteService from "../../services/note_autocomplete.js";
import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.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 attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js";
@ -199,7 +199,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
private $saveAttributesButton!: JQuery<HTMLElement>;
private $errors!: JQuery<HTMLElement>;
private textEditor!: BalloonEditor;
private textEditor!: AttributeEditor;
private lastUpdatedNoteId!: string | undefined;
private lastSavedContent!: string;
@ -373,7 +373,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
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.editing.view.document.on(
"enter",

View File

@ -18,7 +18,7 @@ import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
import type FNote from "../../entities/fnote.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;
@ -128,7 +128,7 @@ function buildListOfLanguages() {
export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
private contentLanguage?: string | null;
private watchdog!: EditorWatchdog<DecoupledEditor | BalloonEditor>;
private watchdog!: EditorWatchdog<ClassicEditor | PopupEditor>;
private $editor!: JQuery<HTMLElement>;
@ -151,14 +151,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
async initEditor() {
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,
// 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.
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).
// 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
@ -252,7 +252,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
$classicToolbarWidget.empty();
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()) {
@ -260,7 +263,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
// 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 DecoupledEditor).ui.view.toolbar;
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
for (const item of toolbarView.items) {
if (!("panelView" in item)) {
continue;
@ -317,8 +320,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
mermaid: {
lazyLoad: async () => (await import("mermaid")).default, // FIXME
config: getMermaidConfig()
},
plugins: COMMON_PLUGINS
}
});
}
@ -443,6 +445,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
await this.initialized;
if (!this.watchdog.editor) {
return;
}
if (callback) {
callback(this.watchdog.editor);
}
@ -489,7 +495,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
}
}
if (!selection.hasAttribute("linkHref")) {
if (!selection?.hasAttribute("linkHref")) {
return;
}

View File

@ -1,3 +1,22 @@
import "ckeditor5/ckeditor5.css";
export { EditorWatchdog, BalloonEditor, DecoupledEditor } from "ckeditor5";
export * from "./plugins.js";
import { COMMON_PLUGINS, POPUP_EDITOR_PLUGINS } from "./plugins";
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 CutToNotePlugin from "./plugins/cuttonote.js";
import UploadimagePlugin from "./plugins/uploadimage.js";
@ -110,4 +110,9 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
Style
];
export const POPUP_EDITOR_PLUGINS: typeof Plugin[] = [
...COMMON_PLUGINS,
BlockToolbar
];
export const COMMON_SETTINGS = { };

View File

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

View File

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