From ade03d09f108e0fe3e0483f9c5e47f9eb89e9f26 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 2 Feb 2019 02:39:12 +0100 Subject: [PATCH] fix(markdown-widget): handle leading or trailing whitespace (#1517) --- .../src/serializers/__tests__/slate.spec.js | 54 +++++++++++++++++++ .../src/serializers/slateRemark.js | 42 +++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/packages/netlify-cms-widget-markdown/src/serializers/__tests__/slate.spec.js b/packages/netlify-cms-widget-markdown/src/serializers/__tests__/slate.spec.js index 28d78e77..65a30120 100644 --- a/packages/netlify-cms-widget-markdown/src/serializers/__tests__/slate.spec.js +++ b/packages/netlify-cms-widget-markdown/src/serializers/__tests__/slate.spec.js @@ -33,4 +33,58 @@ describe('slate', () => { it('should not escape markdown entities in html', () => { expect(process('*')).toEqual('*'); }); + + it('should not produce invalid markdown when a styled block has trailing whitespace', () => { + const slateAst = { + object: 'block', + type: 'root', + nodes: [ + { + object: 'block', + type: 'paragraph', + nodes: [ + { + object: 'text', + data: undefined, + leaves: [ + { + text: 'foo ', // <-- + marks: [{ type: 'bold' }], + }, + ], + }, + { object: 'text', data: undefined, leaves: [{ text: 'bar' }] }, + ], + }, + ], + }; + expect(slateToMarkdown(slateAst)).toEqual('**foo** bar'); + }); + + it('should not produce invalid markdown when a styled block has leading whitespace', () => { + const slateAst = { + object: 'block', + type: 'root', + nodes: [ + { + object: 'block', + type: 'paragraph', + nodes: [ + { object: 'text', data: undefined, leaves: [{ text: 'foo' }] }, + { + object: 'text', + data: undefined, + leaves: [ + { + text: ' bar', // <-- + marks: [{ type: 'bold' }], + }, + ], + }, + ], + }, + ], + }; + expect(slateToMarkdown(slateAst)).toEqual('foo **bar**'); + }); }); diff --git a/packages/netlify-cms-widget-markdown/src/serializers/slateRemark.js b/packages/netlify-cms-widget-markdown/src/serializers/slateRemark.js index 6d9a5e6c..381ff730 100644 --- a/packages/netlify-cms-widget-markdown/src/serializers/slateRemark.js +++ b/packages/netlify-cms-widget-markdown/src/serializers/slateRemark.js @@ -217,6 +217,48 @@ function convertTextNode(node) { */ if (node.leaves) { const processedLeaves = node.leaves.map(processLeaves); + // Compensate for Slate including leading and trailing whitespace in styled text nodes, which + // cannot be represented in markdown (https://github.com/netlify/netlify-cms/issues/1448) + for (let i = 0; i < processedLeaves.length; i += 1) { + const leaf = processedLeaves[i]; + if (leaf.marks.length > 0 && leaf.text && leaf.text.trim() !== leaf.text) { + const [, leadingWhitespace, trailingWhitespace] = leaf.text.match(/^(\s*).*?(\s*)$/); + // Move the leading whitespace to a separate unstyled leaf, unless the current leaf + // is preceded by another one with (at least) the same marks applied: + if ( + leadingWhitespace.length > 0 && + (i === 0 || + !leaf.marks.every( + mark => processedLeaves[i - 1].marks && processedLeaves[i - 1].marks.includes(mark), + )) + ) { + processedLeaves.splice(i, 0, { + text: leadingWhitespace, + marks: [], + textNodeType: leaf.textNodeType, + }); + i += 1; + leaf.text = leaf.text.replace(/^\s+/, ''); + } + // Move the trailing whitespace to a separate unstyled leaf, unless the current leaf + // is followed by another one with (at least) the same marks applied: + if ( + trailingWhitespace.length > 0 && + (i === processedLeaves.length - 1 || + !leaf.marks.every( + mark => processedLeaves[i + 1].marks && processedLeaves[i + 1].marks.includes(mark), + )) + ) { + processedLeaves.splice(i + 1, 0, { + text: trailingWhitespace, + marks: [], + textNodeType: leaf.textNodeType, + }); + i += 1; + leaf.text = leaf.text.replace(/\s+$/, ''); + } + } + } const condensedNodes = processedLeaves.reduce(condenseNodesReducer, { nodes: [] }); return condensedNodes.nodes; }