jpuri / draftjs-to-html

Library for converting Draftjs editor content state to HTML

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

emoji add fontSize or style by draftToHtmt

bestwishforyou95 opened this issue · comments

example 😫

output

example ��

Could you please help me. Thank you!

Hey I think, I've found the issue.

Javascript stores some emojis(unicodes) inside multiple list indexes (see illustration below). Also, read more here.

image

Then inside editorContent.blocks there seems an inlineStyleRange config mapping to a single index (length: 1)

image

In getInlineStyleSections method, multiple space indexed emoji separated by inlineStyleRanges, which makes the output;

<span style="font-size: 12px;">�</span>�

So, this issue might be related to the official repository due to indexing mistake in editorContent.blocks

Below you can find my work around;

Inside onEditorStateChange method, I'm updating blocks array with the right ones. Which is calculated by finding emoji indexes inside a block's text and updating inlineStyleRange objects with appropriate offset/length values.

...
onEditorStateChange = (editorState) => {
    this.setState({ editorState })
    const rawState = convertToRaw(editorState.getCurrentContent())
    const blocks = manipulateRawBlocks(rawState)
    const newRawState = { ...rawState, blocks }
    const _html = draftToHtml(newRawState)
}
...
/*
 return all index / length pairs which include an emoji
 */
const getEmojiIndexes = (text) => {
  const emojiRegex = require('emoji-regex')
  // Note: because the regular expression has the global flag set, this module
  // exports a function that returns the regex rather than exporting the regular
  // expression itself, to make it impossible to (accidentally) mutate the
  // original regular expression.
  const regex = emojiRegex()
  const result = []
  let match
  while (match = regex.exec(text)) {
    const emoji = match[0]
    const index = text.indexOf(match[0])
    const { length } = emoji
    result.push({ index, length })
  }
  return result
}

/*
 check if inlineStyleRange object's range (offset, length) includes an emoji
 */
const indexMatch = (range, emojiIndex, emojiLength) => {
  const { offset, length } = range
  const rangeEnd = offset + length
  const emojiEnd = emojiIndex + emojiLength
  return offset < emojiEnd && rangeEnd >= emojiIndex
}

/*
 emojis may treated as a single index inside draft block's inlineStyleRanges
 calculate appropriate offset / length for an inlineStyleRange of a raw draft block;
 */
const manipulateStyleRange = (range, emojiIndex, emojiLength) => {
  const { offset, length } = range
  const newOffset = Math.min(offset, emojiIndex)
  const emojiEnd = emojiIndex + emojiLength
  const rangeEnd = offset + length
  const newLength = Math.max(emojiEnd, rangeEnd) - newOffset
  return { offset: newOffset, length: newLength }
}

/*
 find inlineStyleRange objects covering emoji indexes inside a raw draft block,
 update offset and length attributes of these inlineStyleRange objects with an appropriate values
 return manipulated raw editor state object
 */
export const manipulateRawBlocks = rawState => rawState.blocks.map((entry) => {
  const emojiIndexes = getEmojiIndexes(entry.text)
  let { inlineStyleRanges } = entry
  emojiIndexes.forEach(({ index, length }) => {
    inlineStyleRanges = inlineStyleRanges
      .map((inline) => {
        const matches = indexMatch(inline, index, length)
        if (matches) {
          const newRangeConfig = manipulateStyleRange(inline, index, length)
          return { ...inline, ...newRangeConfig }
        }
        return inline
      })
  })
  return { ...entry, inlineStyleRanges }
})

@enginaryum your solution still not work.
Before saved
screen shot 2018-10-03 at 14 21 53
After saved
screen shot 2018-10-03 at 14 22 11
Could you please take a look at this issue?

Hi @ndinhphi ,

Sorry, I couldn't have time lately to look on this. As, I see in the first look the issue relies on htmlToDraft part of things vs draftToHtml. There needs to be another adapter finding emoji indexes and create the initial editorState with updated inlineStyleSections.

I'll work on a fix when I have free time.

@enginaryum Thank you for your support. Now I use Tiny MCE instead of DraftJs. When this issue is fixed, I will use DraftJs again :)

@enginaryum im having the same issue, can't figure out a fix. Would appreciate it if you found a fix for this.

@enginaryum, thanks, your solution works as expected!

@ndinhphi Here is how I solved this issue on my project. Hope it is helpful to you and anyone face this issue.

It is based on @enginaryum solution but support for multiple Emoji at different positions. It requires lodash/inRange.

onEditorStateChange part is the same as @enginaryum one.

import inRange from 'lodash/inRange';

const emojiRegex = require('emoji-regex');

const calculateEmojiNumberWithinRange = (emojiPosition, start, end) => {
  // calculate how many emoji appear in a certain range
  let counter = 0;
  emojiPosition.forEach(pos => {
    if (inRange(pos, start, end)) {
      counter++;
    }
  });
  return counter;
};

const calculateNewOffsetLength = (emojiPosition, inline) => {
  // For new offset, the value should be original value + how many Emoji appeared from 0 to offset
  // For new length,
  // the value should be original length + how many Emoji appeared from offset to offset + length
  const newOffset =
    inline.offset + calculateEmojiNumberWithinRange(emojiPosition, 0, inline.offset);
  const newLength =
    inline.length +
    calculateEmojiNumberWithinRange(emojiPosition, inline.offset, inline.offset + inline.length);
  return { offset: newOffset, length: newLength, style: inline.style };
};

const findEmojiPosition = text => {
  const regex = emojiRegex();
  let resultArray = [];
  const returnArray = [];
  let minusCount = 0;
  while ((resultArray = regex.exec(text)) !== null) {
    // If target character is Emoji, push it's index to the returnArray
    // As Emoji is count as 2 index here but in draftjs-to-html it count as 1,
    // therefore need to reduce it's index by how many Emoji appeared before
    returnArray.push(resultArray.index - minusCount);
    minusCount += resultArray[0].length - 1;
  }
  return returnArray;
};

const handleEmojiExtraIndex = entry => {
  // get the Array of position where the Emoji exist
  const emojiPosition = findEmojiPosition(entry.text);
  let { inlineStyleRanges } = entry;
  inlineStyleRanges = inlineStyleRanges.map(inline => {
    // modify all the inlineStyleRanges offset and length one by one
    return calculateNewOffsetLength(emojiPosition, inline);
  });
  return { ...entry, inlineStyleRanges };
};

const manipulateRawBlocks = rawState => {
  // Loop all the rawState blocks first
  return rawState.blocks.map(entry => {
    return handleEmojiExtraIndex(entry);
  });
};