const getRootBlocks = (
  block: ContentBlockNode,
  blockMap: BlockMap,
): Array<string> => {
  const headKey = block.getKey();
  let rootBlock = block;
  let rootBlocks = [];

  // sometimes the fragment head block will not be part of the blockMap itself this can happen when
  // the fragment head is used to update the target block, however when this does not happen we need
  // to make sure that we include it on the rootBlocks since the first block of a fragment is always a
  // fragment root block
  if (blockMap.get(headKey)) {
    rootBlocks.push(headKey);
  }

  while (rootBlock && rootBlock.getNextSiblingKey()) {
    const lastSiblingKey = rootBlock.getNextSiblingKey();

    if (!lastSiblingKey) {
      break;
    }

    rootBlocks.push(lastSiblingKey);
    rootBlock = blockMap.get(lastSiblingKey);
  }

  return rootBlocks;
};
  return blockMap.withMutations(blocks => {
    const originalBlockKey = originalBlock.getKey();
    const belowBlockKey = belowBlock.getKey();

    // update block parent
    transformBlock(originalBlock.getParentKey(), blocks, block => {
      const parentChildrenList = block.getChildKeys();
      const insertionIndex = parentChildrenList.indexOf(originalBlockKey) + 1;
      const newChildrenArray = parentChildrenList.toArray();

      newChildrenArray.splice(insertionIndex, 0, belowBlockKey);

      return block.merge({
        children: List(newChildrenArray),
      });
    });

    // update original next block
    transformBlock(originalBlock.getNextSiblingKey(), blocks, block =>
      block.merge({
        prevSibling: belowBlockKey,
      }),
    );

    // update original block
    transformBlock(originalBlockKey, blocks, block =>
      block.merge({
        nextSibling: belowBlockKey,
      }),
    );

    // update below block
    transformBlock(belowBlockKey, blocks, block =>
      block.merge({
        prevSibling: originalBlockKey,
      }),
    );
  });
Example #3
0
test('must have appropriate default values', () => {
  const text = 'Alpha';
  const block = new ContentBlockNode({
    key: 'a',
    type: 'unstyled',
    text,
  });

  const characterList = Immutable.List(
    Immutable.Repeat(CharacterMetadata.EMPTY, text.length),
  );

  expect(block.getKey()).toBe('a');
  expect(block.getText()).toBe('Alpha');
  expect(block.getType()).toBe('unstyled');
  expect(block.getLength()).toBe(5);
  expect(block.getCharacterList().count()).toBe(5);
  expect(block.getCharacterList()).toEqual(characterList);
});
        (blockMap: BlockMap, block: RawDraftContentBlock, index: number) => {
          invariant(
            Array.isArray(block.children),
            'invalid RawDraftContentBlock can not be converted to ContentBlockNode',
          );

          // ensure children have valid keys to enable sibling links
          const children = block.children.map(addKeyIfMissing);

          // root level nodes
          const contentBlockNode = new ContentBlockNode({
            ...decodeBlockNodeConfig(block, entityMap),
            prevSibling: index === 0 ? null : blocks[index - 1].key,
            nextSibling:
              index === blocks.length - 1 ? null : blocks[index + 1].key,
            children: List(children.map((child: any) => child.key)),
          });

          // push root node to blockMap
          blockMap = blockMap.set(contentBlockNode.getKey(), contentBlockNode);

          // this stack is used to ensure we visit all nodes respecting depth ordering
          let stack = updateNodeStack([], children, contentBlockNode);

          // start computing children nodes
          while (stack.length > 0) {
            // we pop from the stack and start processing this node
            const node: any = stack.pop();

            // parentRef already points to a converted ContentBlockNode
            const parentRef: ContentBlockNode = node.parentRef;
            const siblings = parentRef.getChildKeys();
            const index = siblings.indexOf(node.key);
            const isValidBlock = Array.isArray(node.children);

            if (!isValidBlock) {
              invariant(
                isValidBlock,
                'invalid RawDraftContentBlock can not be converted to ContentBlockNode',
              );
              break;
            }

            // ensure children have valid keys to enable sibling links
            const children = node.children.map(addKeyIfMissing);

            const contentBlockNode = new ContentBlockNode({
              ...decodeBlockNodeConfig(node, entityMap),
              parent: parentRef.getKey(),
              children: List(children.map((child: any) => child.key)),
              prevSibling: index === 0 ? null : siblings.get(index - 1),
              nextSibling:
                index === siblings.size - 1 ? null : siblings.get(index + 1),
            });

            // push node to blockMap
            blockMap = blockMap.set(
              contentBlockNode.getKey(),
              contentBlockNode,
            );

            // this stack is used to ensure we visit all nodes respecting depth ordering
            stack = updateNodeStack(stack, children, contentBlockNode);
          }

          return blockMap;
        },
