Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/content_editor/extensions
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/content_editor/extensions')
-rw-r--r--app/assets/javascripts/content_editor/extensions/attachment.js53
-rw-r--r--app/assets/javascripts/content_editor/extensions/blockquote.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/bold.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/bullet_list.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/code.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/code_block_highlight.js18
-rw-r--r--app/assets/javascripts/content_editor/extensions/document.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/dropcursor.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/emoji.js93
-rw-r--r--app/assets/javascripts/content_editor/extensions/gapcursor.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/hard_break.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/heading.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/history.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/horizontal_rule.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/image.js103
-rw-r--r--app/assets/javascripts/content_editor/extensions/inline_diff.js50
-rw-r--r--app/assets/javascripts/content_editor/extensions/italic.js5
-rw-r--r--app/assets/javascripts/content_editor/extensions/link.js18
-rw-r--r--app/assets/javascripts/content_editor/extensions/list_item.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/loading.js24
-rw-r--r--app/assets/javascripts/content_editor/extensions/ordered_list.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/paragraph.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/reference.js78
-rw-r--r--app/assets/javascripts/content_editor/extensions/strike.js10
-rw-r--r--app/assets/javascripts/content_editor/extensions/subscript.js1
-rw-r--r--app/assets/javascripts/content_editor/extensions/superscript.js1
-rw-r--r--app/assets/javascripts/content_editor/extensions/table.js8
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_cell.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_header.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_row.js48
-rw-r--r--app/assets/javascripts/content_editor/extensions/task_item.js33
-rw-r--r--app/assets/javascripts/content_editor/extensions/task_list.js30
-rw-r--r--app/assets/javascripts/content_editor/extensions/text.js6
33 files changed, 392 insertions, 273 deletions
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';