diff --git a/src/ckeditor/CKEditor.ts b/src/ckeditor/CKEditor.ts index ad6594f..68eee70 100644 --- a/src/ckeditor/CKEditor.ts +++ b/src/ckeditor/CKEditor.ts @@ -224,8 +224,8 @@ export default defineComponent({ this.$emit('click', { evt, data }, editor) }) - editor.model.document.on('contextedLinkAutocomplete', (evt, data) => { - this.$emit('contextedLinkAutocomplete', { evt, data }) + editor.model.document.on('contextedLinkAutocomplete', (_, data) => { + this.$emit('contextedLinkAutocomplete', data) }) }, }, diff --git a/src/ckeditor/ContextedPlugin.ts b/src/ckeditor/ContextedPlugin.ts index 5508278..47b4e9e 100644 --- a/src/ckeditor/ContextedPlugin.ts +++ b/src/ckeditor/ContextedPlugin.ts @@ -1,5 +1,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin' import { TwoStepCaretMovement, inlineHighlight } from 'ckeditor5/src/typing' +import AttributeCommand from '@ckeditor/ckeditor5-basic-styles/src/attributecommand' +import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect' const HIGHLIGHT_CLASS = 'ck-link_selected' @@ -10,6 +12,10 @@ export default class ContextedLinkEditing extends Plugin { const twoStepCaretMovementPlugin = this.editor.plugins.get(TwoStepCaretMovement) twoStepCaretMovementPlugin.registerAttribute('contextedLink') inlineHighlight(this.editor, 'contextedLink', 'a', HIGHLIGHT_CLASS) + this.editor.commands.add( + 'autocomplete', + new AttributeCommand(this.editor, 'autocomplete') + ) } afterInit() { // const editor = this.editor @@ -28,7 +34,7 @@ export default class ContextedLinkEditing extends Plugin { // Extend the text node's schema to accept the abbreviation attribute. schema.extend('$text', { - allowAttributes: ['abbreviation', 'contextedLink'], + allowAttributes: ['contextedLink', 'autocomplete'], }) } _defineConverters() { @@ -62,10 +68,9 @@ export default class ContextedLinkEditing extends Plugin { _addAutocomplete() { // Copied from: node_modules/@ckeditor/ckeditor5-autoformat/src/inlineautoformatediting.js const editor = this.editor - editor.model.document.on('change:data', (_, batch) => { - if (batch.isUndo || !batch.isLocal) { - return - } + // let autocomplete = false + editor.model.document.on('change', (_, batch) => { + if (batch.isUndo || !batch.isLocal) return const model = editor.model const selection = model.document.selection // Do nothing if selection is not collapsed. @@ -75,7 +80,7 @@ export default class ContextedLinkEditing extends Plugin { // Typing is represented by only a single change. if ( changes.length != 1 || - entry.type !== 'insert' || + (entry.type !== 'insert' && entry.type !== 'remove') || entry.name != '$text' || entry.length != 1 ) { @@ -84,21 +89,60 @@ export default class ContextedLinkEditing extends Plugin { const focus = selection.focus const block = focus?.parent if (!block || !focus) return - const { text, range } = getTextAfterCode( + const { text } = getTextAfterCode( model.createRange(model.createPositionAt(block, 0), focus), model ) const inputText = (text as string).split(']]').at(-1) - const autocompleteOpenMatch = (inputText as string).match(/(?<=\[\[).+/g) - const autocompleteCloseMatch = (inputText as string).match(/(?<=\[\[).+?(?=]])/g) - const openAutocomplete = autocompleteOpenMatch && !autocompleteCloseMatch - if (openAutocomplete) { - editor.model.document.fire('contextedLinkAutocomplete', autocompleteOpenMatch) + const autocompleteText = (inputText as string).match(/(?<=\[\[).*/g) + const cursorNodes = [focus.textNode, focus.nodeBefore, focus.nodeAfter] + const autocompleteNode: any = cursorNodes.find((node) => + node?.hasAttribute('autocomplete') + ) + interface AutocompleteEvent { + position?: any + autocompleteText?: string + show: boolean + } + if (Boolean(autocompleteText) || Boolean(autocompleteNode)) { + if (Boolean(autocompleteText) !== Boolean(autocompleteNode)) { + editor.execute('autocomplete') + if (autocompleteNode) + editor.model.document.fire('contextedLinkAutocomplete', { show: false }) + } else { + const event: AutocompleteEvent = { + position: getNodePosition( + editor, + editor.model.createPositionFromPath( + autocompleteNode.root, + autocompleteNode.getPath() + ) + ), + autocompleteText: autocompleteNode.data, + show: true, + } + editor.model.document.fire('contextedLinkAutocomplete', event) + } } }) } } +function getNodePosition(editor: any, modelPosition: any) { + try { + const mapper = editor.editing.mapper + const viewPosition = mapper.toViewPosition(modelPosition) + const viewRange = editor.editing.view.createRange(viewPosition) + const domConverter = editor.editing.view.domConverter + const rangeRects = Rect.getDomRangeRects( + domConverter.viewRangeToDom(viewRange) + ).pop() + return rangeRects + } catch (e) { + console.log(e) + } +} + // function testOutputToRanges(start: any, arrays: any[], model: any) { // return arrays // .filter((array) => array[0] !== undefined && array[1] !== undefined) diff --git a/src/components/NoteEditor.vue b/src/components/NoteEditor.vue index 6a8bcd7..0df295d 100644 --- a/src/components/NoteEditor.vue +++ b/src/components/NoteEditor.vue @@ -1,4 +1,5 @@