summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/bold.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/code.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/inline_diff.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/inline_html.js46
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/italic.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/link.js21
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/math.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/strike.js15
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/blockquote.js13
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/code_block.js79
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_details.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_term.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/details.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/doc.js15
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/emoji.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/hard_break.js10
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/heading.js13
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/image.js52
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/list_item.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js10
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/paragraph.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/reference.js52
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/summary.js27
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table.js25
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_body.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_cell.js35
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_head.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js43
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js33
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_row.js38
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/task_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js49
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/text.js20
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/video.js54
-rw-r--r--package.json5
-rw-r--r--spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--yarn.lock239
41 files changed, 1317 insertions, 8 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/marks/bold.js b/app/assets/javascripts/behaviors/markdown/marks/bold.js
new file mode 100644
index 00000000000..b537954c1cb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/bold.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Bold as BaseBold } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Bold extends BaseBold {
+ get toMarkdown() {
+ return defaultMarkdownSerializer.marks.strong;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/code.js b/app/assets/javascripts/behaviors/markdown/marks/code.js
new file mode 100644
index 00000000000..a760ee80dd0
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/code.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Code as BaseCode } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Code extends BaseCode {
+ get toMarkdown() {
+ return 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
new file mode 100644
index 00000000000..ce425e80cd3
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js
@@ -0,0 +1,41 @@
+/* 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,
+ },
+ },
+ 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 ? '+}' : '-}';
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/inline_html.js b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js
new file mode 100644
index 00000000000..ebed8698e21
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js
@@ -0,0 +1,46 @@
+/* eslint-disable class-methods-use-this */
+
+import { Mark } from 'tiptap';
+import _ from 'underscore';
+
+// 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))}"` : ''
+ }>`;
+ },
+ close(state, 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
new file mode 100644
index 00000000000..44b35c97739
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/italic.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Italic as BaseItalic } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Italic extends BaseItalic {
+ get toMarkdown() {
+ return defaultMarkdownSerializer.marks.em;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/link.js b/app/assets/javascripts/behaviors/markdown/marks/link.js
new file mode 100644
index 00000000000..5c23d6a5ceb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/link.js
@@ -0,0 +1,21 @@
+/* eslint-disable class-methods-use-this */
+
+import { Link as BaseLink } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// 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;
+ },
+ 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
new file mode 100644
index 00000000000..e582fb18f15
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/math.js
@@ -0,0 +1,41 @@
+/* eslint-disable class-methods-use-this */
+
+import { Mark } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// 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: 51,
+ },
+ // 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)}`;
+ },
+ 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
new file mode 100644
index 00000000000..c2951a40a4b
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/strike.js
@@ -0,0 +1,15 @@
+/* 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,
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js
new file mode 100644
index 00000000000..b0bc8f79643
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js
@@ -0,0 +1,13 @@
+/* eslint-disable class-methods-use-this */
+
+import { Blockquote as BaseBlockquote } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Blockquote extends BaseBlockquote {
+ 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
new file mode 100644
index 00000000000..3b0792e1af8
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { BulletList as BaseBulletList } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class BulletList extends BaseBulletList {
+ 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
new file mode 100644
index 00000000000..b9b894b3348
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/code_block.js
@@ -0,0 +1,79 @@
+/* eslint-disable class-methods-use-this */
+
+import { CodeBlock as BaseCodeBlock } from 'tiptap-extensions';
+
+const PLAINTEXT_LANG = 'plaintext';
+
+// Transforms generated HTML back to GFM for:
+// - Banzai::Filter::SyntaxHighlightFilter
+// - Banzai::Filter::MathFilter
+// - Banzai::Filter::MermaidFilter
+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 or Banzai::Filter::MermaidFilter
+ {
+ 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::MathFilter,
+ // after being transformed by app/assets/javascripts/behaviors/markdown/render_mermaid.js
+ {
+ tag: 'svg.mermaid',
+ preserveWhitespace: 'full',
+ contentElement: 'text.source',
+ attrs: { lang: 'mermaid' },
+ },
+ ],
+ toDOM: node => ['pre', { class: 'code highlight', lang: node.attrs.lang }, ['code', 0]],
+ };
+ }
+
+ toMarkdown(state, node) {
+ if (!node.childCount) return;
+
+ const {
+ textContent: text,
+ attrs: { lang },
+ } = node;
+
+ // Prefixes lines with 4 spaces if the code contains a line that starts with triple backticks
+ if (lang === PLAINTEXT_LANG && text.match(/^```/gm)) {
+ state.wrapBlock(' ', null, node, () => state.text(text, false));
+ return;
+ }
+
+ state.write('```');
+ if (lang !== PLAINTEXT_LANG) state.write(lang);
+
+ state.ensureNewLine();
+ state.text(text, false);
+ state.ensureNewLine();
+
+ 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
new file mode 100644
index 00000000000..a4451d8ce8d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_details.js
@@ -0,0 +1,28 @@
+/* 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';
+ }
+
+ get schema() {
+ return {
+ content: 'text*',
+ marks: '',
+ defining: true,
+ parseDOM: [{ tag: 'dd' }],
+ toDOM: () => ['dd', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.flushClose(1);
+ state.write('<dd>');
+ 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
new file mode 100644
index 00000000000..6aa1aca29d7
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_list.js
@@ -0,0 +1,28 @@
+/* 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],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('<dl>\n');
+ state.wrapBlock(' ', null, node, () => state.renderContent(node));
+ state.flushClose(1);
+ 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
new file mode 100644
index 00000000000..89057ec6444
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_term.js
@@ -0,0 +1,28 @@
+/* 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],
+ };
+ }
+
+ 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
new file mode 100644
index 00000000000..1c40dbb8168
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/details.js
@@ -0,0 +1,28 @@
+/* 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],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('<details>\n');
+ state.renderContent(node);
+ state.flushClose(1);
+ 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
new file mode 100644
index 00000000000..88b16fd85da
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/doc.js
@@ -0,0 +1,15 @@
+/* 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+',
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/emoji.js b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js
new file mode 100644
index 00000000000..a7cc3e828f5
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js
@@ -0,0 +1,41 @@
+/* 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: {},
+ },
+ parseDOM: [
+ {
+ tag: 'gl-emoji',
+ getAttrs: el => ({
+ name: el.dataset.name,
+ title: el.getAttribute('title'),
+ moji: el.textContent,
+ }),
+ },
+ ],
+ 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
new file mode 100644
index 00000000000..59e5d8ab3e2
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js
@@ -0,0 +1,10 @@
+/* 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 {
+ 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
new file mode 100644
index 00000000000..fec8608cf5d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/heading.js
@@ -0,0 +1,13 @@
+/* eslint-disable class-methods-use-this */
+
+import { Heading as BaseHeading } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Heading extends BaseHeading {
+ 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
new file mode 100644
index 00000000000..695c7160bde
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { HorizontalRule as BaseHorizontalRule } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class HorizontalRule extends BaseHorizontalRule {
+ 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
new file mode 100644
index 00000000000..c225a5ed876
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/image.js
@@ -0,0 +1,52 @@
+/* eslint-disable class-methods-use-this */
+
+import { Image as BaseImage } from 'tiptap-extensions';
+import { placeholderImage } from '~/lazy_loader';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+export default class Image extends BaseImage {
+ get schema() {
+ return {
+ attrs: {
+ src: {},
+ alt: {
+ default: null,
+ },
+ title: {
+ default: null,
+ },
+ },
+ group: 'inline',
+ inline: true,
+ draggable: true,
+ parseDOM: [
+ // Matches HTML generated by Banzai::Filter::ImageLinkFilter
+ {
+ tag: 'a.no-attachment-icon',
+ priority: 51,
+ skip: true,
+ },
+ // Matches HTML generated by Banzai::Filter::ImageLazyLoadFilter
+ {
+ tag: 'img[src]',
+ 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'),
+ };
+ },
+ },
+ ],
+ 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
new file mode 100644
index 00000000000..4237637ed9a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/list_item.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { ListItem as BaseListItem } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class ListItem extends BaseListItem {
+ 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
new file mode 100644
index 00000000000..4c1542d14ea
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js
@@ -0,0 +1,10 @@
+/* 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 {
+ 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
new file mode 100644
index 00000000000..25c4976a1bc
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// 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: 51,
+ 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
new file mode 100644
index 00000000000..dec3207b1bb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js
@@ -0,0 +1,24 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// 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],
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.paragraph(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/reference.js b/app/assets/javascripts/behaviors/markdown/nodes/reference.js
new file mode 100644
index 00000000000..5d6bbeca833
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/reference.js
@@ -0,0 +1,52 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// 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: {},
+ },
+ parseDOM: [
+ {
+ tag: 'a.gfm:not([data-link=true])',
+ priority: 51,
+ 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,
+ ],
+ };
+ }
+
+ 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
new file mode 100644
index 00000000000..2e36e316d71
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/summary.js
@@ -0,0 +1,27 @@
+/* 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],
+ };
+ }
+
+ 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
new file mode 100644
index 00000000000..a7fcb9227cd
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table.js
@@ -0,0 +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 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],
+ };
+ }
+
+ 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
new file mode 100644
index 00000000000..403556dc0c8
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_body.js
@@ -0,0 +1,24 @@
+/* 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) {
+ 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
new file mode 100644
index 00000000000..c63bfe10e39
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js
@@ -0,0 +1,35 @@
+/* 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 },
+ },
+ 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) {
+ 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
new file mode 100644
index 00000000000..4cb94bf088c
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_head.js
@@ -0,0 +1,24 @@
+/* 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) {
+ 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
new file mode 100644
index 00000000000..e7eee636402
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js
@@ -0,0 +1,43 @@
+/* eslint-disable class-methods-use-this */
+
+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: 51,
+ },
+ ],
+ toDOM: () => ['tr', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ const cellWidths = super.toMarkdown(state, node);
+
+ state.flushClose(1);
+
+ state.write('|');
+ node.forEach((cell, _, i) => {
+ if (i) state.write('|');
+
+ state.write(cell.attrs.align === CENTER_ALIGN ? ':' : '-');
+ state.write(state.repeat('-', cellWidths[i]));
+ state.write(cell.attrs.align === CENTER_ALIGN || cell.attrs.align === 'right' ? ':' : '-');
+ });
+ 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
new file mode 100644
index 00000000000..20c7fa8a9ab
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
@@ -0,0 +1,33 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// 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: 51,
+ },
+ {
+ tag: 'p.table-of-contents',
+ priority: 51,
+ },
+ ],
+ 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
new file mode 100644
index 00000000000..5852502773a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_row.js
@@ -0,0 +1,38 @@
+/* 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) {
+ 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;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js
new file mode 100644
index 00000000000..ab33bc21502
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// 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: 51,
+ 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
new file mode 100644
index 00000000000..d0ee7333d5e
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
@@ -0,0 +1,49 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// 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,
+ },
+ },
+ defining: true,
+ draggable: false,
+ content: 'paragraph block*',
+ parseDOM: [
+ {
+ priority: 51,
+ 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],
+ ];
+ },
+ };
+ }
+
+ 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
new file mode 100644
index 00000000000..84838c14999
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/text.js
@@ -0,0 +1,20 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+export default class Text extends Node {
+ get name() {
+ return 'text';
+ }
+
+ get schema() {
+ return {
+ 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
new file mode 100644
index 00000000000..516f983397d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/video.js
@@ -0,0 +1,54 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::VideoLinkFilter
+export default class Video extends Node {
+ get name() {
+ return 'video';
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ src: {},
+ alt: {
+ default: null,
+ },
+ },
+ group: 'block',
+ draggable: true,
+ parseDOM: [
+ {
+ tag: '.video-container',
+ skip: true,
+ },
+ {
+ tag: '.video-container p',
+ priority: 51,
+ ignore: true,
+ },
+ {
+ tag: 'video[src]',
+ getAttrs: el => ({ src: el.getAttribute('src'), alt: el.dataset.title }),
+ },
+ ],
+ toDOM: node => [
+ 'video',
+ {
+ src: node.attrs.src,
+ width: '400',
+ controls: true,
+ 'data-setup': '{}',
+ 'data-title': node.attrs.alt,
+ },
+ ],
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.image(state, node);
+ state.closeBlock(node);
+ }
+}
diff --git a/package.json b/package.json
index 6c771e377b8..b37a021214d 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,8 @@
"pikaday": "^1.6.1",
"popper.js": "^1.14.3",
"prismjs": "^1.6.0",
+ "prosemirror-markdown": "^1.3.0",
+ "prosemirror-model": "^1.6.4",
"raphael": "^2.2.7",
"raven-js": "^3.22.1",
"raw-loader": "^1.0.0",
@@ -101,6 +103,9 @@
"three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4",
"timeago.js": "^3.0.2",
+ "tiptap": "^1.8.0",
+ "tiptap-commands": "^1.4.0",
+ "tiptap-extensions": "^1.8.0",
"underscore": "^1.9.0",
"url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
index b709b937180..fe827bb1e18 100644
--- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -186,7 +186,7 @@ describe('ShortcutsIssuable', function() {
it('adds the quoted selection to the input', () => {
ShortcutsIssuable.replyWithSelectedText(true);
- expect($(FORM_SELECTOR).val()).toBe('> _Selected text._\n\n');
+ expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
});
it('triggers `focus`', () => {
diff --git a/yarn.lock b/yarn.lock
index bb948ad703c..fd3172bab08 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2282,12 +2282,7 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-colors@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
- integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
-
-colors@^1.1.2:
+colors@^1.1.0, colors@^1.1.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
@@ -4094,6 +4089,13 @@ fastparse@^1.1.1:
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
integrity sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=
+fault@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
+ integrity sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==
+ dependencies:
+ format "^0.2.2"
+
faye-websocket@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -4313,6 +4315,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+format@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
formdata-polyfill@^3.0.11:
version "3.0.11"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-3.0.11.tgz#c82b4b4bea3356c0a6752219e54ce1edb2a7fb5b"
@@ -4816,7 +4823,7 @@ he@^1.1.0, he@^1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
-highlight.js@^9.13.1:
+highlight.js@^9.13.1, highlight.js@~9.13.0:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
@@ -6467,6 +6474,13 @@ lightercollective@^0.1.0:
resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.1.0.tgz#70df102c530dcb8d0ccabfe6175a8d00d5f61300"
integrity sha512-J9tg5uraYoQKaWbmrzDDexbG6hHnMcWS1qLYgJSWE+mpA3U5OCSeMUhb+K55otgZJ34oFdR0ECvdIb3xuO5JOQ==
+linkify-it@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
+ integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
+ dependencies:
+ uc.micro "^1.0.1"
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -6628,6 +6642,14 @@ lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
+lowlight@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
+ integrity sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==
+ dependencies:
+ fault "^1.0.2"
+ highlight.js "~9.13.0"
+
lru-cache@2.2.x:
version "2.2.4"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
@@ -6691,6 +6713,17 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
+markdown-it@^8.4.2:
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
+ integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==
+ dependencies:
+ argparse "^1.0.7"
+ entities "~1.1.1"
+ linkify-it "^2.0.0"
+ mdurl "^1.0.1"
+ uc.micro "^1.0.5"
+
marked@^0.3.12, marked@~0.3.6:
version "0.3.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
@@ -6709,6 +6742,11 @@ md5.js@^1.3.4:
hash-base "^3.0.0"
inherits "^2.0.1"
+mdurl@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+ integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -7420,6 +7458,11 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
+orderedmap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.0.0.tgz#d90fc2ba1ed085190907d601dec6e6a53f8d41ba"
+ integrity sha1-2Q/Cuh7QhRkJB9YB3sbmpT+NQbo=
+
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -7963,6 +8006,122 @@ prompts@^0.1.9:
kleur "^2.0.1"
sisteransi "^0.1.1"
+prosemirror-commands@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz#e5a2ba821e29ea7065c88277fe2c3d7f6b0b9d37"
+ integrity sha512-IR8yMSdw7XlKuF68tydAak1J9P/lLD5ohsrL7pzoLsJAJAQU7mVPDXtGbQrrm0mesddFjcc1zNo/cJQN3lRYnA==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-dropcursor@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.1.tgz#c60ed1ed6c58804a06a75db06a0d993b087b7622"
+ integrity sha512-GeUyMO/tOEf8MXrP7Xb7UIMrfK86OGh0fnyBrHfhav4VjY9cw65mNoqHy87CklE5711AhCP5Qzfp8RL/hVKusg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+ prosemirror-view "^1.1.0"
+
+prosemirror-gapcursor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.0.3.tgz#acc6537fc5a35e9b38966f91a199a382dfc715c4"
+ integrity sha512-X+hJhr42PcHWiSWL+lI5f/UeOhXCxlBFb8M6O8aG1hssmaRrW7sS2/Fjg5jFV+pTdS1REFkmm1occh01FMdDIQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-history@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.0.3.tgz#5fb8591adfc272afaaf0b41bec64ee7d9522a118"
+ integrity sha512-IfFGbhafSx+R3aq7nLJGkXeu2iaUiP8mkU3aRu2uQcIIjU8Fq7RJfuvhIOJ2RNUoSyqF/ANkdTjnZ74F5eHs1Q==
+ dependencies:
+ prosemirror-state "^1.2.2"
+ prosemirror-transform "^1.0.0"
+ rope-sequence "^1.2.0"
+
+prosemirror-inputrules@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.0.1.tgz#f63305fd966379f218e82ca76a2a9b328b66dc7b"
+ integrity sha512-UHy22NmwxS5WIMQYkzraDttQAF8mpP82FfbJsmKFfx6jwkR/SZa+ZhbkLY0zKQ5fBdJN7euj36JG/B5iAlrpxA==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz#03ef32b828e3a859dfb570eb84928bf2e5330bc2"
+ integrity sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ w3c-keyname "^1.1.8"
+
+prosemirror-markdown@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.3.0.tgz#a100d14c27da7d8fb70818230d786898eeadb7fa"
+ integrity sha512-76l3yLB/suy6sA7LpzRJvRRWkHtKwOTpgWVNwmlIAIIZJeMypWSPldT/gFyIG604eyXEPZitnx+j80Y2DpbnUQ==
+ dependencies:
+ markdown-it "^8.4.2"
+ prosemirror-model "^1.0.0"
+
+prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.6.4:
+ version "1.6.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.6.4.tgz#2ac37a629448a7dbfd1635450e2fdd63c3450d7d"
+ integrity sha512-C2ALle8fZsAza+6stUF9Gv28jH9XtpNeczb33bowGlnb2cpNI4FZf1HHUyZjf6ou4cEvOlbt6fAYsT4NCKmlcQ==
+ dependencies:
+ orderedmap "^1.0.0"
+
+prosemirror-schema-list@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.0.1.tgz#f216e0cf4809b6074aa27912449ac89897f1ae94"
+ integrity sha512-AiLIX6qm6PEeDtMCKZLcSLi55WXo1ls7DnRK+4hSkoi0IIzNdxGsRlecCd3MzEu//DVz3nAEh+zEmslyW+uk8g==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-state@^1.0.0, prosemirror-state@^1.2.1, prosemirror-state@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.2.2.tgz#8df26d95fd6fd327c0f9984a760e84d863204154"
+ integrity sha512-j8aC/kf9BJSCQau485I/9pj39XQoce+TqH5xzekT7WWFARTsRYFLJtiXBcCKakv1VSeev+sC3bJP0pLfz7Ft8g==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-tables@^0.7.10, prosemirror-tables@^0.7.9:
+ version "0.7.10"
+ resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.7.10.tgz#4b0f623422b4b8f84cdc9c559f8a87579846b3ba"
+ integrity sha512-VIu7UGS9keYEHs0Y6AEOTGbNE9QI2rL1OKng4vV6yoTshW/lYcb+s3hGXI12i+WLMjDVm7ujhfdWrpKpvFZOkQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz#28cfdf1f9ee514edc40466be7b7db39eed545fdf"
+ integrity sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ==
+ dependencies:
+ prosemirror-model "^1.0.0"
+
+prosemirror-utils@^0.7.5:
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.5.tgz#11b477647b672ec8f10679ab298a5823dad6457a"
+ integrity sha512-F+63BUiBkUQb1S07c3rGHXjE4MDaZ5OjsNhmaO7eDdSh1lUNORTJJHrvlFEZKnLM7ChoDDXTIKhWNQwnCssQfA==
+
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.6.8:
+ version "1.6.8"
+ resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.6.8.tgz#33fc1a6e2731633e5d6dc1af1967378f15810b74"
+ integrity sha512-YWX3rfji77xsU5EErt4ZoecVytYW9/4oHBYhV1MUHGMYIcppe+QZEBgRlyPMBUuu0lxdZX4m3sq7fCsDvv/MlQ==
+ dependencies:
+ prosemirror-model "^1.1.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -8565,6 +8724,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^2.0.0"
inherits "^2.0.1"
+rope-sequence@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.2.2.tgz#49c4e5c2f54a48e990b050926771e2871bcb31ce"
+ integrity sha1-ScTlwvVKSOmQsFCSZ3HihxvLMc4=
+
rsvp@^3.3.3:
version "3.6.2"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
@@ -9527,6 +9691,57 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==
+tiptap-commands@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.4.0.tgz#0cfb3ac138ee3099de56114cb119abd841fbcbe7"
+ integrity sha512-ytO8jFXgufK5DziamTaVojzUTolWvL4m2xNXaLkAVJYy9CWXruMK7avqeLoFYPI4GZlhleMn5i4gzYTbD7e2jA==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-schema-list "^1.0.1"
+ prosemirror-state "^1.2.2"
+ tiptap-utils "^1.1.1"
+
+tiptap-extensions@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.8.0.tgz#3067620a024f1a9e5fae4450790b143d7ebe4394"
+ integrity sha512-1JN9uk5QnA7DTID1j07gIBEqeOnRd6lwZ5rx/zqWXJLyreZu8VDPvP939tfP41GskO4oicGlhmsQ0aEnA5QYDw==
+ dependencies:
+ lowlight "^1.11.0"
+ prosemirror-history "^1.0.3"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.10"
+ prosemirror-utils "^0.7.5"
+ prosemirror-view "^1.6.8"
+ tiptap "^1.8.0"
+ tiptap-commands "^1.4.0"
+
+tiptap-utils@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.1.1.tgz#e7aad3e84eb35f7abed704d15da0420029789d0d"
+ integrity sha512-yPIWwLFaL5a0GC7fcO7aoPlASnH3wOUQex0IlepNWbDCNycSL8shXhVx0HMN/tCnlp943zw1bwcYzpTW3wA4tw==
+ dependencies:
+ prosemirror-model "^1.6.4"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.9"
+ prosemirror-utils "^0.7.5"
+
+tiptap@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.8.0.tgz#c671188075ffa5ee4f86470f95818fd9ce6f1040"
+ integrity sha512-zIcVY8U1Wgj4bg3R4pX5a2BCpZUw/dTCh259VZ9g5MtClnzdLW2XpKCcwqfa9iUBEs6MCPSnB3t8jGRtGciHJg==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-dropcursor "^1.1.1"
+ prosemirror-gapcursor "^1.0.3"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-keymap "^1.0.1"
+ prosemirror-model "^1.6.4"
+ prosemirror-state "^1.2.1"
+ prosemirror-view "^1.6.8"
+ tiptap-commands "^1.4.0"
+ tiptap-utils "^1.1.1"
+
tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -9678,6 +9893,11 @@ typescript@^2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
+uc.micro@^1.0.1, uc.micro@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
+ integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
+
uglify-js@^3.1.4:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
@@ -10081,6 +10301,11 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
+w3c-keyname@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
+ integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
+
walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"