Skip to content

Commit

Permalink
Merge pull request #8 from drinking-code/fix-emoji-replacing
Browse files Browse the repository at this point in the history
Fix emoji replacing
  • Loading branch information
drinking-code authored May 23, 2021
2 parents a1e01a8 + af04a52 commit 8f09489
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 70 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
node_modules
lib
src
*.tgz
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const App = () => {
export default App;
```

> **Fun fact:** Importing all 3914 emojis at once is actually just as fast as the other methods because all emojis are dynamically loaded:
> **Fun fact:** Importing all 3678 emojis at once is actually just as fast as the other methods because all emojis are dynamically loaded:
```jsx
import React from 'react';
Expand Down
143 changes: 76 additions & 67 deletions build/main.js
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';
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "react-openmoji",
"version": "0.5.0",
"version": "0.5.1",
"description": "openmoji for react",
"main": "lib/main.js",
"scripts": {
"postinstall": "node build/build.js && npx babel --presets @babel/preset-react src --out-dir lib --copy-files && node build/correct-dom-properties.js"
"postinstall": "node build/build.js && npx babel --presets @babel/preset-react src --out-dir lib --copy-files && node build/correct-dom-properties.js && rm -rf src"
},
"author": "drinking-code",
"license": "CC-BY-SA-4.0",
Expand Down

0 comments on commit 8f09489

Please sign in to comment.