mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 18:05:55 +01:00 
			
		
		
		
	refactor(client): fix type errors related to CKEditor
This commit is contained in:
		| @@ -3,6 +3,7 @@ import appContext from "../components/app_context.js"; | ||||
| import noteCreateService from "./note_create.js"; | ||||
| import froca from "./froca.js"; | ||||
| import { t } from "./i18n.js"; | ||||
| import type { MentionFeedObjectItem } from "@triliumnext/ckeditor5"; | ||||
|  | ||||
| // this key needs to have this value, so it's hit by the tooltip | ||||
| const SELECTED_NOTE_PATH_KEY = "data-note-path"; | ||||
| @@ -43,7 +44,7 @@ interface Options { | ||||
| } | ||||
|  | ||||
| async function autocompleteSourceForCKEditor(queryText: string) { | ||||
|     return await new Promise<MentionItem[]>((res, rej) => { | ||||
|     return await new Promise<MentionFeedObjectItem[]>((res, rej) => { | ||||
|         autocompleteSource( | ||||
|             queryText, | ||||
|             (rows) => { | ||||
|   | ||||
							
								
								
									
										113
									
								
								apps/client/src/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										113
									
								
								apps/client/src/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -209,119 +209,6 @@ declare global { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     interface Range { | ||||
|         toJSON(): object; | ||||
|         getItems(): TextNode[]; | ||||
|     } | ||||
|     interface Writer { | ||||
|         setAttribute(name: string, value: string, el: CKNode); | ||||
|         createPositionAt(el: CKNode, opt?: "end" | number); | ||||
|         setSelection(pos: number, pos2?: number); | ||||
|         insertText(text: string, opts: Record<string, unknown> | undefined | TextPosition, position?: TextPosition); | ||||
|         addMarker(name: string, opts: { | ||||
|             range: Range; | ||||
|             usingOperation: boolean; | ||||
|         }); | ||||
|         removeMarker(name: string); | ||||
|         createRange(start: number, end: number): Range; | ||||
|         createElement(type: string, opts: Record<string, string | null | undefined>); | ||||
|     } | ||||
|     interface TextNode { | ||||
|         previousSibling?: TextNode; | ||||
|         name: string; | ||||
|         data: string; | ||||
|         startOffset: number; | ||||
|         _attrs: { | ||||
|             get(key: string): { | ||||
|                 length: number | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     interface TextPosition { | ||||
|         textNode: TextNode; | ||||
|         offset: number; | ||||
|         compareWith(pos: TextPosition): string; | ||||
|     } | ||||
|  | ||||
|     interface TextRange { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     interface Marker { | ||||
|         name: string; | ||||
|     } | ||||
|  | ||||
|     interface CKNode { | ||||
|         _children: CKNode[]; | ||||
|         name: string; | ||||
|         childCount: number; | ||||
|         isEmpty: boolean; | ||||
|         toJSON(): object; | ||||
|         is(type: string, name?: string); | ||||
|         getAttribute(name: string): string; | ||||
|         getChild(index: number): CKNode; | ||||
|         data: string; | ||||
|         startOffset: number; | ||||
|         root: { | ||||
|             document: { | ||||
|                 model: { | ||||
|                     createRangeIn(el: CKNode): TextRange; | ||||
|                     markers: { | ||||
|                         getMarkersIntersectingRange(range: TextRange): Marker[]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     interface CKEvent { | ||||
|         stop(): void; | ||||
|     } | ||||
|  | ||||
|     interface PluginEventData { | ||||
|         title: string; | ||||
|         message: { | ||||
|             message: string; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     interface EditingState { | ||||
|         highlightedResult: string; | ||||
|         results: unknown[]; | ||||
|     } | ||||
|  | ||||
|     interface CKFindResult { | ||||
|         results: { | ||||
|             get(number): { | ||||
|                 marker: { | ||||
|                     getStart(): TextPosition; | ||||
|                     getRange(): number; | ||||
|                 }; | ||||
|             } | ||||
|         } & []; | ||||
|     } | ||||
|  | ||||
|     interface MentionItem { | ||||
|         action?: string; | ||||
|         noteTitle?: string; | ||||
|         id: string; | ||||
|         name: string; | ||||
|         link?: string; | ||||
|         notePath?: string; | ||||
|         highlightedNotePathTitle?: string; | ||||
|     } | ||||
|  | ||||
|     interface MentionConfig { | ||||
|         feeds: { | ||||
|             marker: string; | ||||
|             feed: (queryText: string) => MentionItem[] | Promise<MentionItem[]>; | ||||
|             itemRenderer?: (item: { | ||||
|                 highlightedNotePathTitle: string | ||||
|             }) => void; | ||||
|             minimumCharacters: number; | ||||
|         }[]; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Panzoom | ||||
|      */ | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { t } from "../../services/i18n.js"; | ||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||
| import noteAutocompleteService, { type Suggestion } 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 { AttributeEditor } from "@triliumnext/ckeditor5"; | ||||
| import { AttributeEditor, type EditorConfig, type Element, type MentionFeed, type Node, type Position } from "@triliumnext/ckeditor5"; | ||||
| import froca from "../../services/froca.js"; | ||||
| import attributeRenderer from "../../services/attribute_renderer.js"; | ||||
| import noteCreateService from "../../services/note_create.js"; | ||||
| @@ -84,12 +84,12 @@ const TPL = /*html*/` | ||||
| </div> | ||||
| `; | ||||
|  | ||||
| const mentionSetup: MentionConfig = { | ||||
|     feeds: [ | ||||
| const mentionSetup: MentionFeed[] = [ | ||||
|     { | ||||
|         marker: "@", | ||||
|         feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), | ||||
|             itemRenderer: (item) => { | ||||
|         itemRenderer: (_item) => { | ||||
|             const item = _item as Suggestion; | ||||
|             const itemElement = document.createElement("button"); | ||||
|  | ||||
|             itemElement.innerHTML = `${item.highlightedNotePathTitle} `; | ||||
| @@ -126,15 +126,16 @@ const mentionSetup: MentionConfig = { | ||||
|         }, | ||||
|         minimumCharacters: 0 | ||||
|     } | ||||
|     ] | ||||
| }; | ||||
| ]; | ||||
|  | ||||
| const editorConfig = { | ||||
| const editorConfig: EditorConfig = { | ||||
|     toolbar: { | ||||
|         items: [] | ||||
|     }, | ||||
|     placeholder: t("attribute_editor.placeholder"), | ||||
|     mention: mentionSetup, | ||||
|     mention: { | ||||
|         feeds: mentionSetup | ||||
|     }, | ||||
|     licenseKey: "GPL" | ||||
| }; | ||||
|  | ||||
| @@ -334,7 +335,10 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem | ||||
|         ); | ||||
|  | ||||
|         // disable spellcheck for attribute editor | ||||
|         this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", this.textEditor.editing.view.document.getRoot())); | ||||
|         const documentRoot = this.textEditor.editing.view.document.getRoot(); | ||||
|         if (documentRoot) { | ||||
|             this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     dataChanged() { | ||||
| @@ -411,18 +415,18 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem | ||||
|         this.$editor.tooltip("show"); | ||||
|     } | ||||
|  | ||||
|     getClickIndex(pos: TextPosition) { | ||||
|         let clickIndex = pos.offset - pos.textNode.startOffset; | ||||
|     getClickIndex(pos: Position) { | ||||
|         let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0); | ||||
|  | ||||
|         let curNode = pos.textNode; | ||||
|         let curNode: Node | Text | Element | null = pos.textNode; | ||||
|  | ||||
|         while (curNode.previousSibling) { | ||||
|         while (curNode?.previousSibling) { | ||||
|             curNode = curNode.previousSibling; | ||||
|  | ||||
|             if (curNode.name === "reference") { | ||||
|                 clickIndex += curNode._attrs.get("notePath").length + 1; | ||||
|             } else { | ||||
|                 clickIndex += curNode.data.length; | ||||
|             if ((curNode as Element).name === "reference") { | ||||
|                 clickIndex += (curNode.getAttribute("notePath") as string).length + 1; | ||||
|             } else if ("data" in curNode) { | ||||
|                 clickIndex += (curNode.data as string).length; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -480,8 +484,12 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem | ||||
|         this.$editor.trigger("focus"); | ||||
|  | ||||
|         this.textEditor.model.change((writer) => { | ||||
|             const positionAt = writer.createPositionAt(this.textEditor.model.document.getRoot(), "end"); | ||||
|             const documentRoot = this.textEditor.editing.model.document.getRoot(); | ||||
|             if (!documentRoot) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const positionAt = writer.createPositionAt(documentRoot, "end"); | ||||
|             writer.setSelection(positionAt); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import type { FindAndReplaceState, FindCommandResult } from "@triliumnext/ckeditor5"; | ||||
| import type { FindResult } from "./find.js"; | ||||
| import type FindWidget from "./find.js"; | ||||
|  | ||||
| @@ -14,8 +15,8 @@ interface Match { | ||||
| export default class FindInText { | ||||
|  | ||||
|     private parent: FindWidget; | ||||
|     private findResult?: CKFindResult | null; | ||||
|     private editingState?: EditingState; | ||||
|     private findResult?: FindCommandResult | null; | ||||
|     private editingState?: FindAndReplaceState; | ||||
|  | ||||
|     constructor(parent: FindWidget) { | ||||
|         this.parent = parent; | ||||
| @@ -40,7 +41,7 @@ export default class FindInText { | ||||
|  | ||||
|         // Clear | ||||
|         const findAndReplaceEditing = textEditor.plugins.get("FindAndReplaceEditing"); | ||||
|         findAndReplaceEditing.state.clear(model); | ||||
|         findAndReplaceEditing.state?.clear(model); | ||||
|         findAndReplaceEditing.stop(); | ||||
|         this.editingState = findAndReplaceEditing.state; | ||||
|         if (searchTerm !== "") { | ||||
| @@ -52,14 +53,14 @@ export default class FindInText { | ||||
|             // let m = text.match(re); | ||||
|             // totalFound = m ? m.length : 0; | ||||
|             const options = { matchCase: matchCase, wholeWords: wholeWord }; | ||||
|             findResult = textEditor.execute<CKFindResult>("find", searchTerm, options); | ||||
|             findResult = textEditor.execute("find", searchTerm, options); | ||||
|             totalFound = findResult.results.length; | ||||
|             // Find the result beyond the cursor | ||||
|             const cursorPos = model.document.selection.getLastPosition(); | ||||
|             for (let i = 0; i < findResult.results.length; ++i) { | ||||
|                 const marker = findResult.results.get(i).marker; | ||||
|                 const fromPos = marker.getStart(); | ||||
|                 if (cursorPos && fromPos.compareWith(cursorPos) !== "before") { | ||||
|                 const marker = findResult.results.get(i)?.marker; | ||||
|                 const fromPos = marker?.getStart(); | ||||
|                 if (cursorPos && fromPos && fromPos.compareWith(cursorPos) !== "before") { | ||||
|                     currentFound = i; | ||||
|                     break; | ||||
|                 } | ||||
| @@ -75,7 +76,7 @@ export default class FindInText { | ||||
|             // XXX Do this accessing the private data? | ||||
|             // See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findnextcommand.js | ||||
|             for (let i = 0; i < currentFound; ++i) { | ||||
|                 textEditor?.execute("findNext", searchTerm); | ||||
|                 textEditor?.execute("findNext"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -109,17 +110,17 @@ export default class FindInText { | ||||
|             // Clear the markers and set the caret to the | ||||
|             // current occurrence | ||||
|             const model = textEditor.model; | ||||
|             const range = this.findResult?.results?.get(currentFound).marker.getRange(); | ||||
|             const range = this.findResult?.results?.get(currentFound)?.marker?.getRange(); | ||||
|             // From | ||||
|             // https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findandreplace.js#L92 | ||||
|             // XXX Roll our own since already done for codeEditor and | ||||
|             //     will probably allow more refactoring? | ||||
|             let findAndReplaceEditing = textEditor.plugins.get("FindAndReplaceEditing"); | ||||
|             findAndReplaceEditing.state.clear(model); | ||||
|             findAndReplaceEditing.state?.clear(model); | ||||
|             findAndReplaceEditing.stop(); | ||||
|             if (range) { | ||||
|                 model.change((writer) => { | ||||
|                     writer.setSelection(range, 0); | ||||
|                     writer.setSelection(range); | ||||
|                 }); | ||||
|             } | ||||
|             textEditor.editing.view.scrollToTheSelection(); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { t } from "../../services/i18n.js"; | ||||
| import libraryLoader from "../../services/library_loader.js"; | ||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||
| import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js"; | ||||
| import mimeTypesService from "../../services/mime_types.js"; | ||||
| import utils, { hasTouchBar } from "../../services/utils.js"; | ||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | ||||
| @@ -17,27 +17,25 @@ 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 { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor } from "@triliumnext/ckeditor5"; | ||||
| import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig } from "@triliumnext/ckeditor5"; | ||||
| import "@triliumnext/ckeditor5/index.css"; | ||||
|  | ||||
| const ENABLE_INSPECTOR = false; | ||||
|  | ||||
| const mentionSetup: MentionConfig = { | ||||
|     feeds: [ | ||||
| const mentionSetup: MentionFeed[] = [ | ||||
|     { | ||||
|         marker: "@", | ||||
|         feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), | ||||
|         itemRenderer: (item) => { | ||||
|             const itemElement = document.createElement("button"); | ||||
|  | ||||
|                 itemElement.innerHTML = `${item.highlightedNotePathTitle} `; | ||||
|             itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `; | ||||
|  | ||||
|             return itemElement; | ||||
|         }, | ||||
|         minimumCharacters: 0 | ||||
|     } | ||||
|     ] | ||||
| }; | ||||
| ]; | ||||
|  | ||||
| const TPL = /*html*/` | ||||
| <div class="note-detail-editable-text note-detail-printable"> | ||||
| @@ -128,7 +126,7 @@ function buildListOfLanguages() { | ||||
| export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|  | ||||
|     private contentLanguage?: string | null; | ||||
|     private watchdog!: EditorWatchdog<CKTextEditor>; | ||||
|     private watchdog!: EditorWatchdog<ClassicEditor | PopupEditor>; | ||||
|  | ||||
|     private $editor!: JQuery<HTMLElement>; | ||||
|  | ||||
| @@ -158,7 +156,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|         // display of $widget in both branches. | ||||
|         this.$widget.show(); | ||||
|  | ||||
|         this.watchdog = new EditorWatchdog<CKTextEditor>(editorClass, { | ||||
|         const config: WatchdogConfig = { | ||||
|             // 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 | ||||
| @@ -173,7 +171,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|             // A minimum number of milliseconds between saving the editor data internally (defaults to 5000). | ||||
|             // Note that for large documents, this might impact the editor performance. | ||||
|             saveInterval: 5000 | ||||
|         }); | ||||
|         }; | ||||
|         this.watchdog = isClassicEditor ? new EditorWatchdog(ClassicEditor, config) : new EditorWatchdog(PopupEditor, config); | ||||
|  | ||||
|         this.watchdog.on("stateChange", () => { | ||||
|             const currentState = this.watchdog.state; | ||||
| @@ -226,7 +225,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|             const editor = await editorClass.create(elementOrData, finalConfig); | ||||
|  | ||||
|             const notificationsPlugin = editor.plugins.get("Notification"); | ||||
|             notificationsPlugin.on("show:warning", (evt: CKEvent, data: PluginEventData) => { | ||||
|             notificationsPlugin.on("show:warning", (evt, data) => { | ||||
|                 const title = data.title; | ||||
|                 const message = data.message.message; | ||||
|  | ||||
| @@ -447,10 +446,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | ||||
|         } | ||||
|  | ||||
|         if (callback) { | ||||
|             callback(this.watchdog.editor); | ||||
|             callback(this.watchdog.editor as CKTextEditor); | ||||
|         } | ||||
|  | ||||
|         resolve(this.watchdog.editor); | ||||
|         resolve(this.watchdog.editor as CKTextEditor); | ||||
|     } | ||||
|  | ||||
|     addLinkToTextCommand() { | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import "ckeditor5/ckeditor5.css"; | ||||
| import { COMMON_PLUGINS, CORE_PLUGINS, POPUP_EDITOR_PLUGINS } from "./plugins"; | ||||
| import { BalloonEditor, DecoupledEditor } from "ckeditor5"; | ||||
| import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } from "ckeditor5"; | ||||
| export { EditorWatchdog } from "ckeditor5"; | ||||
| export type { EditorConfig, MentionFeed, MentionFeedObjectItem, Node, Position, Element, WatchdogConfig } from "ckeditor5"; | ||||
|  | ||||
| /** | ||||
|  * Short-hand for the CKEditor classes supported by Trilium for text editing. | ||||
| @@ -12,6 +13,9 @@ export type CKTextEditor = (ClassicEditor | PopupEditor) & { | ||||
|     removeSelection(): Promise<void>; | ||||
| }; | ||||
|  | ||||
| export type FindAndReplaceState = FindAndReplaceEditing["state"]; | ||||
| export type FindCommandResult = ReturnType<FindCommand["execute"]>; | ||||
|  | ||||
| /** | ||||
|  * The text editor that can be used for editing attributes and relations. | ||||
|  */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user