From dbf8868c428b45e23679ae3e206889519f836ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Alc=C3=A1ntara?= <ealcantara@gitlab.com> Date: Tue, 12 Jul 2022 18:25:17 +0000 Subject: [PATCH] Skip unknown tags in the Content Editor Skip HTML tags that are not explicitly supported in the Content Editor. Also skip all the children of these unsupported tags --- .../content_editor/extensions/sourcemap.js | 10 + .../services/hast_to_prosemirror_converter.js | 6 +- .../services/markdown_serializer.js | 12 +- .../services/remark_markdown_deserializer.js | 5 + .../services/serialization_helpers.js | 10 +- glfm_specification/example_snapshots/html.yml | 103 ++- .../example_snapshots/prosemirror_json.yml | 705 +++++++++++++++--- .../remark_markdown_processing_spec.js | 76 ++ .../services/markdown_serializer_spec.js | 18 +- 9 files changed, 750 insertions(+), 195 deletions(-) diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index 87118074462a2..618f17b1c5e7b 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -9,6 +9,7 @@ import FootnoteDefinition from './footnote_definition'; import Heading from './heading'; import HardBreak from './hard_break'; import HorizontalRule from './horizontal_rule'; +import HTMLNodes from './html_nodes'; import Image from './image'; import Italic from './italic'; import Link from './link'; @@ -51,13 +52,22 @@ export default Extension.create({ TableCell.name, TableHeader.name, TableRow.name, + ...HTMLNodes.map((htmlNode) => htmlNode.name), ], attributes: { + /** + * The reason to add a function that returns an empty + * string in these attributes is indicate that these + * attributes shouldn’t be rendered in the ProseMirror + * view. + */ sourceMarkdown: { default: null, + renderHTML: () => '', }, sourceMapKey: { default: null, + renderHTML: () => '', }, }, }, diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js index 2c462cdde91b3..6859ebc2c5a18 100644 --- a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js +++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js @@ -570,11 +570,7 @@ export const createProseMirrorDocFromMdastTree = ({ const factory = findFactory(hastNode, ancestors, proseMirrorNodeFactories); if (!factory) { - throw new Error( - `Hast node of type "${ - hastNode.tagName || hastNode.type - }" not supported by this converter. Please, provide an specification.`, - ); + return SKIP; } const parent = findParent(ancestors, factory.parent); diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index c7e05072927a5..c1c7af6b1afb6 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -170,12 +170,6 @@ const defaultSerializerConfig = { [HardBreak.name]: preserveUnchanged(renderHardBreak), [Heading.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.heading), [HorizontalRule.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.horizontal_rule), - ...HTMLNodes.reduce((serializers, htmlNode) => { - return { - ...serializers, - [htmlNode.name]: (state, node) => renderHTMLNode(htmlNode.options.tagName)(state, node), - }; - }, {}), [Image.name]: preserveUnchanged(renderImage), [ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item), [OrderedList.name]: preserveUnchanged(renderOrderedList), @@ -202,6 +196,12 @@ const defaultSerializerConfig = { [Text.name]: defaultMarkdownSerializer.nodes.text, [Video.name]: renderPlayable, [WordBreak.name]: (state) => state.write('<wbr>'), + ...HTMLNodes.reduce((serializers, htmlNode) => { + return { + ...serializers, + [htmlNode.name]: (state, node) => renderHTMLNode(htmlNode.options.tagName)(state, node), + }; + }, {}), }, }; 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 da10c684b0b6a..8c99dc157e6d2 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -88,6 +88,11 @@ const factorySpecs = { selector: (hastNode, ancestors) => hastNode.tagName === 'input' && isTaskItem(ancestors[ancestors.length - 1]), }, + div: { + type: 'block', + selector: 'div', + wrapInParagraph: true, + }, table: { type: 'block', selector: 'table', diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 88f5192af771b..7d5e718b41cfa 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -5,6 +5,8 @@ const defaultAttrs = { th: { colspan: 1, rowspan: 1, colwidth: null }, }; +const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey']; + const ignoreAttrs = { dd: ['isTerm'], dt: ['isTerm'], @@ -101,13 +103,17 @@ function htmlEncode(str = '') { .replace(/"/g, '"'); } +const shouldIgnoreAttr = (tagName, attrKey, attrValue) => + ignoreAttrs[tagName]?.includes(attrKey) || + defaultIgnoreAttrs.includes(attrKey) || + defaultAttrs[tagName]?.[attrKey] === attrValue; + export function openTag(tagName, attrs) { let str = `<${tagName}`; str += Object.entries(attrs || {}) .map(([key, value]) => { - if ((ignoreAttrs[tagName] || []).includes(key) || defaultAttrs[tagName]?.[key] === value) - return ''; + if (shouldIgnoreAttr(tagName, key, value)) return ''; return ` ${key}="${htmlEncode(value?.toString())}"`; }) diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml index 701f7ccef0e90..6a9acdd03a4a7 100644 --- a/glfm_specification/example_snapshots/html.yml +++ b/glfm_specification/example_snapshots/html.yml @@ -1527,8 +1527,9 @@ <a></a> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p> + *hello* + </p></div> 04_06__leaf_blocks__html_blocks__004: canonical: | </div> @@ -1549,8 +1550,7 @@ <p data-sourcepos="3:1-3:10"><em>Markdown</em></p> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p><em>Markdown</em></p></div> 04_06__leaf_blocks__html_blocks__006: canonical: | <div id="foo" @@ -1560,8 +1560,7 @@ <div> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div></div> 04_06__leaf_blocks__html_blocks__007: canonical: | <div id="foo" class="bar @@ -1571,8 +1570,7 @@ <div> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div></div> 04_06__leaf_blocks__html_blocks__008: canonical: | <div> @@ -1584,8 +1582,9 @@ <p data-sourcepos="4:1-4:5"><em>bar</em></p> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p> + *foo* + </p><p><em>bar</em></p></div> 04_06__leaf_blocks__html_blocks__009: canonical: | <div id="foo" @@ -1616,8 +1615,7 @@ static: |- <div><a href="bar">*foo*</a></div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p><a target="_blank" rel="noopener noreferrer nofollow" href="bar">*foo*</a></p></div> 04_06__leaf_blocks__html_blocks__013: canonical: | <table><tr><td> @@ -1643,8 +1641,7 @@ int x = 33; ``` wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div></div> 04_06__leaf_blocks__html_blocks__015: canonical: | <a href="foo"> @@ -1667,8 +1664,7 @@ *bar* wysiwyg: |- - Error - check implementation: - Hast node of type "warning" not supported by this converter. Please, provide an specification. + <p></p> 04_06__leaf_blocks__html_blocks__017: canonical: | <i class="foo"> @@ -1761,8 +1757,7 @@ <p data-sourcepos="6:1-6:4" dir="auto">okay</p> wysiwyg: |- - Error - check implementation: - Hast node of type "script" not supported by this converter. Please, provide an specification. + <p>okay</p> 04_06__leaf_blocks__html_blocks__024: canonical: | <style @@ -1780,8 +1775,7 @@ <p data-sourcepos="7:1-7:4" dir="auto">okay</p> wysiwyg: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. + <p>okay</p> 04_06__leaf_blocks__html_blocks__025: canonical: | <style @@ -1793,8 +1787,7 @@ foo wysiwyg: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. + <p></p> 04_06__leaf_blocks__html_blocks__026: canonical: | <blockquote> @@ -1811,8 +1804,9 @@ </div> </blockquote> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <blockquote multiline="false"><div><p> + foo + </p></div></blockquote> 04_06__leaf_blocks__html_blocks__027: canonical: | <ul> @@ -1831,8 +1825,7 @@ </li> </ul> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <ul bullet="*"><li><p></p><div></div></li><li><p>foo</p></li></ul> 04_06__leaf_blocks__html_blocks__028: canonical: | <style>p{color:red;}</style> @@ -1841,8 +1834,7 @@ p{color:red;} <p data-sourcepos="2:1-2:5" dir="auto"><em>foo</em></p> wysiwyg: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. + <p><em>foo</em></p> 04_06__leaf_blocks__html_blocks__029: canonical: | <!-- foo -->*bar* @@ -1861,8 +1853,7 @@ static: |- 1. *bar* wysiwyg: |- - Error - check implementation: - Hast node of type "script" not supported by this converter. Please, provide an specification. + <p>1. *bar*</p> 04_06__leaf_blocks__html_blocks__031: canonical: | <!-- Foo @@ -1955,8 +1946,7 @@ </div> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><pre class="content-editor-code-block undefined code highlight"><code><div></code></pre></div> 04_06__leaf_blocks__html_blocks__037: canonical: | <p>Foo</p> @@ -1969,8 +1959,7 @@ bar </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <p>Foo</p> 04_06__leaf_blocks__html_blocks__038: canonical: | <div> @@ -1983,8 +1972,9 @@ </div> *foo* wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p> + bar + </p></div> 04_06__leaf_blocks__html_blocks__039: canonical: | <p>Foo @@ -2008,8 +1998,7 @@ <p data-sourcepos="3:1-3:18"><em>Emphasized</em> text.</p> </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p><em>Emphasized</em> text.</p></div> 04_06__leaf_blocks__html_blocks__041: canonical: | <div> @@ -2020,8 +2009,9 @@ *Emphasized* text. </div> wysiwyg: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + <div><p> + *Emphasized* text. + </p></div> 04_06__leaf_blocks__html_blocks__042: canonical: | <table> @@ -2157,8 +2147,7 @@ <p data-sourcepos="1:1-1:17" dir="auto">[foo]: (baz)</p> <p data-sourcepos="3:1-3:5" dir="auto">[foo]</p> wysiwyg: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + <p>[foo]: </p> 04_07__leaf_blocks__link_reference_definitions__011: canonical: | <p><a href="/url%5Cbar*baz" title="foo"bar\baz">foo</a></p> @@ -6096,8 +6085,7 @@ static: |- <p data-sourcepos="1:1-2:5" dir="auto">[link]()</p> wysiwyg: |- - Error - check implementation: - Hast node of type "foo" not supported by this converter. Please, provide an specification. + <p>[link](</p> 06_07__inlines__links__009: canonical: | <p><a href="b)c">a</a></p> @@ -6336,8 +6324,7 @@ static: |- <p data-sourcepos="1:1-1:24" dir="auto">[foo </p> wysiwyg: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + <p>[foo </p> 06_07__inlines__links__041: canonical: | <p>[foo<code>](/uri)</code></p> @@ -6422,8 +6409,7 @@ static: |- <p data-sourcepos="1:1-1:24" dir="auto">[foo </p> wysiwyg: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + <p>[foo </p> 06_07__inlines__links__053: canonical: | <p>[foo<code>][ref]</code></p> @@ -7085,16 +7071,14 @@ static: |- <p data-sourcepos="1:1-1:13" dir="auto"><a></a></p> wysiwyg: |- - Error - check implementation: - Hast node of type "bab" not supported by this converter. Please, provide an specification. + <p></p> 06_11__inlines__raw_html__002: canonical: | <p><a/><b2/></p> static: |- <p data-sourcepos="1:1-1:9" dir="auto"><a></a></p> wysiwyg: |- - Error - check implementation: - Hast node of type "b2" not supported by this converter. Please, provide an specification. + <p></p> 06_11__inlines__raw_html__003: canonical: | <p><a /><b2 @@ -7102,8 +7086,7 @@ static: |- <p data-sourcepos="1:1-2:12" dir="auto"><a></a></p> wysiwyg: |- - Error - check implementation: - Hast node of type "b2" not supported by this converter. Please, provide an specification. + <p></p> 06_11__inlines__raw_html__004: canonical: | <p><a foo="bar" bam = 'baz <em>"</em>' @@ -7118,8 +7101,7 @@ static: |- <p data-sourcepos="1:1-1:38" dir="auto">Foo </p> wysiwyg: |- - Error - check implementation: - Hast node of type "responsive-image" not supported by this converter. Please, provide an specification. + <p>Foo </p> 06_11__inlines__raw_html__006: canonical: | <p><33> <__></p> @@ -7260,8 +7242,7 @@ <xmp> is disallowed. <XMP> is also disallowed. </blockquote></strong></p> wysiwyg: |- - Error - check implementation: - Hast node of type "title" not supported by this converter. Please, provide an specification. + <p></p> 06_13__inlines__hard_line_breaks__001: canonical: | <p>foo<br /> @@ -7434,11 +7415,11 @@ 07_01__gitlab_specific_markdown__footnotes__001: canonical: "" static: |- - <p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-4281" id="fnref-1-4281" data-footnote-ref>1</a></sup></p> + <p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-794" id="fnref-1-794" data-footnote-ref>1</a></sup></p> <section data-footnotes class="footnotes"> <ol> - <li id="fn-1-4281"> - <p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-4281" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <li id="fn-1-794"> + <p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-794" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> </ol> </section> diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml index a221350775d6b..85d499ef552b5 100644 --- a/glfm_specification/example_snapshots/prosemirror_json.yml +++ b/glfm_specification/example_snapshots/prosemirror_json.yml @@ -2941,8 +2941,25 @@ ] } 04_06__leaf_blocks__html_blocks__003: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\n *hello*\n " + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__004: |- { "type": "doc", @@ -2959,17 +2976,82 @@ ] } 04_06__leaf_blocks__html_blocks__005: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "Markdown" + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__006: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div" + } + ] + } 04_06__leaf_blocks__html_blocks__007: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div" + } + ] + } 04_06__leaf_blocks__html_blocks__008: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\n*foo*\n" + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "bar" + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__009: |- { "type": "doc", @@ -2998,8 +3080,37 @@ ] } 04_06__leaf_blocks__html_blocks__012: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "link", + "attrs": { + "href": "bar", + "target": "_blank", + "class": null, + "title": null, + "canonicalSrc": null + } + } + ], + "text": "*foo*" + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__013: |- { "type": "doc", @@ -3039,8 +3150,23 @@ ] } 04_06__leaf_blocks__html_blocks__014: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div" + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\n``` c\nint x = 33;\n```" + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__015: |- { "type": "doc", @@ -3069,8 +3195,14 @@ ] } 04_06__leaf_blocks__html_blocks__016: |- - Error - check implementation: - Hast node of type "warning" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } 04_06__leaf_blocks__html_blocks__017: |- { "type": "doc", @@ -3126,10 +3258,259 @@ } ] } -04_06__leaf_blocks__html_blocks__020: |- - Error - check implementation: - Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined. -04_06__leaf_blocks__html_blocks__021: |- +04_06__leaf_blocks__html_blocks__020: |- + Error - check implementation: + Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined. +04_06__leaf_blocks__html_blocks__021: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + }, + { + "type": "strike" + } + ], + "text": "foo" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__022: |- + { + "type": "doc", + "content": [ + { + "type": "codeBlock", + "attrs": { + "language": null, + "class": "code highlight" + }, + "content": [ + { + "type": "text", + "text": "\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags" + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "okay" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__023: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "okay" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__024: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "okay" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__025: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } +04_06__leaf_blocks__html_blocks__026: |- + { + "type": "doc", + "content": [ + { + "type": "blockquote", + "attrs": { + "multiline": false + }, + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\nfoo\n" + } + ] + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "bar" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__027: |- + { + "type": "doc", + "content": [ + { + "type": "bulletList", + "attrs": { + "bullet": "*" + }, + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph" + }, + { + "type": "div" + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "foo" + } + ] + } + ] + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__028: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "foo" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__029: |- + Error - check implementation: + Cannot read properties of undefined (reading 'wrapper') +04_06__leaf_blocks__html_blocks__030: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "1. *bar*" + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__031: |- + Error - check implementation: + Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. +04_06__leaf_blocks__html_blocks__032: |- + Error - check implementation: + Cannot read properties of undefined (reading 'wrapper') +04_06__leaf_blocks__html_blocks__033: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } +04_06__leaf_blocks__html_blocks__034: |- + Error - check implementation: + Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. +04_06__leaf_blocks__html_blocks__035: |- + Error - check implementation: + Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. +04_06__leaf_blocks__html_blocks__036: |- + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "codeBlock", + "attrs": { + "language": null, + "class": "code highlight" + }, + "content": [ + { + "type": "text", + "text": "<div>" + } + ] + } + ] + } + ] + } +04_06__leaf_blocks__html_blocks__037: |- { "type": "doc", "content": [ @@ -3138,34 +3519,41 @@ "content": [ { "type": "text", - "marks": [ - { - "type": "italic" - }, + "text": "Foo" + } + ] + }, + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ { - "type": "strike" + "type": "text", + "text": "\nbar\n" } - ], - "text": "foo" + ] } ] } ] } -04_06__leaf_blocks__html_blocks__022: |- +04_06__leaf_blocks__html_blocks__038: |- { "type": "doc", "content": [ { - "type": "codeBlock", - "attrs": { - "language": null, - "class": "code highlight" - }, + "type": "div", "content": [ { - "type": "text", - "text": "\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags" + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\nbar\n" + } + ] } ] }, @@ -3174,66 +3562,12 @@ "content": [ { "type": "text", - "text": "okay" + "text": "\n*foo*" } ] } ] } -04_06__leaf_blocks__html_blocks__023: |- - Error - check implementation: - Hast node of type "script" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__024: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__025: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__026: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__027: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__028: |- - Error - check implementation: - Hast node of type "style" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__029: |- - Error - check implementation: - Cannot read properties of undefined (reading 'wrapper') -04_06__leaf_blocks__html_blocks__030: |- - Error - check implementation: - Hast node of type "script" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__031: |- - Error - check implementation: - Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. -04_06__leaf_blocks__html_blocks__032: |- - Error - check implementation: - Cannot read properties of undefined (reading 'wrapper') -04_06__leaf_blocks__html_blocks__033: |- - { - "type": "doc", - "content": [ - { - "type": "paragraph" - } - ] - } -04_06__leaf_blocks__html_blocks__034: |- - Error - check implementation: - Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. -04_06__leaf_blocks__html_blocks__035: |- - Error - check implementation: - Cannot destructure property 'className' of 'hastNode.properties' as it is undefined. -04_06__leaf_blocks__html_blocks__036: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__037: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. -04_06__leaf_blocks__html_blocks__038: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. 04_06__leaf_blocks__html_blocks__039: |- { "type": "doc", @@ -3266,11 +3600,54 @@ ] } 04_06__leaf_blocks__html_blocks__040: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "Emphasized" + }, + { + "type": "text", + "text": " text." + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__041: |- - Error - check implementation: - Hast node of type "div" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "div", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "\n*Emphasized* text.\n" + } + ] + } + ] + } + ] + } 04_06__leaf_blocks__html_blocks__042: |- { "type": "doc", @@ -3586,8 +3963,29 @@ ] } 04_07__leaf_blocks__link_reference_definitions__010: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[foo]: " + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[foo]" + } + ] + } + ] + } 04_07__leaf_blocks__link_reference_definitions__011: |- { "type": "doc", @@ -13715,8 +14113,20 @@ ] } 06_07__inlines__links__008: |- - Error - check implementation: - Hast node of type "foo" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[link](" + } + ] + } + ] + } 06_07__inlines__links__009: |- { "type": "doc", @@ -14573,8 +14983,20 @@ ] } 06_07__inlines__links__040: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[foo " + } + ] + } + ] + } 06_07__inlines__links__041: |- { "type": "doc", @@ -14938,8 +15360,20 @@ ] } 06_07__inlines__links__052: |- - Error - check implementation: - Hast node of type "bar" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "[foo " + } + ] + } + ] + } 06_07__inlines__links__053: |- { "type": "doc", @@ -17446,14 +17880,32 @@ ] } 06_11__inlines__raw_html__001: |- - Error - check implementation: - Hast node of type "bab" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } 06_11__inlines__raw_html__002: |- - Error - check implementation: - Hast node of type "b2" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } 06_11__inlines__raw_html__003: |- - Error - check implementation: - Hast node of type "b2" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph" + } + ] + } 06_11__inlines__raw_html__004: |- { "type": "doc", @@ -17464,8 +17916,20 @@ ] } 06_11__inlines__raw_html__005: |- - Error - check implementation: - Hast node of type "responsive-image" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Foo " + } + ] + } + ] + } 06_11__inlines__raw_html__006: |- { "type": "doc", @@ -17662,8 +18126,25 @@ ] } 06_12__inlines__disallowed_raw_html_extension__001: |- - Error - check implementation: - Hast node of type "title" not supported by this converter. Please, provide an specification. + { + "type": "doc", + "content": [ + { + "type": "paragraph" + }, + { + "type": "blockquote", + "attrs": { + "multiline": false + }, + "content": [ + { + "type": "paragraph" + } + ] + } + ] + } 06_13__inlines__hard_line_breaks__001: |- { "type": "doc", diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js index e969e3627ca9c..d1690c4f6c11b 100644 --- a/spec/frontend/content_editor/remark_markdown_processing_spec.js +++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js @@ -6,6 +6,7 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight import FootnoteDefinition from '~/content_editor/extensions/footnote_definition'; import FootnoteReference from '~/content_editor/extensions/footnote_reference'; import HardBreak from '~/content_editor/extensions/hard_break'; +import HTMLNodes from '~/content_editor/extensions/html_nodes'; import Heading from '~/content_editor/extensions/heading'; import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; import Image from '~/content_editor/extensions/image'; @@ -52,6 +53,7 @@ const tiptapEditor = createTestEditor({ TableCell, TaskList, TaskItem, + ...HTMLNodes, ], }); @@ -64,6 +66,7 @@ const { bulletList, code, codeBlock, + div, footnoteDefinition, footnoteReference, hardBreak, @@ -108,6 +111,13 @@ const { tableRow: { nodeType: TableRow.name }, taskItem: { nodeType: TaskItem.name }, taskList: { nodeType: TaskList.name }, + ...HTMLNodes.reduce( + (builders, htmlNode) => ({ + ...builders, + [htmlNode.name]: { nodeType: htmlNode.name }, + }), + {}, + ), }, }); @@ -915,6 +925,12 @@ Paragraph paragraph(source('Paragraph'), 'Paragraph'), ), }, + { + markdown: ` +<div>div</div> +`, + expectedDoc: doc(div(source('<div>div</div>'), paragraph(source('div'), 'div'))), + }, ]; const runOnly = examples.find((example) => example.only === true); @@ -928,4 +944,64 @@ Paragraph expect(document.toJSON()).toEqual(expectedDoc.toJSON()); expect(serialize(document)).toEqual(trimmed); }); + + /** + * DISCLAIMER: THIS IS A SECURITY ORIENTED TEST THAT ENSURES + * THE CLIENT-SIDE PARSER IGNORES DANGEROUS TAGS THAT ARE NOT + * EXPLICITELY SUPPORTED. + * + * PLEASE CONSIDER THIS INFORMATION WHILE MODIFYING THESE TESTS + */ + it.each([ + { + markdown: ` +<script> +alert("Hello world") +</script> + `, + expectedHtml: '<p></p>', + }, + { + markdown: ` +<foo>Hello</foo> + `, + expectedHtml: '<p></p>', + }, + { + markdown: ` +<h1 class="heading-with-class">Header</h1> + `, + expectedHtml: '<h1>Header</h1>', + }, + { + markdown: ` +<a id="link-id">Header</a> and other text + `, + expectedHtml: + '<p><a target="_blank" rel="noopener noreferrer nofollow">Header</a> and other text</p>', + }, + { + markdown: ` +<style> +body { + display: none; +} +</style> + `, + expectedHtml: '<p></p>', + }, + { + markdown: '<div style="transform">div</div>', + expectedHtml: '<div><p>div</p></div>', + }, + ])( + 'removes unknown tags and unsupported attributes from HTML output', + async ({ markdown, expectedHtml }) => { + const document = await deserialize(markdown); + + tiptapEditor.commands.setContent(document.toJSON()); + + expect(tiptapEditor.getHTML()).toEqual(expectedHtml); + }, + ); }); diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 156095dad14fc..68806f9c26add 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -57,8 +57,6 @@ const tiptapEditor = createTestEditor({ HardBreak, Heading, HorizontalRule, - ...HTMLMarks, - ...HTMLNodes, Image, InlineDiff, Italic, @@ -73,6 +71,8 @@ const tiptapEditor = createTestEditor({ TableRow, TaskItem, TaskList, + ...HTMLMarks, + ...HTMLNodes, ], }); @@ -122,13 +122,6 @@ const { codeBlock: { nodeType: CodeBlockHighlight.name }, details: { nodeType: Details.name }, detailsContent: { nodeType: DetailsContent.name }, - ...HTMLNodes.reduce( - (builders, htmlNode) => ({ - ...builders, - [htmlNode.name]: { nodeType: htmlNode.name }, - }), - {}, - ), descriptionItem: { nodeType: DescriptionItem.name }, descriptionList: { nodeType: DescriptionList.name }, emoji: { markType: Emoji.name }, @@ -153,6 +146,13 @@ const { tableRow: { nodeType: TableRow.name }, taskItem: { nodeType: TaskItem.name }, taskList: { nodeType: TaskList.name }, + ...HTMLNodes.reduce( + (builders, htmlNode) => ({ + ...builders, + [htmlNode.name]: { nodeType: htmlNode.name }, + }), + {}, + ), }, }); -- GitLab