Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Commit

Permalink
Implement untab operations
Browse files Browse the repository at this point in the history
Summary:
This diff implements the `unTab` operation for nested lists.

It covers the following four cases (refer to the diagrams below for a better explanation):
1. Block is not nested => do nothing
2. Block is first child => move as previous sibling of parent
3. Block is last child => move as next sibling of parent
4. Block is neither the first nor the last child => split the block at the child, keep previous children on original parent, move block as next sibling of parent & add the new block with the next children as its next sibling.

1 - 3 =>
{F138170258}

4 =>
{F138170261}

**TODO:**
Still figuring out the correct paradigm for dealing with the block's children when the first nested block is un-nested & will add behaviors + test cases for that in the next diff.

Reviewed By: vdurmont

Differential Revision: D9757713

fbshipit-source-id: 01796b003acbfcfcc6fa8d5f6f4576d4f2a13ec6
  • Loading branch information
niveditc authored and facebook-github-bot committed Sep 14, 2018
1 parent 8bb9c6c commit 77e6844
Show file tree
Hide file tree
Showing 3 changed files with 1,588 additions and 6 deletions.
87 changes: 81 additions & 6 deletions src/model/modifier/exploration/NestedRichTextEditorUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ import type {DataObjectForLink, RichTextUtils} from 'RichTextUtils';
import type SelectionState from 'SelectionState';
import type URI from 'URI';

const ContentBlockNode = require('ContentBlockNode');
const DraftModifier = require('DraftModifier');
const DraftTreeOperations = require('DraftTreeOperations');
const EditorState = require('EditorState');
const RichTextEditorUtil = require('RichTextEditorUtil');

const adjustBlockDepthForContentState = require('adjustBlockDepthForContentState');
const generateRandomKey = require('generateRandomKey');
const invariant = require('invariant');

// Eventually we could allow to control this list by either allowing user configuration
// and/or a schema in conjunction to DraftBlockRenderMap
Expand Down Expand Up @@ -307,10 +310,10 @@ const NestedRichTextEditorUtil: RichTextUtils = {
}

