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.
Then inside editorContent.blocks there seems an inlineStyleRange config mapping to a single index (length: 1)
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
After saved
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);
});
};