-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from drinking-code/fix-emoji-replacing
Fix emoji replacing
- Loading branch information
Showing
4 changed files
with
80 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
node_modules | ||
lib | ||
src | ||
*.tgz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,98 @@ | ||
const React = require('react') | ||
const Emoji = require('./index'); | ||
require('./default-svg.css') | ||
import React from 'react' | ||
import * as Emoji from './index' | ||
import './default-svg.css' | ||
|
||
function reactReplaceEmojis(reactChild, options) { | ||
let newReactChild; | ||
if (Array.isArray(reactChild.props.children)) { | ||
let newChildren = []; | ||
for (let i in reactChild.props.children) { | ||
if (!reactChild.props.children.hasOwnProperty(i)) continue | ||
const child = reactChild.props.children[Number(i)]; | ||
if (React.isValidElement(child)) { | ||
newChildren[i] = reactReplaceEmojis(child, options) | ||
} else { | ||
newChildren[i] = React.cloneElement( | ||
reactChild, | ||
{}, | ||
replaceEmojis(child, options) | ||
) | ||
} | ||
export default function reactReplaceEmojis(reactElement, options) { | ||
// shortcut -> no children means nothing to replace | ||
if (!reactElement.props.children) return reactElement | ||
let newReactElement, | ||
hasMultipleChildren = Array.isArray(reactElement.props.children), | ||
newChildren = [] | ||
|
||
// if element has multiple children call the recursive function once for each | ||
if (hasMultipleChildren) { | ||
for (let i in reactElement.props.children) { | ||
if (!reactElement.props.children.hasOwnProperty(i)) continue | ||
const child = reactElement.props.children[Number(i)]; | ||
newChildren[i] = _applyCorrectReplace(child, options) | ||
} | ||
newReactChild = React.cloneElement( | ||
reactChild, | ||
{}, | ||
newChildren | ||
); | ||
} else { | ||
newReactChild = React.cloneElement( | ||
reactChild, | ||
{}, | ||
replaceEmojis(reactChild.props.children, options) | ||
) | ||
} | ||
return newReactChild; | ||
} else // if element has only one child call the recursive function directly | ||
newChildren = _applyCorrectReplace(reactElement.props.children, options) | ||
|
||
newReactElement = React.cloneElement( | ||
reactElement, | ||
{}, | ||
newChildren | ||
); | ||
return newReactElement; | ||
} | ||
|
||
function replaceEmojis(string, options) { | ||
const _applyCorrectReplace = (child, options) => | ||
React.isValidElement(child) | ||
? reactReplaceEmojis(child, options) | ||
: replaceEmojis(child, options) | ||
|
||
export function replaceEmojis(string, options) { | ||
if (!string) return; | ||
let array = [string] | ||
|
||
options = { | ||
size: typeof options?.size === 'string' ? options.size : undefined, | ||
outline: typeof options?.outline === 'boolean' ? options.outline : undefined | ||
} | ||
if (!string) return; | ||
const emojis = string.match(/[\p{Emoji}\u200d\ufe0f]+/gu); | ||
if (!emojis) return string; | ||
}; | ||
|
||
string = string.split(/([\p{Emoji}\u200d\ufe0f]+)/gu); | ||
/* | ||
* matches all emojis matches all attached components | ||
* \p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]? un-matches "text style" | ||
* (\u200d\p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]?)*[\u2640\u2642]?\ufe0f?(?!\ufe0e)/gu | ||
* matches all joined (ZWJ) emojis with all attached components matches attached gender | ||
*/ | ||
|
||
// replace emojis with SVGs | ||
emojis.forEach((emoji, i) => { | ||
// get the char codes of the emojis | ||
let unicode = ""; | ||
const regex = /\p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]?(\u200d\p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]?)*[\u2640\u2642]?\ufe0f?(?!\ufe0e)/gu; | ||
let m, j = 0; | ||
|
||
function getNextChar(pointer) { | ||
const subUnicode = emoji.codePointAt(pointer); | ||
if (!subUnicode) return; | ||
if (!(subUnicode >= 56320 && subUnicode <= 57343)) { // 56320-57343: Low Surrogates Character | ||
unicode += '-' + subUnicode.toString(16); | ||
} | ||
getNextChar(++pointer); | ||
while ((m = regex.exec(string)) !== null) { | ||
// This is necessary to avoid infinite loops with zero-width matches | ||
if (m.index === regex.lastIndex) { | ||
regex.lastIndex++; | ||
} | ||
|
||
getNextChar(0); | ||
unicode = unicode.substr(1); | ||
// find the code for the emoji | ||
let emojiName = 'U', done = false | ||
for (let i = 0; !done; i++) { | ||
let subUnicode = m[0].codePointAt(i) | ||
// dismiss low surrogates characters (56320-57343) | ||
if ((subUnicode >= 56320 && subUnicode <= 57343)) continue | ||
emojiName += '_' + subUnicode?.toString(16).toUpperCase() | ||
|
||
const emojiName = `U_${unicode.toUpperCase().replace(/-/g, '_')}`; | ||
// check if is done: if this hexcode is longer than 4, check the next but one codepoint | ||
done = m[0].codePointAt(i)?.toString(16).length > 4 | ||
? !m[0].codePointAt(i + 2) | ||
: !m[0].codePointAt(i + 1) | ||
} | ||
|
||
const emojiIndex = string.indexOf(emoji); | ||
let emojiSvg = Emoji[emojiName]; | ||
|
||
options.key = i | ||
|
||
options.key = j | ||
j++ | ||
if (emojiSvg) { | ||
string[emojiIndex] = React.createElement(emojiSvg, options); | ||
// gets last string ['String with {Emoji} and {Emoji} in it'] -> 'String with {Emoji} and {Emoji} in it' | ||
let workingString = array.pop() | ||
// 'String with {Emoji} and {Emoji} in it' -> ['String with ', ' and ', ' in it'] | ||
workingString = workingString.split(m[0]) | ||
// [] -> ['String with '] | ||
// ['String with ', ' and ', ' in it'] -> [' and ', ' in it'] | ||
array.push(workingString.shift()) | ||
// ['String with '] -> ['String with ', <Emoji>] | ||
array.push(React.createElement(emojiSvg, options)) | ||
// [' and ', ' in it'] -> ' and {Emoji} in it' | ||
// ['String with ', <Emoji>] -> ['String with ', <Emoji>, ' and {Emoji} in it'] | ||
array.push(workingString.join(m[0])) | ||
} else { | ||
console.warn('SVG not found: ' + emojiName); | ||
} | ||
}) | ||
|
||
// return converted react HTML | ||
return string; | ||
} | ||
} | ||
|
||
reactReplaceEmojis.replaceEmojis = replaceEmojis | ||
for (const key in Emoji) { | ||
if (!Emoji.hasOwnProperty(key)) continue | ||
reactReplaceEmojis[key] = Emoji[key] | ||
return array; | ||
} | ||
|
||
module.exports = reactReplaceEmojis | ||
export * from './index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters