/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format * * @emails oncall+draft_js */ 'use strict'; function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var ContentBlock = require("./ContentBlock"); var ContentBlockNode = require("./ContentBlockNode"); var ContentState = require("./ContentState"); var DraftEntity = require("./DraftEntity"); var DraftTreeAdapter = require("./DraftTreeAdapter"); var DraftTreeInvariants = require("./DraftTreeInvariants"); var SelectionState = require("./SelectionState"); var createCharacterList = require("./createCharacterList"); var decodeEntityRanges = require("./decodeEntityRanges"); var decodeInlineStyleRanges = require("./decodeInlineStyleRanges"); var generateRandomKey = require("./generateRandomKey"); var gkx = require("./gkx"); var Immutable = require("immutable"); var invariant = require("fbjs/lib/invariant"); var experimentalTreeDataSupport = gkx('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 _objectSpread({}, range, { key: entityMap[range.key] }); }))); }; var addKeyIfMissing = function addKeyIfMissing(block) { return _objectSpread({}, 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 _objectSpread({}, 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(_objectSpread({}, 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(_objectSpread({}, 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 = rawState.blocks.find(function (block) { return Array.isArray(block.children) && block.children.length > 0; }); var rawBlocks = experimentalTreeDataSupport && !isTreeRawBlock ? DraftTreeAdapter.fromRawStateToRawTreeState(rawState).blocks : rawState.blocks; if (!experimentalTreeDataSupport) { return decodeContentBlocks(isTreeRawBlock ? DraftTreeAdapter.fromRawTreeStateToRawState(rawState).blocks : rawBlocks, entityMap); } var blockMap = decodeContentBlockNodes(rawBlocks, entityMap); // in dev mode, check that the tree invariants are met if (process.env.NODE_ENV !== "production") { !DraftTreeInvariants.isValidTree(blockMap) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Should be a valid tree') : invariant(false) : void 0; } return blockMap; }; 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;