diff options
Diffstat (limited to 'app/assets/javascripts/content_editor/extensions')
4 files changed, 107 insertions, 7 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index 204ac07d401..61f379fc0a2 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -1,10 +1,21 @@ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'; -import { lowlight } from 'lowlight/lib/all'; +import { textblockTypeInputRule } from '@tiptap/core'; +import codeBlockLanguageLoader from '../services/code_block_language_loader'; const extractLanguage = (element) => element.getAttribute('lang'); +export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/; +export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/; export default CodeBlockLowlight.extend({ isolating: true, + exitOnArrowDown: false, + + addOptions() { + return { + ...this.parent?.(), + languageLoader: codeBlockLanguageLoader, + }; + }, addAttributes() { return { @@ -18,16 +29,40 @@ export default CodeBlockLowlight.extend({ }, }; }, + addInputRules() { + const { languageLoader } = this.options; + const getAttributes = (match) => languageLoader?.loadLanguageFromInputRule(match) || {}; + + return [ + textblockTypeInputRule({ + find: backtickInputRegex, + type: this.type, + getAttributes, + }), + textblockTypeInputRule({ + find: tildeInputRegex, + type: this.type, + getAttributes, + }), + ]; + }, + parseHTML() { + return [ + ...(this.parent?.() || []), + { + tag: 'div.markdown-code-block', + skip: true, + }, + ]; + }, renderHTML({ HTMLAttributes }) { return [ 'pre', { ...HTMLAttributes, - class: `content-editor-code-block ${HTMLAttributes.class}`, + class: `content-editor-code-block ${gon.user_color_scheme} ${HTMLAttributes.class}`, }, ['code', {}, 0], ]; }, -}).configure({ - lowlight, }); diff --git a/app/assets/javascripts/content_editor/extensions/diagram.js b/app/assets/javascripts/content_editor/extensions/diagram.js new file mode 100644 index 00000000000..d192b815092 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/diagram.js @@ -0,0 +1,56 @@ +import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; +import CodeBlockHighlight from './code_block_highlight'; + +export default CodeBlockHighlight.extend({ + name: 'diagram', + + isolating: true, + + addAttributes() { + return { + language: { + default: null, + parseHTML: (element) => { + return element.dataset.diagram; + }, + }, + }; + }, + + parseHTML() { + return [ + { + priority: PARSE_HTML_PRIORITY_HIGHEST, + tag: '[data-diagram]', + getContent(element, schema) { + const source = atob(element.dataset.diagramSrc.replace('data:text/plain;base64,', '')); + const node = schema.node('paragraph', {}, [schema.text(source)]); + return node.content; + }, + }, + ]; + }, + + renderHTML({ HTMLAttributes: { language, ...HTMLAttributes } }) { + return [ + 'div', + [ + 'pre', + { + language, + class: `content-editor-code-block code highlight`, + ...HTMLAttributes, + }, + ['code', {}, 0], + ], + ]; + }, + + addCommands() { + return {}; + }, + + addInputRules() { + return []; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js index 519f7f168ce..311db8151cb 100644 --- a/app/assets/javascripts/content_editor/extensions/image.js +++ b/app/assets/javascripts/content_editor/extensions/image.js @@ -1,6 +1,6 @@ import { Image } from '@tiptap/extension-image'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; -import ImageWrapper from '../components/wrappers/image.vue'; +import MediaWrapper from '../components/wrappers/media.vue'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; const resolveImageEl = (element) => @@ -78,6 +78,6 @@ export default Image.extend({ ]; }, addNodeView() { - return VueNodeViewRenderer(ImageWrapper); + return VueNodeViewRenderer(MediaWrapper); }, }); diff --git a/app/assets/javascripts/content_editor/extensions/playable.js b/app/assets/javascripts/content_editor/extensions/playable.js index 0062bc563db..2c5269377c5 100644 --- a/app/assets/javascripts/content_editor/extensions/playable.js +++ b/app/assets/javascripts/content_editor/extensions/playable.js @@ -1,6 +1,8 @@ /* eslint-disable @gitlab/require-i18n-strings */ import { Node } from '@tiptap/core'; +import { VueNodeViewRenderer } from '@tiptap/vue-2'; +import MediaWrapper from '../components/wrappers/media.vue'; const queryPlayableElement = (element, mediaType) => element.querySelector(mediaType); @@ -11,6 +13,9 @@ export default Node.create({ addAttributes() { return { + uploading: { + default: false, + }, src: { default: null, parseHTML: (element) => { @@ -60,7 +65,11 @@ export default Node.create({ ...this.extraElementAttrs, }, ], - ['a', { href: node.attrs.src }, node.attrs.alt], + ['a', { href: node.attrs.src }, node.attrs.title || node.attrs.alt || ''], ]; }, + + addNodeView() { + return VueNodeViewRenderer(MediaWrapper); + }, }); |