Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 

307 rader
11 KiB

  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. import type { BlockMap } from "./BlockMap";
  13. import type CharacterMetadata from "./CharacterMetadata";
  14. import type ContentState from "./ContentState";
  15. import type SelectionState from "./SelectionState";
  16. const ContentBlockNode = require("./ContentBlockNode");
  17. const getNextDelimiterBlockKey = require("./getNextDelimiterBlockKey");
  18. const Immutable = require("immutable");
  19. const {
  20. List,
  21. Map
  22. } = Immutable;
  23. const transformBlock = (key: ?string, blockMap: BlockMap, func: (block: ContentBlockNode) => ContentBlockNode): void => {
  24. if (!key) {
  25. return;
  26. }
  27. const block = blockMap.get(key);
  28. if (!block) {
  29. return;
  30. }
  31. blockMap.set(key, func(block));
  32. };
  33. /**
  34. * Ancestors needs to be preserved when there are non selected
  35. * children to make sure we do not leave any orphans behind
  36. */
  37. const getAncestorsKeys = (blockKey: ?string, blockMap: BlockMap): Array<string> => {
  38. const parents = [];
  39. if (!blockKey) {
  40. return parents;
  41. }
  42. let blockNode = blockMap.get(blockKey);
  43. while (blockNode && blockNode.getParentKey()) {
  44. const parentKey = blockNode.getParentKey();
  45. if (parentKey) {
  46. parents.push(parentKey);
  47. }
  48. blockNode = parentKey ? blockMap.get(parentKey) : null;
  49. }
  50. return parents;
  51. };
  52. /**
  53. * Get all next delimiter keys until we hit a root delimiter and return
  54. * an array of key references
  55. */
  56. const getNextDelimitersBlockKeys = (block: ContentBlockNode, blockMap: BlockMap): Array<string> => {
  57. const nextDelimiters = [];
  58. if (!block) {
  59. return nextDelimiters;
  60. }
  61. let nextDelimiter = getNextDelimiterBlockKey(block, blockMap);
  62. while (nextDelimiter && blockMap.get(nextDelimiter)) {
  63. const block = blockMap.get(nextDelimiter);
  64. nextDelimiters.push(nextDelimiter); // we do not need to keep checking all root node siblings, just the first occurance
  65. nextDelimiter = block.getParentKey() ? getNextDelimiterBlockKey(block, blockMap) : null;
  66. }
  67. return nextDelimiters;
  68. };
  69. const getNextValidSibling = (block: ?ContentBlockNode, blockMap: BlockMap, originalBlockMap: BlockMap): ?string => {
  70. if (!block) {
  71. return null;
  72. } // note that we need to make sure we refer to the original block since this
  73. // function is called within a withMutations
  74. let nextValidSiblingKey = originalBlockMap.get(block.getKey()).getNextSiblingKey();
  75. while (nextValidSiblingKey && !blockMap.get(nextValidSiblingKey)) {
  76. nextValidSiblingKey = originalBlockMap.get(nextValidSiblingKey).getNextSiblingKey() || null;
  77. }
  78. return nextValidSiblingKey;
  79. };
  80. const getPrevValidSibling = (block: ?ContentBlockNode, blockMap: BlockMap, originalBlockMap: BlockMap): ?string => {
  81. if (!block) {
  82. return null;
  83. } // note that we need to make sure we refer to the original block since this
  84. // function is called within a withMutations
  85. let prevValidSiblingKey = originalBlockMap.get(block.getKey()).getPrevSiblingKey();
  86. while (prevValidSiblingKey && !blockMap.get(prevValidSiblingKey)) {
  87. prevValidSiblingKey = originalBlockMap.get(prevValidSiblingKey).getPrevSiblingKey() || null;
  88. }
  89. return prevValidSiblingKey;
  90. };
  91. const updateBlockMapLinks = (blockMap: BlockMap, startBlock: ContentBlockNode, endBlock: ContentBlockNode, originalBlockMap: BlockMap): BlockMap => {
  92. return blockMap.withMutations(blocks => {
  93. // update start block if its retained
  94. transformBlock(startBlock.getKey(), blocks, block => block.merge({
  95. nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
  96. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  97. })); // update endblock if its retained
  98. transformBlock(endBlock.getKey(), blocks, block => block.merge({
  99. nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
  100. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  101. })); // update start block parent ancestors
  102. getAncestorsKeys(startBlock.getKey(), originalBlockMap).forEach(parentKey => transformBlock(parentKey, blocks, block => block.merge({
  103. children: block.getChildKeys().filter(key => blocks.get(key)),
  104. nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
  105. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  106. }))); // update start block next - can only happen if startBlock == endBlock
  107. transformBlock(startBlock.getNextSiblingKey(), blocks, block => block.merge({
  108. prevSibling: startBlock.getPrevSiblingKey()
  109. })); // update start block prev
  110. transformBlock(startBlock.getPrevSiblingKey(), blocks, block => block.merge({
  111. nextSibling: getNextValidSibling(block, blocks, originalBlockMap)
  112. })); // update end block next
  113. transformBlock(endBlock.getNextSiblingKey(), blocks, block => block.merge({
  114. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  115. })); // update end block prev
  116. transformBlock(endBlock.getPrevSiblingKey(), blocks, block => block.merge({
  117. nextSibling: endBlock.getNextSiblingKey()
  118. })); // update end block parent ancestors
  119. getAncestorsKeys(endBlock.getKey(), originalBlockMap).forEach(parentKey => {
  120. transformBlock(parentKey, blocks, block => block.merge({
  121. children: block.getChildKeys().filter(key => blocks.get(key)),
  122. nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
  123. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  124. }));
  125. }); // update next delimiters all the way to a root delimiter
  126. getNextDelimitersBlockKeys(endBlock, originalBlockMap).forEach(delimiterKey => transformBlock(delimiterKey, blocks, block => block.merge({
  127. nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
  128. prevSibling: getPrevValidSibling(block, blocks, originalBlockMap)
  129. }))); // if parent (startBlock) was deleted
  130. if (blockMap.get(startBlock.getKey()) == null && blockMap.get(endBlock.getKey()) != null && endBlock.getParentKey() === startBlock.getKey() && endBlock.getPrevSiblingKey() == null) {
  131. const prevSiblingKey = startBlock.getPrevSiblingKey(); // endBlock becomes next sibling of parent's prevSibling
  132. transformBlock(endBlock.getKey(), blocks, block => block.merge({
  133. prevSibling: prevSiblingKey
  134. }));
  135. transformBlock(prevSiblingKey, blocks, block => block.merge({
  136. nextSibling: endBlock.getKey()
  137. })); // Update parent for previous parent's children, and children for that parent
  138. const prevSibling = prevSiblingKey ? blockMap.get(prevSiblingKey) : null;
  139. const newParentKey = prevSibling ? prevSibling.getParentKey() : null;
  140. startBlock.getChildKeys().forEach(childKey => {
  141. transformBlock(childKey, blocks, block => block.merge({
  142. parent: newParentKey // set to null if there is no parent
  143. }));
  144. });
  145. if (newParentKey != null) {
  146. const newParent = blockMap.get(newParentKey);
  147. transformBlock(newParentKey, blocks, block => block.merge({
  148. children: newParent.getChildKeys().concat(startBlock.getChildKeys())
  149. }));
  150. } // last child of deleted parent should point to next sibling
  151. transformBlock(startBlock.getChildKeys().find(key => {
  152. const block = (blockMap.get(key): ContentBlockNode);
  153. return block.getNextSiblingKey() === null;
  154. }), blocks, block => block.merge({
  155. nextSibling: startBlock.getNextSiblingKey()
  156. }));
  157. }
  158. });
  159. };
  160. const removeRangeFromContentState = (contentState: ContentState, selectionState: SelectionState): ContentState => {
  161. if (selectionState.isCollapsed()) {
  162. return contentState;
  163. }
  164. const blockMap = contentState.getBlockMap();
  165. const startKey = selectionState.getStartKey();
  166. const startOffset = selectionState.getStartOffset();
  167. const endKey = selectionState.getEndKey();
  168. const endOffset = selectionState.getEndOffset();
  169. const startBlock = blockMap.get(startKey);
  170. const endBlock = blockMap.get(endKey); // we assume that ContentBlockNode and ContentBlocks are not mixed together
  171. const isExperimentalTreeBlock = startBlock instanceof ContentBlockNode; // used to retain blocks that should not be deleted to avoid orphan children
  172. let parentAncestors = [];
  173. if (isExperimentalTreeBlock) {
  174. const endBlockchildrenKeys = endBlock.getChildKeys();
  175. const endBlockAncestors = getAncestorsKeys(endKey, blockMap); // endBlock has unselected siblings so we can not remove its ancestors parents
  176. if (endBlock.getNextSiblingKey()) {
  177. parentAncestors = parentAncestors.concat(endBlockAncestors);
  178. } // endBlock has children so can not remove this block or any of its ancestors
  179. if (!endBlockchildrenKeys.isEmpty()) {
  180. parentAncestors = parentAncestors.concat(endBlockAncestors.concat([endKey]));
  181. } // we need to retain all ancestors of the next delimiter block
  182. parentAncestors = parentAncestors.concat(getAncestorsKeys(getNextDelimiterBlockKey(endBlock, blockMap), blockMap));
  183. }
  184. let characterList;
  185. if (startBlock === endBlock) {
  186. characterList = removeFromList(startBlock.getCharacterList(), startOffset, endOffset);
  187. } else {
  188. characterList = startBlock.getCharacterList().slice(0, startOffset).concat(endBlock.getCharacterList().slice(endOffset));
  189. }
  190. const modifiedStart = startBlock.merge({
  191. text: startBlock.getText().slice(0, startOffset) + endBlock.getText().slice(endOffset),
  192. characterList
  193. }); // If cursor (collapsed) is at the start of the first child, delete parent
  194. // instead of child
  195. const shouldDeleteParent = isExperimentalTreeBlock && startOffset === 0 && endOffset === 0 && endBlock.getParentKey() === startKey && endBlock.getPrevSiblingKey() == null;
  196. 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) => {
  197. return k === startKey ? modifiedStart : null;
  198. });
  199. let updatedBlockMap = blockMap.merge(newBlocks).filter(block => !!block); // Only update tree block pointers if the range is across blocks
  200. if (isExperimentalTreeBlock && startBlock !== endBlock) {
  201. updatedBlockMap = updateBlockMapLinks(updatedBlockMap, startBlock, endBlock, blockMap);
  202. }
  203. return contentState.merge({
  204. blockMap: updatedBlockMap,
  205. selectionBefore: selectionState,
  206. selectionAfter: selectionState.merge({
  207. anchorKey: startKey,
  208. anchorOffset: startOffset,
  209. focusKey: startKey,
  210. focusOffset: startOffset,
  211. isBackward: false
  212. })
  213. });
  214. };
  215. /**
  216. * Maintain persistence for target list when removing characters on the
  217. * head and tail of the character list.
  218. */
  219. const removeFromList = (targetList: List<CharacterMetadata>, startOffset: number, endOffset: number): List<CharacterMetadata> => {
  220. if (startOffset === 0) {
  221. while (startOffset < endOffset) {
  222. targetList = targetList.shift();
  223. startOffset++;
  224. }
  225. } else if (endOffset === targetList.count()) {
  226. while (endOffset > startOffset) {
  227. targetList = targetList.pop();
  228. endOffset--;
  229. }
  230. } else {
  231. const head = targetList.slice(0, startOffset);
  232. const tail = targetList.slice(endOffset);
  233. targetList = head.concat(tail).toList();
  234. }
  235. return targetList;
  236. };
  237. module.exports = removeRangeFromContentState;