diff --git a/src/ckeditor/ContextedPlugin.ts b/src/ckeditor/ContextedPlugin.ts index 6c857f5..71905d7 100644 --- a/src/ckeditor/ContextedPlugin.ts +++ b/src/ckeditor/ContextedPlugin.ts @@ -13,10 +13,7 @@ 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') - ) + this.editor.commands.add('autocomplete', new AttributeCommand(this.editor, 'autocomplete')) } afterInit() { this._addAutocomplete() @@ -27,7 +24,7 @@ export default class ContextedLinkEditing extends Plugin { // Extend the text node's schema to accept the abbreviation attribute. schema.extend('$text', { - allowAttributes: ['contextedLink', 'autocomplete'], + allowAttributes: ['contextedLink', 'autocomplete'] }) } _defineConverters() { @@ -43,19 +40,19 @@ export default class ContextedLinkEditing extends Plugin { const { writer } = conversionApi return writer.createAttributeElement('a', { - 'data-contexted-link': modelAttributeValue, + 'data-contexted-link': modelAttributeValue }) - }, + } }) conversion.for('upcast').elementToAttribute({ view: { name: 'a', - key: 'data-contexted-link', + key: 'data-contexted-link' }, model: { - key: 'contextedLink', + key: 'contextedLink' }, - converterPriority: 'high', + converterPriority: 'high' }) } _addAutocomplete() { @@ -83,7 +80,7 @@ export default class ContextedLinkEditing extends Plugin { const focus = selection.focus const block = focus?.parent if (!block || !focus) return - const { text } = getTextAfterCode( + const { text, range } = getTextAfterCode( model.createRange(model.createPositionAt(block, 0), focus), model ) @@ -91,9 +88,7 @@ export default class ContextedLinkEditing extends Plugin { const autocompleteText = (inputText as string).match(/(?<=\[\[).*/g) const cursorNodes = [focus.textNode, focus.nodeBefore, focus.nodeAfter] const autocompleteNode: any = cursorNodes.find((node) => - ['contextedLink', 'autocomplete'].some((attribute) => - node?.hasAttribute(attribute) - ) + ['contextedLink', 'autocomplete'].some((attribute) => node?.hasAttribute(attribute)) ) if (Boolean(autocompleteText) !== Boolean(autocompleteNode)) { @@ -104,6 +99,35 @@ export default class ContextedLinkEditing extends Plugin { }) showAutocomplete = autocompleteActive fireAutocompleteEvent(editor, showAutocomplete, autocompleteNode) + + const regexFormat = /(\[\[)([^[]+?)(\]\])$/g + let result + const format: Array[] = [] + while ((result = regexFormat.exec(text as string)) !== null) { + if (result && result.length < 4) { + break + } + let index = result.index + const { '1': leftDel, '2': content, '3': rightDel } = result + // Real matched string - there might be some non-capturing groups so we need to recalculate starting index. + const found = leftDel + content + rightDel + index += result[0].length - found.length + format.push([index + leftDel.length, index + leftDel.length + content.length]) + } + model.enqueueChange((writer) => { + const rangesToFormat = format.map((array) => + model.createRange(range.start.getShiftedBy(array[0]), range.start.getShiftedBy(array[1])) + ) + + const validRanges = editor.model.schema.getValidRanges(rangesToFormat, 'contextedLink') + for (const range of validRanges) { + for (const item of range.getItems()) { + if ((item as any).data) { + writer.setAttribute('contextedLink', true, range) + } + } + } + }) }) } _addContextedKeyHandler() { @@ -121,15 +145,15 @@ export default class ContextedLinkEditing extends Plugin { } const keyCodes = [...keyCodesConfirm, ...keyCodesCycle] const selection = editor.model.document.selection - const selectionInContextedLink = ['contextedLink', 'autocomplete'].some( - (attribute) => selection.hasAttribute(attribute) + const selectionInContextedLink = ['contextedLink', 'autocomplete'].some((attribute) => + selection.hasAttribute(attribute) ) if (selectionInContextedLink && keyCodes.includes(keyCode)) { if (selection.hasAttribute('contextedLink')) { const autocompleteNode = [ selection.focus?.nodeBefore, selection.focus?.textNode, - selection.focus?.nodeAfter, + selection.focus?.nodeAfter ].find((node) => Boolean(node)) fireAutocompleteEvent(editor, true, autocompleteNode) } @@ -149,9 +173,7 @@ function getNodePosition(editor: any, modelPosition: any) { 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() + const rangeRects = Rect.getDomRangeRects(domConverter.viewRangeToDom(viewRange)).pop() return rangeRects } catch (e) { console.log(e) @@ -194,18 +216,15 @@ function fireAutocompleteEvent(editor: any, show: boolean, autocompleteNode?: an event = { position: getNodePosition( editor, - editor.model.createPositionFromPath( - autocompleteNode.root, - autocompleteNode.getPath() - ) + editor.model.createPositionFromPath(autocompleteNode.root, autocompleteNode.getPath()) ), autocompleteText: autocompleteNode.data, domElement, - show: true, + show: true } } else { event = { - show: false, + show: false } } editor.model.document.fire('contextedLinkAutocomplete', event)