/** * 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 convertFromRawToDraftState * @format * */ 'use strict'; var _assign = require('object-assign'); var _extends = _assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var ContentBlock = require('./ContentBlock'); var ContentBlockNode = require('./ContentBlockNode'); var ContentState = require('./ContentState'); var DraftEntity = require('./DraftEntity'); var DraftFeatureFlags = require('./DraftFeatureFlags'); var DraftTreeAdapter = require('./DraftTreeAdapter'); var Immutable = require('immutable'); var SelectionState = require('./SelectionState'); var createCharacterList = require('./createCharacterList'); var decodeEntityRanges = require('./decodeEntityRanges'); var decodeInlineStyleRanges = require('./decodeInlineStyleRanges'); var generateRandomKey = require('./generateRandomKey'); var invariant = require('fbjs/lib/invariant'); var experimentalTreeDataSupport = DraftFeatureFlags.draft_tree_data_support; var List = Immutable.List, Map = Immutable.Map, OrderedMap = Immutable.OrderedMap; var decodeBlockNodeConfig = function decodeBlockNodeConfig(block, entityMap) { var key = block.key, type = block.type, data = block.data, text = block.text, depth = block.depth; var blockNodeConfig = { text: text, depth: depth || 0, type: type || 'unstyled', key: key || generateRandomKey(), data: Map(data), characterList: decodeCharacterList(block, entityMap) }; return blockNodeConfig; }; var decodeCharacterList = function decodeCharacterList(block, entityMap) { var text = block.text, rawEntityRanges = block.entityRanges, rawInlineStyleRanges = block.inlineStyleRanges; var entityRanges = rawEntityRanges || []; var inlineStyleRanges = rawInlineStyleRanges || []; // Translate entity range keys to the DraftEntity map. return createCharacterList(decodeInlineStyleRanges(text, inlineStyleRanges), decodeEntityRanges(text, entityRanges.filter(function (range) { return entityMap.hasOwnProperty(range.key); }).map(function (range) { return _extends({}, range, { key: entityMap[range.key] }); }))); }; var addKeyIfMissing = function addKeyIfMissing(block) { return _extends({}, block, { key: block.key || generateRandomKey() }); }; /** * Node stack is responsible to ensure we traverse the tree only once * in depth order, while also providing parent refs to inner nodes to * construct their links. */ var updateNodeStack = function updateNodeStack(stack, nodes, parentRef) { var nodesWithParentRef = nodes.map(function (block) { return _extends({}, block, { parentRef: parentRef }); }); // since we pop nodes from the stack we need to insert them in reverse return stack.concat(nodesWithParentRef.reverse()); }; /** * This will build a tree draft content state by creating the node * reference links into a single tree walk. Each node has a link * reference to "parent", "children", "nextSibling" and "prevSibling" * blockMap will be created using depth ordering. */ var decodeContentBlockNodes = function decodeContentBlockNodes(blocks, entityMap) { return blocks // ensure children have valid keys to enable sibling links .map(addKeyIfMissing).reduce(function (blockMap, block, index) { !Array.isArray(block.children) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'invalid RawDraftContentBlock can not be converted to ContentBlockNode') : invariant(false) : void 0; // ensure children have valid keys to enable sibling links var children = block.children.map(addKeyIfMissing); // root level nodes var contentBlockNode = new ContentBlockNode(_extends({}, 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(function (child) { return 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 var stack = updateNodeStack([], children, contentBlockNode); // start computing children nodes while (stack.length > 0) { // we pop from the stack and start processing this node var node = stack.pop(); // parentRef already points to a converted ContentBlockNode var parentRef = node.parentRef; var siblings = parentRef.getChildKeys(); var _index = siblings.indexOf(node.key); var isValidBlock = Array.isArray(node.children); if (!isValidBlock) { !isValidBlock ? process.env.NODE_ENV !== 'production' ? invariant(false, 'invalid RawDraftContentBlock can not be converted to ContentBlockNode') : invariant(false) : void 0; break; } // ensure children have valid keys to enable sibling links var _children = node.children.map(addKeyIfMissing); var _contentBlockNode = new ContentBlockNode(_extends({}, decodeBlockNodeConfig(node, entityMap), { parent: parentRef.getKey(), children: List(_children.map(function (child) { return 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; }, OrderedMap()); }; var decodeContentBlocks = function decodeContentBlocks(blocks, entityMap) { return OrderedMap(blocks.map(function (block) { var contentBlock = new ContentBlock(decodeBlockNodeConfig(block, entityMap)); return [contentBlock.getKey(), contentBlock]; })); }; var decodeRawBlocks = function decodeRawBlocks(rawState, entityMap) { var isTreeRawBlock = Array.isArray(rawState.blocks[0].children); var rawBlocks = experimentalTreeDataSupport && !isTreeRawBlock ? DraftTreeAdapter.fromRawStateToRawTreeState(rawState).blocks : rawState.blocks; if (!experimentalTreeDataSupport) { return decodeContentBlocks(isTreeRawBlock ? DraftTreeAdapter.fromRawTreeStateToRawState(rawState).blocks : rawBlocks, entityMap); } return decodeContentBlockNodes(rawBlocks, entityMap); }; var decodeRawEntityMap = function decodeRawEntityMap(rawState) { var rawEntityMap = rawState.entityMap; var entityMap = {}; // TODO: Update this once we completely remove DraftEntity Object.keys(rawEntityMap).forEach(function (rawEntityKey) { var _rawEntityMap$rawEnti = rawEntityMap[rawEntityKey], type = _rawEntityMap$rawEnti.type, mutability = _rawEntityMap$rawEnti.mutability, data = _rawEntityMap$rawEnti.data; // get the key reference to created entity entityMap[rawEntityKey] = DraftEntity.__create(type, mutability, data || {}); }); return entityMap; }; var convertFromRawToDraftState = function convertFromRawToDraftState(rawState) { !Array.isArray(rawState.blocks) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'invalid RawDraftContentState') : invariant(false) : void 0; // decode entities var entityMap = decodeRawEntityMap(rawState); // decode blockMap var blockMap = decodeRawBlocks(rawState, entityMap); // create initial selection var selectionState = blockMap.isEmpty() ? new SelectionState() : SelectionState.createEmpty(blockMap.first().getKey()); return new ContentState({ blockMap: blockMap, entityMap: entityMap, selectionBefore: selectionState, selectionAfter: selectionState }); }; module.exports = convertFromRawToDraftState;