HubSpot/draft-convert

convertFromHTML and inline images

MamorukunBE opened this issue · 0 comments

Hi everyone,

in draftjs I permits the users to add png smileys. They are added in the blockState as a special charater (⅋) linked to an entity holding the image data. At the render, a custom decorator permits me to display the smiley in place of the special character.

When I serialize the daftjs content, I use convertToHTML (to end up with a user-friendly html content), I transform that special character/entity into a usual <img /> whithin the text (ie. "This smiley <img src="..."> is quite pretty"), and all is fine.

The problem is when I try to deserialize that html content: using htmlToEntity, I end up with my smiley in its own atomic block cuting the text in two parts ; and using textToEntity I end up with my smiley no more linked to any character.

In other words, what I need, when deserializing my html, is to create the corresponding entity BUT ALSO to recreate that special character which will be the link to the created entity. Any hint on how to do this?

Thank you very much in advance :)

EDIT:
While waiting for an answer, I found some kind of trick to bypass draft-convert limitations on this matter: preprocess the html to replace all the smileys <img> tags by "⅋{{image_src}}" (which is not touched by convertFromHTML, and thus finishes in the contentState), then change all those entries in the said contentState manually with this functionnality:

export function InsertPngSmileys(editorState) {
  // Scan each block for inline smileys
  //-----------------------------------
  let workContentState = editorState.getCurrentContent();
  let smileysData = {};
  getAllBlocks(editorState).forEach((block, i) => {
    const blockKey = block.getKey();
    const blockText = block.getText();
    //-----
    Object.keys(smileysData).forEach(smileyKey => smileysData[smileyKey].positionsData = []);   // Clean previous block positions

    // Replace all the smileys entries by the png smileys anchor
    //----------------------------------------------------------
    let match = null, regex = RegExp(`${constants.PNG_SMILEY_ANCHOR}({{([^}]*)}})`, 'g');
    while ((match = regex.exec(blockText)) !== null) {
      let smileySrcId = hashString(match[2]);
      let dataStartPos = match.index + constants.PNG_SMILEY_ANCHOR.length;
      //-----
      if (!smileysData[smileySrcId])
        smileysData[smileySrcId] = { src: match[2], entityKey: null, positionsData: [] };
      smileysData[smileySrcId].positionsData.push({ anchorPos: match.index, dataStart: dataStartPos, dataEnd: dataStartPos + match[1].length
      });
    }

    // Then, if any, clean the smiley anchors, create the corresponding entities and link them
    //----------------------------------------------------------------------------------------
    let blockSelection = SelectionState.createEmpty(blockKey);
    for (let smileyData of Object.values(smileysData)) {
      // Create the entity, if not done yet
      //-----------------------------------
      if (!smileyData.entityKey) {
        workContentState = workContentState.createEntity('smiley', 'MUTABLE', { src: smileyData.src });
        smileyData.entityKey = workContentState.getLastCreatedEntityKey();
      }

      // Then processes all its positions. Do it in reverse order so
      // we don't have to recalculate the positions at each change
      //------------------------------------------------------------
      smileyData.positionsData.reverse().forEach(positionData => {
        // Clean the smiley anchor
        //------------------------
        blockSelection = blockSelection.merge({ anchorOffset: positionData.dataStart, focusOffset: positionData.dataEnd });
        workContentState = Modifier.replaceText(workContentState, blockSelection, '');

        // Link the entity to each position which needs it
        //------------------------------------------------
        const smileyAnchorPosition = SelectionState.createEmpty(blockKey).merge({ anchorOffset: positionData.anchorPos, focusOffset: positionData.dataStart })
        workContentState = Modifier.applyEntity(workContentState, smileyAnchorPosition, smileyData.entityKey);
      });
    }
  });

  // And finaly, create and return a new EditorState with the modified content
  //--------------------------------------------------------------------------
  return EditorState.createWithContent(workContentState);
}