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

Commit 0688fa3

Browse files
niveditcfacebook-github-bot
authored andcommitted
Perform untab on backspace for nested items
Summary: For nested lists, when we backspace at the start of a nested item we want to perform the same action as if we hit `Shift + Tab`. * Abstract out the untab logic to use in both `onTab` and `onBackspace`. * No regression in untab (`Shift + Tab`) behavior. * Backspace on a nested block untabs it (diagrams below) {F139898511} Reviewed By: claudiopro Differential Revision: D10118538 fbshipit-source-id: d6b95bd6ca85f6b9443d54de38e1e15bbfc81eba
1 parent 7daa87e commit 0688fa3

File tree

3 files changed

+502
-110
lines changed

3 files changed

+502
-110
lines changed

src/model/modifier/exploration/NestedRichTextEditorUtil.js

Lines changed: 126 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* This is unstable and not part of the public API and should not be used by
1414
* production systems. This file may be update/removed without notice.
1515
*/
16+
import type {BlockMap} from 'BlockMap';
1617
import type ContentState from 'ContentState';
1718
import type {DraftBlockType} from 'DraftBlockType';
1819
import type {DraftEditorCommand} from 'DraftEditorCommand';
@@ -172,7 +173,12 @@ const NestedRichTextEditorUtil: RichTextUtils = {
172173
return EditorState.push(
173174
editorState,
174175
withoutBlockStyle,
175-
'change-block-type',
176+
withoutBlockStyle
177+
.getBlockMap()
178+
.get(currentBlock.getKey())
179+
.getType() === 'unstyled'
180+
? 'change-block-type'
181+
: 'adjust-depth',
176182
);
177183
}
178184

@@ -350,117 +356,10 @@ const NestedRichTextEditorUtil: RichTextUtils = {
350356
// on un-tab
351357
} else {
352358
// if the block isn't nested, do nothing
353-
const parentKey = block.getParentKey();
354-
if (parentKey == null) {
359+
if (block.getParentKey() == null) {
355360
return editorState;
356361
}
357-
const parent = blockMap.get(parentKey);
358-
const existingChildren = parent.getChildKeys();
359-
const blockIndex = existingChildren.indexOf(key);
360-
if (blockIndex === 0 || blockIndex === existingChildren.count() - 1) {
361-
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
362-
} else {
363-
// split the block into [0, blockIndex] in parent & the rest in a new block
364-
const prevChildren = existingChildren.slice(0, blockIndex + 1);
365-
const nextChildren = existingChildren.slice(blockIndex + 1);
366-
blockMap = blockMap.set(
367-
parentKey,
368-
parent.merge({children: prevChildren}),
369-
);
370-
const newBlock = new ContentBlockNode({
371-
key: generateRandomKey(),
372-
text: '',
373-
depth: parent.getDepth(),
374-
type: parent.getType(),
375-
children: nextChildren,
376-
parent: parent.getParentKey(),
377-
});
378-
// add new block just before its the original next sibling in the block map
379-
// TODO(T33894878): Remove the map reordering code & fix converter after launch
380-
invariant(
381-
nextSiblingKey != null,
382-
'block must have a next sibling here',
383-
);
384-
const blocks = blockMap.toSeq();
385-
blockMap = blocks
386-
.takeUntil(block => block.getKey() === nextSiblingKey)
387-
.concat(
388-
[[newBlock.getKey(), newBlock]],
389-
blocks.skipUntil(block => block.getKey() === nextSiblingKey),
390-
)
391-
.toOrderedMap();
392-
393-
// set the nextChildren's parent to the new block
394-
blockMap = blockMap.map(
395-
block =>
396-
nextChildren.includes(block.getKey())
397-
? block.merge({parent: newBlock.getKey()})
398-
: block,
399-
);
400-
// update the next/previous pointers for the children at the split
401-
blockMap = blockMap
402-
.set(key, block.merge({nextSibling: null}))
403-
.set(
404-
nextSiblingKey,
405-
blockMap.get(nextSiblingKey).merge({prevSibling: null}),
406-
);
407-
const parentNextSiblingKey = parent.getNextSiblingKey();
408-
if (parentNextSiblingKey != null) {
409-
blockMap = DraftTreeOperations.updateSibling(
410-
blockMap,
411-
newBlock.getKey(),
412-
parentNextSiblingKey,
413-
);
414-
}
415-
blockMap = DraftTreeOperations.updateSibling(
416-
blockMap,
417-
parentKey,
418-
newBlock.getKey(),
419-
);
420-
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
421-
}
422-
423-
// on untab, we also want to unnest any sibling blocks that become two levels deep
424-
// ensure that block's old parent does not have a non-leaf as its first child.
425-
let childWasUntabbed = false;
426-
if (parentKey != null) {
427-
let parent = blockMap.get(parentKey);
428-
while (parent != null) {
429-
const children = parent.getChildKeys();
430-
const firstChildKey = children.first();
431-
invariant(
432-
firstChildKey != null,
433-
'parent must have at least one child',
434-
);
435-
const firstChild = blockMap.get(firstChildKey);
436-
if (firstChild.getChildKeys().count() === 0) {
437-
break;
438-
} else {
439-
blockMap = DraftTreeOperations.moveChildUp(blockMap, firstChildKey);
440-
parent = blockMap.get(parentKey);
441-
childWasUntabbed = true;
442-
}
443-
}
444-
}
445-
446-
// now, we may be in a state with two non-leaf blocks of the same type
447-
// next to each other
448-
if (childWasUntabbed && parentKey != null) {
449-
const parent = blockMap.get(parentKey);
450-
const prevSiblingKey =
451-
parent != null // parent may have been deleted
452-
? parent.getPrevSiblingKey()
453-
: null;
454-
if (prevSiblingKey != null && parent.getChildKeys().count() > 0) {
455-
const prevSibling = blockMap.get(prevSiblingKey);
456-
if (prevSibling != null && prevSibling.getChildKeys().count() > 0) {
457-
blockMap = DraftTreeOperations.mergeBlocks(
458-
blockMap,
459-
prevSiblingKey,
460-
);
461-
}
462-
}
463-
}
362+
blockMap = onUntab(blockMap, block);
464363
}
465364
content = editorState.getCurrentContent().merge({
466365
blockMap: blockMap,
@@ -574,12 +473,129 @@ const NestedRichTextEditorUtil: RichTextUtils = {
574473
return null;
575474
}
576475

476+
const depth = block.getDepth();
577477
if (type !== 'unstyled') {
478+
if (
479+
(type === 'unordered-list-item' || type === 'ordered-list-item') &&
480+
depth > 0
481+
) {
482+
let newBlockMap = onUntab(content.getBlockMap(), block);
483+
newBlockMap = newBlockMap.set(
484+
key,
485+
newBlockMap.get(key).merge({depth: depth - 1}),
486+
);
487+
return content.merge({blockMap: newBlockMap});
488+
}
578489
return DraftModifier.setBlockType(content, selection, 'unstyled');
579490
}
580491
}
581492
return null;
582493
},
583494
};
584495

