Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

224 строки
7.9 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. * @flow
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. declare var __DEV__: boolean;
  13. import type { BlockMap } from "./BlockMap";
  14. import type { BlockNodeConfig } from "./BlockNode";
  15. import type CharacterMetadata from "./CharacterMetadata";
  16. import type { RawDraftContentBlock } from "./RawDraftContentBlock";
  17. import type { RawDraftContentState } from "./RawDraftContentState";
  18. const ContentBlock = require("./ContentBlock");
  19. const ContentBlockNode = require("./ContentBlockNode");
  20. const ContentState = require("./ContentState");
  21. const DraftEntity = require("./DraftEntity");
  22. const DraftTreeAdapter = require("./DraftTreeAdapter");
  23. const DraftTreeInvariants = require("./DraftTreeInvariants");
  24. const SelectionState = require("./SelectionState");
  25. const createCharacterList = require("./createCharacterList");
  26. const decodeEntityRanges = require("./decodeEntityRanges");
  27. const decodeInlineStyleRanges = require("./decodeInlineStyleRanges");
  28. const generateRandomKey = require("./generateRandomKey");
  29. const gkx = require("./gkx");
  30. const Immutable = require("immutable");
  31. const invariant = require("fbjs/lib/invariant");
  32. const experimentalTreeDataSupport = gkx('draft_tree_data_support');
  33. const {
  34. List,
  35. Map,
  36. OrderedMap
  37. } = Immutable;
  38. const decodeBlockNodeConfig = (block: RawDraftContentBlock, entityMap: *): BlockNodeConfig => {
  39. const {
  40. key,
  41. type,
  42. data,
  43. text,
  44. depth
  45. } = block;
  46. const blockNodeConfig: BlockNodeConfig = {
  47. text,
  48. depth: depth || 0,
  49. type: type || 'unstyled',
  50. key: key || generateRandomKey(),
  51. data: Map(data),
  52. characterList: decodeCharacterList(block, entityMap)
  53. };
  54. return blockNodeConfig;
  55. };
  56. const decodeCharacterList = (block: RawDraftContentBlock, entityMap: *): List<CharacterMetadata> => {
  57. const {
  58. text,
  59. entityRanges: rawEntityRanges,
  60. inlineStyleRanges: rawInlineStyleRanges
  61. } = block;
  62. const entityRanges = rawEntityRanges || [];
  63. const inlineStyleRanges = rawInlineStyleRanges || []; // Translate entity range keys to the DraftEntity map.
  64. return createCharacterList(decodeInlineStyleRanges(text, inlineStyleRanges), decodeEntityRanges(text, entityRanges.filter(range => entityMap.hasOwnProperty(range.key)).map(range => ({ ...range,
  65. key: entityMap[range.key]
  66. }))));
  67. };
  68. const addKeyIfMissing = (block: RawDraftContentBlock): RawDraftContentBlock => {
  69. return { ...block,
  70. key: block.key || generateRandomKey()
  71. };
  72. };
  73. /**
  74. * Node stack is responsible to ensure we traverse the tree only once
  75. * in depth order, while also providing parent refs to inner nodes to
  76. * construct their links.
  77. */
  78. const updateNodeStack = (stack: Array<*>, nodes: Array<*>, parentRef: ContentBlockNode): Array<*> => {
  79. const nodesWithParentRef = nodes.map(block => {
  80. return { ...block,
  81. parentRef
  82. };
  83. }); // since we pop nodes from the stack we need to insert them in reverse
  84. return stack.concat(nodesWithParentRef.reverse());
  85. };
  86. /**
  87. * This will build a tree draft content state by creating the node
  88. * reference links into a single tree walk. Each node has a link
  89. * reference to "parent", "children", "nextSibling" and "prevSibling"
  90. * blockMap will be created using depth ordering.
  91. */
  92. const decodeContentBlockNodes = (blocks: Array<RawDraftContentBlock>, entityMap: *): BlockMap => {
  93. return blocks // ensure children have valid keys to enable sibling links
  94. .map(addKeyIfMissing).reduce((blockMap: BlockMap, block: RawDraftContentBlock, index: number) => {
  95. invariant(Array.isArray(block.children), 'invalid RawDraftContentBlock can not be converted to ContentBlockNode'); // ensure children have valid keys to enable sibling links
  96. const children = block.children.map(addKeyIfMissing); // root level nodes
  97. const contentBlockNode = new ContentBlockNode({ ...decodeBlockNodeConfig(block, entityMap),
  98. prevSibling: index === 0 ? null : blocks[index - 1].key,
  99. nextSibling: index === blocks.length - 1 ? null : blocks[index + 1].key,
  100. children: List(children.map((child: any) => child.key))
  101. }); // push root node to blockMap
  102. blockMap = blockMap.set(contentBlockNode.getKey(), contentBlockNode); // this stack is used to ensure we visit all nodes respecting depth ordering
  103. let stack = updateNodeStack([], children, contentBlockNode); // start computing children nodes
  104. while (stack.length > 0) {
  105. // we pop from the stack and start processing this node
  106. const node: any = stack.pop(); // parentRef already points to a converted ContentBlockNode
  107. const parentRef: ContentBlockNode = node.parentRef;
  108. const siblings = parentRef.getChildKeys();
  109. const index = siblings.indexOf(node.key);
  110. const isValidBlock = Array.isArray(node.children);
  111. if (!isValidBlock) {
  112. invariant(isValidBlock, 'invalid RawDraftContentBlock can not be converted to ContentBlockNode');
  113. break;
  114. } // ensure children have valid keys to enable sibling links
  115. const children = node.children.map(addKeyIfMissing);
  116. const contentBlockNode = new ContentBlockNode({ ...decodeBlockNodeConfig(node, entityMap),
  117. parent: parentRef.getKey(),
  118. children: List(children.map((child: any) => child.key)),
  119. prevSibling: index === 0 ? null : siblings.get(index - 1),
  120. nextSibling: index === siblings.size - 1 ? null : siblings.get(index + 1)
  121. }); // push node to blockMap
  122. blockMap = blockMap.set(contentBlockNode.getKey(), contentBlockNode); // this stack is used to ensure we visit all nodes respecting depth ordering
  123. stack = updateNodeStack(stack, children, contentBlockNode);
  124. }
  125. return blockMap;
  126. }, OrderedMap());
  127. };
  128. const decodeContentBlocks = (blocks: Array<RawDraftContentBlock>, entityMap: *): BlockMap => {
  129. return OrderedMap(blocks.map((block: RawDraftContentBlock) => {
  130. const contentBlock = new ContentBlock(decodeBlockNodeConfig(block, entityMap));
  131. return [contentBlock.getKey(), contentBlock];
  132. }));
  133. };
  134. const decodeRawBlocks = (rawState: RawDraftContentState, entityMap: *): BlockMap => {
  135. const isTreeRawBlock = rawState.blocks.find(block => Array.isArray(block.children) && block.children.length > 0);
  136. const rawBlocks = experimentalTreeDataSupport && !isTreeRawBlock ? DraftTreeAdapter.fromRawStateToRawTreeState(rawState).blocks : rawState.blocks;
  137. if (!experimentalTreeDataSupport) {
  138. return decodeContentBlocks(isTreeRawBlock ? DraftTreeAdapter.fromRawTreeStateToRawState(rawState).blocks : rawBlocks, entityMap);
  139. }
  140. const blockMap = decodeContentBlockNodes(rawBlocks, entityMap); // in dev mode, check that the tree invariants are met
  141. if (__DEV__) {
  142. invariant(DraftTreeInvariants.isValidTree(blockMap), 'Should be a valid tree');
  143. }
  144. return blockMap;
  145. };
  146. const decodeRawEntityMap = (rawState: RawDraftContentState): * => {
  147. const {
  148. entityMap: rawEntityMap
  149. } = rawState;
  150. const entityMap = {}; // TODO: Update this once we completely remove DraftEntity
  151. Object.keys(rawEntityMap).forEach(rawEntityKey => {
  152. const {
  153. type,
  154. mutability,
  155. data
  156. } = rawEntityMap[rawEntityKey]; // get the key reference to created entity
  157. entityMap[rawEntityKey] = DraftEntity.__create(type, mutability, data || {});
  158. });
  159. return entityMap;
  160. };
  161. const convertFromRawToDraftState = (rawState: RawDraftContentState): ContentState => {
  162. invariant(Array.isArray(rawState.blocks), 'invalid RawDraftContentState'); // decode entities
  163. const entityMap = decodeRawEntityMap(rawState); // decode blockMap
  164. const blockMap = decodeRawBlocks(rawState, entityMap); // create initial selection
  165. const selectionState = blockMap.isEmpty() ? new SelectionState() : SelectionState.createEmpty(blockMap.first().getKey());
  166. return new ContentState({
  167. blockMap,
  168. entityMap,
  169. selectionBefore: selectionState,
  170. selectionAfter: selectionState
  171. });
  172. };
  173. module.exports = convertFromRawToDraftState;