0

So I have been trying to get the method convertFromHTML to translate Images into an atomic block so that it may be compatible with the draft-js-image-plugin since it expects blocks of the type atomic

Given a simple HTML structure with some text and an image out of the box the convertFromHTML produces this contentState:

{
  "blocks": [
    {
      "key": "82k8",
      "text": "‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [],
      "data": {}
    },
    {
      "key": "9jbor",
      "text": "On December 29, 2020, 5:20 PM EST  txwbi.nrjrtn@gmail.com wrote:",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [{ "offset": 34, "length": 23, "key": 0 }],
      "data": {}
    },
    {
      "key": "anq8o",
      "text": "A bunch of text here to test out the body",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [
        { "offset": 3, "length": 13, "style": "ITALIC" },
        { "offset": 3, "length": 13, "style": "UNDERLINE" },
        { "offset": 38, "length": 4, "style": "BOLD" }
      ],
      "entityRanges": [{ "offset": 0, "length": 1, "key": 1 }],
      "data": {}
    }
  ],
  "entityMap": {
    "0": {
      "type": "LINK",
      "mutability": "MUTABLE",
      "data": {
        "href": "mailto:txwbi.nrjrtn@gmail.com",
        "rel": "noreferrer nofollow noopener",
        "target": "_blank",
        "url": "mailto:txwbi.nrjrtn@gmail.com"
      }
    },
    "1": {
      "type": "IMAGE",
      "mutability": "IMMUTABLE",
      "data": {
        "alt": "cory_emoji.png",
        "height": "210",
        "src": "data:image/png;base64, ...BASE64ENCODEDIMAGE WOULD BE HERE I REMOVED BECAUSE OF CHAR LIMITS",
        "width": "173"
      }
    }
  }
}

Which we can see that the img tag takes on a unstyled block which is not what I'd like. So I created the following function that extends the default Block Render Map to account for the img tag:

const {
  EditorState,
  convertToRaw,
  DefaultDraftBlockRenderMap,
  ContentState,
  convertFromHTML,
  getSafeBodyFromHTML
} = require('draft-js');

const Immutable = require('immutable');

module.exports.editorStateFromHTML = htmlBody => {
  console.log('HTML ---> EDITOR ::: RAW BODY', htmlBody);
  const blockRenderMap = Immutable.Map({
    atomic: {
      element: 'figure',
      aliasedElements: ['img']
    }
  });

  const extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(
    blockRenderMap
  );

  const blocksFromHTML = convertFromHTML(
    htmlBody,
    getSafeBodyFromHTML,
    extendedBlockRenderMap
  );

  console.log(blocksFromHTML);
  const state = ContentState.createFromBlockArray(
    blocksFromHTML.contentBlocks,
    blocksFromHTML.entityMap
  );
  console.log(JSON.stringify(convertToRaw(state)));
  const newEditor = EditorState.createWithContent(state);
  return newEditor;
};

Which results in this content State:

{
  "blocks": [
    {
      "key": "fhgqd",
      "text": "‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [],
      "data": {}
    },
    {
      "key": "2nsnk",
      "text": "On December 29, 2020, 5:20 PM EST  txwbi.nrjrtn@gmail.com wrote:",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [{ "offset": 34, "length": 23, "key": 0 }],
      "data": {}
    },
    {
      "key": "7c7cu",
      "text": "A bunch of text here to test out the body",
      "type": "unstyled",
      "depth": 0,
      "inlineStyleRanges": [
        { "offset": 2, "length": 13, "style": "UNDERLINE" },
        { "offset": 2, "length": 13, "style": "ITALIC" },
        { "offset": 37, "length": 4, "style": "BOLD" }
      ],
      "entityRanges": [],
      "data": {}
    },
    {
      "key": "f84vb",
      "text": "",
      "type": "atomic",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [],
      "data": {}
    }
  ],
  "entityMap": {
    "0": {
      "type": "LINK",
      "mutability": "MUTABLE",
      "data": {
        "href": "mailto:txwbi.nrjrtn@gmail.com",
        "rel": "noreferrer nofollow noopener",
        "target": "_blank",
        "url": "mailto:txwbi.nrjrtn@gmail.com"
      }
    }
  }
}

