diff --git a/.gitignore b/.gitignore index 1a93bf0..cd454ee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ lib .DS_Store .log data/raw/ + +# IDE .vscode +.idea/ \ No newline at end of file diff --git a/src/__tests__/__snapshots__/index-test.js.snap b/src/__tests__/__snapshots__/index-test.js.snap index 0f12d22..38c9227 100644 --- a/src/__tests__/__snapshots__/index-test.js.snap +++ b/src/__tests__/__snapshots__/index-test.js.snap @@ -201,6 +201,39 @@ exports[`Emoji emoji with a single codepoint 1`] = ` `; +exports[`Emoji force unicode on img-supported emoji services 1`] = ` + + Hello World! + + ๐Ÿ‘ + + <- should be unicode โš› + + ๐Ÿ˜ฎ + + ctocat: <- should be image on GitHub + +`; + exports[`Emoji four emoji should not add onlyEmojiClassName 1`] = ` @@ -641,6 +674,39 @@ exports[`Emojione emoji with a single codepoint 1`] = ` `; +exports[`Emojione force unicode on img-supported emoji services 1`] = ` + + Hello World! + + ๐Ÿ‘ + + <- should be unicode โš› + + ๐Ÿ˜ฎ + + ctocat: <- should be image on GitHub + +`; + exports[`Emojione four emoji should not add onlyEmojiClassName 1`] = ` @@ -1080,6 +1146,39 @@ exports[`EmojioneV4 emoji with a single codepoint 1`] = ` `; +exports[`EmojioneV4 force unicode on img-supported emoji services 1`] = ` + + Hello World! + + ๐Ÿ‘ + + <- should be unicode โš› + + ๐Ÿ˜ฎ + + ctocat: <- should be image on GitHub + +`; + exports[`EmojioneV4 four emoji should not add onlyEmojiClassName 1`] = ` @@ -1316,13 +1415,13 @@ exports[`EmojioneV4 with svg prop 1`] = ` `; -exports[`Twemoji a mixture of emoji syntax 1`] = ` +exports[`GitHub a mixture of emoji syntax 1`] = ` ๐Ÿ˜† `; -exports[`Twemoji aliases containing underscores 1`] = ` +exports[`GitHub aliases containing underscores 1`] = ` ๐Ÿ˜œ `; -exports[`Twemoji aliases with skin tone modifiers 1`] = ` +exports[`GitHub aliases with skin tone modifiers 1`] = ` Say hello to ๐Ÿ‘ฉ๐Ÿฟ `; -exports[`Twemoji ascii aliases 1`] = ` +exports[`GitHub ascii aliases 1`] = ` That\'s awesome ๐Ÿ˜ƒ `; -exports[`Twemoji composed emojis containing U+200D and U+FE0F chars 1`] = ` +exports[`GitHub composed emojis containing U+200D and U+FE0F chars 1`] = ` ๐Ÿ‘ฉโ€โš•๏ธ `; -exports[`Twemoji does nothing to unknown aliases 1`] = ` +exports[`GitHub does nothing to unknown aliases 1`] = ` An :unknown: alias `; -exports[`Twemoji emoji with a multiple codepoints 1`] = ` +exports[`GitHub emoji with a multiple codepoints 1`] = ` Great work ๐Ÿ‘๐Ÿพ `; -exports[`Twemoji emoji with a single codepoint 1`] = ` +exports[`GitHub emoji with a single codepoint 1`] = ` This โค๏ธ + +`; + +exports[`GitHub force unicode on img-supported emoji services 1`] = ` + + Hello World! + + ๐Ÿ‘ + + <- should be unicode + :atom: + + :octocat: + <- should be image on GitHub + +`; + +exports[`GitHub four emoji should not add onlyEmojiClassName 1`] = ` + + ๐Ÿ‘‹ + ๐Ÿ˜€ + ๐Ÿ‘๐Ÿพ + ๐Ÿ’ž + +`; + +exports[`GitHub just emoji should add onlyEmojiClassName 1`] = ` + + ๐Ÿ˜€ + +`; + +exports[`GitHub just emoticon should add onlyEmojiClassName 1`] = ` + + ๐Ÿ˜„ + +`; + +exports[`GitHub simple aliases 1`] = ` + + This + ๐Ÿ˜„ + is nice + ๐Ÿ‘ + +`; + +exports[`GitHub strings with no emoji 1`] = ` + + Just some words + +`; + +exports[`GitHub three emoji should add onlyEmojiClassName 1`] = ` + + ๐Ÿ˜€ + ๐Ÿ‘๐Ÿพ + ๐Ÿ’ž + +`; + +exports[`GitHub with size prop 1`] = ` + + This + ๐Ÿ‘จ๐Ÿฟ + is + ๐Ÿ‘Œ + +`; + +exports[`GitHub with svg prop 1`] = ` + + This + ๐Ÿ‘จ๐Ÿฟ + is + ๐Ÿ‘Œ + +`; + +exports[`Twemoji a mixture of emoji syntax 1`] = ` + + ๐Ÿ˜† + This is a selection of + ๐Ÿ’ฉ + emoji + ๐Ÿ˜ƒ + + ๐Ÿ‘Œ๐Ÿฟ + +`; + +exports[`Twemoji aliases containing underscores 1`] = ` + + ๐Ÿ˜œ + wow + +`; + +exports[`Twemoji aliases with skin tone modifiers 1`] = ` + + Say hello to + ๐Ÿ‘ฉ๐Ÿฟ + +`; + +exports[`Twemoji ascii aliases 1`] = ` + + That\'s awesome + ๐Ÿ˜ƒ + +`; + +exports[`Twemoji composed emojis containing U+200D and U+FE0F chars 1`] = ` + + ๐Ÿ‘ฉโ€โš•๏ธ + +`; + +exports[`Twemoji does nothing to unknown aliases 1`] = ` + + An :unknown: alias + +`; + +exports[`Twemoji emoji with a multiple codepoints 1`] = ` + + Great work + ๐Ÿ‘๐Ÿพ + + ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ + +`; + +exports[`Twemoji emoji with a single codepoint 1`] = ` + + This + โค๏ธ + is + ๐Ÿ‘Œ + +`; + +exports[`Twemoji force unicode on img-supported emoji services 1`] = ` + + Hello World! + + ๐Ÿ‘ + + <- should be unicode โš› + + ๐Ÿ˜ฎ + + ctocat: <- should be image on GitHub `; diff --git a/src/__tests__/index-test.js b/src/__tests__/index-test.js index d445934..f15a0e6 100644 --- a/src/__tests__/index-test.js +++ b/src/__tests__/index-test.js @@ -1,8 +1,14 @@ import React from "react"; -import Emoji, { Twemoji, Emojione, EmojioneV4, toArray } from "../../src/index"; +import Emoji, { + Twemoji, + Emojione, + EmojioneV4, + GitHub, + toArray, +} from "../../src/index"; import renderer from "react-test-renderer"; -[Emoji, Twemoji, Emojione, EmojioneV4].forEach(Component => { +[Emoji, Twemoji, Emojione, EmojioneV4, GitHub].forEach(Component => { describe(Component.name, () => { test("strings with no emoji", () => { const component = renderer.create(); @@ -121,6 +127,17 @@ import renderer from "react-test-renderer"; let tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); + + test("force unicode on img-supported emoji services", () => { + const component = renderer.create( + + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); }); }); diff --git a/src/aliasRegex.js b/src/aliasRegex.js index b3a70b8..a1b8655 100644 --- a/src/aliasRegex.js +++ b/src/aliasRegex.js @@ -10,7 +10,10 @@ const names = flatten( Object.keys(asciiAliases).map(name => { return asciiAliases[name].map(escapeStringToBeUsedInRegExp); }) -).sort().reverse().join("|"); // reverse sort for most specific match +) + .sort() + .reverse() + .join("|"); // reverse sort for most specific match const edgeCases = [startOfURL].join("|"); diff --git a/src/index.js b/src/index.js index ea063a6..837b91d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; -import Emoji from "./renderer"; +import Emoji, { optionsType } from "./renderer"; + export { toArray } from "./renderer"; let protocol = "https"; @@ -28,7 +29,7 @@ export function Twemoji({ svg, options, ...rest }) { Twemoji.propTypes = { text: PropTypes.string, - options: PropTypes.object, + options: optionsType, svg: PropTypes.bool, }; @@ -49,7 +50,7 @@ export function Emojione({ svg, options, ...rest }) { Emojione.propTypes = { text: PropTypes.string, - options: PropTypes.object, + options: optionsType, svg: PropTypes.bool, }; @@ -70,9 +71,48 @@ export function EmojioneV4({ size, options, ...rest }) { EmojioneV4.propTypes = { text: PropTypes.string, - options: PropTypes.object, + options: optionsType, size: PropTypes.oneOf([32, 64, 128]), }; EmojioneV4.defaultProps = { size: 64, }; + +export function GitHub({ options, forceUnicode, ...rest }) { + options = { + protocol, + baseUrl: `//github.githubassets.com/images/icons/emoji/unicode/`, + customUrl: `//github.githubassets.com/images/icons/emoji/`, + ext: "png", + customAliases: [ + "atom", + "basecamp", + "basecampy", + "bowtie", + "electron", + "feelsgood", + "finnadie", + "goberserk", + "godmode", + "hurtrealbad", + "neckbeard", + "octocat", + "rage1", + "rage2", + "rage3", + "rage4", + "shipit", + "suspect", + "trollface", + ], + forceUnicode: !!forceUnicode, + ...options, + }; + return ; +} + +GitHub.propTypes = { + text: PropTypes.string, + options: optionsType, + forceUnicode: PropTypes.bool, +}; diff --git a/src/renderer.js b/src/renderer.js index 120d3a3..1019917 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -34,7 +34,8 @@ export function toArray(text, options = {}) { const protocol = normalizeProtocol(options.protocol); function replaceUnicodeEmoji(match, i) { - if (!options.baseUrl) { + const isUnicode = !match.startsWith(":"); + if (!options.baseUrl || (options.forceUnicode && isUnicode)) { return ( {match} @@ -42,16 +43,20 @@ export function toArray(text, options = {}) { ); } - let codepoint = unicodeToCodepoint(match, removeHelperCharacters); - // if Emojione we don't want to add helper characters in the URL const removeHelperCharacters = options.emojione; + let codepoint = unicodeToCodepoint(match, removeHelperCharacters); + if (removeHelperCharacters) { codepoint = codepoint.replace(/-200d/g, "").replace(/-fe0f/g, ""); } - const separator = options.size ? "/" : ""; - const src = `${protocol}${options.baseUrl}${options.size}${separator}${codepoint}.${options.ext}`; + const src = `${protocol}${ + isUnicode ? options.baseUrl : options.customUrl + }${options.size || ""}${separator}${ + // slice removes colons from custom emoji alias e.g :electron:->electron + isUnicode ? codepoint : match.slice(1, -1) + }.${options.ext}`; return ( pos) { // text between matches textWithEmoji.push(text.slice(pos, match.index)); @@ -100,7 +111,23 @@ export function toArray(text, options = {}) { return textWithEmoji.join(""); } - return replace(replaceAliases(text), unicodeEmojiRegex, replaceUnicodeEmoji); + const customAliases = options.customAliases || []; + // adds custom aliases to the regex to detect. e.g GitHub's :electron: which isn't part of the unicode standard. + const modifiedRegex = + customAliases.length > 0 + ? new RegExp( + // slice here removes the /.../g on regex tags + `${unicodeEmojiRegex.toString().slice(1, -2)}|:(${customAliases.join( + "|" + )}):`, + "g" + ) + : unicodeEmojiRegex; + return replace( + replaceAliases(text, options), + modifiedRegex, + replaceUnicodeEmoji + ); } export default function Emoji({ @@ -131,15 +158,19 @@ export default function Emoji({ ); } - +export const optionsType = PropTypes.shape({ + baseUrl: PropTypes.string, + customUrl: PropTypes.string, + size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + ext: PropTypes.string, + // image props + props: PropTypes.object, + className: PropTypes.string, + customAliases: PropTypes.arrayOf(PropTypes.string), + forceUnicode: PropTypes.bool, +}); Emoji.propTypes = { text: PropTypes.string, - props: PropTypes.object, onlyEmojiClassName: PropTypes.string, - options: PropTypes.shape({ - baseUrl: PropTypes.string, - size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - ext: PropTypes.string, - className: PropTypes.string, - }), + options: optionsType, };