transformBlock(prevSiblingKey, blocks, block =>
  block.merge({
    nextSibling: endBlock.getKey(),
  }),
  return blockMap.withMutations(blocks => {
    // update start block if its retained
    transformBlock(startBlock.getKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
        prevSibling: getPrevValidSibling(block, blocks, originalBlockMap),
      }),
    );

    // update endblock if its retained
    transformBlock(endBlock.getKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
        prevSibling: getPrevValidSibling(block, blocks, originalBlockMap),
      }),
    );

    // update start block parent ancestors
    getAncestorsKeys(startBlock.getKey(), originalBlockMap).forEach(parentKey =>
      transformBlock(parentKey, blocks, block =>
        block.merge({
          children: block.getChildKeys().filter(key => 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, block =>
      block.merge({
        prevSibling: startBlock.getPrevSiblingKey(),
      }),
    );

    // update start block prev
    transformBlock(startBlock.getPrevSiblingKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
      }),
    );

    // update end block next
    transformBlock(endBlock.getNextSiblingKey(), blocks, block =>
      block.merge({
        prevSibling: getPrevValidSibling(block, blocks, originalBlockMap),
      }),
    );

    // update end block prev
    transformBlock(endBlock.getPrevSiblingKey(), blocks, block =>
      block.merge({
        nextSibling: endBlock.getNextSiblingKey(),
      }),
    );

    // update end block parent ancestors
    getAncestorsKeys(endBlock.getKey(), originalBlockMap).forEach(parentKey => {
      transformBlock(parentKey, blocks, block =>
        block.merge({
          children: block.getChildKeys().filter(key => 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(
      delimiterKey =>
        transformBlock(delimiterKey, blocks, block =>
          block.merge({
            nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
            prevSibling: getPrevValidSibling(block, blocks, originalBlockMap),
          }),
        ),
    );

    // if parent (startBlock) was deleted
    if (
      blockMap.get(startBlock.getKey()) == null &&
      blockMap.get(endBlock.getKey()) != null &&
      endBlock.getParentKey() === startBlock.getKey() &&
      endBlock.getPrevSiblingKey() == null
    ) {
      const prevSiblingKey = startBlock.getPrevSiblingKey();
      // endBlock becomes next sibling of parent's prevSibling
      transformBlock(endBlock.getKey(), blocks, block =>
        block.merge({
          prevSibling: prevSiblingKey,
        }),
      );
      transformBlock(prevSiblingKey, blocks, block =>
        block.merge({
          nextSibling: endBlock.getKey(),
        }),
      );

      // Update parent for previous parent's children, and children for that parent
      const prevSibling = prevSiblingKey ? blockMap.get(prevSiblingKey) : null;
      const newParentKey = prevSibling ? prevSibling.getParentKey() : null;
      startBlock.getChildKeys().forEach(childKey => {
        transformBlock(childKey, blocks, block =>
          block.merge({
            parent: newParentKey, // set to null if there is no parent
          }),
        );
      });
      if (newParentKey != null) {
        const newParent = blockMap.get(newParentKey);
        transformBlock(newParentKey, blocks, block =>
          block.merge({
            children: newParent
              .getChildKeys()
              .concat(startBlock.getChildKeys()),
          }),
        );
      }

      // last child of deleted parent should point to next sibling
      transformBlock(
        startBlock.getChildKeys().find(key => {
          const block = (blockMap.get(key): ContentBlockNode);
          return block.getNextSiblingKey() === null;
        }),
        blocks,
        block =>
          block.merge({
            nextSibling: startBlock.getNextSiblingKey(),
          }),
      );
    }
  });
Example #7
0
        blockMapState.forEach((block, index) => {
          const oldKey = block.getKey();
          const nextKey = block.getNextSiblingKey();
          const prevKey = block.getPrevSiblingKey();
          const childrenKeys = block.getChildKeys();
          const parentKey = block.getParentKey();

          // new key that we will use to build linking
          const key = generateRandomKey();

          // we will add it here to re-use it later
          newKeysRef[oldKey] = key;

          if (nextKey) {
            const nextBlock = blockMapState.get(nextKey);
            if (nextBlock) {
              blockMapState.setIn([nextKey, 'prevSibling'], key);
            } else {
              // this can happen when generating random keys for fragments
              blockMapState.setIn([oldKey, 'nextSibling'], null);
            }
          }

          if (prevKey) {
            const prevBlock = blockMapState.get(prevKey);
            if (prevBlock) {
              blockMapState.setIn([prevKey, 'nextSibling'], key);
            } else {
              // this can happen when generating random keys for fragments
              blockMapState.setIn([oldKey, 'prevSibling'], null);
            }
          }

          if (parentKey && blockMapState.get(parentKey)) {
            const parentBlock = blockMapState.get(parentKey);
            const parentChildrenList = parentBlock.getChildKeys();
            blockMapState.setIn(
              [parentKey, 'children'],
              parentChildrenList.set(
                parentChildrenList.indexOf(block.getKey()),
                key,
              ),
            );
          } else {
            // blocks will then be treated as root block nodes
            blockMapState.setIn([oldKey, 'parent'], null);

            if (lastRootBlock) {
              blockMapState.setIn([lastRootBlock.getKey(), 'nextSibling'], key);
              blockMapState.setIn(
                [oldKey, 'prevSibling'],
                newKeysRef[lastRootBlock.getKey()],
              );
            }

            lastRootBlock = blockMapState.get(oldKey);
          }

          childrenKeys.forEach(childKey => {
            const childBlock = blockMapState.get(childKey);
            if (childBlock) {
              blockMapState.setIn([childKey, 'parent'], key);
            } else {
              blockMapState.setIn(
                [oldKey, 'children'],
                block.getChildKeys().filter(child => child !== childKey),
              );
            }
          });
        });
const onUntab = (blockMap: BlockMap, block: ContentBlockNode): BlockMap => {
  const key = block.getKey();
  const parentKey = block.getParentKey();
  const nextSiblingKey = block.getNextSiblingKey();
  if (parentKey == null) {
    return blockMap;
  }
  const parent = blockMap.get(parentKey);
  const existingChildren = parent.getChildKeys();
  const blockIndex = existingChildren.indexOf(key);
  if (blockIndex === 0 || blockIndex === existingChildren.count() - 1) {
    blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
  } else {
    // split the block into [0, blockIndex] in parent & the rest in a new block
    const prevChildren = existingChildren.slice(0, blockIndex + 1);
    const nextChildren = existingChildren.slice(blockIndex + 1);
    blockMap = blockMap.set(parentKey, parent.merge({children: prevChildren}));
    const newBlock = new ContentBlockNode({
      key: generateRandomKey(),
      text: '',
      depth: parent.getDepth(),
      type: parent.getType(),
      children: nextChildren,
      parent: parent.getParentKey(),
    });
    // add new block just before its the original next sibling in the block map
    // TODO(T33894878): Remove the map reordering code & fix converter after launch
    invariant(nextSiblingKey != null, 'block must have a next sibling here');
    const blocks = blockMap.toSeq();
    blockMap = blocks
      .takeUntil(block => block.getKey() === nextSiblingKey)
      .concat(
        [[newBlock.getKey(), newBlock]],
        blocks.skipUntil(block => block.getKey() === nextSiblingKey),
      )
      .toOrderedMap();

    // set the nextChildren's parent to the new block
    blockMap = blockMap.map(
      block =>
        nextChildren.includes(block.getKey())
          ? block.merge({parent: newBlock.getKey()})
          : block,
    );
    // update the next/previous pointers for the children at the split
    blockMap = blockMap
      .set(key, block.merge({nextSibling: null}))
      .set(
        nextSiblingKey,
        blockMap.get(nextSiblingKey).merge({prevSibling: null}),
      );
    const parentNextSiblingKey = parent.getNextSiblingKey();
    if (parentNextSiblingKey != null) {
      blockMap = DraftTreeOperations.updateSibling(
        blockMap,
        newBlock.getKey(),
        parentNextSiblingKey,
      );
    }
    blockMap = DraftTreeOperations.updateSibling(
      blockMap,
      parentKey,
      newBlock.getKey(),
    );
    blockMap = DraftTreeOperations.moveChildUp(blockMap, key);
  }

  // on untab, we also want to unnest any sibling blocks that become two levels deep
  // ensure that block's old parent does not have a non-leaf as its first child.
  let childWasUntabbed = false;
  if (parentKey != null) {
    let parent = blockMap.get(parentKey);
    while (parent != null) {
      const children = parent.getChildKeys();
      const firstChildKey = children.first();
      invariant(firstChildKey != null, 'parent must have at least one child');
      const firstChild = blockMap.get(firstChildKey);
      if (firstChild.getChildKeys().count() === 0) {
        break;
      } else {
        blockMap = DraftTreeOperations.moveChildUp(blockMap, firstChildKey);
        parent = blockMap.get(parentKey);
        childWasUntabbed = true;
      }
    }
  }

  // now, we may be in a state with two non-leaf blocks of the same type
  // next to each other
  if (childWasUntabbed && parentKey != null) {
    const parent = blockMap.get(parentKey);
    const prevSiblingKey =
      parent != null // parent may have been deleted
        ? parent.getPrevSiblingKey()
        : null;
    if (prevSiblingKey != null && parent.getChildKeys().count() > 0) {
      const prevSibling = blockMap.get(prevSiblingKey);
      if (prevSibling != null && prevSibling.getChildKeys().count() > 0) {
        blockMap = DraftTreeOperations.mergeBlocks(blockMap, prevSiblingKey);
      }
    }
  }
  return blockMap;
};
  return blockMap.withMutations(blockMapState => {
    const targetKey = targetBlock.getKey();
    const headKey = fragmentHeadBlock.getKey();
    const targetNextKey = targetBlock.getNextSiblingKey();
    const targetParentKey = targetBlock.getParentKey();
    const fragmentRootBlocks = getRootBlocks(fragmentHeadBlock, blockMap);
    const lastRootFragmentBlockKey =
      fragmentRootBlocks[fragmentRootBlocks.length - 1];

    if (blockMapState.get(headKey)) {
      // update the fragment head when it is part of the blockMap otherwise
      blockMapState.setIn([targetKey, 'nextSibling'], headKey);
      blockMapState.setIn([headKey, 'prevSibling'], targetKey);
    } else {
      // update the target block that had the fragment head contents merged into it
      blockMapState.setIn(
        [targetKey, 'nextSibling'],
        fragmentHeadBlock.getNextSiblingKey(),
      );
      blockMapState.setIn(
        [fragmentHeadBlock.getNextSiblingKey(), 'prevSibling'],
        targetKey,
      );
    }

    // update the last root block fragment
    blockMapState.setIn(
      [lastRootFragmentBlockKey, 'nextSibling'],
      targetNextKey,
    );

    // update the original target next block
    if (targetNextKey) {
      blockMapState.setIn(
        [targetNextKey, 'prevSibling'],
        lastRootFragmentBlockKey,
      );
    }

    // update fragment parent links
    fragmentRootBlocks.forEach(blockKey =>
      blockMapState.setIn([blockKey, 'parent'], targetParentKey),
    );

    // update targetBlock parent child links
    if (targetParentKey) {
      const targetParent = blockMap.get(targetParentKey);
      const originalTargetParentChildKeys = targetParent.getChildKeys();

      const targetBlockIndex = originalTargetParentChildKeys.indexOf(targetKey);
      const insertionIndex = targetBlockIndex + 1;

      const newChildrenKeysArray = originalTargetParentChildKeys.toArray();

      // insert fragment children
      newChildrenKeysArray.splice(insertionIndex, 0, ...fragmentRootBlocks);

      blockMapState.setIn(
        [targetParentKey, 'children'],
        List(newChildrenKeysArray),
      );
    }
  });
  return blockMap.withMutations(blocks => {
    // update start block if its retained
    transformBlock(startBlock.getKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(startBlock, blocks, originalBlockMap),
        prevSibling: getPrevValidSibling(startBlock, blocks, originalBlockMap),
      }),
    );

    // update endblock if its retained
    transformBlock(endBlock.getKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(endBlock, blocks, originalBlockMap),
        prevSibling: getPrevValidSibling(endBlock, blocks, originalBlockMap),
      }),
    );

    // update start block parent ancestors
    getAncestorsKeys(startBlock.getKey(), originalBlockMap).forEach(parentKey =>
      transformBlock(parentKey, blocks, block =>
        block.merge({
          children: block.getChildKeys().filter(key => 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, block =>
      block.merge({
        prevSibling: startBlock.getPrevSiblingKey(),
      }),
    );

    // update start block prev
    transformBlock(startBlock.getPrevSiblingKey(), blocks, block =>
      block.merge({
        nextSibling: getNextValidSibling(startBlock, blocks, originalBlockMap),
      }),
    );

    // update end block next
    transformBlock(endBlock.getNextSiblingKey(), blocks, block =>
      block.merge({
        prevSibling: getPrevValidSibling(endBlock, blocks, originalBlockMap),
      }),
    );

    // update end block prev
    transformBlock(endBlock.getPrevSiblingKey(), blocks, block =>
      block.merge({
        nextSibling: endBlock.getNextSiblingKey(),
      }),
    );

    // update end block parent ancestors
    getAncestorsKeys(endBlock.getKey(), originalBlockMap).forEach(parentKey => {
      transformBlock(parentKey, blocks, block =>
        block.merge({
          children: block.getChildKeys().filter(key => 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(
      delimiterKey =>
        transformBlock(delimiterKey, blocks, block =>
          block.merge({
            nextSibling: getNextValidSibling(block, blocks, originalBlockMap),
            prevSibling: getPrevValidSibling(block, blocks, originalBlockMap),
          }),
        ),
    );
  });