From b76ae638462ab0f673e5915986070518dd3f9ad3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 19 Aug 2021 09:08:42 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-2-stable-ee --- .../content_editor/extensions/attachment.js | 53 +++++++++++ .../content_editor/extensions/blockquote.js | 6 +- .../javascripts/content_editor/extensions/bold.js | 6 +- .../content_editor/extensions/bullet_list.js | 6 +- .../javascripts/content_editor/extensions/code.js | 6 +- .../extensions/code_block_highlight.js | 18 +--- .../content_editor/extensions/document.js | 4 +- .../content_editor/extensions/dropcursor.js | 4 +- .../javascripts/content_editor/extensions/emoji.js | 93 +++++++++++++++++++ .../content_editor/extensions/gapcursor.js | 4 +- .../content_editor/extensions/hard_break.js | 6 +- .../content_editor/extensions/heading.js | 6 +- .../content_editor/extensions/history.js | 4 +- .../content_editor/extensions/horizontal_rule.js | 4 +- .../javascripts/content_editor/extensions/image.js | 103 +-------------------- .../content_editor/extensions/inline_diff.js | 50 ++++++++++ .../content_editor/extensions/italic.js | 5 +- .../javascripts/content_editor/extensions/link.js | 18 +--- .../content_editor/extensions/list_item.js | 6 +- .../content_editor/extensions/loading.js | 24 +++++ .../content_editor/extensions/ordered_list.js | 6 +- .../content_editor/extensions/paragraph.js | 6 +- .../content_editor/extensions/reference.js | 78 ++++++++++++++++ .../content_editor/extensions/strike.js | 10 +- .../content_editor/extensions/subscript.js | 1 + .../content_editor/extensions/superscript.js | 1 + .../javascripts/content_editor/extensions/table.js | 8 +- .../content_editor/extensions/table_cell.js | 6 +- .../content_editor/extensions/table_header.js | 6 +- .../content_editor/extensions/table_row.js | 48 +--------- .../content_editor/extensions/task_item.js | 33 +++++++ .../content_editor/extensions/task_list.js | 30 ++++++ .../javascripts/content_editor/extensions/text.js | 6 +- 33 files changed, 392 insertions(+), 273 deletions(-) create mode 100644 app/assets/javascripts/content_editor/extensions/attachment.js create mode 100644 app/assets/javascripts/content_editor/extensions/emoji.js create mode 100644 app/assets/javascripts/content_editor/extensions/inline_diff.js create mode 100644 app/assets/javascripts/content_editor/extensions/loading.js create mode 100644 app/assets/javascripts/content_editor/extensions/reference.js create mode 100644 app/assets/javascripts/content_editor/extensions/subscript.js create mode 100644 app/assets/javascripts/content_editor/extensions/superscript.js create mode 100644 app/assets/javascripts/content_editor/extensions/task_item.js create mode 100644 app/assets/javascripts/content_editor/extensions/task_list.js (limited to 'app/assets/javascripts/content_editor/extensions') diff --git a/app/assets/javascripts/content_editor/extensions/attachment.js b/app/assets/javascripts/content_editor/extensions/attachment.js new file mode 100644 index 00000000000..29ee282f2d2 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/attachment.js @@ -0,0 +1,53 @@ +import { Extension } from '@tiptap/core'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { handleFileEvent } from '../services/upload_helpers'; + +export default Extension.create({ + name: 'attachment', + + defaultOptions: { + uploadsPath: null, + renderMarkdown: null, + }, + + addCommands() { + return { + uploadAttachment: ({ file }) => () => { + const { uploadsPath, renderMarkdown } = this.options; + + return handleFileEvent({ file, uploadsPath, renderMarkdown, editor: this.editor }); + }, + }; + }, + addProseMirrorPlugins() { + const { editor } = this; + + return [ + new Plugin({ + key: new PluginKey('attachment'), + props: { + handlePaste: (_, event) => { + const { uploadsPath, renderMarkdown } = this.options; + + return handleFileEvent({ + editor, + file: event.clipboardData.files[0], + uploadsPath, + renderMarkdown, + }); + }, + handleDrop: (_, event) => { + const { uploadsPath, renderMarkdown } = this.options; + + return handleFileEvent({ + editor, + file: event.dataTransfer.files[0], + uploadsPath, + renderMarkdown, + }); + }, + }, + }), + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/blockquote.js b/app/assets/javascripts/content_editor/extensions/blockquote.js index a4297b4550c..45f53fe230b 100644 --- a/app/assets/javascripts/content_editor/extensions/blockquote.js +++ b/app/assets/javascripts/content_editor/extensions/blockquote.js @@ -1,5 +1 @@ -import { Blockquote } from '@tiptap/extension-blockquote'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Blockquote; -export const serializer = defaultMarkdownSerializer.nodes.blockquote; +export { Blockquote as default } from '@tiptap/extension-blockquote'; diff --git a/app/assets/javascripts/content_editor/extensions/bold.js b/app/assets/javascripts/content_editor/extensions/bold.js index e90e7b59da0..0b7b22265b6 100644 --- a/app/assets/javascripts/content_editor/extensions/bold.js +++ b/app/assets/javascripts/content_editor/extensions/bold.js @@ -1,5 +1 @@ -import { Bold } from '@tiptap/extension-bold'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Bold; -export const serializer = defaultMarkdownSerializer.marks.strong; +export { Bold as default } from '@tiptap/extension-bold'; diff --git a/app/assets/javascripts/content_editor/extensions/bullet_list.js b/app/assets/javascripts/content_editor/extensions/bullet_list.js index 178b798e2d4..01ead571fe1 100644 --- a/app/assets/javascripts/content_editor/extensions/bullet_list.js +++ b/app/assets/javascripts/content_editor/extensions/bullet_list.js @@ -1,5 +1 @@ -import { BulletList } from '@tiptap/extension-bullet-list'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = BulletList; -export const serializer = defaultMarkdownSerializer.nodes.bullet_list; +export { BulletList as default } from '@tiptap/extension-bullet-list'; diff --git a/app/assets/javascripts/content_editor/extensions/code.js b/app/assets/javascripts/content_editor/extensions/code.js index 8be50dc39c5..f93c22ad10e 100644 --- a/app/assets/javascripts/content_editor/extensions/code.js +++ b/app/assets/javascripts/content_editor/extensions/code.js @@ -1,5 +1 @@ -import { Code } from '@tiptap/extension-code'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Code; -export const serializer = defaultMarkdownSerializer.marks.code; +export { Code as default } from '@tiptap/extension-code'; 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 50d72f4089a..c6d32fb8547 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,9 @@ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'; import * as lowlight from 'lowlight'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; const extractLanguage = (element) => element.getAttribute('lang'); -const ExtendedCodeBlockLowlight = CodeBlockLowlight.extend({ +export default CodeBlockLowlight.extend({ addAttributes() { return { language: { @@ -15,18 +14,6 @@ const ExtendedCodeBlockLowlight = CodeBlockLowlight.extend({ }; }, }, - /* `params` is the name of the attribute that - prosemirror-markdown uses to extract the language - of a codeblock. - https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/to_markdown.js#L62 - */ - params: { - parseHTML: (element) => { - return { - params: extractLanguage(element), - }; - }, - }, class: { default: 'code highlight js-syntax-highlight', }, @@ -38,6 +25,3 @@ const ExtendedCodeBlockLowlight = CodeBlockLowlight.extend({ }).configure({ lowlight, }); - -export const tiptapExtension = ExtendedCodeBlockLowlight; -export const serializer = defaultMarkdownSerializer.nodes.code_block; diff --git a/app/assets/javascripts/content_editor/extensions/document.js b/app/assets/javascripts/content_editor/extensions/document.js index 99aa8d6235a..27496fd60b7 100644 --- a/app/assets/javascripts/content_editor/extensions/document.js +++ b/app/assets/javascripts/content_editor/extensions/document.js @@ -1,3 +1 @@ -import Document from '@tiptap/extension-document'; - -export const tiptapExtension = Document; +export { Document as default } from '@tiptap/extension-document'; diff --git a/app/assets/javascripts/content_editor/extensions/dropcursor.js b/app/assets/javascripts/content_editor/extensions/dropcursor.js index 44c378ac7db..825dc73b9d9 100644 --- a/app/assets/javascripts/content_editor/extensions/dropcursor.js +++ b/app/assets/javascripts/content_editor/extensions/dropcursor.js @@ -1,3 +1 @@ -import Dropcursor from '@tiptap/extension-dropcursor'; - -export const tiptapExtension = Dropcursor; +export { Dropcursor as default } from '@tiptap/extension-dropcursor'; diff --git a/app/assets/javascripts/content_editor/extensions/emoji.js b/app/assets/javascripts/content_editor/extensions/emoji.js new file mode 100644 index 00000000000..d88b9f92215 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/emoji.js @@ -0,0 +1,93 @@ +import { Node } from '@tiptap/core'; +import { InputRule } from 'prosemirror-inputrules'; +import { initEmojiMap, getAllEmoji } from '~/emoji'; + +export const emojiInputRegex = /(?:^|\s)((?::)((?:\w+))(?::))$/; + +export default Node.create({ + name: 'emoji', + + inline: true, + + group: 'inline', + + draggable: true, + + addAttributes() { + return { + moji: { + default: null, + parseHTML: (element) => { + return { + moji: element.textContent, + }; + }, + }, + name: { + default: null, + parseHTML: (element) => { + return { + name: element.dataset.name, + }; + }, + }, + title: { + default: null, + }, + unicodeVersion: { + default: '6.0', + parseHTML: (element) => { + return { + unicodeVersion: element.dataset.unicodeVersion, + }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'gl-emoji', + }, + ]; + }, + + renderHTML({ node }) { + return [ + 'gl-emoji', + { + 'data-name': node.attrs.name, + title: node.attrs.title, + 'data-unicode-version': node.attrs.unicodeVersion, + }, + node.attrs.moji, + ]; + }, + + addInputRules() { + return [ + new InputRule(emojiInputRegex, (state, match, start, end) => { + const [, , name] = match; + const emojis = getAllEmoji(); + const emoji = emojis[name]; + const { tr } = state; + + if (emoji) { + tr.replaceWith(start, end, [ + state.schema.text(' '), + this.type.create({ name, moji: emoji.e, unicodeVersion: emoji.u, title: emoji.d }), + ]); + + return tr; + } + + return null; + }), + ]; + }, + + onCreate() { + initEmojiMap(); + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/gapcursor.js b/app/assets/javascripts/content_editor/extensions/gapcursor.js index 2db862e4580..ef88cd92b4e 100644 --- a/app/assets/javascripts/content_editor/extensions/gapcursor.js +++ b/app/assets/javascripts/content_editor/extensions/gapcursor.js @@ -1,3 +1 @@ -import Gapcursor from '@tiptap/extension-gapcursor'; - -export const tiptapExtension = Gapcursor; +export { Gapcursor as default } from '@tiptap/extension-gapcursor'; diff --git a/app/assets/javascripts/content_editor/extensions/hard_break.js b/app/assets/javascripts/content_editor/extensions/hard_break.js index 756eefa875c..fb81c6b79b6 100644 --- a/app/assets/javascripts/content_editor/extensions/hard_break.js +++ b/app/assets/javascripts/content_editor/extensions/hard_break.js @@ -1,13 +1,9 @@ import { HardBreak } from '@tiptap/extension-hard-break'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; -const ExtendedHardBreak = HardBreak.extend({ +export default HardBreak.extend({ addKeyboardShortcuts() { return { 'Shift-Enter': () => this.editor.commands.setHardBreak(), }; }, }); - -export const tiptapExtension = ExtendedHardBreak; -export const serializer = defaultMarkdownSerializer.nodes.hard_break; diff --git a/app/assets/javascripts/content_editor/extensions/heading.js b/app/assets/javascripts/content_editor/extensions/heading.js index f69869d1e09..48303cdeca4 100644 --- a/app/assets/javascripts/content_editor/extensions/heading.js +++ b/app/assets/javascripts/content_editor/extensions/heading.js @@ -1,5 +1 @@ -import { Heading } from '@tiptap/extension-heading'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Heading; -export const serializer = defaultMarkdownSerializer.nodes.heading; +export { Heading as default } from '@tiptap/extension-heading'; diff --git a/app/assets/javascripts/content_editor/extensions/history.js b/app/assets/javascripts/content_editor/extensions/history.js index 554d797d30a..7c9d92d7b4e 100644 --- a/app/assets/javascripts/content_editor/extensions/history.js +++ b/app/assets/javascripts/content_editor/extensions/history.js @@ -1,3 +1 @@ -import History from '@tiptap/extension-history'; - -export const tiptapExtension = History; +export { History as default } from '@tiptap/extension-history'; diff --git a/app/assets/javascripts/content_editor/extensions/horizontal_rule.js b/app/assets/javascripts/content_editor/extensions/horizontal_rule.js index c287938af5c..c8ec45d835c 100644 --- a/app/assets/javascripts/content_editor/extensions/horizontal_rule.js +++ b/app/assets/javascripts/content_editor/extensions/horizontal_rule.js @@ -1,12 +1,10 @@ import { nodeInputRule } from '@tiptap/core'; import { HorizontalRule } from '@tiptap/extension-horizontal-rule'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; export const hrInputRuleRegExp = /^---$/; -export const tiptapExtension = HorizontalRule.extend({ +export default HorizontalRule.extend({ addInputRules() { return [nodeInputRule(hrInputRuleRegExp, this.type)]; }, }); -export const serializer = defaultMarkdownSerializer.nodes.horizontal_rule; diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js index 4dd8a1376ad..c9e8dfa4ad9 100644 --- a/app/assets/javascripts/content_editor/extensions/image.js +++ b/app/assets/javascripts/content_editor/extensions/image.js @@ -1,58 +1,14 @@ import { Image } from '@tiptap/extension-image'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; -import { Plugin, PluginKey } from 'prosemirror-state'; -import { __ } from '~/locale'; import ImageWrapper from '../components/wrappers/image.vue'; -import { uploadFile } from '../services/upload_file'; -import { getImageAlt, readFileAsDataURL } from '../services/utils'; - -export const acceptedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg']; const resolveImageEl = (element) => element.nodeName === 'IMG' ? element : element.querySelector('img'); -const startFileUpload = async ({ editor, file, uploadsPath, renderMarkdown }) => { - const encodedSrc = await readFileAsDataURL(file); - const { view } = editor; - - editor.commands.setImage({ uploading: true, src: encodedSrc }); - - const { state } = view; - const position = state.selection.from - 1; - const { tr } = state; - - try { - const { src, canonicalSrc } = await uploadFile({ file, uploadsPath, renderMarkdown }); - - view.dispatch( - tr.setNodeMarkup(position, undefined, { - uploading: false, - src: encodedSrc, - alt: getImageAlt(src), - canonicalSrc, - }), - ); - } catch (e) { - editor.commands.deleteRange({ from: position, to: position + 1 }); - editor.emit('error', __('An error occurred while uploading the image. Please try again.')); - } -}; - -const handleFileEvent = ({ editor, file, uploadsPath, renderMarkdown }) => { - if (acceptedMimes.includes(file?.type)) { - startFileUpload({ editor, file, uploadsPath, renderMarkdown }); - - return true; - } - - return false; -}; - -const ExtendedImage = Image.extend({ +export default Image.extend({ defaultOptions: { ...Image.options, - uploadsPath: null, - renderMarkdown: null, + inline: true, }, addAttributes() { return { @@ -107,62 +63,7 @@ const ExtendedImage = Image.extend({ }, ]; }, - addCommands() { - return { - ...this.parent(), - uploadImage: ({ file }) => () => { - const { uploadsPath, renderMarkdown } = this.options; - - handleFileEvent({ file, uploadsPath, renderMarkdown, editor: this.editor }); - }, - }; - }, - addProseMirrorPlugins() { - const { editor } = this; - - return [ - new Plugin({ - key: new PluginKey('handleDropAndPasteImages'), - props: { - handlePaste: (_, event) => { - const { uploadsPath, renderMarkdown } = this.options; - - return handleFileEvent({ - editor, - file: event.clipboardData.files[0], - uploadsPath, - renderMarkdown, - }); - }, - handleDrop: (_, event) => { - const { uploadsPath, renderMarkdown } = this.options; - - return handleFileEvent({ - editor, - file: event.dataTransfer.files[0], - uploadsPath, - renderMarkdown, - }); - }, - }, - }), - ]; - }, addNodeView() { return VueNodeViewRenderer(ImageWrapper); }, }); - -const serializer = (state, node) => { - const { alt, canonicalSrc, src, title } = node.attrs; - const quotedTitle = title ? ` ${state.quote(title)}` : ''; - - state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`); -}; - -export const configure = ({ renderMarkdown, uploadsPath }) => { - return { - tiptapExtension: ExtendedImage.configure({ inline: true, renderMarkdown, uploadsPath }), - serializer, - }; -}; diff --git a/app/assets/javascripts/content_editor/extensions/inline_diff.js b/app/assets/javascripts/content_editor/extensions/inline_diff.js new file mode 100644 index 00000000000..9471d324764 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/inline_diff.js @@ -0,0 +1,50 @@ +import { Mark, markInputRule, mergeAttributes } from '@tiptap/core'; + +export const inputRegexAddition = /(\{\+(.+?)\+\})$/gm; +export const inputRegexDeletion = /(\{-(.+?)-\})$/gm; + +export default Mark.create({ + name: 'inlineDiff', + + defaultOptions: { + HTMLAttributes: {}, + }, + + addAttributes() { + return { + type: { + default: 'addition', + parseHTML: (element) => { + return { + type: element.classList.contains('deletion') ? 'deletion' : 'addition', + }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'span.idiff', + }, + ]; + }, + + renderHTML({ HTMLAttributes: { type, ...HTMLAttributes } }) { + return [ + 'span', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + class: `idiff left right ${type}`, + }), + 0, + ]; + }, + + addInputRules() { + return [ + markInputRule(inputRegexAddition, this.type, () => ({ type: 'addition' })), + markInputRule(inputRegexDeletion, this.type, () => ({ type: 'deletion' })), + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/italic.js b/app/assets/javascripts/content_editor/extensions/italic.js index b8a7c4aba3e..99e9922044d 100644 --- a/app/assets/javascripts/content_editor/extensions/italic.js +++ b/app/assets/javascripts/content_editor/extensions/italic.js @@ -1,4 +1 @@ -import { Italic } from '@tiptap/extension-italic'; - -export const tiptapExtension = Italic; -export const serializer = { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true }; +export { Italic as default } from '@tiptap/extension-italic'; diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index 12019ab4636..53104fe07a3 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -20,7 +20,11 @@ export const extractHrefFromMarkdownLink = (match) => { return extractHrefFromMatch(match); }; -export const tiptapExtension = Link.extend({ +export default Link.extend({ + defaultOptions: { + ...Link.options, + openOnClick: false, + }, addInputRules() { return [ markInputRule(markdownLinkSyntaxInputRuleRegExp, this.type, extractHrefFromMarkdownLink), @@ -48,16 +52,4 @@ export const tiptapExtension = Link.extend({ }, }; }, -}).configure({ - openOnClick: false, }); - -export const serializer = { - open() { - return '['; - }, - close(state, mark) { - const href = mark.attrs.canonicalSrc || mark.attrs.href; - return `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`; - }, -}; diff --git a/app/assets/javascripts/content_editor/extensions/list_item.js b/app/assets/javascripts/content_editor/extensions/list_item.js index 86da98f6df7..72454b0905d 100644 --- a/app/assets/javascripts/content_editor/extensions/list_item.js +++ b/app/assets/javascripts/content_editor/extensions/list_item.js @@ -1,5 +1 @@ -import { ListItem } from '@tiptap/extension-list-item'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = ListItem; -export const serializer = defaultMarkdownSerializer.nodes.list_item; +export { ListItem as default } from '@tiptap/extension-list-item'; diff --git a/app/assets/javascripts/content_editor/extensions/loading.js b/app/assets/javascripts/content_editor/extensions/loading.js new file mode 100644 index 00000000000..2324e9b132d --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/loading.js @@ -0,0 +1,24 @@ +import { Node } from '@tiptap/core'; + +export default Node.create({ + name: 'loading', + inline: true, + group: 'inline', + + addAttributes() { + return { + label: { + default: null, + }, + }; + }, + + renderHTML({ node }) { + return [ + 'span', + { class: 'gl-display-inline-flex gl-align-items-center' }, + ['span', { class: 'gl-spinner gl-mx-2' }], + ['span', { class: 'gl-link' }, node.attrs.label], + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/ordered_list.js b/app/assets/javascripts/content_editor/extensions/ordered_list.js index d980ab8bf10..9a79187d9c1 100644 --- a/app/assets/javascripts/content_editor/extensions/ordered_list.js +++ b/app/assets/javascripts/content_editor/extensions/ordered_list.js @@ -1,5 +1 @@ -import { OrderedList } from '@tiptap/extension-ordered-list'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = OrderedList; -export const serializer = defaultMarkdownSerializer.nodes.ordered_list; +export { OrderedList as default } from '@tiptap/extension-ordered-list'; diff --git a/app/assets/javascripts/content_editor/extensions/paragraph.js b/app/assets/javascripts/content_editor/extensions/paragraph.js index 6c9f204b8ac..33bf1c94003 100644 --- a/app/assets/javascripts/content_editor/extensions/paragraph.js +++ b/app/assets/javascripts/content_editor/extensions/paragraph.js @@ -1,5 +1 @@ -import { Paragraph } from '@tiptap/extension-paragraph'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Paragraph; -export const serializer = defaultMarkdownSerializer.nodes.paragraph; +export { Paragraph as default } from '@tiptap/extension-paragraph'; diff --git a/app/assets/javascripts/content_editor/extensions/reference.js b/app/assets/javascripts/content_editor/extensions/reference.js new file mode 100644 index 00000000000..5f4484af9c8 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/reference.js @@ -0,0 +1,78 @@ +import { Node } from '@tiptap/core'; + +export default Node.create({ + name: 'reference', + + inline: true, + + group: 'inline', + + atom: true, + + addAttributes() { + return { + className: { + default: null, + parseHTML: (element) => { + return { + className: element.className, + }; + }, + }, + referenceType: { + default: null, + parseHTML: (element) => { + return { + referenceType: element.dataset.referenceType, + }; + }, + }, + originalText: { + default: null, + parseHTML: (element) => { + return { + originalText: element.dataset.original, + }; + }, + }, + href: { + default: null, + parseHTML: (element) => { + return { + href: element.getAttribute('href'), + }; + }, + }, + text: { + default: null, + parseHTML: (element) => { + return { + text: element.textContent, + }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'a.gfm:not([data-link=true])', + priority: 51, + }, + ]; + }, + + renderHTML({ node }) { + return [ + 'a', + { + class: node.attrs.className, + href: node.attrs.href, + 'data-reference-type': node.attrs.referenceType, + 'data-original': node.attrs.originalText, + }, + node.attrs.text, + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/strike.js b/app/assets/javascripts/content_editor/extensions/strike.js index 6f228e00994..b6c9a968fc2 100644 --- a/app/assets/javascripts/content_editor/extensions/strike.js +++ b/app/assets/javascripts/content_editor/extensions/strike.js @@ -1,9 +1 @@ -import { Strike } from '@tiptap/extension-strike'; - -export const tiptapExtension = Strike; -export const serializer = { - open: '~~', - close: '~~', - mixable: true, - expelEnclosingWhitespace: true, -}; +export { Strike as default } from '@tiptap/extension-strike'; diff --git a/app/assets/javascripts/content_editor/extensions/subscript.js b/app/assets/javascripts/content_editor/extensions/subscript.js new file mode 100644 index 00000000000..4bf89796efe --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/subscript.js @@ -0,0 +1 @@ +export { Subscript as default } from '@tiptap/extension-subscript'; diff --git a/app/assets/javascripts/content_editor/extensions/superscript.js b/app/assets/javascripts/content_editor/extensions/superscript.js new file mode 100644 index 00000000000..3eb7d86d90d --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/superscript.js @@ -0,0 +1 @@ +export { Superscript as default } from '@tiptap/extension-superscript'; diff --git a/app/assets/javascripts/content_editor/extensions/table.js b/app/assets/javascripts/content_editor/extensions/table.js index 566f7a21a85..0f0477cba2e 100644 --- a/app/assets/javascripts/content_editor/extensions/table.js +++ b/app/assets/javascripts/content_editor/extensions/table.js @@ -1,7 +1 @@ -import { Table } from '@tiptap/extension-table'; - -export const tiptapExtension = Table; - -export function serializer(state, node) { - state.renderContent(node); -} +export { Table as default } from '@tiptap/extension-table'; diff --git a/app/assets/javascripts/content_editor/extensions/table_cell.js b/app/assets/javascripts/content_editor/extensions/table_cell.js index 6c25b867466..5bdc39231a1 100644 --- a/app/assets/javascripts/content_editor/extensions/table_cell.js +++ b/app/assets/javascripts/content_editor/extensions/table_cell.js @@ -1,9 +1,5 @@ import { TableCell } from '@tiptap/extension-table-cell'; -export const tiptapExtension = TableCell.extend({ +export default TableCell.extend({ content: 'inline*', }); - -export function serializer(state, node) { - state.renderInline(node); -} diff --git a/app/assets/javascripts/content_editor/extensions/table_header.js b/app/assets/javascripts/content_editor/extensions/table_header.js index 3475857b9e6..23509706e4b 100644 --- a/app/assets/javascripts/content_editor/extensions/table_header.js +++ b/app/assets/javascripts/content_editor/extensions/table_header.js @@ -1,9 +1,5 @@ import { TableHeader } from '@tiptap/extension-table-header'; -export const tiptapExtension = TableHeader.extend({ +export default TableHeader.extend({ content: 'inline*', }); - -export function serializer(state, node) { - state.renderInline(node); -} diff --git a/app/assets/javascripts/content_editor/extensions/table_row.js b/app/assets/javascripts/content_editor/extensions/table_row.js index 07d2eb4faa2..541257a6cbf 100644 --- a/app/assets/javascripts/content_editor/extensions/table_row.js +++ b/app/assets/javascripts/content_editor/extensions/table_row.js @@ -1,51 +1,5 @@ import { TableRow } from '@tiptap/extension-table-row'; -export const tiptapExtension = TableRow.extend({ +export default TableRow.extend({ allowGapCursor: false, }); - -export function serializer(state, node) { - const isHeaderRow = node.child(0).type.name === 'tableHeader'; - - const renderRow = () => { - const cellWidths = []; - - state.flushClose(1); - - state.write('| '); - node.forEach((cell, _, i) => { - if (i) state.write(' | '); - - const { length } = state.out; - state.render(cell, node, i); - cellWidths.push(state.out.length - length); - }); - state.write(' |'); - - state.closeBlock(node); - - return cellWidths; - }; - - const renderHeaderRow = (cellWidths) => { - state.flushClose(1); - - state.write('|'); - node.forEach((cell, _, i) => { - if (i) state.write('|'); - - state.write(cell.attrs.align === 'center' ? ':' : '-'); - state.write(state.repeat('-', cellWidths[i])); - state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-'); - }); - state.write('|'); - - state.closeBlock(node); - }; - - if (isHeaderRow) { - renderHeaderRow(renderRow()); - } else { - renderRow(); - } -} diff --git a/app/assets/javascripts/content_editor/extensions/task_item.js b/app/assets/javascripts/content_editor/extensions/task_item.js new file mode 100644 index 00000000000..6163c0e043b --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/task_item.js @@ -0,0 +1,33 @@ +import { TaskItem } from '@tiptap/extension-task-item'; + +export default TaskItem.extend({ + defaultOptions: { + nested: true, + HTMLAttributes: {}, + }, + + addAttributes() { + return { + checked: { + default: false, + parseHTML: (element) => { + const checkbox = element.querySelector('input[type=checkbox].task-list-item-checkbox'); + return { checked: checkbox?.checked }; + }, + renderHTML: (attributes) => ({ + 'data-checked': attributes.checked, + }), + keepOnSplit: false, + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'li.task-list-item', + priority: 100, + }, + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/task_list.js b/app/assets/javascripts/content_editor/extensions/task_list.js new file mode 100644 index 00000000000..b7f6c857bc7 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/task_list.js @@ -0,0 +1,30 @@ +import { mergeAttributes } from '@tiptap/core'; +import { TaskList } from '@tiptap/extension-task-list'; + +export default TaskList.extend({ + addAttributes() { + return { + type: { + default: 'ul', + parseHTML: (element) => { + return { + type: element.tagName.toLowerCase() === 'ol' ? 'ol' : 'ul', + }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: '.task-list', + priority: 100, + }, + ]; + }, + + renderHTML({ HTMLAttributes: { type, ...HTMLAttributes } }) { + return [type, mergeAttributes(HTMLAttributes, { 'data-type': 'taskList' }), 0]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/text.js b/app/assets/javascripts/content_editor/extensions/text.js index 0d76aa1f1a7..a2865e7010b 100644 --- a/app/assets/javascripts/content_editor/extensions/text.js +++ b/app/assets/javascripts/content_editor/extensions/text.js @@ -1,5 +1 @@ -import { Text } from '@tiptap/extension-text'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; - -export const tiptapExtension = Text; -export const serializer = defaultMarkdownSerializer.nodes.text; +export { Text as default } from '@tiptap/extension-text'; -- cgit v1.2.3