diff options
Diffstat (limited to 'app/assets/javascripts/behaviors')
46 files changed, 800 insertions, 977 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/editor_extensions.js b/app/assets/javascripts/behaviors/markdown/editor_extensions.js index b512e4dbc8b..165031c3e7d 100644 --- a/app/assets/javascripts/behaviors/markdown/editor_extensions.js +++ b/app/assets/javascripts/behaviors/markdown/editor_extensions.js @@ -48,54 +48,48 @@ import Video from './nodes/video'; // from GFM should have a node or mark here. // The GFM-to-HTML-to-GFM cycle is tested in spec/features/markdown/copy_as_gfm_spec.rb. -export default [ - new Doc(), - new Paragraph(), - new Text(), +export default { + nodes: [ + Doc(), + Paragraph(), + Text(), - new Blockquote(), - new CodeBlock(), - new HardBreak(), - new Heading({ maxLevel: 6 }), - new HorizontalRule(), - new Image(), + Blockquote(), + CodeBlock(), + HardBreak(), + Heading(), + HorizontalRule(), + Image(), - new Table(), - new TableHead(), - new TableBody(), - new TableHeaderRow(), - new TableRow(), - new TableCell(), + Table(), + TableHead(), + TableBody(), + TableHeaderRow(), + TableRow(), + TableCell(), - new Emoji(), - new Reference(), + Emoji(), + Reference(), - new TableOfContents(), - new Video(), - new Audio(), + TableOfContents(), + Video(), + Audio(), - new BulletList(), - new OrderedList(), - new ListItem(), + BulletList(), + OrderedList(), + ListItem(), - new DescriptionList(), - new DescriptionTerm(), - new DescriptionDetails(), + DescriptionList(), + DescriptionTerm(), + DescriptionDetails(), - new TaskList(), - new OrderedTaskList(), - new TaskListItem(), + TaskList(), + OrderedTaskList(), + TaskListItem(), - new Summary(), - new Details(), + Summary(), + Details(), + ], - new Bold(), - new Italic(), - new Strike(), - new InlineDiff(), - - new Link(), - new Code(), - new MathMark(), - new InlineHTML(), -]; + marks: [Bold(), Italic(), Strike(), InlineDiff(), Link(), Code(), MathMark(), InlineHTML()], +}; diff --git a/app/assets/javascripts/behaviors/markdown/marks/bold.js b/app/assets/javascripts/behaviors/markdown/marks/bold.js index 89e373220af..dd730947a5f 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/bold.js +++ b/app/assets/javascripts/behaviors/markdown/marks/bold.js @@ -1,11 +1,17 @@ -/* eslint-disable class-methods-use-this */ - -import { Bold as BaseBold } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Bold extends BaseBold { - get toMarkdown() { - return defaultMarkdownSerializer.marks.strong; - } -} +export default () => { + return { + name: 'bold', + schema: { + parseDOM: [ + { + tag: 'strong', + }, + ], + toDOM: () => ['strong', 0], + }, + toMarkdown: defaultMarkdownSerializer.marks.strong, + }; +}; diff --git a/app/assets/javascripts/behaviors/markdown/marks/code.js b/app/assets/javascripts/behaviors/markdown/marks/code.js index 68368dec676..ea5af8b4a1f 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/code.js +++ b/app/assets/javascripts/behaviors/markdown/marks/code.js @@ -1,11 +1,12 @@ -/* eslint-disable class-methods-use-this */ - -import { Code as BaseCode } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Code extends BaseCode { - get toMarkdown() { - return defaultMarkdownSerializer.marks.code; - } -} +export default () => ({ + name: 'code', + schema: { + excludes: '_', + parseDOM: [{ tag: 'code' }], + toDOM: () => ['code', 0], + }, + toMarkdown: defaultMarkdownSerializer.marks.code, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js b/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js index 7f1506cd5d9..69d345c81e4 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js +++ b/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js @@ -1,41 +1,29 @@ -/* eslint-disable class-methods-use-this */ - -import { Mark } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::InlineDiffFilter -export default class InlineDiff extends Mark { - get name() { - return 'inline_diff'; - } - - get schema() { - return { - attrs: { - addition: { - default: true, - }, +export default () => ({ + name: 'inline_diff', + schema: { + attrs: { + addition: { + default: true, }, - parseDOM: [ - { tag: 'span.idiff.addition', attrs: { addition: true } }, - { tag: 'span.idiff.deletion', attrs: { addition: false } }, - ], - toDOM: (node) => [ - 'span', - { class: `idiff left right ${node.attrs.addition ? 'addition' : 'deletion'}` }, - 0, - ], - }; - } - - get toMarkdown() { - return { - mixable: true, - open(state, mark) { - return mark.attrs.addition ? '{+' : '{-'; - }, - close(state, mark) { - return mark.attrs.addition ? '+}' : '-}'; - }, - }; - } -} + }, + parseDOM: [ + { tag: 'span.idiff.addition', attrs: { addition: true } }, + { tag: 'span.idiff.deletion', attrs: { addition: false } }, + ], + toDOM: (node) => [ + 'span', + { class: `idiff left right ${node.attrs.addition ? 'addition' : 'deletion'}` }, + 0, + ], + }, + toMarkdown: { + mixable: true, + open(_, mark) { + return mark.attrs.addition ? '{+' : '{-'; + }, + close(_, mark) { + return mark.attrs.addition ? '+}' : '-}'; + }, + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/inline_html.js b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js index e957f81b774..4520598e0ab 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/inline_html.js +++ b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js @@ -1,46 +1,35 @@ -/* eslint-disable class-methods-use-this */ - import { escape } from 'lodash'; -import { Mark } from 'tiptap'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class InlineHTML extends Mark { - get name() { - return 'inline_html'; - } - - get schema() { - return { - excludes: '', - attrs: { - tag: {}, - title: { default: null }, - }, - parseDOM: [ - { - tag: 'sup, sub, kbd, q, samp, var', - getAttrs: (el) => ({ tag: el.nodeName.toLowerCase() }), - }, - { - tag: 'abbr', - getAttrs: (el) => ({ tag: 'abbr', title: el.getAttribute('title') }), - }, - ], - toDOM: (node) => [node.attrs.tag, { title: node.attrs.title }, 0], - }; - } - - get toMarkdown() { - return { - mixable: true, - open(state, mark) { - return `<${mark.attrs.tag}${ - mark.attrs.title ? ` title="${state.esc(escape(mark.attrs.title))}"` : '' - }>`; +export default () => ({ + name: 'inline_html', + schema: { + excludes: '', + attrs: { + tag: {}, + title: { default: null }, + }, + parseDOM: [ + { + tag: 'sup, sub, kbd, q, samp, var', + getAttrs: (el) => ({ tag: el.nodeName.toLowerCase() }), }, - close(state, mark) { - return `</${mark.attrs.tag}>`; + { + tag: 'abbr', + getAttrs: (el) => ({ tag: 'abbr', title: el.getAttribute('title') }), }, - }; - } -} + ], + toDOM: (node) => [node.attrs.tag, { title: node.attrs.title }, 0], + }, + toMarkdown: { + mixable: true, + open(state, mark) { + return `<${mark.attrs.tag}${ + mark.attrs.title ? ` title="${state.esc(escape(mark.attrs.title))}"` : '' + }>`; + }, + close(_, mark) { + return `</${mark.attrs.tag}>`; + }, + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/italic.js b/app/assets/javascripts/behaviors/markdown/marks/italic.js index 7dc86102f18..3ec8f0071e9 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/italic.js +++ b/app/assets/javascripts/behaviors/markdown/marks/italic.js @@ -1,11 +1,11 @@ -/* eslint-disable class-methods-use-this */ - -import { Italic as BaseItalic } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Italic extends BaseItalic { - get toMarkdown() { - return defaultMarkdownSerializer.marks.em; - } -} +export default () => ({ + name: 'italic', + schema: { + parseDOM: [{ tag: 'em' }], + toDOM: () => ['em', 0], + }, + toMarkdown: defaultMarkdownSerializer.marks.em, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/link.js b/app/assets/javascripts/behaviors/markdown/marks/link.js index b5e09017d83..977453fee01 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/link.js +++ b/app/assets/javascripts/behaviors/markdown/marks/link.js @@ -1,21 +1,47 @@ -/* eslint-disable class-methods-use-this */ - -import { Link as BaseLink } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Link extends BaseLink { - get toMarkdown() { - return { - mixable: true, - open(state, mark, parent, index) { - const open = defaultMarkdownSerializer.marks.link.open(state, mark, parent, index); - return open === '<' ? '' : open; +export default () => ({ + name: 'link', + schema: { + attrs: { + href: { + default: null, + }, + target: { + default: null, + }, + }, + inclusive: false, + parseDOM: [ + { + tag: 'a[href]', + getAttrs: (dom) => ({ + href: dom.getAttribute('href'), + target: dom.getAttribute('target'), + }), }, - close(state, mark, parent, index) { - const close = defaultMarkdownSerializer.marks.link.close(state, mark, parent, index); - return close === '>' ? '' : close; + ], + toDOM: (node) => [ + 'a', + { + ...node.attrs, + // eslint-disable-next-line @gitlab/require-i18n-strings + rel: 'noopener noreferrer nofollow', + target: node.attrs.target, }, - }; - } -} + 0, + ], + }, + toMarkdown: { + mixable: true, + open(state, mark, parent, index) { + const open = defaultMarkdownSerializer.marks.link.open(state, mark, parent, index); + return open === '<' ? '' : open; + }, + close(state, mark, parent, index) { + const close = defaultMarkdownSerializer.marks.link.close(state, mark, parent, index); + return close === '>' ? '' : close; + }, + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/math.js b/app/assets/javascripts/behaviors/markdown/marks/math.js index ca25ff7d07d..a50a649b6eb 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/math.js +++ b/app/assets/javascripts/behaviors/markdown/marks/math.js @@ -1,42 +1,31 @@ -/* eslint-disable class-methods-use-this */ - -import { Mark } from 'tiptap'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::MathFilter -export default class MathMark extends Mark { - get name() { - return 'math'; - } - - get schema() { - return { - parseDOM: [ - // Matches HTML generated by Banzai::Filter::MathFilter - { - tag: 'code.code.math[data-math-style=inline]', - priority: HIGHER_PARSE_RULE_PRIORITY, - }, - // Matches HTML after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js - { - tag: 'span.katex', - contentElement: 'annotation[encoding="application/x-tex"]', - }, - ], - toDOM: () => ['code', { class: 'code math', 'data-math-style': 'inline' }, 0], - }; - } - - get toMarkdown() { - return { - escape: false, - open(state, mark, parent, index) { - return `$${defaultMarkdownSerializer.marks.code.open(state, mark, parent, index)}`; +export default () => ({ + name: 'math', + schema: { + parseDOM: [ + // Matches HTML generated by Banzai::Filter::MathFilter + { + tag: 'code.code.math[data-math-style=inline]', + priority: HIGHER_PARSE_RULE_PRIORITY, }, - close(state, mark, parent, index) { - return `${defaultMarkdownSerializer.marks.code.close(state, mark, parent, index)}$`; + // Matches HTML after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js + { + tag: 'span.katex', + contentElement: 'annotation[encoding="application/x-tex"]', }, - }; - } -} + ], + toDOM: () => ['code', { class: 'code math', 'data-math-style': 'inline' }, 0], + }, + toMarkdown: { + escape: false, + open(state, mark, parent, index) { + return `$${defaultMarkdownSerializer.marks.code.open(state, mark, parent, index)}`; + }, + close(state, mark, parent, index) { + return `${defaultMarkdownSerializer.marks.code.close(state, mark, parent, index)}$`; + }, + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/marks/strike.js b/app/assets/javascripts/behaviors/markdown/marks/strike.js index c2951a40a4b..967c0a120cd 100644 --- a/app/assets/javascripts/behaviors/markdown/marks/strike.js +++ b/app/assets/javascripts/behaviors/markdown/marks/strike.js @@ -1,15 +1,18 @@ -/* eslint-disable class-methods-use-this */ - -import { Strike as BaseStrike } from 'tiptap-extensions'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Strike extends BaseStrike { - get toMarkdown() { - return { - open: '~~', - close: '~~', - mixable: true, - expelEnclosingWhitespace: true, - }; - } -} +export default () => ({ + name: 'strike', + schema: { + parseDOM: [ + { + tag: 'del', + }, + ], + toDOM: () => ['s', 0], + }, + toMarkdown: { + open: '~~', + close: '~~', + mixable: true, + expelEnclosingWhitespace: true, + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/audio.js b/app/assets/javascripts/behaviors/markdown/nodes/audio.js index 146349b118c..97ab86c6d23 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/audio.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/audio.js @@ -1,9 +1,4 @@ -import Playable from './playable'; +import playable from './playable'; // Transforms generated HTML back to GFM for Banzai::Filter::AudioLinkFilter -export default class Audio extends Playable { - constructor() { - super(); - this.mediaType = 'audio'; - } -} +export default () => playable({ mediaType: 'audio' }); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js index 8b14a04e2fe..6a4552d47e4 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js @@ -1,13 +1,19 @@ -/* eslint-disable class-methods-use-this */ - -import { Blockquote as BaseBlockquote } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Blockquote extends BaseBlockquote { +export default () => ({ + name: 'blockquote', + schema: { + content: 'block*', + group: 'block', + defining: true, + draggable: false, + parseDOM: [{ tag: 'blockquote' }], + toDOM: () => ['blockquote', 0], + }, toMarkdown(state, node) { if (!node.childCount) return; defaultMarkdownSerializer.nodes.blockquote(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js b/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js index ef1eafaa419..95cd3605da5 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js @@ -1,11 +1,15 @@ -/* eslint-disable class-methods-use-this */ - -import { BulletList as BaseBulletList } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class BulletList extends BaseBulletList { +export default () => ({ + name: 'bullet_list', + schema: { + content: 'list_item+', + group: 'block', + parseDOM: [{ tag: 'ul' }], + toDOM: () => ['ul', 0], + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.bullet_list(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/code_block.js b/app/assets/javascripts/behaviors/markdown/nodes/code_block.js index cd90d67c60d..0ff59779e7d 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/code_block.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/code_block.js @@ -1,7 +1,3 @@ -/* eslint-disable class-methods-use-this */ - -import { CodeBlock as BaseCodeBlock } from 'tiptap-extensions'; - const PLAINTEXT_LANG = 'plaintext'; // Transforms generated HTML back to GFM for: @@ -9,68 +5,67 @@ const PLAINTEXT_LANG = 'plaintext'; // - Banzai::Filter::MathFilter // - Banzai::Filter::MermaidFilter // - Banzai::Filter::SuggestionFilter -export default class CodeBlock extends BaseCodeBlock { - get schema() { - return { - content: 'text*', - marks: '', - group: 'block', - code: true, - defining: true, - attrs: { - lang: { default: PLAINTEXT_LANG }, - }, - parseDOM: [ - // Matches HTML generated by Banzai::Filter::SyntaxHighlightFilter, Banzai::Filter::MathFilter, Banzai::Filter::MermaidFilter, or Banzai::Filter::SuggestionFilter - { - tag: 'pre.code.highlight', - preserveWhitespace: 'full', - getAttrs: (el) => { - const lang = el.getAttribute('lang'); - if (!lang || lang === '') return {}; +export default () => ({ + name: 'code_block', + schema: { + content: 'text*', + marks: '', + group: 'block', + code: true, + defining: true, + attrs: { + lang: { default: PLAINTEXT_LANG }, + }, + parseDOM: [ + // Matches HTML generated by Banzai::Filter::SyntaxHighlightFilter, Banzai::Filter::MathFilter, Banzai::Filter::MermaidFilter, or Banzai::Filter::SuggestionFilter + { + tag: 'pre.code.highlight', + preserveWhitespace: 'full', + getAttrs: (el) => { + const lang = el.getAttribute('lang'); + if (!lang || lang === '') return {}; - return { lang }; - }, - }, - // Matches HTML generated by Banzai::Filter::MathFilter, - // after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js - { - tag: 'span.katex-display', - preserveWhitespace: 'full', - contentElement: 'annotation[encoding="application/x-tex"]', - attrs: { lang: 'math' }, - }, - // Matches HTML generated by Banzai::Filter::MermaidFilter, - // after being transformed by app/assets/javascripts/behaviors/markdown/render_mermaid.js - { - tag: 'svg.mermaid', - preserveWhitespace: 'full', - contentElement: 'text.source', - attrs: { lang: 'mermaid' }, - }, - // Matches HTML generated by Banzai::Filter::SuggestionFilter, - // after being transformed by app/assets/javascripts/vue_shared/components/markdown/suggestions.vue - { - tag: '.md-suggestion', - skip: true, - }, - { - tag: '.md-suggestion-header', - ignore: true, + return { lang }; }, - { - tag: '.md-suggestion-diff', - preserveWhitespace: 'full', - getContent: (el, schema) => - [...el.querySelectorAll('.line_content.new span')].map((span) => - schema.text(span.innerText), - ), - attrs: { lang: 'suggestion' }, - }, - ], - toDOM: (node) => ['pre', { class: 'code highlight', lang: node.attrs.lang }, ['code', 0]], - }; - } + }, + // Matches HTML generated by Banzai::Filter::MathFilter, + // after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js + { + tag: 'span.katex-display', + preserveWhitespace: 'full', + contentElement: 'annotation[encoding="application/x-tex"]', + attrs: { lang: 'math' }, + }, + // Matches HTML generated by Banzai::Filter::MermaidFilter, + // after being transformed by app/assets/javascripts/behaviors/markdown/render_mermaid.js + { + tag: 'svg.mermaid', + preserveWhitespace: 'full', + contentElement: 'text.source', + attrs: { lang: 'mermaid' }, + }, + // Matches HTML generated by Banzai::Filter::SuggestionFilter, + // after being transformed by app/assets/javascripts/vue_shared/components/markdown/suggestions.vue + { + tag: '.md-suggestion', + skip: true, + }, + { + tag: '.md-suggestion-header', + ignore: true, + }, + { + tag: '.md-suggestion-diff', + preserveWhitespace: 'full', + getContent: (el, schema) => + [...el.querySelectorAll('.line_content.new span')].map((span) => + schema.text(span.innerText), + ), + attrs: { lang: 'suggestion' }, + }, + ], + toDOM: (node) => ['pre', { class: 'code highlight', lang: node.attrs.lang }, ['code', 0]], + }, toMarkdown(state, node) { if (!node.childCount) return; @@ -95,5 +90,5 @@ export default class CodeBlock extends BaseCodeBlock { state.write('```'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_details.js b/app/assets/javascripts/behaviors/markdown/nodes/description_details.js index a4451d8ce8d..20760286045 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/description_details.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/description_details.js @@ -1,22 +1,14 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class DescriptionDetails extends Node { - get name() { - return 'description_details'; - } +export default () => ({ + name: 'description_details', - get schema() { - return { - content: 'text*', - marks: '', - defining: true, - parseDOM: [{ tag: 'dd' }], - toDOM: () => ['dd', 0], - }; - } + schema: { + content: 'text*', + marks: '', + defining: true, + parseDOM: [{ tag: 'dd' }], + toDOM: () => ['dd', 0], + }, toMarkdown(state, node) { state.flushClose(1); @@ -24,5 +16,5 @@ export default class DescriptionDetails extends Node { state.text(node.textContent, false); state.write('</dd>'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_list.js b/app/assets/javascripts/behaviors/markdown/nodes/description_list.js index 6aa1aca29d7..c5305c48423 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/description_list.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/description_list.js @@ -1,21 +1,12 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class DescriptionList extends Node { - get name() { - return 'description_list'; - } - - get schema() { - return { - content: '(description_term+ description_details+)+', - group: 'block', - parseDOM: [{ tag: 'dl' }], - toDOM: () => ['dl', 0], - }; - } +export default () => ({ + name: 'description_list', + schema: { + content: '(description_term+ description_details+)+', + group: 'block', + parseDOM: [{ tag: 'dl' }], + toDOM: () => ['dl', 0], + }, toMarkdown(state, node) { state.write('<dl>\n'); @@ -24,5 +15,5 @@ export default class DescriptionList extends Node { state.ensureNewLine(); state.write('</dl>'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_term.js b/app/assets/javascripts/behaviors/markdown/nodes/description_term.js index 89057ec6444..f78f7f13fc4 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/description_term.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/description_term.js @@ -1,28 +1,18 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class DescriptionTerm extends Node { - get name() { - return 'description_term'; - } - - get schema() { - return { - content: 'text*', - marks: '', - defining: true, - parseDOM: [{ tag: 'dt' }], - toDOM: () => ['dt', 0], - }; - } - +export default () => ({ + name: 'description_term', + schema: { + content: 'text*', + marks: '', + defining: true, + parseDOM: [{ tag: 'dt' }], + toDOM: () => ['dt', 0], + }, toMarkdown(state, node) { state.flushClose(state.closed && state.closed.type === node.type ? 1 : 2); state.write('<dt>'); state.text(node.textContent, false); state.write('</dt>'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/details.js b/app/assets/javascripts/behaviors/markdown/nodes/details.js index 1c40dbb8168..9fb0d60b93a 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/details.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/details.js @@ -1,22 +1,12 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Details extends Node { - get name() { - return 'details'; - } - - get schema() { - return { - content: 'summary block*', - group: 'block', - parseDOM: [{ tag: 'details' }], - toDOM: () => ['details', { open: true, onclick: 'return false', tabindex: '-1' }, 0], - }; - } - +export default () => ({ + name: 'details', + schema: { + content: 'summary block*', + group: 'block', + parseDOM: [{ tag: 'details' }], + toDOM: () => ['details', { open: true, onclick: 'return false', tabindex: '-1' }, 0], + }, toMarkdown(state, node) { state.write('<details>\n'); state.renderContent(node); @@ -24,5 +14,5 @@ export default class Details extends Node { state.ensureNewLine(); state.write('</details>'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/doc.js b/app/assets/javascripts/behaviors/markdown/nodes/doc.js index 88b16fd85da..3101e6e0e3a 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/doc.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/doc.js @@ -1,15 +1,6 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - -export default class Doc extends Node { - get name() { - return 'doc'; - } - - get schema() { - return { - content: 'block+', - }; - } -} +export default () => ({ + name: 'doc', + schema: { + content: 'block+', + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/emoji.js b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js index 9d0890aa1b4..086c277bad4 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/emoji.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js @@ -1,53 +1,43 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::EmojiFilter -export default class Emoji extends Node { - get name() { - return 'emoji'; - } - - get schema() { - return { - inline: true, - group: 'inline', - attrs: { - name: {}, - title: {}, - moji: {}, +export default () => ({ + name: 'emoji', + schema: { + inline: true, + group: 'inline', + attrs: { + name: {}, + title: {}, + moji: {}, + }, + parseDOM: [ + { + tag: 'gl-emoji', + getAttrs: (el) => ({ + name: el.dataset.name, + title: el.getAttribute('title'), + moji: el.textContent, + }), }, - parseDOM: [ - { - tag: 'gl-emoji', - getAttrs: (el) => ({ - name: el.dataset.name, - title: el.getAttribute('title'), - moji: el.textContent, - }), - }, - { - tag: 'img.emoji', - getAttrs: (el) => { - const name = el.getAttribute('title').replace(/^:|:$/g, ''); + { + tag: 'img.emoji', + getAttrs: (el) => { + const name = el.getAttribute('title').replace(/^:|:$/g, ''); - return { - name, - title: name, - moji: name, - }; - }, + return { + name, + title: name, + moji: name, + }; }, - ], - toDOM: (node) => [ - 'gl-emoji', - { 'data-name': node.attrs.name, title: node.attrs.title }, - node.attrs.moji, - ], - }; - } - + }, + ], + toDOM: (node) => [ + 'gl-emoji', + { 'data-name': node.attrs.name, title: node.attrs.title }, + node.attrs.moji, + ], + }, toMarkdown(state, node) { state.write(`:${node.attrs.name}:`); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js b/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js index 59e5d8ab3e2..1668af9c3f4 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js @@ -1,10 +1,14 @@ -/* eslint-disable class-methods-use-this */ - -import { HardBreak as BaseHardBreak } from 'tiptap-extensions'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class HardBreak extends BaseHardBreak { +export default () => ({ + name: 'hard_break', + schema: { + inline: true, + group: 'inline', + selectable: false, + parseDOM: [{ tag: 'br' }], + toDOM: () => ['br'], + }, toMarkdown(state) { if (!state.atBlank()) state.write(' \n'); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/heading.js b/app/assets/javascripts/behaviors/markdown/nodes/heading.js index 29967e61ffa..21b4ec69b70 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/heading.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/heading.js @@ -1,13 +1,27 @@ -/* eslint-disable class-methods-use-this */ - -import { Heading as BaseHeading } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Heading extends BaseHeading { +export default ({ levels = [1, 2, 3, 4, 5, 6] } = {}) => ({ + name: 'heading', + schema: { + attrs: { + level: { + default: 1, + }, + }, + content: 'inline*', + group: 'block', + defining: true, + draggable: false, + parseDOM: levels.map((level) => ({ + tag: `h${level}`, + attrs: { level }, + })), + toDOM: (node) => [`h${node.attrs.level}`, 0], + }, toMarkdown(state, node) { if (!node.childCount) return; defaultMarkdownSerializer.nodes.heading(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js b/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js index ee3aa145dc3..2d7074e567f 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js @@ -1,11 +1,14 @@ -/* eslint-disable class-methods-use-this */ - -import { HorizontalRule as BaseHorizontalRule } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class HorizontalRule extends BaseHorizontalRule { +export default () => ({ + name: 'horizontal_rule', + schema: { + group: 'block', + parseDOM: [{ tag: 'hr' }], + toDOM: () => ['hr'], + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.horizontal_rule(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/image.js b/app/assets/javascripts/behaviors/markdown/nodes/image.js index 16647d2f96e..370cc347a05 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/image.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/image.js @@ -1,53 +1,48 @@ -/* eslint-disable class-methods-use-this */ - -import { Image as BaseImage } from 'tiptap-extensions'; import { placeholderImage } from '~/lazy_loader'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; -export default class Image extends BaseImage { - get schema() { - return { - attrs: { - src: {}, - alt: { - default: null, - }, - title: { - default: null, - }, +export default () => ({ + name: 'image', + schema: { + attrs: { + src: {}, + alt: { + default: null, }, - group: 'inline', - inline: true, - draggable: true, - parseDOM: [ - // Matches HTML generated by Banzai::Filter::ImageLinkFilter - { - tag: 'a.no-attachment-icon', - priority: HIGHER_PARSE_RULE_PRIORITY, - skip: true, - }, - // Matches HTML generated by Banzai::Filter::ImageLazyLoadFilter - { - tag: 'img[src]:not(.emoji)', - getAttrs: (el) => { - const imageSrc = el.src; - const imageUrl = - imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || ''; + title: { + default: null, + }, + }, + group: 'inline', + inline: true, + draggable: true, + parseDOM: [ + // Matches HTML generated by Banzai::Filter::ImageLinkFilter + { + tag: 'a.no-attachment-icon', + priority: HIGHER_PARSE_RULE_PRIORITY, + skip: true, + }, + // Matches HTML generated by Banzai::Filter::ImageLazyLoadFilter + { + tag: 'img[src]:not(.emoji)', + getAttrs: (el) => { + const imageSrc = el.src; + const imageUrl = + imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || ''; - return { - src: imageUrl, - title: el.getAttribute('title'), - alt: el.getAttribute('alt'), - }; - }, + return { + src: imageUrl, + title: el.getAttribute('title'), + alt: el.getAttribute('alt'), + }; }, - ], - toDOM: (node) => ['img', node.attrs], - }; - } - + }, + ], + toDOM: (node) => ['img', node.attrs], + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.image(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/list_item.js b/app/assets/javascripts/behaviors/markdown/nodes/list_item.js index 7204b7c09ba..97c1f07427d 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/list_item.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/list_item.js @@ -1,11 +1,16 @@ -/* eslint-disable class-methods-use-this */ - -import { ListItem as BaseListItem } from 'tiptap-extensions'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class ListItem extends BaseListItem { +export default () => ({ + name: 'list_item', + schema: { + content: 'paragraph block*', + defining: true, + draggable: false, + parseDOM: [{ tag: 'li' }], + toDOM: () => ['li', 0], + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.list_item(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js b/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js index 4c1542d14ea..f2f3eff266a 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js @@ -1,10 +1,25 @@ -/* eslint-disable class-methods-use-this */ - -import { OrderedList as BaseOrderedList } from 'tiptap-extensions'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class OrderedList extends BaseOrderedList { +export default () => ({ + name: 'ordered_list', + schema: { + attrs: { + order: { + default: 1, + }, + }, + content: 'list_item+', + group: 'block', + parseDOM: [ + { + tag: 'ol', + getAttrs: (dom) => ({ + order: dom.hasAttribute('start') ? dom.getAttribute('start') + 1 : 1, + }), + }, + ], + toDOM: (node) => (node.attrs.order === 1 ? ['ol', 0] : ['ol', { start: node.attrs.order }, 0]), + }, toMarkdown(state, node) { state.renderList(node, ' ', () => '1. '); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js b/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js index a28d7be3758..53a6a0d9e07 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js @@ -1,29 +1,21 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter -export default class OrderedTaskList extends Node { - get name() { - return 'ordered_task_list'; - } - - get schema() { - return { - group: 'block', - content: '(task_list_item|list_item)+', - parseDOM: [ - { - priority: HIGHER_PARSE_RULE_PRIORITY, - tag: 'ol.task-list', - }, - ], - toDOM: () => ['ol', { class: 'task-list' }, 0], - }; - } +export default () => ({ + name: 'ordered_task_list', + schema: { + group: 'block', + content: '(task_list_item|list_item)+', + parseDOM: [ + { + priority: HIGHER_PARSE_RULE_PRIORITY, + tag: 'ol.task-list', + }, + ], + toDOM: () => ['ol', { class: 'task-list' }, 0], + }, toMarkdown(state, node) { state.renderList(node, ' ', () => '1. '); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js b/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js index 5fd098cd46f..310feebb390 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js @@ -1,24 +1,15 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Paragraph extends Node { - get name() { - return 'paragraph'; - } - - get schema() { - return { - content: 'inline*', - group: 'block', - parseDOM: [{ tag: 'p' }], - toDOM: () => ['p', 0], - }; - } - +export default () => ({ + name: 'paragraph', + schema: { + content: 'inline*', + group: 'block', + parseDOM: [{ tag: 'p' }], + toDOM: () => ['p', 0], + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.paragraph(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/playable.js b/app/assets/javascripts/behaviors/markdown/nodes/playable.js index 90cbaf9ef4c..7559c2a6a8a 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/playable.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/playable.js @@ -1,7 +1,3 @@ -/* eslint-disable class-methods-use-this */ -/* eslint-disable @gitlab/require-i18n-strings */ - -import { Node } from 'tiptap'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; /** @@ -10,62 +6,51 @@ import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer * the `mediaType` property in their constructors. * @abstract */ -export default class Playable extends Node { - constructor() { - super(); - this.mediaType = ''; - this.extraElementAttrs = {}; - } - - get name() { - return this.mediaType; - } - - get schema() { - const attrs = { - src: {}, - alt: { - default: null, - }, - }; - - const parseDOM = [ +export default ({ mediaType, extraElementAttrs = {} }) => { + const attrs = { + src: {}, + alt: { + default: null, + }, + }; + const parseDOM = [ + { + // eslint-disable-next-line @gitlab/require-i18n-strings + tag: `.${mediaType}-container`, + getAttrs: (el) => ({ + src: el.querySelector(mediaType).src, + alt: el.querySelector(mediaType).dataset.title, + }), + }, + ]; + const toDOM = (node) => [ + 'span', + { class: `media-container ${mediaType}-container` }, + [ + mediaType, { - tag: `.${this.mediaType}-container`, - getAttrs: (el) => ({ - src: el.querySelector(this.mediaType).src, - alt: el.querySelector(this.mediaType).dataset.title, - }), + src: node.attrs.src, + controls: true, + 'data-setup': '{}', + 'data-title': node.attrs.alt, + ...extraElementAttrs, }, - ]; - - const toDOM = (node) => [ - 'span', - { class: `media-container ${this.mediaType}-container` }, - [ - this.mediaType, - { - src: node.attrs.src, - controls: true, - 'data-setup': '{}', - 'data-title': node.attrs.alt, - ...this.extraElementAttrs, - }, - ], - ['a', { href: node.attrs.src }, node.attrs.alt], - ]; + ], + ['a', { href: node.attrs.src }, node.attrs.alt], + ]; - return { + return { + name: mediaType, + schema: { attrs, group: 'inline', inline: true, draggable: true, parseDOM, toDOM, - }; - } - - toMarkdown(state, node) { - defaultMarkdownSerializer.nodes.image(state, node); - } -} + }, + toMarkdown(state, node) { + defaultMarkdownSerializer.nodes.image(state, node); + }, + }; +}; diff --git a/app/assets/javascripts/behaviors/markdown/nodes/reference.js b/app/assets/javascripts/behaviors/markdown/nodes/reference.js index dd82ea58ea5..9ae6ab07004 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/reference.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/reference.js @@ -1,53 +1,44 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::ReferenceFilter and subclasses -export default class Reference extends Node { - get name() { - return 'reference'; - } - - get schema() { - return { - inline: true, - group: 'inline', - atom: true, - attrs: { - className: {}, - referenceType: {}, - originalText: { default: null }, - href: {}, - text: {}, +export default () => ({ + name: 'reference', + schema: { + inline: true, + group: 'inline', + atom: true, + attrs: { + className: {}, + referenceType: {}, + originalText: { default: null }, + href: {}, + text: {}, + }, + parseDOM: [ + { + tag: 'a.gfm:not([data-link=true])', + priority: HIGHER_PARSE_RULE_PRIORITY, + getAttrs: (el) => ({ + className: el.className, + referenceType: el.dataset.referenceType, + originalText: el.dataset.original, + href: el.getAttribute('href'), + text: el.textContent, + }), }, - parseDOM: [ - { - tag: 'a.gfm:not([data-link=true])', - priority: HIGHER_PARSE_RULE_PRIORITY, - getAttrs: (el) => ({ - className: el.className, - referenceType: el.dataset.referenceType, - originalText: el.dataset.original, - href: el.getAttribute('href'), - text: el.textContent, - }), - }, - ], - toDOM: (node) => [ - 'a', - { - class: node.attrs.className, - href: node.attrs.href, - 'data-reference-type': node.attrs.referenceType, - 'data-original': node.attrs.originalText, - }, - node.attrs.text, - ], - }; - } - + ], + toDOM: (node) => [ + 'a', + { + class: node.attrs.className, + href: node.attrs.href, + 'data-reference-type': node.attrs.referenceType, + 'data-original': node.attrs.originalText, + }, + node.attrs.text, + ], + }, toMarkdown(state, node) { state.write(node.attrs.originalText || node.attrs.text); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/summary.js b/app/assets/javascripts/behaviors/markdown/nodes/summary.js index 2e36e316d71..eb91b3c981e 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/summary.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/summary.js @@ -1,27 +1,17 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Summary extends Node { - get name() { - return 'summary'; - } - - get schema() { - return { - content: 'text*', - marks: '', - defining: true, - parseDOM: [{ tag: 'summary' }], - toDOM: () => ['summary', 0], - }; - } - +export default () => ({ + name: 'summary', + schema: { + content: 'text*', + marks: '', + defining: true, + parseDOM: [{ tag: 'summary' }], + toDOM: () => ['summary', 0], + }, toMarkdown(state, node) { state.write('<summary>'); state.text(node.textContent, false); state.write('</summary>'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table.js b/app/assets/javascripts/behaviors/markdown/nodes/table.js index a7fcb9227cd..c766f7f1fba 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table.js @@ -1,25 +1,15 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class Table extends Node { - get name() { - return 'table'; - } - - get schema() { - return { - content: 'table_head table_body', - group: 'block', - isolating: true, - parseDOM: [{ tag: 'table' }], - toDOM: () => ['table', 0], - }; - } - +export default () => ({ + name: 'table', + schema: { + content: 'table_head table_body', + group: 'block', + isolating: true, + parseDOM: [{ tag: 'table' }], + toDOM: () => ['table', 0], + }, toMarkdown(state, node) { state.renderContent(node); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_body.js b/app/assets/javascripts/behaviors/markdown/nodes/table_body.js index 403556dc0c8..0a49fb558ae 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_body.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_body.js @@ -1,24 +1,14 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class TableBody extends Node { - get name() { - return 'table_body'; - } - - get schema() { - return { - content: 'table_row+', - parseDOM: [{ tag: 'tbody' }], - toDOM: () => ['tbody', 0], - }; - } - - toMarkdown(state, node) { +export default () => ({ + name: 'table_body', + schema: { + content: 'table_row+', + parseDOM: [{ tag: 'tbody' }], + toDOM: () => ['tbody', 0], + }, + toMarkdown: (state, node) => { state.flushClose(1); state.renderContent(node); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js b/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js index ebb66cd4da5..f46344ba43c 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js @@ -1,35 +1,25 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class TableCell extends Node { - get name() { - return 'table_cell'; - } - - get schema() { - return { - attrs: { - header: { default: false }, - align: { default: null }, +export default () => ({ + name: 'table_cell', + schema: { + attrs: { + header: { default: false }, + align: { default: null }, + }, + content: 'inline*', + isolating: true, + parseDOM: [ + { + tag: 'td, th', + getAttrs: (el) => ({ + header: el.tagName === 'TH', + align: el.getAttribute('align') || el.style.textAlign, + }), }, - content: 'inline*', - isolating: true, - parseDOM: [ - { - tag: 'td, th', - getAttrs: (el) => ({ - header: el.tagName === 'TH', - align: el.getAttribute('align') || el.style.textAlign, - }), - }, - ], - toDOM: (node) => [node.attrs.header ? 'th' : 'td', { align: node.attrs.align }, 0], - }; - } - - toMarkdown(state, node) { + ], + toDOM: (node) => [node.attrs.header ? 'th' : 'td', { align: node.attrs.align }, 0], + }, + toMarkdown: (state, node) => { state.renderInline(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_head.js b/app/assets/javascripts/behaviors/markdown/nodes/table_head.js index 4cb94bf088c..2e9b53ee0ac 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_head.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_head.js @@ -1,24 +1,14 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class TableHead extends Node { - get name() { - return 'table_head'; - } - - get schema() { - return { - content: 'table_header_row', - parseDOM: [{ tag: 'thead' }], - toDOM: () => ['thead', 0], - }; - } - - toMarkdown(state, node) { +export default () => ({ + name: 'table_head', + schema: { + content: 'table_header_row', + parseDOM: [{ tag: 'thead' }], + toDOM: () => ['thead', 0], + }, + toMarkdown: (state, node) => { state.flushClose(1); state.renderContent(node); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js b/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js index 2cb2bb9e7fe..d8aa497066c 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js @@ -1,31 +1,23 @@ -/* eslint-disable class-methods-use-this */ - import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; import TableRow from './table_row'; const CENTER_ALIGN = 'center'; // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class TableHeaderRow extends TableRow { - get name() { - return 'table_header_row'; - } - - get schema() { - return { - content: 'table_cell+', - parseDOM: [ - { - tag: 'thead tr', - priority: HIGHER_PARSE_RULE_PRIORITY, - }, - ], - toDOM: () => ['tr', 0], - }; - } - - toMarkdown(state, node) { - const cellWidths = super.toMarkdown(state, node); +export default () => ({ + name: 'table_header_row', + schema: { + content: 'table_cell+', + parseDOM: [ + { + tag: 'thead tr', + priority: HIGHER_PARSE_RULE_PRIORITY, + }, + ], + toDOM: () => ['tr', 0], + }, + toMarkdown: (state, node) => { + const cellWidths = TableRow().toMarkdown(state, node); state.flushClose(1); @@ -40,5 +32,5 @@ export default class TableHeaderRow extends TableRow { state.write('|'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js index db9072acc3a..4a0256c4644 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js @@ -1,35 +1,26 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { __ } from '~/locale'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::TableOfContentsFilter -export default class TableOfContents extends Node { - get name() { - return 'table_of_contents'; - } - - get schema() { - return { - group: 'block', - atom: true, - parseDOM: [ - { - tag: 'ul.section-nav', - priority: HIGHER_PARSE_RULE_PRIORITY, - }, - { - tag: 'p.table-of-contents', - priority: HIGHER_PARSE_RULE_PRIORITY, - }, - ], - toDOM: () => ['p', { class: 'table-of-contents' }, __('Table of Contents')], - }; - } - - toMarkdown(state, node) { +export default () => ({ + name: 'table_of_contents', + schema: { + group: 'block', + atom: true, + parseDOM: [ + { + tag: 'ul.section-nav', + priority: HIGHER_PARSE_RULE_PRIORITY, + }, + { + tag: 'p.table-of-contents', + priority: HIGHER_PARSE_RULE_PRIORITY, + }, + ], + toDOM: () => ['p', { class: 'table-of-contents' }, __('Table of Contents')], + }, + toMarkdown: (state, node) => { state.write('[[_TOC_]]'); state.closeBlock(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_row.js b/app/assets/javascripts/behaviors/markdown/nodes/table_row.js index 5852502773a..3830dae4f0d 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/table_row.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/table_row.js @@ -1,22 +1,12 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; - // Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter -export default class TableRow extends Node { - get name() { - return 'table_row'; - } - - get schema() { - return { - content: 'table_cell+', - parseDOM: [{ tag: 'tr' }], - toDOM: () => ['tr', 0], - }; - } - - toMarkdown(state, node) { +export default () => ({ + name: 'table_row', + schema: { + content: 'table_cell+', + parseDOM: [{ tag: 'tr' }], + toDOM: () => ['tr', 0], + }, + toMarkdown: (state, node) => { const cellWidths = []; state.flushClose(1); @@ -34,5 +24,5 @@ export default class TableRow extends Node { state.closeBlock(node); return cellWidths; - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js index 35ba2eb0674..3c3812ad8f7 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/task_list.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js @@ -1,29 +1,20 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter -export default class TaskList extends Node { - get name() { - return 'task_list'; - } - - get schema() { - return { - group: 'block', - content: '(task_list_item|list_item)+', - parseDOM: [ - { - priority: HIGHER_PARSE_RULE_PRIORITY, - tag: 'ul.task-list', - }, - ], - toDOM: () => ['ul', { class: 'task-list' }, 0], - }; - } - +export default () => ({ + name: 'task_list', + schema: { + group: 'block', + content: '(task_list_item|list_item)+', + parseDOM: [ + { + priority: HIGHER_PARSE_RULE_PRIORITY, + tag: 'ul.task-list', + }, + ], + toDOM: () => ['ul', { class: 'task-list' }, 0], + }, toMarkdown(state, node) { state.renderList(node, ' ', () => '* '); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js index 56c2b17286d..10ffce9b1b8 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js @@ -1,50 +1,38 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { HIGHER_PARSE_RULE_PRIORITY } from '../constants'; // Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter -export default class TaskListItem extends Node { - get name() { - return 'task_list_item'; - } - - get schema() { - return { - attrs: { - done: { - default: false, - }, +export default () => ({ + name: 'task_list_item', + schema: { + attrs: { + done: { + default: false, }, - defining: true, - draggable: false, - content: 'paragraph block*', - parseDOM: [ - { - priority: HIGHER_PARSE_RULE_PRIORITY, - tag: 'li.task-list-item', - getAttrs: (el) => { - const checkbox = el.querySelector('input[type=checkbox].task-list-item-checkbox'); - return { done: checkbox && checkbox.checked }; - }, + }, + defining: true, + draggable: false, + content: 'paragraph block*', + parseDOM: [ + { + priority: HIGHER_PARSE_RULE_PRIORITY, + tag: 'li.task-list-item', + getAttrs: (el) => { + const checkbox = el.querySelector('input[type=checkbox].task-list-item-checkbox'); + return { done: checkbox && checkbox.checked }; }, - ], - toDOM(node) { - return [ - 'li', - { class: 'task-list-item' }, - [ - 'input', - { type: 'checkbox', class: 'task-list-item-checkbox', checked: node.attrs.done }, - ], - ['div', { class: 'todo-content' }, 0], - ]; }, - }; - } - + ], + toDOM(node) { + return [ + 'li', + { class: 'task-list-item' }, + ['input', { type: 'checkbox', class: 'task-list-item-checkbox', checked: node.attrs.done }], + ['div', { class: 'todo-content' }, 0], + ]; + }, + }, toMarkdown(state, node) { state.write(`[${node.attrs.done ? 'x' : ' '}] `); state.renderContent(node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/text.js b/app/assets/javascripts/behaviors/markdown/nodes/text.js index 0dc77a12f5c..0e1f0bc0e40 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/text.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/text.js @@ -1,20 +1,11 @@ -/* eslint-disable class-methods-use-this */ - -import { Node } from 'tiptap'; import { defaultMarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; -export default class Text extends Node { - get name() { - return 'text'; - } - - get schema() { - return { - group: 'inline', - }; - } - +export default () => ({ + name: 'text', + schema: { + group: 'inline', + }, toMarkdown(state, node) { defaultMarkdownSerializer.nodes.text(state, node); - } -} + }, +}); diff --git a/app/assets/javascripts/behaviors/markdown/nodes/video.js b/app/assets/javascripts/behaviors/markdown/nodes/video.js index 68085c2c416..aa1088826da 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/video.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/video.js @@ -1,10 +1,4 @@ -import Playable from './playable'; +import playable from './playable'; // Transforms generated HTML back to GFM for Banzai::Filter::VideoLinkFilter -export default class Video extends Playable { - constructor() { - super(); - this.mediaType = 'video'; - this.extraElementAttrs = { width: '400' }; - } -} +export default () => playable({ mediaType: 'video', extraElementAttrs: { width: '400' } }); diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js index 1d54a1b0c04..85a991a1ec9 100644 --- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js @@ -88,7 +88,7 @@ function renderMermaidEl(el, source) { const iframeEl = document.createElement('iframe'); setAttributes(iframeEl, { src: getSandboxFrameSrc(), - sandbox: 'allow-scripts', + sandbox: 'allow-scripts allow-popups', frameBorder: 0, scrolling: 'no', width: '100%', diff --git a/app/assets/javascripts/behaviors/markdown/schema.js b/app/assets/javascripts/behaviors/markdown/schema.js index 8bea24584cc..1b0f46ff4cb 100644 --- a/app/assets/javascripts/behaviors/markdown/schema.js +++ b/app/assets/javascripts/behaviors/markdown/schema.js @@ -1,24 +1,20 @@ import { Schema } from 'prosemirror-model'; import editorExtensions from './editor_extensions'; -const nodes = editorExtensions - .filter((extension) => extension.type === 'node') - .reduce( - (ns, { name, schema }) => ({ - ...ns, - [name]: schema, - }), - {}, - ); +const nodes = editorExtensions.nodes.reduce( + (ns, { name, schema }) => ({ + ...ns, + [name]: schema, + }), + {}, +); -const marks = editorExtensions - .filter((extension) => extension.type === 'mark') - .reduce( - (ms, { name, schema }) => ({ - ...ms, - [name]: schema, - }), - {}, - ); +const marks = editorExtensions.marks.reduce( + (ms, { name, schema }) => ({ + ...ms, + [name]: schema, + }), + {}, +); export default new Schema({ nodes, marks }); diff --git a/app/assets/javascripts/behaviors/markdown/serializer.js b/app/assets/javascripts/behaviors/markdown/serializer.js index a5f97d7748a..e3e8a380cd5 100644 --- a/app/assets/javascripts/behaviors/markdown/serializer.js +++ b/app/assets/javascripts/behaviors/markdown/serializer.js @@ -1,24 +1,20 @@ import { MarkdownSerializer } from '~/lib/prosemirror_markdown_serializer'; import editorExtensions from './editor_extensions'; -const nodes = editorExtensions - .filter((extension) => extension.type === 'node') - .reduce( - (ns, { name, toMarkdown }) => ({ - ...ns, - [name]: toMarkdown, - }), - {}, - ); +const nodes = editorExtensions.nodes.reduce( + (ns, { name, toMarkdown }) => ({ + ...ns, + [name]: toMarkdown, + }), + {}, +); -const marks = editorExtensions - .filter((extension) => extension.type === 'mark') - .reduce( - (ms, { name, toMarkdown }) => ({ - ...ms, - [name]: toMarkdown, - }), - {}, - ); +const marks = editorExtensions.marks.reduce( + (ms, { name, toMarkdown }) => ({ + ...ms, + [name]: toMarkdown, + }), + {}, +); export default new MarkdownSerializer(nodes, marks); diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js index b27dccabdf8..23b66405844 100644 --- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -131,6 +131,13 @@ export const ITALIC_TEXT = { customizable: false, }; +export const STRIKETHROUGH_TEXT = { + id: 'editing.strikethroughText', + description: __('Strikethrough text'), + defaultKeys: ['mod+shift+x'], + customizable: false, +}; + export const LINK_TEXT = { id: 'editing.linkText', description: __('Link text'), @@ -511,7 +518,14 @@ export const GLOBAL_SHORTCUTS_GROUP = { export const EDITING_SHORTCUTS_GROUP = { id: 'editing', name: __('Editing'), - keybindings: [BOLD_TEXT, ITALIC_TEXT, LINK_TEXT, TOGGLE_MARKDOWN_PREVIEW, EDIT_RECENT_COMMENT], + keybindings: [ + BOLD_TEXT, + ITALIC_TEXT, + STRIKETHROUGH_TEXT, + LINK_TEXT, + TOGGLE_MARKDOWN_PREVIEW, + EDIT_RECENT_COMMENT, + ], }; export const WIKI_SHORTCUTS_GROUP = { diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 9297b14aac9..4d78c7b56a0 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -173,12 +173,7 @@ export default class Shortcuts { e.preventDefault(); const canaryCookieName = 'gitlab_canary'; const currentValue = parseBoolean(getCookie(canaryCookieName)); - setCookie(canaryCookieName, (!currentValue).toString(), { - expires: 365, - path: '/', - // next.gitlab.com uses a leading period. See https://gitlab.com/gitlab-org/gitlab/-/issues/350186 - domain: `.${window.location.hostname}`, - }); + setCookie(canaryCookieName, (!currentValue).toString(), { expires: 365, path: '/' }); refreshCurrentPage(); } |