306 lines
9.7 KiB
JavaScript
306 lines
9.7 KiB
JavaScript
/**
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule removeRangeFromContentState
|
|
* @format
|
|
*
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var ContentBlockNode = require('./ContentBlockNode');
|
|
var Immutable = require('immutable');
|
|
|
|
var getNextDelimiterBlockKey = require('./getNextDelimiterBlockKey');
|
|
|
|
var List = Immutable.List,
|
|
Map = Immutable.Map;
|
|
|
|
|
|
var transformBlock = function transformBlock(key, blockMap, func) {
|
|
if (!key) {
|
|
return;
|
|
}
|
|
|
|
var block = blockMap.get(key);
|
|
|
|
if (!block) {
|
|
return;
|
|
}
|
|
|
|
blockMap.set(key, func(block));
|
|
};
|
|
|
|
/**
|
|
* Ancestors needs to be preserved when there are non selected
|
|
* children to make sure we do not leave any orphans behind
|
|
*/
|
|
var getAncestorsKeys = function getAncestorsKeys(blockKey, blockMap) {
|
|
var parents = [];
|
|
|
|
if (!blockKey) {
|
|
return parents;
|
|
}
|
|
|
|
var blockNode = blockMap.get(blockKey);
|
|
while (blockNode && blockNode.getParentKey()) {
|
|
var parentKey = blockNode.getParentKey();
|
|
if (parentKey) {
|
|
parents.push(parentKey);
|
|
}
|
|
blockNode = parentKey ? blockMap.get(parentKey) : null;
|
|
}
|
|
|
|
return parents;
|
|
};
|
|
|
|
/**
|
|
* Get all next delimiter keys until we hit a root delimiter and return
|
|
* an array of key references
|
|
*/
|
|
var getNextDelimitersBlockKeys = function getNextDelimitersBlockKeys(block, blockMap) {
|
|
var nextDelimiters = [];
|
|
|
|
if (!block) {
|
|
return nextDelimiters;
|
|
}
|
|
|
|
var nextDelimiter = getNextDelimiterBlockKey(block, blockMap);
|
|
while (nextDelimiter && blockMap.get(nextDelimiter)) {
|
|
var _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;
|
|
};
|
|
|
|
var getNextValidSibling = function getNextValidSibling(block, blockMap, originalBlockMap) {
|
|
if (!block) {
|
|
return null;
|
|
}
|
|
|
|
// note that we need to make sure we refer to the original block since this
|
|
// function is called within a withMutations
|
|
var nextValidSiblingKey = originalBlockMap.get(block.getKey()).getNextSiblingKey();
|
|
|
|
while (nextValidSiblingKey && !blockMap.get(nextValidSiblingKey)) {
|
|
nextValidSiblingKey = originalBlockMap.get(nextValidSiblingKey).getNextSiblingKey() || null;
|
|
}
|
|
|
|
return nextValidSiblingKey;
|
|
};
|
|
|
|
var getPrevValidSibling = function getPrevValidSibling(block, blockMap, originalBlockMap) {
|
|
if (!block) {
|
|
return null;
|
|
}
|
|
|
|
// note that we need to make sure we refer to the original block since this
|
|
// function is called within a withMutations
|
|
var prevValidSiblingKey = originalBlockMap.get(block.getKey()).getPrevSiblingKey();
|
|
|
|
while (prevValidSiblingKey && !blockMap.get(prevValidSiblingKey)) {
|
|
prevValidSiblingKey = originalBlockMap.get(prevValidSiblingKey).getPrevSiblingKey() || null;
|
|
}
|
|
|
|
return prevValidSiblingKey;
|
|
};
|
|
|
|
var updateBlockMapLinks = function updateBlockMapLinks(blockMap, startBlock, endBlock, originalBlockMap) {
|
|
return blockMap.withMutations(function (blocks) {
|
|
// update start block if its retained
|
|
transformBlock(startBlock.getKey(), blocks, function (block) {
|
|
return block.merge({
|
|
nextSibling: getNextValidSibling(startBlock, blocks, originalBlockMap),
|
|
prevSibling: getPrevValidSibling(startBlock, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
|
|
// update endblock if its retained
|
|
transformBlock(endBlock.getKey(), blocks, function (block) {
|
|
return block.merge({
|
|
nextSibling: getNextValidSibling(endBlock, blocks, originalBlockMap),
|
|
prevSibling: getPrevValidSibling(endBlock, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
|
|
// update start block parent ancestors
|
|
getAncestorsKeys(startBlock.getKey(), originalBlockMap).forEach(function (parentKey) {
|
|
return transformBlock(parentKey, blocks, function (block) {
|
|
return block.merge({
|
|
children: block.getChildKeys().filter(function (key) {
|
|
return blocks.get(key);
|
|
}),
|
|
nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
|
|
prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
});
|
|
|
|
// update start block next - can only happen if startBlock == endBlock
|
|
transformBlock(startBlock.getNextSiblingKey(), blocks, function (block) {
|
|
return block.merge({
|
|
prevSibling: startBlock.getPrevSiblingKey()
|
|
});
|
|
});
|
|
|
|
// update start block prev
|
|
transformBlock(startBlock.getPrevSiblingKey(), blocks, function (block) {
|
|
return block.merge({
|
|
nextSibling: getNextValidSibling(startBlock, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
|
|
// update end block next
|
|
transformBlock(endBlock.getNextSiblingKey(), blocks, function (block) {
|
|
return block.merge({
|
|
prevSibling: getPrevValidSibling(endBlock, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
|
|
// update end block prev
|
|
transformBlock(endBlock.getPrevSiblingKey(), blocks, function (block) {
|
|
return block.merge({
|
|
nextSibling: endBlock.getNextSiblingKey()
|
|
});
|
|
});
|
|
|
|
// update end block parent ancestors
|
|
getAncestorsKeys(endBlock.getKey(), originalBlockMap).forEach(function (parentKey) {
|
|
transformBlock(parentKey, blocks, function (block) {
|
|
return block.merge({
|
|
children: block.getChildKeys().filter(function (key) {
|
|
return blocks.get(key);
|
|
}),
|
|
nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
|
|
prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
});
|
|
|
|
// update next delimiters all the way to a root delimiter
|
|
getNextDelimitersBlockKeys(endBlock, originalBlockMap).forEach(function (delimiterKey) {
|
|
return transformBlock(delimiterKey, blocks, function (block) {
|
|
return block.merge({
|
|
nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
|
|
prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
var removeRangeFromContentState = function removeRangeFromContentState(contentState, selectionState) {
|
|
if (selectionState.isCollapsed()) {
|
|
return contentState;
|
|
}
|
|
|
|
var blockMap = contentState.getBlockMap();
|
|
var startKey = selectionState.getStartKey();
|
|
var startOffset = selectionState.getStartOffset();
|
|
var endKey = selectionState.getEndKey();
|
|
var endOffset = selectionState.getEndOffset();
|
|
|
|
var startBlock = blockMap.get(startKey);
|
|
var endBlock = blockMap.get(endKey);
|
|
|
|
// we assume that ContentBlockNode and ContentBlocks are not mixed together
|
|
var isExperimentalTreeBlock = startBlock instanceof ContentBlockNode;
|
|
|
|
// used to retain blocks that should not be deleted to avoid orphan children
|
|
var parentAncestors = [];
|
|
|
|
if (isExperimentalTreeBlock) {
|
|
var endBlockchildrenKeys = endBlock.getChildKeys();
|
|
var endBlockAncestors = getAncestorsKeys(endKey, blockMap);
|
|
|
|
// endBlock has unselected sibblings 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));
|
|
}
|
|
|
|
var characterList = void 0;
|
|
|
|
if (startBlock === endBlock) {
|
|
characterList = removeFromList(startBlock.getCharacterList(), startOffset, endOffset);
|
|
} else {
|
|
characterList = startBlock.getCharacterList().slice(0, startOffset).concat(endBlock.getCharacterList().slice(endOffset));
|
|
}
|
|
|
|
var modifiedStart = startBlock.merge({
|
|
text: startBlock.getText().slice(0, startOffset) + endBlock.getText().slice(endOffset),
|
|
characterList: characterList
|
|
});
|
|
|
|
var newBlocks = blockMap.toSeq().skipUntil(function (_, k) {
|
|
return k === startKey;
|
|
}).takeUntil(function (_, k) {
|
|
return k === endKey;
|
|
}).filter(function (_, k) {
|
|
return parentAncestors.indexOf(k) === -1;
|
|
}).concat(Map([[endKey, null]])).map(function (_, k) {
|
|
return k === startKey ? modifiedStart : null;
|
|
});
|
|
|
|
var updatedBlockMap = blockMap.merge(newBlocks).filter(function (block) {
|
|
return !!block;
|
|
});
|
|
|
|
if (isExperimentalTreeBlock) {
|
|
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
|
|
})
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Maintain persistence for target list when removing characters on the
|
|
* head and tail of the character list.
|
|
*/
|
|
var removeFromList = function removeFromList(targetList, startOffset, endOffset) {
|
|
if (startOffset === 0) {
|
|
while (startOffset < endOffset) {
|
|
targetList = targetList.shift();
|
|
startOffset++;
|
|
}
|
|
} else if (endOffset === targetList.count()) {
|
|
while (endOffset > startOffset) {
|
|
targetList = targetList.pop();
|
|
endOffset--;
|
|
}
|
|
} else {
|
|
var head = targetList.slice(0, startOffset);
|
|
var tail = targetList.slice(endOffset);
|
|
targetList = head.concat(tail).toList();
|
|
}
|
|
return targetList;
|
|
};
|
|
|
|
module.exports = removeRangeFromContentState; |