/** * 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 * @flow * @emails oncall+draft_js */ 'use strict'; import type { BlockNodeRecord } from "./BlockNodeRecord"; import type ContentState from "./ContentState"; import type { RawDraftContentBlock } from "./RawDraftContentBlock"; import type { RawDraftContentState } from "./RawDraftContentState"; const ContentBlock = require("./ContentBlock"); const ContentBlockNode = require("./ContentBlockNode"); const DraftStringKey = require("./DraftStringKey"); const encodeEntityRanges = require("./encodeEntityRanges"); const encodeInlineStyleRanges = require("./encodeInlineStyleRanges"); const invariant = require("fbjs/lib/invariant"); const createRawBlock = (block: BlockNodeRecord, entityStorageMap: *) => { return { key: block.getKey(), text: block.getText(), type: block.getType(), depth: block.getDepth(), inlineStyleRanges: encodeInlineStyleRanges(block), entityRanges: encodeEntityRanges(block, entityStorageMap), data: block.getData().toObject() }; }; const insertRawBlock = (block: BlockNodeRecord, entityMap: *, rawBlocks: Array, blockCacheRef: *) => { if (block instanceof ContentBlock) { rawBlocks.push(createRawBlock(block, entityMap)); return; } invariant(block instanceof ContentBlockNode, 'block is not a BlockNode'); const parentKey = block.getParentKey(); const rawBlock = blockCacheRef[block.getKey()] = { ...createRawBlock(block, entityMap), children: [] }; if (parentKey) { blockCacheRef[parentKey].children.push(rawBlock); return; } rawBlocks.push(rawBlock); }; const encodeRawBlocks = (contentState: ContentState, rawState: RawDraftContentState): RawDraftContentState => { const { entityMap } = rawState; const rawBlocks = []; const blockCacheRef = {}; const entityCacheRef = {}; let entityStorageKey = 0; contentState.getBlockMap().forEach(block => { block.findEntityRanges(character => character.getEntity() !== null, start => { const entityKey = block.getEntityAt(start); // Stringify to maintain order of otherwise numeric keys. const stringifiedEntityKey = DraftStringKey.stringify(entityKey); // This makes this function resilient to two entities // erroneously having the same key if (entityCacheRef[stringifiedEntityKey]) { return; } entityCacheRef[stringifiedEntityKey] = entityKey; // we need the `any` casting here since this is a temporary state // where we will later on flip the entity map and populate it with // real entity, at this stage we just need to map back the entity // key used by the BlockNode entityMap[stringifiedEntityKey] = (`${entityStorageKey}`: any); entityStorageKey++; }); insertRawBlock(block, entityMap, rawBlocks, blockCacheRef); }); return { blocks: rawBlocks, entityMap }; }; // Flip storage map so that our storage keys map to global // DraftEntity keys. const encodeRawEntityMap = (contentState: ContentState, rawState: RawDraftContentState): RawDraftContentState => { const { blocks, entityMap } = rawState; const rawEntityMap = {}; Object.keys(entityMap).forEach((key, index) => { const entity = contentState.getEntity(DraftStringKey.unstringify(key)); rawEntityMap[index] = { type: entity.getType(), mutability: entity.getMutability(), data: entity.getData() }; }); return { blocks, entityMap: rawEntityMap }; }; const convertFromDraftStateToRaw = (contentState: ContentState): RawDraftContentState => { let rawDraftContentState = { entityMap: {}, blocks: [] }; // add blocks rawDraftContentState = encodeRawBlocks(contentState, rawDraftContentState); // add entities rawDraftContentState = encodeRawEntityMap(contentState, rawDraftContentState); return rawDraftContentState; }; module.exports = convertFromDraftStateToRaw;