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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js')
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js180
1 files changed, 138 insertions, 42 deletions
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
index 770de1df0d0..da10c684b0b 100644
--- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -2,39 +2,51 @@ import { isString } from 'lodash';
import { render } from '~/lib/gfm';
import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter';
+const wrappableTags = ['img', 'br', 'code', 'i', 'em', 'b', 'strong', 'a', 'strike', 's', 'del'];
+
+const isTaskItem = (hastNode) => {
+ const { className } = hastNode.properties;
+
+ return (
+ hastNode.tagName === 'li' && Array.isArray(className) && className.includes('task-list-item')
+ );
+};
+
+const getTableCellAttrs = (hastNode) => ({
+ colspan: parseInt(hastNode.properties.colSpan, 10) || 1,
+ rowspan: parseInt(hastNode.properties.rowSpan, 10) || 1,
+});
+
const factorySpecs = {
- blockquote: { block: 'blockquote' },
- p: { block: 'paragraph' },
- li: { block: 'listItem', wrapTextInParagraph: true },
- ul: { block: 'bulletList' },
- ol: { block: 'orderedList' },
- h1: {
- block: 'heading',
- getAttrs: () => ({ level: 1 }),
- },
- h2: {
- block: 'heading',
- getAttrs: () => ({ level: 2 }),
- },
- h3: {
- block: 'heading',
- getAttrs: () => ({ level: 3 }),
- },
- h4: {
- block: 'heading',
- getAttrs: () => ({ level: 4 }),
- },
- h5: {
- block: 'heading',
- getAttrs: () => ({ level: 5 }),
- },
- h6: {
- block: 'heading',
- getAttrs: () => ({ level: 6 }),
- },
- pre: {
- block: 'codeBlock',
+ blockquote: { type: 'block', selector: 'blockquote' },
+ paragraph: { type: 'block', selector: 'p' },
+ listItem: {
+ type: 'block',
+ wrapInParagraph: true,
+ selector: (hastNode) => hastNode.tagName === 'li' && !hastNode.properties.className,
+ processText: (text) => text.trimRight(),
+ },
+ orderedList: {
+ type: 'block',
+ selector: (hastNode) => hastNode.tagName === 'ol' && !hastNode.properties.className,
+ },
+ bulletList: {
+ type: 'block',
+ selector: (hastNode) => hastNode.tagName === 'ul' && !hastNode.properties.className,
+ },
+ heading: {
+ type: 'block',
+ selector: (hastNode) => ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(hastNode.tagName),
+ getAttrs: (hastNode) => {
+ const level = parseInt(/(\d)$/.exec(hastNode.tagName)?.[1], 10) || 1;
+
+ return { level };
+ },
+ },
+ codeBlock: {
+ type: 'block',
skipChildren: true,
+ selector: 'pre',
getContent: ({ hastNodeText }) => hastNodeText.replace(/\n$/, ''),
getAttrs: (hastNode) => {
const languageClass = hastNode.children[0]?.properties.className?.[0];
@@ -43,28 +55,111 @@ const factorySpecs = {
return { language };
},
},
- hr: { inline: 'horizontalRule' },
- img: {
- inline: 'image',
+ horizontalRule: {
+ type: 'block',
+ selector: 'hr',
+ },
+ taskList: {
+ type: 'block',
+ selector: (hastNode) => {
+ const { className } = hastNode.properties;
+
+ return (
+ ['ul', 'ol'].includes(hastNode.tagName) &&
+ Array.isArray(className) &&
+ className.includes('contains-task-list')
+ );
+ },
+ getAttrs: (hastNode) => ({
+ numeric: hastNode.tagName === 'ol',
+ }),
+ },
+ taskItem: {
+ type: 'block',
+ wrapInParagraph: true,
+ selector: isTaskItem,
+ getAttrs: (hastNode) => ({
+ checked: hastNode.children[0].properties.checked,
+ }),
+ processText: (text) => text.trimLeft(),
+ },
+ taskItemCheckbox: {
+ type: 'ignore',
+ selector: (hastNode, ancestors) =>
+ hastNode.tagName === 'input' && isTaskItem(ancestors[ancestors.length - 1]),
+ },
+ table: {
+ type: 'block',
+ selector: 'table',
+ },
+ tableRow: {
+ type: 'block',
+ selector: 'tr',
+ parent: 'table',
+ },
+ tableHeader: {
+ type: 'block',
+ selector: 'th',
+ getAttrs: getTableCellAttrs,
+ wrapInParagraph: true,
+ },
+ tableCell: {
+ type: 'block',
+ selector: 'td',
+ getAttrs: getTableCellAttrs,
+ wrapInParagraph: true,
+ },
+ ignoredTableNodes: {
+ type: 'ignore',
+ selector: (hastNode) => ['thead', 'tbody', 'tfoot'].includes(hastNode.tagName),
+ },
+ footnoteDefinition: {
+ type: 'block',
+ selector: 'footnotedefinition',
+ getAttrs: (hastNode) => hastNode.properties,
+ },
+ image: {
+ type: 'inline',
+ selector: 'img',
getAttrs: (hastNode) => ({
src: hastNode.properties.src,
title: hastNode.properties.title,
alt: hastNode.properties.alt,
}),
},
- br: { inline: 'hardBreak' },
- code: { mark: 'code' },
- em: { mark: 'italic' },
- i: { mark: 'italic' },
- strong: { mark: 'bold' },
- b: { mark: 'bold' },
- a: {
- mark: 'link',
+ hardBreak: {
+ type: 'inline',
+ selector: 'br',
+ },
+ footnoteReference: {
+ type: 'inline',
+ selector: 'footnotereference',
+ getAttrs: (hastNode) => hastNode.properties,
+ },
+ code: {
+ type: 'mark',
+ selector: 'code',
+ },
+ italic: {
+ type: 'mark',
+ selector: (hastNode) => ['em', 'i'].includes(hastNode.tagName),
+ },
+ bold: {
+ type: 'mark',
+ selector: (hastNode) => ['strong', 'b'].includes(hastNode.tagName),
+ },
+ link: {
+ type: 'mark',
+ selector: 'a',
getAttrs: (hastNode) => ({
href: hastNode.properties.href,
title: hastNode.properties.title,
}),
},
+ strike: {
+ type: 'mark',
+ selector: (hastNode) => ['strike', 's', 'del'].includes(hastNode.tagName),
+ },
};
export default () => {
@@ -77,6 +172,7 @@ export default () => {
schema,
factorySpecs,
tree,
+ wrappableTags,
source: markdown,
}),
});