DraftTreeAdapter.js.flow 3.63 KB
Newer Older
qianyuheng committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
/**
 * 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 DraftTreeAdapter
 * @format
 * @flow
 *
 * This is unstable and not part of the public API and should not be used by
 * production systems. This file may be update/removed without notice.
 */

import type { RawDraftContentBlock } from './RawDraftContentBlock';
import type { RawDraftContentState } from './RawDraftContentState';

const invariant = require('fbjs/lib/invariant');

const traverseInDepthOrder = (blocks: Array<RawDraftContentBlock>, fn: (block: RawDraftContentBlock) => void) => {
  let stack = [...blocks].reverse();
  while (stack.length) {
    const block = stack.pop();

    fn(block);

    const children = block.children;

    invariant(Array.isArray(children), 'Invalid tree raw block');

    stack = stack.concat([...children.reverse()]);
  }
};

const isListBlock = (block?: RawDraftContentBlock): boolean => {
  if (!(block && block.type)) {
    return false;
  }
  const { type } = block;
  return type === 'unordered-list-item' || type === 'ordered-list-item';
};

const addDepthToChildren = (block: RawDraftContentBlock) => {
  if (Array.isArray(block.children)) {
    block.children = block.children.map(child => child.type === block.type ? { ...child, depth: (block.depth || 0) + 1 } : child);
  }
};

/**
 * This adapter is intended to be be used as an adapter to draft tree data
 *
 * draft state <=====> draft tree state
 */
const DraftTreeAdapter = {
  /**
   * Converts from a tree raw state back to  draft raw state
   */
  fromRawTreeStateToRawState(draftTreeState: RawDraftContentState): RawDraftContentState {
    const { blocks } = draftTreeState;
    const transformedBlocks = [];

    invariant(Array.isArray(blocks), 'Invalid raw state');

    if (!Array.isArray(blocks) || !blocks.length) {
      return draftTreeState;
    }

    traverseInDepthOrder(blocks, block => {
      const newBlock = {
        ...block
      };

      if (isListBlock(block)) {
        newBlock.depth = newBlock.depth || 0;
        addDepthToChildren(block);
      }

      delete newBlock.children;

      transformedBlocks.push(newBlock);
    });

    draftTreeState.blocks = transformedBlocks;

    return {
      ...draftTreeState,
      blocks: transformedBlocks
    };
  },

  /**
   * Converts from draft raw state to tree draft state
   */
  fromRawStateToRawTreeState(draftState: RawDraftContentState): RawDraftContentState {
    let lastListDepthCacheRef = {};
    const transformedBlocks = [];

    draftState.blocks.forEach(block => {
      const isList = isListBlock(block);
      const depth = block.depth || 0;
      const treeBlock = {
        ...block,
        children: []
      };

      if (!isList) {
        // reset the cache path
        lastListDepthCacheRef = {};
        transformedBlocks.push(treeBlock);
        return;
      }

      // update our depth cache reference path
      lastListDepthCacheRef[depth] = treeBlock;

      // if we are greater than zero we must have seen a parent already
      if (depth > 0) {
        const parent = lastListDepthCacheRef[depth - 1];

        invariant(parent, 'Invalid depth for RawDraftContentBlock');

        // push nested list blocks
        parent.children.push(treeBlock);
        return;
      }

      // push root list blocks
      transformedBlocks.push(treeBlock);
    });

    return {
      ...draftState,
      blocks: transformedBlocks
    };
  }
};

module.exports = DraftTreeAdapter;