diff options
Diffstat (limited to 'app/assets/javascripts/content_editor/extensions/reference.js')
-rw-r--r-- | app/assets/javascripts/content_editor/extensions/reference.js | 84 |
1 files changed, 83 insertions, 1 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/reference.js b/app/assets/javascripts/content_editor/extensions/reference.js index b56aa8596a0..ef69b9bbda6 100644 --- a/app/assets/javascripts/content_editor/extensions/reference.js +++ b/app/assets/javascripts/content_editor/extensions/reference.js @@ -1,4 +1,4 @@ -import { Node } from '@tiptap/core'; +import { Node, InputRule } from '@tiptap/core'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; import ReferenceWrapper from '../components/wrappers/reference.vue'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; @@ -8,6 +8,21 @@ const getAnchor = (element) => { return element.querySelector('a'); }; +const findReference = (editor, reference) => { + let position; + + editor.view.state.doc.descendants((descendant, pos) => { + if (descendant.isText && descendant.text.includes(reference)) { + position = pos + descendant.text.indexOf(reference); + return false; + } + + return true; + }); + + return position; +}; + export default Node.create({ name: 'reference', @@ -17,6 +32,12 @@ export default Node.create({ atom: true, + addOptions() { + return { + assetResolver: null, + }; + }, + addAttributes() { return { className: { @@ -42,6 +63,54 @@ export default Node.create({ }; }, + addInputRules() { + const { editor } = this; + const { assetResolver } = this.options; + const referenceInputRegex = /(?:^|\s)([\w/]*([!&#])\d+(\+?s?))(?:\s|\n)/m; + const referenceTypes = { + '#': 'issue', + '!': 'merge_request', + '&': 'epic', + }; + + return [ + new InputRule({ + find: referenceInputRegex, + handler: async ({ match }) => { + const [, referenceId, referenceSymbol, expansionType] = match; + const referenceType = referenceTypes[referenceSymbol]; + + const { + href, + text, + expandedText, + fullyExpandedText, + } = await assetResolver.resolveReference(referenceId); + + if (!text) return; + + let referenceText = text; + if (expansionType === '+') referenceText = expandedText; + if (expansionType === '+s') referenceText = fullyExpandedText; + + const position = findReference(editor, referenceId); + if (!position) return; + + editor.view.dispatch( + editor.state.tr.replaceWith(position, position + referenceId.length, [ + this.type.create({ + referenceType, + originalText: referenceId, + href, + text: referenceText, + }), + ]), + ); + }, + }), + ]; + }, + parseHTML() { return [ { @@ -51,6 +120,19 @@ export default Node.create({ ]; }, + renderHTML({ node }) { + return [ + 'gl-reference', + { + 'data-reference-type': node.attrs.referenceType, + 'data-original-text': node.attrs.originalText, + href: node.attrs.href, + text: node.attrs.text, + }, + node.attrs.text, + ]; + }, + addNodeView() { return new VueNodeViewRenderer(ReferenceWrapper); }, |