diff --git a/src/components/Widgets/Markdown/serializers/__tests__/remarkAllowHtmlEntities.spec.js b/src/components/Widgets/Markdown/serializers/__tests__/remarkAllowHtmlEntities.spec.js new file mode 100644 index 00000000..a7eed4c4 --- /dev/null +++ b/src/components/Widgets/Markdown/serializers/__tests__/remarkAllowHtmlEntities.spec.js @@ -0,0 +1,24 @@ +import unified from 'unified'; +import markdownToRemark from 'remark-parse'; +import remarkAllowHtmlEntities from '../remarkAllowHtmlEntities'; + +const process = markdown => { + const mdast = unified().use(markdownToRemark).use(remarkAllowHtmlEntities).parse(markdown); + + /** + * The MDAST will look like: + * + * { type: 'root', children: [ + * { type: 'paragraph', children: [ + * // results here + * ]} + * ]} + */ + return mdast.children[0].children[0].value; +}; + +describe('remarkAllowHtmlEntities', () => { + it('should not decode HTML entities', () => { + expect(process('<div>')).toEqual('<div>'); + }); +}); diff --git a/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js b/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js index b602eca1..fd3bda41 100644 --- a/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js +++ b/src/components/Widgets/Markdown/serializers/__tests__/slate.spec.js @@ -8,7 +8,7 @@ describe('slate', () => { expect(process('a\n')).toEqual('a\n'); }); - xit('should not decode encoded html entities in inline code', () => { + it('should not decode encoded html entities in inline code', () => { expect(process('<div>')).toEqual('<div>\n'); }); diff --git a/src/components/Widgets/Markdown/serializers/index.js b/src/components/Widgets/Markdown/serializers/index.js index 19e2a0e9..5b00150d 100644 --- a/src/components/Widgets/Markdown/serializers/index.js +++ b/src/components/Widgets/Markdown/serializers/index.js @@ -18,6 +18,7 @@ import remarkImagesToText from './remarkImagesToText'; import remarkShortcodes from './remarkShortcodes'; import remarkEscapeMarkdownEntities from './remarkEscapeMarkdownEntities'; import remarkStripTrailingBreaks from './remarkStripTrailingBreaks'; +import remarkAllowHtmlEntities from './remarkAllowHtmlEntities'; import slateToRemark from './slateRemark'; import registry from '../../../../lib/registry'; @@ -66,6 +67,7 @@ export const markdownToRemark = markdown => { const parsed = unified() .use(markdownToRemarkPlugin, { fences: true, commonmark: true }) .use(markdownToRemarkRemoveTokenizers, { inlineTokenizers: ['url'] }) + .use(remarkAllowHtmlEntities) .parse(markdown); /** diff --git a/src/components/Widgets/Markdown/serializers/remarkAllowHtmlEntities.js b/src/components/Widgets/Markdown/serializers/remarkAllowHtmlEntities.js new file mode 100644 index 00000000..62e4d3be --- /dev/null +++ b/src/components/Widgets/Markdown/serializers/remarkAllowHtmlEntities.js @@ -0,0 +1,59 @@ +export default function remarkAllowHtmlEntities() { + this.Parser.prototype.inlineTokenizers.text = text; + + /** + * This is a port of the `remark-parse` text tokenizer, adapted to exclude + * HTML entity decoding. + */ + function text(eat, value, silent) { + var self = this; + var methods; + var tokenizers; + var index; + var length; + var subvalue; + var position; + var tokenizer; + var name; + var min; + var now; + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + methods = self.inlineMethods; + length = methods.length; + tokenizers = self.inlineTokenizers; + index = -1; + min = value.length; + + while (++index < length) { + name = methods[index]; + + if (name === 'text' || !tokenizers[name]) { + continue; + } + + tokenizer = tokenizers[name].locator; + + if (!tokenizer) { + eat.file.fail('Missing locator: `' + name + '`'); + } + + position = tokenizer.call(self, value, 1); + + if (position !== -1 && position < min) { + min = position; + } + } + + subvalue = value.slice(0, min); + + eat(subvalue)({ + type: 'text', + value: subvalue, + }); + } +};