As you can see the entityMap now only has one key and all the data for the image is no longer there. How can I get it to make the img tag an atomic block while still creating the IMAGE entity in the entityMap???

Tekill
  • 1,171
  • 1
  • 14
  • 30

2 Answers2

0

My work around was has follow it's kind of ugly but it works and gives me the right output:

const {
  EditorState,
  convertToRaw,
  convertFromRaw,
  DefaultDraftBlockRenderMap,
  ContentState,
  convertFromHTML,
  getSafeBodyFromHTML
} = require('draft-js');

const Immutable = require('immutable');
const clone = require('rfdc')();

module.exports.editorStateFromHTML = htmlBody => {
  console.log('HTML ---> EDITOR ::: RAW BODY', htmlBody);
  const blockRenderMap = Immutable.Map({
    image: {
      element: 'img'
    }
  });

  const extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(
    blockRenderMap
  );

  const blocksFromHTML = convertFromHTML(
    htmlBody,
    getSafeBodyFromHTML,
    extendedBlockRenderMap
  );

  const state = ContentState.createFromBlockArray(
    blocksFromHTML.contentBlocks,
    blocksFromHTML.entityMap
  );
  const { blocks, entityMap } = convertToRaw(state);
  const imgCount = blocks.filter(b => b.type === 'image').length;

  if (imgCount > 0) {
    const fixedEntityMap = clone(entityMap);
    let arrKeys = Object.keys(fixedEntityMap);
    arrKeys = arrKeys.map(k => parseInt(k, 10));
    const lastKey = Math.max(...arrKeys);
    let blockCounter = lastKey;

    const fixedContentBlocks = blocks.map(blck => {
      if (blck.type === 'image') {
        blockCounter += 1;
        return {
          ...blck,
          text: ' ',
          type: 'atomic',
          entityRanges: [{ offset: 0, length: 1, key: blockCounter }]
        };
      }
      return blck;
    });

    const blocksFromHTML2 = convertFromHTML(htmlBody);
    const state2 = ContentState.createFromBlockArray(
      blocksFromHTML2.contentBlocks,
      blocksFromHTML2.entityMap
    );
    const { entityMap: imgEntities } = convertToRaw(state2);

    let entityCounter = lastKey;
    // eslint-disable-next-line no-restricted-syntax
    for (const [key, value] of Object.entries(imgEntities)) {
      if (value.type === 'IMAGE') {
        entityCounter += 1;
        fixedEntityMap[entityCounter] = value;
      }
    }

    const editorDefaultValue = {
      blocks: fixedContentBlocks,
      entityMap: fixedEntityMap
    };
    return EditorState.createWithContent(convertFromRaw(editorDefaultValue));
  }

  return EditorState.createWithContent(state);
};

If Anyone has a better solution I am all ears.

Tekill
  • 1,171
  • 1
  • 14
  • 30
0

AtomicBlockUtils.insertAtomicBlock replaces selected block with atomic block and unstyled dividers before and after it. Therefore its enough to set EditorState.selection to the corresponding block, then use insertAtomicBlock method:

if (value) {
    const blocksFromHTML = convertFromHTML(value);
    const contentState = ContentState.createFromBlockArray(
        blocksFromHTML.contentBlocks,
        blocksFromHTML.entityMap
    );

    let resultEditorState = EditorState.createWithContent(contentState, decorator);

    contentState.getBlocksAsArray().forEach(block => {

        //searching blocks with images. There may be other strategy, but this suited me
        const entityKey = contentState.getBlockForKey(block.key).getEntityAt(0);
        const hasImage =
            entityKey &&
            contentState
                .getEntityMap()
                .get(entityKey)
                .getType() === 'IMAGE';

        //replacing blocks with image with atomic
        if (hasImage) {
            resultEditorState = EditorState.acceptSelection(
                resultEditorState,
                SelectionState.createEmpty(block.key)
            );
            resultEditorState = AtomicBlockUtils.insertAtomicBlock(resultEditorState, entityKey, ' ');
        }
    });

    return resultEditorState;
}

return EditorState.createEmpty(decorator);