From fbd18ceadd76607cf9119302db692948368fbb41 Mon Sep 17 00:00:00 2001 From: Nivedita Chopra Date: Fri, 14 Sep 2018 16:45:07 -0700 Subject: [PATCH] Merge successive non-leaf blocks resulting from tab Summary: When you tab a block & both its neighbors are non-leaves, we should merge the two blocks. https://pxl.cl/ht30 This is case 6 below, which I had postponed back in D9631917: {F137497472} Reviewed By: vdurmont Differential Revision: D9836483 fbshipit-source-id: 2c74a772e5c1d0b2751db10e760632ac75e77273 --- .../exploration/NestedRichTextEditorUtil.js | 22 +- .../NestedRichTextEditorUtil-test.js | 89 +++++++- .../NestedRichTextEditorUtil-test.js.snap | 199 ++++++++++++++++++ 3 files changed, 293 insertions(+), 17 deletions(-) diff --git a/src/model/modifier/exploration/NestedRichTextEditorUtil.js b/src/model/modifier/exploration/NestedRichTextEditorUtil.js index 162fdf91d6..53efd3493c 100644 --- a/src/model/modifier/exploration/NestedRichTextEditorUtil.js +++ b/src/model/modifier/exploration/NestedRichTextEditorUtil.js @@ -322,22 +322,22 @@ const NestedRichTextEditorUtil: RichTextUtils = { const prevSibling = blockMap.get(prevSiblingKey); const nextSibling = nextSiblingKey != null ? blockMap.get(nextSiblingKey) : null; - if ( - prevSibling != null && - prevSibling.getText() === '' && - prevSibling.getChildKeys().count() > 0 - ) { + const prevSiblingNonLeaf = + prevSibling != null && prevSibling.getChildKeys().count() > 0; + const nextSiblingNonLeaf = + nextSibling != null && nextSibling.getChildKeys().count() > 0; + if (prevSiblingNonLeaf) { blockMap = DraftTreeOperations.updateAsSiblingsChild( blockMap, key, 'previous', ); - // else, if next sibling is a non-leaf move node as child of next sibling - } else if ( - nextSibling != null && - nextSibling.getText() === '' && - nextSibling.getChildKeys().count() > 0 - ) { + // if next sibling is also non-leaf, merge the previous & next siblings + if (nextSiblingNonLeaf) { + blockMap = DraftTreeOperations.mergeBlocks(blockMap, prevSiblingKey); + } + // else, if only next sibling is non-leaf move node as child of next sibling + } else if (nextSiblingNonLeaf) { blockMap = DraftTreeOperations.updateAsSiblingsChild( blockMap, key, diff --git a/src/model/modifier/exploration/__tests__/NestedRichTextEditorUtil-test.js b/src/model/modifier/exploration/__tests__/NestedRichTextEditorUtil-test.js index 1b380e138b..94bfcd7de7 100644 --- a/src/model/modifier/exploration/__tests__/NestedRichTextEditorUtil-test.js +++ b/src/model/modifier/exploration/__tests__/NestedRichTextEditorUtil-test.js @@ -466,6 +466,83 @@ test('onTab when siblings are at the same depth creates a new parent', () => { ); }); +const contentBlockNodes3 = [ + new ContentBlockNode({ + key: 'A', + parent: null, + text: 'alpha', + children: Immutable.List([]), + prevSibling: null, + nextSibling: 'X', + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'X', + parent: null, + text: '', + children: Immutable.List(['B']), + prevSibling: 'A', + nextSibling: 'C', + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'B', + parent: 'X', + text: 'beta', + children: Immutable.List([]), + prevSibling: null, + nextSibling: null, + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'C', + parent: null, + text: 'charlie', + children: Immutable.List([]), + prevSibling: 'X', + nextSibling: 'Y', + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'Y', + parent: null, + text: '', + children: Immutable.List(['D', 'E']), + prevSibling: 'C', + nextSibling: null, + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'D', + parent: 'Y', + text: 'delta', + children: Immutable.List([]), + prevSibling: null, + nextSibling: 'E', + type: 'ordered-list-item', + }), + new ContentBlockNode({ + key: 'E', + parent: 'Y', + text: 'epsilon', + children: Immutable.List([]), + prevSibling: 'D', + nextSibling: null, + type: 'ordered-list-item', + }), +]; + +test('onTab when both siblings are non-leaf merges blocks', () => { + assertNestedUtilOperation( + editorState => onTab({preventDefault: () => {}}, editorState, 1), + { + anchorKey: 'C', + focusKey: 'C', + }, + contentBlockNodes3, + ); +}); + test('onTab (untab) on a block with no parent does nothing', () => { assertNestedUtilOperation( editorState => @@ -502,7 +579,7 @@ test('onTab (untab) on a last child moves block as next sibling of parent', () = ); }); -const contentBlockNodes3 = [ +const contentBlockNodes4 = [ new ContentBlockNode({ key: 'A', nextSibling: 'X', @@ -581,11 +658,11 @@ test('onTab (untab) on a middle child splits the block at that child', () => { anchorKey: 'E', focusKey: 'E', }, - contentBlockNodes3, + contentBlockNodes4, ); }); -const contentBlockNodes4 = [ +const contentBlockNodes5 = [ new ContentBlockNode({ key: 'A', parent: null, @@ -641,11 +718,11 @@ test('onTab (untab) unnests non-leaf next sibling', () => { anchorKey: 'B', focusKey: 'B', }, - contentBlockNodes4, + contentBlockNodes5, ); }); -const contentBlockNodes5 = [ +const contentBlockNodes6 = [ new ContentBlockNode({ key: 'A', parent: null, @@ -719,7 +796,7 @@ test('onTab (untab) merges adjacent non-leaf blocks', () => { anchorKey: 'B', focusKey: 'B', }, - contentBlockNodes5, + contentBlockNodes6, ); }); diff --git a/src/model/modifier/exploration/__tests__/__snapshots__/NestedRichTextEditorUtil-test.js.snap b/src/model/modifier/exploration/__tests__/__snapshots__/NestedRichTextEditorUtil-test.js.snap index 3eb2329862..e09554f9af 100644 --- a/src/model/modifier/exploration/__tests__/__snapshots__/NestedRichTextEditorUtil-test.js.snap +++ b/src/model/modifier/exploration/__tests__/__snapshots__/NestedRichTextEditorUtil-test.js.snap @@ -2223,6 +2223,205 @@ Object { } `; +exports[`onTab when both siblings are non-leaf merges blocks 1`] = ` +Object { + "A": Object { + "characterList": Array [ + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + ], + "children": Array [], + "data": Object {}, + "depth": 0, + "key": "A", + "nextSibling": "X", + "parent": null, + "prevSibling": null, + "text": "alpha", + "type": "ordered-list-item", + }, + "B": Object { + "characterList": Array [ + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + ], + "children": Array [], + "data": Object {}, + "depth": 0, + "key": "B", + "nextSibling": "C", + "parent": "X", + "prevSibling": null, + "text": "beta", + "type": "ordered-list-item", + }, + "C": Object { + "characterList": Array [ + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + ], + "children": Array [], + "data": Object {}, + "depth": 1, + "key": "C", + "nextSibling": "D", + "parent": "X", + "prevSibling": "B", + "text": "charlie", + "type": "ordered-list-item", + }, + "D": Object { + "characterList": Array [ + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + ], + "children": Array [], + "data": Object {}, + "depth": 0, + "key": "D", + "nextSibling": "E", + "parent": "X", + "prevSibling": "C", + "text": "delta", + "type": "ordered-list-item", + }, + "E": Object { + "characterList": Array [ + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + Object { + "entity": null, + "style": Array [], + }, + ], + "children": Array [], + "data": Object {}, + "depth": 0, + "key": "E", + "nextSibling": null, + "parent": "X", + "prevSibling": "D", + "text": "epsilon", + "type": "ordered-list-item", + }, + "X": Object { + "characterList": Array [], + "children": Array [ + "B", + "C", + "D", + "E", + ], + "data": Object {}, + "depth": 0, + "key": "X", + "nextSibling": null, + "parent": null, + "prevSibling": "A", + "text": "", + "type": "ordered-list-item", + }, +} +`; + exports[`onTab when siblings are at the same depth creates a new parent 1`] = ` Object { "A": Object {