From b4c5fc77839a7459fd2733f105514a86c8c43c22 Mon Sep 17 00:00:00 2001 From: Erez Rokah Date: Fri, 31 Jan 2020 16:49:10 -0800 Subject: [PATCH] fix(editor): merge adjacent text nodes with same marks (#3173) --- .../serializers/__tests__/remarkSlate.spec.js | 70 ++++++++++++++++++- .../src/serializers/remarkSlate.js | 38 +++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/packages/netlify-cms-widget-markdown/src/serializers/__tests__/remarkSlate.spec.js b/packages/netlify-cms-widget-markdown/src/serializers/__tests__/remarkSlate.spec.js index 4d6bf4dc..9c952f25 100644 --- a/packages/netlify-cms-widget-markdown/src/serializers/__tests__/remarkSlate.spec.js +++ b/packages/netlify-cms-widget-markdown/src/serializers/__tests__/remarkSlate.spec.js @@ -1,4 +1,4 @@ -import { wrapInlinesWithTexts } from '../remarkSlate'; +import { wrapInlinesWithTexts, mergeAdjacentTexts } from '../remarkSlate'; describe('remarkSlate', () => { describe('wrapInlinesWithTexts', () => { it('should handle empty array', () => { @@ -74,4 +74,72 @@ describe('remarkSlate', () => { ]); }); }); + + describe('mergeAdjacentTexts', () => { + it('should handle empty array', () => { + const children = []; + expect(mergeAdjacentTexts(children)).toBe(children); + }); + + it('should merge adjacent texts with same marks', () => { + const children = [ + { object: 'text', text: '', marks: [] }, + { object: 'text', text: 'Netlify', marks: [] }, + { object: 'text', text: '', marks: [] }, + ]; + + expect(mergeAdjacentTexts(children)).toEqual([ + { + object: 'text', + text: 'Netlify', + marks: [], + }, + ]); + }); + + it('should not merge adjacent texts with different marks', () => { + const children = [ + { object: 'text', text: '', marks: [] }, + { object: 'text', text: 'Netlify', marks: ['b'] }, + { object: 'text', text: '', marks: [] }, + ]; + + expect(mergeAdjacentTexts(children)).toEqual(children); + }); + + it('should handle mixed children array', () => { + const children = [ + { object: 'inline' }, + { object: 'text', text: '', marks: [] }, + { object: 'text', text: 'Netlify', marks: [] }, + { object: 'text', text: '', marks: [] }, + { object: 'inline' }, + { object: 'text', text: '', marks: [] }, + { object: 'text', text: 'Netlify', marks: ['b'] }, + { object: 'text', text: '', marks: [] }, + { object: 'text', text: '', marks: [] }, + { object: 'inline' }, + { object: 'text', text: '', marks: [] }, + ]; + + expect(mergeAdjacentTexts(children)).toEqual([ + { object: 'inline' }, + { + object: 'text', + text: 'Netlify', + marks: [], + }, + { object: 'inline' }, + { object: 'text', text: '', marks: [] }, + { object: 'text', text: 'Netlify', marks: ['b'] }, + { + object: 'text', + text: '', + marks: [], + }, + { object: 'inline' }, + { object: 'text', text: '', marks: [] }, + ]); + }); + }); }); diff --git a/packages/netlify-cms-widget-markdown/src/serializers/remarkSlate.js b/packages/netlify-cms-widget-markdown/src/serializers/remarkSlate.js index da9dbf96..f2fba685 100644 --- a/packages/netlify-cms-widget-markdown/src/serializers/remarkSlate.js +++ b/packages/netlify-cms-widget-markdown/src/serializers/remarkSlate.js @@ -1,4 +1,4 @@ -import { isEmpty, isArray, flatMap, map, flatten } from 'lodash'; +import { isEmpty, isArray, flatMap, map, flatten, isEqual } from 'lodash'; /** * Map of MDAST node types to Slate node types. @@ -30,6 +30,7 @@ const markMap = { const isInline = node => node.object === 'inline'; const isText = node => node.object === 'text'; +const isMarksEqual = (node1, node2) => isEqual(node1.marks, node2.marks); export const wrapInlinesWithTexts = children => { if (children.length <= 0) { @@ -64,6 +65,39 @@ export const wrapInlinesWithTexts = children => { return children; }; +export const mergeAdjacentTexts = children => { + if (children.length <= 0) { + return children; + } + + const mergedChildren = []; + + let isMerging = false; + let current; + + for (let i = 0; i < children.length - 1; i++) { + if (!isMerging) { + current = children[i]; + } + const next = children[i + 1]; + if (isText(current) && isText(next) && isMarksEqual(current, next)) { + isMerging = true; + current = { ...current, text: `${current.text}${next.text}` }; + } else { + mergedChildren.push(current); + isMerging = false; + } + } + + if (isMerging) { + mergedChildren.push(current); + } else { + mergedChildren.push(children[children.length - 1]); + } + + return mergedChildren; +}; + /** * A Remark plugin for converting an MDAST to Slate Raw AST. Remark plugins * return a `transformNode` function that receives the MDAST as it's first argument. @@ -87,6 +121,8 @@ export default function remarkToSlate({ voidCodeBlock } = {}) { if (Array.isArray(children)) { // Ensure that inline nodes are surrounded by text nodes to conform to slate schema children = wrapInlinesWithTexts(children); + // Merge adjacent text nodes with the same marks to conform to slate schema + children = mergeAdjacentTexts(children); } /**