blockMapWithoutBlocksToBeMoved = blockMap.withMutations(blocks => { const nextSiblingKey = blockToBeMoved.getNextSiblingKey(); const nextDelimiterBlockKey = getNextDelimiterBlockKey( blockToBeMoved, blocks, ); blocks .toSeq() .skipUntil(block => block.getKey() === blockKey) .takeWhile(block => { const key = block.getKey(); const isBlockToBeMoved = key === blockKey; const hasNextSiblingAndIsNotNextSibling = nextSiblingKey && key !== nextSiblingKey; const doesNotHaveNextSiblingAndIsNotDelimiter = !nextSiblingKey && block.getParentKey() && (!nextDelimiterBlockKey || key !== nextDelimiterBlockKey); return !!( isBlockToBeMoved || hasNextSiblingAndIsNotNextSibling || doesNotHaveNextSiblingAndIsNotDelimiter ); }) .forEach(block => { blocksToBeMoved.push(block); blocks.delete(block.getKey()); }); });
const getNextDelimitersBlockKeys = ( block: ContentBlockNode, blockMap: BlockMap, ): Array<string> => { const nextDelimiters = []; if (!block) { return nextDelimiters; } let nextDelimiter = getNextDelimiterBlockKey(block, blockMap); while (nextDelimiter && blockMap.get(nextDelimiter)) { const block = blockMap.get(nextDelimiter); nextDelimiters.push(nextDelimiter); // we do not need to keep checking all root node siblings, just the first occurance nextDelimiter = block.getParentKey() ? getNextDelimiterBlockKey(block, blockMap) : null; } return nextDelimiters; };
const removeRangeFromContentState = ( contentState: ContentState, selectionState: SelectionState, ): ContentState => { if (selectionState.isCollapsed()) { return contentState; } const blockMap = contentState.getBlockMap(); const startKey = selectionState.getStartKey(); const startOffset = selectionState.getStartOffset(); const endKey = selectionState.getEndKey(); const endOffset = selectionState.getEndOffset(); const startBlock = blockMap.get(startKey); const endBlock = blockMap.get(endKey); // we assume that ContentBlockNode and ContentBlocks are not mixed together const isExperimentalTreeBlock = startBlock instanceof ContentBlockNode; // used to retain blocks that should not be deleted to avoid orphan children let parentAncestors = []; if (isExperimentalTreeBlock) { const endBlockchildrenKeys = endBlock.getChildKeys(); const endBlockAncestors = getAncestorsKeys(endKey, blockMap); // endBlock has unselected siblings so we can not remove its ancestors parents if (endBlock.getNextSiblingKey()) { parentAncestors = parentAncestors.concat(endBlockAncestors); } // endBlock has children so can not remove this block or any of its ancestors if (!endBlockchildrenKeys.isEmpty()) { parentAncestors = parentAncestors.concat( endBlockAncestors.concat([endKey]), ); } // we need to retain all ancestors of the next delimiter block parentAncestors = parentAncestors.concat( getAncestorsKeys(getNextDelimiterBlockKey(endBlock, blockMap), blockMap), ); } let characterList; if (startBlock === endBlock) { characterList = removeFromList( startBlock.getCharacterList(), startOffset, endOffset, ); } else { characterList = startBlock .getCharacterList() .slice(0, startOffset) .concat(endBlock.getCharacterList().slice(endOffset)); } const modifiedStart = startBlock.merge({ text: startBlock.getText().slice(0, startOffset) + endBlock.getText().slice(endOffset), characterList, }); // If cursor (collapsed) is at the start of the first child, delete parent // instead of child const shouldDeleteParent = isExperimentalTreeBlock && startOffset === 0 && endOffset === 0 && endBlock.getParentKey() === startKey && endBlock.getPrevSiblingKey() == null; const newBlocks = shouldDeleteParent ? Map([[startKey, null]]) : blockMap .toSeq() .skipUntil((_, k) => k === startKey) .takeUntil((_, k) => k === endKey) .filter((_, k) => parentAncestors.indexOf(k) === -1) .concat(Map([[endKey, null]])) .map((_, k) => { return k === startKey ? modifiedStart : null; }); let updatedBlockMap = blockMap.merge(newBlocks).filter(block => !!block); // Only update tree block pointers if the range is across blocks if (isExperimentalTreeBlock && startBlock !== endBlock) { updatedBlockMap = updateBlockMapLinks( updatedBlockMap, startBlock, endBlock, blockMap, ); } return contentState.merge({ blockMap: updatedBlockMap, selectionBefore: selectionState, selectionAfter: selectionState.merge({ anchorKey: startKey, anchorOffset: startOffset, focusKey: startKey, focusOffset: startOffset, isBackward: false, }), }); };