Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

248 righe
9.3 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. *
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. var BlockMapBuilder = require("./BlockMapBuilder");
  13. var ContentBlockNode = require("./ContentBlockNode");
  14. var Immutable = require("immutable");
  15. var insertIntoList = require("./insertIntoList");
  16. var invariant = require("fbjs/lib/invariant");
  17. var randomizeBlockMapKeys = require("./randomizeBlockMapKeys");
  18. var List = Immutable.List;
  19. var updateExistingBlock = function updateExistingBlock(contentState, selectionState, blockMap, fragmentBlock, targetKey, targetOffset) {
  20. var mergeBlockData = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 'REPLACE_WITH_NEW_DATA';
  21. var targetBlock = blockMap.get(targetKey);
  22. var text = targetBlock.getText();
  23. var chars = targetBlock.getCharacterList();
  24. var finalKey = targetKey;
  25. var finalOffset = targetOffset + fragmentBlock.getText().length;
  26. var data = null;
  27. switch (mergeBlockData) {
  28. case 'MERGE_OLD_DATA_TO_NEW_DATA':
  29. data = fragmentBlock.getData().merge(targetBlock.getData());
  30. break;
  31. case 'REPLACE_WITH_NEW_DATA':
  32. data = fragmentBlock.getData();
  33. break;
  34. }
  35. var newBlock = targetBlock.merge({
  36. text: text.slice(0, targetOffset) + fragmentBlock.getText() + text.slice(targetOffset),
  37. characterList: insertIntoList(chars, fragmentBlock.getCharacterList(), targetOffset),
  38. data: data
  39. });
  40. return contentState.merge({
  41. blockMap: blockMap.set(targetKey, newBlock),
  42. selectionBefore: selectionState,
  43. selectionAfter: selectionState.merge({
  44. anchorKey: finalKey,
  45. anchorOffset: finalOffset,
  46. focusKey: finalKey,
  47. focusOffset: finalOffset,
  48. isBackward: false
  49. })
  50. });
  51. };
  52. /**
  53. * Appends text/characterList from the fragment first block to
  54. * target block.
  55. */
  56. var updateHead = function updateHead(block, targetOffset, fragment) {
  57. var text = block.getText();
  58. var chars = block.getCharacterList(); // Modify head portion of block.
  59. var headText = text.slice(0, targetOffset);
  60. var headCharacters = chars.slice(0, targetOffset);
  61. var appendToHead = fragment.first();
  62. return block.merge({
  63. text: headText + appendToHead.getText(),
  64. characterList: headCharacters.concat(appendToHead.getCharacterList()),
  65. type: headText ? block.getType() : appendToHead.getType(),
  66. data: appendToHead.getData()
  67. });
  68. };
  69. /**
  70. * Appends offset text/characterList from the target block to the last
  71. * fragment block.
  72. */
  73. var updateTail = function updateTail(block, targetOffset, fragment) {
  74. // Modify tail portion of block.
  75. var text = block.getText();
  76. var chars = block.getCharacterList(); // Modify head portion of block.
  77. var blockSize = text.length;
  78. var tailText = text.slice(targetOffset, blockSize);
  79. var tailCharacters = chars.slice(targetOffset, blockSize);
  80. var prependToTail = fragment.last();
  81. return prependToTail.merge({
  82. text: prependToTail.getText() + tailText,
  83. characterList: prependToTail.getCharacterList().concat(tailCharacters),
  84. data: prependToTail.getData()
  85. });
  86. };
  87. var getRootBlocks = function getRootBlocks(block, blockMap) {
  88. var headKey = block.getKey();
  89. var rootBlock = block;
  90. var rootBlocks = []; // sometimes the fragment head block will not be part of the blockMap itself this can happen when
  91. // the fragment head is used to update the target block, however when this does not happen we need
  92. // to make sure that we include it on the rootBlocks since the first block of a fragment is always a
  93. // fragment root block
  94. if (blockMap.get(headKey)) {
  95. rootBlocks.push(headKey);
  96. }
  97. while (rootBlock && rootBlock.getNextSiblingKey()) {
  98. var lastSiblingKey = rootBlock.getNextSiblingKey();
  99. if (!lastSiblingKey) {
  100. break;
  101. }
  102. rootBlocks.push(lastSiblingKey);
  103. rootBlock = blockMap.get(lastSiblingKey);
  104. }
  105. return rootBlocks;
  106. };
  107. var updateBlockMapLinks = function updateBlockMapLinks(blockMap, originalBlockMap, targetBlock, fragmentHeadBlock) {
  108. return blockMap.withMutations(function (blockMapState) {
  109. var targetKey = targetBlock.getKey();
  110. var headKey = fragmentHeadBlock.getKey();
  111. var targetNextKey = targetBlock.getNextSiblingKey();
  112. var targetParentKey = targetBlock.getParentKey();
  113. var fragmentRootBlocks = getRootBlocks(fragmentHeadBlock, blockMap);
  114. var lastRootFragmentBlockKey = fragmentRootBlocks[fragmentRootBlocks.length - 1];
  115. if (blockMapState.get(headKey)) {
  116. // update the fragment head when it is part of the blockMap otherwise
  117. blockMapState.setIn([targetKey, 'nextSibling'], headKey);
  118. blockMapState.setIn([headKey, 'prevSibling'], targetKey);
  119. } else {
  120. // update the target block that had the fragment head contents merged into it
  121. blockMapState.setIn([targetKey, 'nextSibling'], fragmentHeadBlock.getNextSiblingKey());
  122. blockMapState.setIn([fragmentHeadBlock.getNextSiblingKey(), 'prevSibling'], targetKey);
  123. } // update the last root block fragment
  124. blockMapState.setIn([lastRootFragmentBlockKey, 'nextSibling'], targetNextKey); // update the original target next block
  125. if (targetNextKey) {
  126. blockMapState.setIn([targetNextKey, 'prevSibling'], lastRootFragmentBlockKey);
  127. } // update fragment parent links
  128. fragmentRootBlocks.forEach(function (blockKey) {
  129. return blockMapState.setIn([blockKey, 'parent'], targetParentKey);
  130. }); // update targetBlock parent child links
  131. if (targetParentKey) {
  132. var targetParent = blockMap.get(targetParentKey);
  133. var originalTargetParentChildKeys = targetParent.getChildKeys();
  134. var targetBlockIndex = originalTargetParentChildKeys.indexOf(targetKey);
  135. var insertionIndex = targetBlockIndex + 1;
  136. var newChildrenKeysArray = originalTargetParentChildKeys.toArray(); // insert fragment children
  137. newChildrenKeysArray.splice.apply(newChildrenKeysArray, [insertionIndex, 0].concat(fragmentRootBlocks));
  138. blockMapState.setIn([targetParentKey, 'children'], List(newChildrenKeysArray));
  139. }
  140. });
  141. };
  142. var insertFragment = function insertFragment(contentState, selectionState, blockMap, fragment, targetKey, targetOffset) {
  143. var isTreeBasedBlockMap = blockMap.first() instanceof ContentBlockNode;
  144. var newBlockArr = [];
  145. var fragmentSize = fragment.size;
  146. var target = blockMap.get(targetKey);
  147. var head = fragment.first();
  148. var tail = fragment.last();
  149. var finalOffset = tail.getLength();
  150. var finalKey = tail.getKey();
  151. var shouldNotUpdateFromFragmentBlock = isTreeBasedBlockMap && (!target.getChildKeys().isEmpty() || !head.getChildKeys().isEmpty());
  152. blockMap.forEach(function (block, blockKey) {
  153. if (blockKey !== targetKey) {
  154. newBlockArr.push(block);
  155. return;
  156. }
  157. if (shouldNotUpdateFromFragmentBlock) {
  158. newBlockArr.push(block);
  159. } else {
  160. newBlockArr.push(updateHead(block, targetOffset, fragment));
  161. } // Insert fragment blocks after the head and before the tail.
  162. fragment // when we are updating the target block with the head fragment block we skip the first fragment
  163. // head since its contents have already been merged with the target block otherwise we include
  164. // the whole fragment
  165. .slice(shouldNotUpdateFromFragmentBlock ? 0 : 1, fragmentSize - 1).forEach(function (fragmentBlock) {
  166. return newBlockArr.push(fragmentBlock);
  167. }); // update tail
  168. newBlockArr.push(updateTail(block, targetOffset, fragment));
  169. });
  170. var updatedBlockMap = BlockMapBuilder.createFromArray(newBlockArr);
  171. if (isTreeBasedBlockMap) {
  172. updatedBlockMap = updateBlockMapLinks(updatedBlockMap, blockMap, target, head);
  173. }
  174. return contentState.merge({
  175. blockMap: updatedBlockMap,
  176. selectionBefore: selectionState,
  177. selectionAfter: selectionState.merge({
  178. anchorKey: finalKey,
  179. anchorOffset: finalOffset,
  180. focusKey: finalKey,
  181. focusOffset: finalOffset,
  182. isBackward: false
  183. })
  184. });
  185. };
  186. var insertFragmentIntoContentState = function insertFragmentIntoContentState(contentState, selectionState, fragmentBlockMap) {
  187. var mergeBlockData = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'REPLACE_WITH_NEW_DATA';
  188. !selectionState.isCollapsed() ? process.env.NODE_ENV !== "production" ? invariant(false, '`insertFragment` should only be called with a collapsed selection state.') : invariant(false) : void 0;
  189. var blockMap = contentState.getBlockMap();
  190. var fragment = randomizeBlockMapKeys(fragmentBlockMap);
  191. var targetKey = selectionState.getStartKey();
  192. var targetOffset = selectionState.getStartOffset();
  193. var targetBlock = blockMap.get(targetKey);
  194. if (targetBlock instanceof ContentBlockNode) {
  195. !targetBlock.getChildKeys().isEmpty() ? process.env.NODE_ENV !== "production" ? invariant(false, '`insertFragment` should not be called when a container node is selected.') : invariant(false) : void 0;
  196. } // When we insert a fragment with a single block we simply update the target block
  197. // with the contents of the inserted fragment block
  198. if (fragment.size === 1) {
  199. return updateExistingBlock(contentState, selectionState, blockMap, fragment.first(), targetKey, targetOffset, mergeBlockData);
  200. }
  201. return insertFragment(contentState, selectionState, blockMap, fragment, targetKey, targetOffset);
  202. };
  203. module.exports = insertFragmentIntoContentState;