Skip to content

Commit 8f09489

Browse files
Merge pull request #8 from drinking-code/fix-emoji-replacing
Fix emoji replacing
2 parents a1e01a8 + af04a52 commit 8f09489

4 files changed

Lines changed: 80 additions & 70 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
node_modules
44
lib
55
src
6+
*.tgz

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const App = () => {
155155
export default App;
156156
```
157157

158-
> **Fun fact:** Importing all 3914 emojis at once is actually just as fast as the other methods because all emojis are dynamically loaded:
158+
> **Fun fact:** Importing all 3678 emojis at once is actually just as fast as the other methods because all emojis are dynamically loaded:
159159
160160
```jsx
161161
import React from 'react';

build/main.js

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,98 @@
1-
const React = require('react')
2-
const Emoji = require('./index');
3-
require('./default-svg.css')
1+
import React from 'react'
2+
import * as Emoji from './index'
3+
import './default-svg.css'
44

5-
function reactReplaceEmojis(reactChild, options) {
6-
let newReactChild;
7-
if (Array.isArray(reactChild.props.children)) {
8-
let newChildren = [];
9-
for (let i in reactChild.props.children) {
10-
if (!reactChild.props.children.hasOwnProperty(i)) continue
11-
const child = reactChild.props.children[Number(i)];
12-
if (React.isValidElement(child)) {
13-
newChildren[i] = reactReplaceEmojis(child, options)
14-
} else {
15-
newChildren[i] = React.cloneElement(
16-
reactChild,
17-
{},
18-
replaceEmojis(child, options)
19-
)
20-
}
5+
export default function reactReplaceEmojis(reactElement, options) {
6+
// shortcut -> no children means nothing to replace
7+
if (!reactElement.props.children) return reactElement
8+
let newReactElement,
9+
hasMultipleChildren = Array.isArray(reactElement.props.children),
10+
newChildren = []
11+
12+
// if element has multiple children call the recursive function once for each
13+
if (hasMultipleChildren) {
14+
for (let i in reactElement.props.children) {
15+
if (!reactElement.props.children.hasOwnProperty(i)) continue
16+
const child = reactElement.props.children[Number(i)];
17+
newChildren[i] = _applyCorrectReplace(child, options)
2118
}
22-
newReactChild = React.cloneElement(
23-
reactChild,
24-
{},
25-
newChildren
26-
);
27-
} else {
28-
newReactChild = React.cloneElement(
29-
reactChild,
30-
{},
31-
replaceEmojis(reactChild.props.children, options)
32-
)
33-
}
34-
return newReactChild;
19+
} else // if element has only one child call the recursive function directly
20+
newChildren = _applyCorrectReplace(reactElement.props.children, options)
21+
22+
newReactElement = React.cloneElement(
23+
reactElement,
24+
{},
25+
newChildren
26+
);
27+
return newReactElement;
3528
}
3629

37-
function replaceEmojis(string, options) {
30+
const _applyCorrectReplace = (child, options) =>
31+
React.isValidElement(child)
32+
? reactReplaceEmojis(child, options)
33+
: replaceEmojis(child, options)
34+
35+
export function replaceEmojis(string, options) {
36+
if (!string) return;
37+
let array = [string]
38+
3839
options = {
3940
size: typeof options?.size === 'string' ? options.size : undefined,
4041
outline: typeof options?.outline === 'boolean' ? options.outline : undefined
41-
}
42-
if (!string) return;
43-
const emojis = string.match(/[\p{Emoji}\u200d\ufe0f]+/gu);
44-
if (!emojis) return string;
42+
};
4543

46-
string = string.split(/([\p{Emoji}\u200d\ufe0f]+)/gu);
44+
/*
45+
* matches all emojis matches all attached components
46+
* \p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]? un-matches "text style"
47+
* (\u200d\p{Extended_Pictographic}[\u{1f3fb}-\u{1f3ff}\u{1f9b0}-\u{1f9b3}]?)*[\u2640\u2642]?\ufe0f?(?!\ufe0e)/gu
48+
* matches all joined (ZWJ) emojis with all attached components matches attached gender
49+
*/
4750

48-
// replace emojis with SVGs
49-
emojis.forEach((emoji, i) => {
50-
// get the char codes of the emojis
51-
let unicode = "";
51+
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;
52+
let m, j = 0;
5253

53-
function getNextChar(pointer) {
54-
const subUnicode = emoji.codePointAt(pointer);
55-
if (!subUnicode) return;
56-
if (!(subUnicode >= 56320 && subUnicode <= 57343)) { // 56320-57343: Low Surrogates Character
57-
unicode += '-' + subUnicode.toString(16);
58-
}
59-
getNextChar(++pointer);
54+
while ((m = regex.exec(string)) !== null) {
55+
// This is necessary to avoid infinite loops with zero-width matches
56+
if (m.index === regex.lastIndex) {
57+
regex.lastIndex++;
6058
}
6159

62-
getNextChar(0);
63-
unicode = unicode.substr(1);
60+
// find the code for the emoji
61+
let emojiName = 'U', done = false
62+
for (let i = 0; !done; i++) {
63+
let subUnicode = m[0].codePointAt(i)
64+
// dismiss low surrogates characters (56320-57343)
65+
if ((subUnicode >= 56320 && subUnicode <= 57343)) continue
66+
emojiName += '_' + subUnicode?.toString(16).toUpperCase()
6467

65-
const emojiName = `U_${unicode.toUpperCase().replace(/-/g, '_')}`;
68+
// check if is done: if this hexcode is longer than 4, check the next but one codepoint
69+
done = m[0].codePointAt(i)?.toString(16).length > 4
70+
? !m[0].codePointAt(i + 2)
71+
: !m[0].codePointAt(i + 1)
72+
}
6673

67-
const emojiIndex = string.indexOf(emoji);
6874
let emojiSvg = Emoji[emojiName];
69-
70-
options.key = i
71-
75+
options.key = j
76+
j++
7277
if (emojiSvg) {
73-
string[emojiIndex] = React.createElement(emojiSvg, options);
78+
// gets last string ['String with {Emoji} and {Emoji} in it'] -> 'String with {Emoji} and {Emoji} in it'
79+
let workingString = array.pop()
80+
// 'String with {Emoji} and {Emoji} in it' -> ['String with ', ' and ', ' in it']
81+
workingString = workingString.split(m[0])
82+
// [] -> ['String with ']
83+
// ['String with ', ' and ', ' in it'] -> [' and ', ' in it']
84+
array.push(workingString.shift())
85+
// ['String with '] -> ['String with ', <Emoji>]
86+
array.push(React.createElement(emojiSvg, options))
87+
// [' and ', ' in it'] -> ' and {Emoji} in it'
88+
// ['String with ', <Emoji>] -> ['String with ', <Emoji>, ' and {Emoji} in it']
89+
array.push(workingString.join(m[0]))
7490
} else {
7591
console.warn('SVG not found: ' + emojiName);
7692
}
77-
})
78-
79-
// return converted react HTML
80-
return string;
81-
}
93+
}
8294

83-
reactReplaceEmojis.replaceEmojis = replaceEmojis
84-
for (const key in Emoji) {
85-
if (!Emoji.hasOwnProperty(key)) continue
86-
reactReplaceEmojis[key] = Emoji[key]
95+
return array;
8796
}
8897

89-
module.exports = reactReplaceEmojis
98+
export * from './index';

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "react-openmoji",
3-
"version": "0.5.0",
3+
"version": "0.5.1",
44
"description": "openmoji for react",
55
"main": "lib/main.js",
66
"scripts": {
7-
"postinstall": "node build/build.js && npx babel --presets @babel/preset-react src --out-dir lib --copy-files && node build/correct-dom-properties.js"
7+
"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"
88
},
99
"author": "drinking-code",
1010
"license": "CC-BY-SA-4.0",

0 commit comments

Comments
 (0)