// implement nested tree behaviour for onTab
let blockMap = editorState.getCurrentContent().getBlockMap();
const prevSiblingKey = block.getPrevSiblingKey();
const nextSiblingKey = block.getNextSiblingKey();
if (!event.shiftKey) {
let blockMap = editorState.getCurrentContent().getBlockMap();
const prevSiblingKey = block.getPrevSiblingKey();
const nextSiblingKey = block.getNextSiblingKey();
// if there is no previous sibling, we do nothing
if (prevSiblingKey == null) {
return editorState;
Expand Down Expand Up @@ -344,10 +347,82 @@ const NestedRichTextEditorUtil: RichTextUtils = {
} else {
blockMap = DraftTreeOperations.createNewParent(blockMap, key);
}
content = editorState.getCurrentContent().merge({
blockMap: blockMap,
});
// on un-tab
} else {
// if the block isn't nested, do nothing
const parentKey = block.getParentKey();
if (parentKey == null) {
return editorState;
}
const parent = blockMap.get(parentKey);
const existingChildren = parent.getChildKeys();
const blockIndex = existingChildren.indexOf(key);
if (blockIndex === 0 || blockIndex === existingChildren.count() - 1) {
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
} else {
// split the block into [0, blockIndex] in parent & the rest in a new block
const prevChildren = existingChildren.slice(0, blockIndex + 1);
const nextChildren = existingChildren.slice(blockIndex + 1);
blockMap = blockMap.set(
parentKey,
parent.merge({children: prevChildren}),
);
const newBlock = new ContentBlockNode({
key: generateRandomKey(),
text: '',
depth: parent.getDepth(),
type: parent.getType(),
children: nextChildren,
parent: parent.getParentKey(),
});
// add new block just before its the original next sibling in the block map
// TODO(T33894878): Remove the map reordering code & fix converter after launch
invariant(
nextSiblingKey != null,
'block must have a next sibling here',
);
const blocks = blockMap.toSeq();
blockMap = blocks
.takeUntil(block => block.getKey() === nextSiblingKey)
.concat(
[[newBlock.getKey(), newBlock]],
blocks.skipUntil(block => block.getKey() === nextSiblingKey),
)
.toOrderedMap();

// set the nextChildren's parent to the new block
blockMap = blockMap.map(
block =>
nextChildren.includes(block.getKey())
? block.merge({parent: newBlock.getKey()})
: block,
);
// update the next/previous pointers for the children at the split
blockMap = blockMap
.set(key, block.merge({nextSibling: null}))
.set(
nextSiblingKey,
blockMap.get(nextSiblingKey).merge({prevSibling: null}),
);
const parentNextSiblingKey = parent.getNextSiblingKey();
if (parentNextSiblingKey != null) {
blockMap = DraftTreeOperations.updateSibling(
blockMap,
newBlock.getKey(),
parentNextSiblingKey,
);
}
blockMap = DraftTreeOperations.updateSibling(
blockMap,
parentKey,
newBlock.getKey(),
);
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
}
}
content = editorState.getCurrentContent().merge({
blockMap: blockMap,
});

const withAdjustment = adjustBlockDepthForContentState(
content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,125 @@ test('onTab when siblings are at the same depth creates a new parent', () => {
);
});

test('onTab (untab) on a block with no parent does nothing', () => {
assertNestedUtilOperation(
editorState =>
onTab({preventDefault: () => {}, shiftKey: true}, editorState, 1),
{
anchorKey: 'B',
focusKey: 'B',
},
contentBlockNodes2,
);
});

test('onTab (untab) on a first child moves block as previous sibling of parent', () => {
assertNestedUtilOperation(
editorState =>
onTab({preventDefault: () => {}, shiftKey: true}, editorState, 2),
{
anchorKey: 'D',
focusKey: 'D',
},
contentBlockNodes2,
);
});

test('onTab (untab) on a last child moves block as next sibling of parent', () => {
assertNestedUtilOperation(
editorState =>
onTab({preventDefault: () => {}, shiftKey: true}, editorState, 2),
{
anchorKey: 'H',
focusKey: 'H',
},
contentBlockNodes2,
);
});

const contentBlockNodes3 = [
new ContentBlockNode({
key: 'A',
nextSibling: 'X',
text: 'alpha',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'X',
prevSibling: 'A',
nextSibling: 'G',
text: '',
type: 'ordered-list-item',
children: List(['B', 'C', 'D', 'E', 'F']),
}),
new ContentBlockNode({
key: 'B',
parent: 'X',
prevSibling: null,
nextSibling: 'C',
text: 'beta',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'C',
parent: 'X',
prevSibling: 'B',
nextSibling: 'D',
text: 'charlie',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'D',
parent: 'X',
prevSibling: 'C',
nextSibling: 'E',
text: 'delta',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'E',
parent: 'X',
prevSibling: 'D',
nextSibling: 'F',
text: 'epsilon',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'F',
parent: 'X',
prevSibling: 'E',
nextSibling: null,
text: 'foo',
type: 'ordered-list-item',
children: List([]),
}),
new ContentBlockNode({
key: 'G',
prevSibling: 'X',
nextSibling: null,
text: 'gamma',
type: 'ordered-list-item',
children: List([]),
}),
];

test('onTab (untab) on a middle child splits the block at that child', () => {
assertNestedUtilOperation(
editorState =>
onTab({preventDefault: () => {}, shiftKey: true}, editorState, 2),
{
anchorKey: 'E',
focusKey: 'E',
},
contentBlockNodes3,
);
});

// TODO (T32099101)
test('onSplitParent must split a nested block retaining parent', () => {
expect(true).toBe(true);
Expand Down
Loading

0 comments on commit 77e6844

Please sign in to comment.