draftjs-to-html icon indicating copy to clipboard operation
draftjs-to-html copied to clipboard

emoji add fontSize or style by draftToHtmt

Open bestwishforyou95 opened this issue 7 years ago • 8 comments

example 😫

output

example �

Could you please help me. Thank you!

bestwishforyou95 avatar Mar 15 '18 08:03 bestwishforyou95

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

enginaryum avatar Jul 05 '18 21:07 enginaryum

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 avatar Jul 06 '18 17:07 enginaryum

@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?

ndinhphi avatar Oct 03 '18 07:10 ndinhphi

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 avatar Oct 23 '18 18:10 enginaryum

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

ndinhphi avatar Nov 06 '18 09:11 ndinhphi

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

donibehrami avatar Nov 08 '18 16:11 donibehrami

@enginaryum, thanks, your solution works as expected!

phen0menon avatar Feb 21 '19 20:02 phen0menon

@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);
  });
};

chanhosum avatar Feb 09 '21 03:02 chanhosum