496+
const onUntab = (blockMap: BlockMap, block: ContentBlockNode): BlockMap => {
497+
const key = block.getKey();
498+
const parentKey = block.getParentKey();
499+
const nextSiblingKey = block.getNextSiblingKey();
500+
if (parentKey == null) {
501+
return blockMap;
502+
}
503+
const parent = blockMap.get(parentKey);
504+
const existingChildren = parent.getChildKeys();
505+
const blockIndex = existingChildren.indexOf(key);
506+
if (blockIndex === 0 || blockIndex === existingChildren.count() - 1) {
507+
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
508+
} else {
509+
// split the block into [0, blockIndex] in parent & the rest in a new block
510+
const prevChildren = existingChildren.slice(0, blockIndex + 1);
511+
const nextChildren = existingChildren.slice(blockIndex + 1);
512+
blockMap = blockMap.set(parentKey, parent.merge({children: prevChildren}));
513+
const newBlock = new ContentBlockNode({
514+
key: generateRandomKey(),
515+
text: '',
516+
depth: parent.getDepth(),
517+
type: parent.getType(),
518+
children: nextChildren,
519+
parent: parent.getParentKey(),
520+
});
521+
// add new block just before its the original next sibling in the block map
522+
// TODO(T33894878): Remove the map reordering code & fix converter after launch
523+
invariant(nextSiblingKey != null, 'block must have a next sibling here');
524+
const blocks = blockMap.toSeq();
525+
blockMap = blocks
526+
.takeUntil(block => block.getKey() === nextSiblingKey)
527+
.concat(
528+
[[newBlock.getKey(), newBlock]],
529+
blocks.skipUntil(block => block.getKey() === nextSiblingKey),
530+
)
531+
.toOrderedMap();
532+
533+
// set the nextChildren's parent to the new block
534+
blockMap = blockMap.map(
535+
block =>
536+
nextChildren.includes(block.getKey())
537+
? block.merge({parent: newBlock.getKey()})
538+
: block,
539+
);
540+
// update the next/previous pointers for the children at the split
541+
blockMap = blockMap
542+
.set(key, block.merge({nextSibling: null}))
543+
.set(
544+
nextSiblingKey,
545+
blockMap.get(nextSiblingKey).merge({prevSibling: null}),
546+
);
547+
const parentNextSiblingKey = parent.getNextSiblingKey();
548+
if (parentNextSiblingKey != null) {
549+
blockMap = DraftTreeOperations.updateSibling(
550+
blockMap,
551+
newBlock.getKey(),
552+
parentNextSiblingKey,
553+
);
554+
}
555+
blockMap = DraftTreeOperations.updateSibling(
556+
blockMap,
557+
parentKey,
558+
newBlock.getKey(),
559+
);
560+
blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
561+
}
562+
563+
// on untab, we also want to unnest any sibling blocks that become two levels deep
564+
// ensure that block's old parent does not have a non-leaf as its first child.
565+
let childWasUntabbed = false;
566+
if (parentKey != null) {
567+
let parent = blockMap.get(parentKey);
568+
while (parent != null) {
569+
const children = parent.getChildKeys();
570+
const firstChildKey = children.first();
571+
invariant(firstChildKey != null, 'parent must have at least one child');
572+
const firstChild = blockMap.get(firstChildKey);
573+
if (firstChild.getChildKeys().count() === 0) {
574+
break;
575+
} else {
576+
blockMap = DraftTreeOperations.moveChildUp(blockMap, firstChildKey);
577+
parent = blockMap.get(parentKey);
578+
childWasUntabbed = true;
579+
}
580+
}
581+
}
582+
583+
// now, we may be in a state with two non-leaf blocks of the same type
584+
// next to each other
585+
if (childWasUntabbed && parentKey != null) {
586+
const parent = blockMap.get(parentKey);
587+
const prevSiblingKey =
588+
parent != null // parent may have been deleted
589+
? parent.getPrevSiblingKey()
590+
: null;
591+
if (prevSiblingKey != null && parent.getChildKeys().count() > 0) {
592+
const prevSibling = blockMap.get(prevSiblingKey);
593+
if (prevSibling != null && prevSibling.getChildKeys().count() > 0) {
594+
blockMap = DraftTreeOperations.mergeBlocks(blockMap, prevSiblingKey);
595+
}
596+
}
597+
}
598+
return blockMap;
599+
};
600+
585601
module.exports = NestedRichTextEditorUtil;

0 commit comments

Comments
 (0)