You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

moveBlockInContentState.js.flow 6.4 KiB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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 { BlockNodeRecord } from "./BlockNodeRecord";
  14. import type ContentState from "./ContentState";
  15. import type { DraftInsertionType } from "./DraftInsertionType";
  16. const ContentBlockNode = require("./ContentBlockNode");
  17. const getNextDelimiterBlockKey = require("./getNextDelimiterBlockKey");
  18. const Immutable = require("immutable");
  19. const invariant = require("fbjs/lib/invariant");
  20. const {
  21. OrderedMap,
  22. List
  23. } = Immutable;
  24. const transformBlock = (key: ?string, blockMap: BlockMap, func: (block: ContentBlockNode) => ContentBlockNode): void => {
  25. if (!key) {
  26. return;
  27. }
  28. const block = blockMap.get(key);
  29. if (!block) {
  30. return;
  31. }
  32. blockMap.set(key, func(block));
  33. };
  34. const updateBlockMapLinks = (blockMap: BlockMap, originalBlockToBeMoved: BlockNodeRecord, originalTargetBlock: BlockNodeRecord, insertionMode: DraftInsertionType, isExperimentalTreeBlock: boolean): BlockMap => {
  35. if (!isExperimentalTreeBlock) {
  36. return blockMap;
  37. } // possible values of 'insertionMode' are: 'after', 'before'
  38. const isInsertedAfterTarget = insertionMode === 'after';
  39. const originalBlockKey = originalBlockToBeMoved.getKey();
  40. const originalTargetKey = originalTargetBlock.getKey();
  41. const originalParentKey = originalBlockToBeMoved.getParentKey();
  42. const originalNextSiblingKey = originalBlockToBeMoved.getNextSiblingKey();
  43. const originalPrevSiblingKey = originalBlockToBeMoved.getPrevSiblingKey();
  44. const newParentKey = originalTargetBlock.getParentKey();
  45. const newNextSiblingKey = isInsertedAfterTarget ? originalTargetBlock.getNextSiblingKey() : originalTargetKey;
  46. const newPrevSiblingKey = isInsertedAfterTarget ? originalTargetKey : originalTargetBlock.getPrevSiblingKey();
  47. return blockMap.withMutations(blocks => {
  48. // update old parent
  49. transformBlock(originalParentKey, blocks, block => {
  50. const parentChildrenList = block.getChildKeys();
  51. return block.merge({
  52. children: parentChildrenList.delete(parentChildrenList.indexOf(originalBlockKey))
  53. });
  54. }); // update old prev
  55. transformBlock(originalPrevSiblingKey, blocks, block => block.merge({
  56. nextSibling: originalNextSiblingKey
  57. })); // update old next
  58. transformBlock(originalNextSiblingKey, blocks, block => block.merge({
  59. prevSibling: originalPrevSiblingKey
  60. })); // update new next
  61. transformBlock(newNextSiblingKey, blocks, block => block.merge({
  62. prevSibling: originalBlockKey
  63. })); // update new prev
  64. transformBlock(newPrevSiblingKey, blocks, block => block.merge({
  65. nextSibling: originalBlockKey
  66. })); // update new parent
  67. transformBlock(newParentKey, blocks, block => {
  68. const newParentChildrenList = block.getChildKeys();
  69. const targetBlockIndex = newParentChildrenList.indexOf(originalTargetKey);
  70. const insertionIndex = isInsertedAfterTarget ? targetBlockIndex + 1 : targetBlockIndex !== 0 ? targetBlockIndex - 1 : 0;
  71. const newChildrenArray = newParentChildrenList.toArray();
  72. newChildrenArray.splice(insertionIndex, 0, originalBlockKey);
  73. return block.merge({
  74. children: List(newChildrenArray)
  75. });
  76. }); // update block
  77. transformBlock(originalBlockKey, blocks, block => block.merge({
  78. nextSibling: newNextSiblingKey,
  79. prevSibling: newPrevSiblingKey,
  80. parent: newParentKey
  81. }));
  82. });
  83. };
  84. const moveBlockInContentState = (contentState: ContentState, blockToBeMoved: BlockNodeRecord, targetBlock: BlockNodeRecord, insertionMode: DraftInsertionType): ContentState => {
  85. invariant(insertionMode !== 'replace', 'Replacing blocks is not supported.');
  86. const targetKey = targetBlock.getKey();
  87. const blockKey = blockToBeMoved.getKey();
  88. invariant(blockKey !== targetKey, 'Block cannot be moved next to itself.');
  89. const blockMap = contentState.getBlockMap();
  90. const isExperimentalTreeBlock = blockToBeMoved instanceof ContentBlockNode;
  91. let blocksToBeMoved = [blockToBeMoved];
  92. let blockMapWithoutBlocksToBeMoved = blockMap.delete(blockKey);
  93. if (isExperimentalTreeBlock) {
  94. blocksToBeMoved = [];
  95. blockMapWithoutBlocksToBeMoved = blockMap.withMutations(blocks => {
  96. const nextSiblingKey = blockToBeMoved.getNextSiblingKey();
  97. const nextDelimiterBlockKey = getNextDelimiterBlockKey(blockToBeMoved, blocks);
  98. blocks.toSeq().skipUntil(block => block.getKey() === blockKey).takeWhile(block => {
  99. const key = block.getKey();
  100. const isBlockToBeMoved = key === blockKey;
  101. const hasNextSiblingAndIsNotNextSibling = nextSiblingKey && key !== nextSiblingKey;
  102. const doesNotHaveNextSiblingAndIsNotDelimiter = !nextSiblingKey && block.getParentKey() && (!nextDelimiterBlockKey || key !== nextDelimiterBlockKey);
  103. return !!(isBlockToBeMoved || hasNextSiblingAndIsNotNextSibling || doesNotHaveNextSiblingAndIsNotDelimiter);
  104. }).forEach(block => {
  105. blocksToBeMoved.push(block);
  106. blocks.delete(block.getKey());
  107. });
  108. });
  109. }
  110. const blocksBefore = blockMapWithoutBlocksToBeMoved.toSeq().takeUntil(v => v === targetBlock);
  111. const blocksAfter = blockMapWithoutBlocksToBeMoved.toSeq().skipUntil(v => v === targetBlock).skip(1);
  112. const slicedBlocks = blocksToBeMoved.map(block => [block.getKey(), block]);
  113. let newBlocks = OrderedMap();
  114. if (insertionMode === 'before') {
  115. const blockBefore = contentState.getBlockBefore(targetKey);
  116. invariant(!blockBefore || blockBefore.getKey() !== blockToBeMoved.getKey(), 'Block cannot be moved next to itself.');
  117. newBlocks = blocksBefore.concat([...slicedBlocks, [targetKey, targetBlock]], blocksAfter).toOrderedMap();
  118. } else if (insertionMode === 'after') {
  119. const blockAfter = contentState.getBlockAfter(targetKey);
  120. invariant(!blockAfter || blockAfter.getKey() !== blockKey, 'Block cannot be moved next to itself.');
  121. newBlocks = blocksBefore.concat([[targetKey, targetBlock], ...slicedBlocks], blocksAfter).toOrderedMap();
  122. }
  123. return contentState.merge({
  124. blockMap: updateBlockMapLinks(newBlocks, blockToBeMoved, targetBlock, insertionMode, isExperimentalTreeBlock),
  125. selectionBefore: contentState.getSelectionAfter(),
  126. selectionAfter: contentState.getSelectionAfter().merge({
  127. anchorKey: blockKey,
  128. focusKey: blockKey
  129. })
  130. });
  131. };
  132. module.exports = moveBlockInContentState;