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

Commit 8d3cfba

Browse files
niveditcfacebook-github-bot
authored andcommitted
Implement onTab in NestedRichTextEditorUtil
Summary: Implement `onTab` for `NestedRichTextEditorUtil` * Cases 1-5 in the image below * Decided to postpone case 6 since its not essential for the implementation * Note that case 1 & 3 are logically equivalent, and the `createNewParent` method implemented in D9624285 handles both. {F137497472} Reviewed By: mitermayer Differential Revision: D9631917 fbshipit-source-id: 9f4298488d8c2a8f172cd82423c30c16d773a8ab
1 parent 6f73657 commit 8d3cfba

File tree

3 files changed

+1687
-2
lines changed

3 files changed

+1687
-2
lines changed

src/model/modifier/exploration/NestedRichTextEditorUtil.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type SelectionState from 'SelectionState';
2121
import type URI from 'URI';
2222

2323
const DraftModifier = require('DraftModifier');
24+
const DraftTreeOperations = require('DraftTreeOperations');
2425
const EditorState = require('EditorState');
2526
const RichTextEditorUtil = require('RichTextEditorUtil');
2627

@@ -291,7 +292,7 @@ const NestedRichTextEditorUtil: RichTextUtils = {
291292
return editorState;
292293
}
293294

294-
const content = editorState.getCurrentContent();
295+
let content = editorState.getCurrentContent();
295296
const block = content.getBlockForKey(key);
296297
const type = block.getType();
297298
if (type !== 'unordered-list-item' && type !== 'ordered-list-item') {
@@ -305,6 +306,49 @@ const NestedRichTextEditorUtil: RichTextUtils = {
305306
return editorState;
306307
}
307308

309+
// implement nested tree behaviour for onTab
310+
if (!event.shiftKey) {
311+
let blockMap = editorState.getCurrentContent().getBlockMap();
312+
const prevSiblingKey = block.getPrevSiblingKey();
313+
const nextSiblingKey = block.getNextSiblingKey();
314+
// if there is no previous sibling, we do nothing
315+
if (prevSiblingKey == null) {
316+
return editorState;
317+
}
318+
// if previous sibling is a non-leaf move node as child of previous sibling
319+
const prevSibling = blockMap.get(prevSiblingKey);
320+
const nextSibling =
321+
nextSiblingKey != null ? blockMap.get(nextSiblingKey) : null;
322+
if (
323+
prevSibling != null &&
324+
prevSibling.getText() === '' &&
325+
prevSibling.getChildKeys().count() > 0
326+
) {
327+
blockMap = DraftTreeOperations.updateAsSiblingsChild(
328+
blockMap,
329+
key,
330+
'previous',
331+
);
332+
// else, if next sibling is a non-leaf move node as child of next sibling
333+
} else if (
334+
nextSibling != null &&
335+
nextSibling.getText() === '' &&
336+
nextSibling.getChildKeys().count() > 0
337+
) {
338+
blockMap = DraftTreeOperations.updateAsSiblingsChild(
339+
blockMap,
340+
key,
341+
'next',
342+
);
343+
// if none of the siblings are non-leaf, we need to create a new parent
344+
} else {
345+
blockMap = DraftTreeOperations.createNewParent(blockMap, key);
346+
}
347+
content = editorState.getCurrentContent().merge({
348+
blockMap: blockMap,
349+
});
350+
}
351+
308352
const withAdjustment = adjustBlockDepthForContentState(
309353
content,
310354
selection,

src/model/modifier/exploration/__tests__/NestedRichTextEditorUtil-test.js

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const Immutable = require('immutable');
2727
const {List} = Immutable;
2828

2929
const {editorState, contentState, selectionState} = getSampleStateForTesting();
30-
const {onBackspace, onDelete} = NestedRichTextEditorUtil;
30+
const {onBackspace, onDelete, onTab} = NestedRichTextEditorUtil;
3131

3232
const contentBlockNodes = [
3333
new ContentBlockNode({
@@ -331,6 +331,141 @@ test('onDelete is a no-op the end of a leaf', () => {
331331
});
332332
});
333333

334+
/**
335+
* Tests for onTab
336+
*/
337+
const contentBlockNodes2 = [
338+
new ContentBlockNode({
339+
key: 'A',
340+
nextSibling: 'B',
341+
text: 'Item 1',
342+
type: 'ordered-list-item',
343+
children: List([]),
344+
}),
345+
new ContentBlockNode({
346+
key: 'B',
347+
prevSibling: 'A',
348+
nextSibling: 'C',
349+
text: 'Item 2',
350+
type: 'ordered-list-item',
351+
children: List([]),
352+
}),
353+
new ContentBlockNode({
354+
key: 'C',
355+
prevSibling: 'B',
356+
nextSibling: 'J',
357+
text: '',
358+
type: 'ordered-list-item',
359+
children: List(['D', 'E', 'F', 'I']),
360+
}),
361+
new ContentBlockNode({
362+
key: 'D',
363+
parent: 'C',
364+
prevSibling: null,
365+
nextSibling: 'E',
366+
text: 'Item 2a',
367+
type: 'ordered-list-item',
368+
children: List([]),
369+
}),
370+
new ContentBlockNode({
371+
key: 'E',
372+
parent: 'C',
373+
prevSibling: 'D',
374+
nextSibling: 'F',
375+
text: 'Item 2b',
376+
type: 'ordered-list-item',
377+
children: List([]),
378+
}),
379+
new ContentBlockNode({
380+
key: 'F',
381+
parent: 'C',
382+
prevSibling: 'E',
383+
nextSibling: 'I',
384+
text: '',
385+
type: 'ordered-list-item',
386+
children: List(['G', 'H']),
387+
}),
388+
new ContentBlockNode({
389+
key: 'G',
390+
parent: 'F',
391+
prevSibling: null,
392+
nextSibling: 'H',
393+
text: 'Item 2b i',
394+
type: 'ordered-list-item',
395+
children: List([]),
396+
}),
397+
new ContentBlockNode({
398+
key: 'H',
399+
parent: 'F',
400+
prevSibling: 'G',
401+
nextSibling: null,
402+
text: 'Item 2b ii',
403+
type: 'ordered-list-item',
404+
children: List([]),
405+
}),
406+
new ContentBlockNode({
407+
key: 'I',
408+
parent: 'C',
409+
prevSibling: 'F',
410+
nextSibling: null,
411+
text: 'Item 2c',
412+
type: 'ordered-list-item',
413+
children: List([]),
414+
}),
415+
new ContentBlockNode({
416+
key: 'J',
417+
prevSibling: 'C',
418+
nextSibling: null,
419+
text: 'Item 3',
420+
type: 'ordered-list-item',
421+
children: List([]),
422+
}),
423+
];
424+
425+
test('onTab with leaf as previous block & non-leaf as next block merges to the next block', () => {
426+
assertNestedUtilOperation(
427+
editorState => onTab({preventDefault: () => {}}, editorState, 2),
428+
{
429+
anchorKey: 'E',
430+
focusKey: 'E',
431+
},
432+
contentBlockNodes2,
433+
);
434+
});
435+
436+
test('onTab with non-leaf as previous block merges to the previous block', () => {
437+
assertNestedUtilOperation(
438+
editorState => onTab({preventDefault: () => {}}, editorState, 2),
439+
{
440+
anchorKey: 'I',
441+
focusKey: 'I',
442+
},
443+
contentBlockNodes2,
444+
);
445+
});
446+
447+
test('onTab with no previous block does nothing', () => {
448+
assertNestedUtilOperation(
449+
editorState => onTab({preventDefault: () => {}}, editorState, 1),
450+
{
451+
anchorKey: 'A',
452+
focusKey: 'A',
453+
},
454+
contentBlockNodes2,
455+
);
456+
});
457+
458+
test('onTab when siblings are at the same depth creates a new parent', () => {
459+
assertNestedUtilOperation(
460+
editorState => onTab({preventDefault: () => {}}, editorState, 1),
461+
{
462+
anchorKey: 'H',
463+
focusKey: 'H',
464+
},
465+
contentBlockNodes2,
466+
);
467+
});
468+
334469
// TODO (T32099101)
335470
test('onSplitParent must split a nested block retaining parent', () => {
336471
expect(true).toBe(true);

0 commit comments

Comments
 (0)