UNPKG

emoji-node

Version:
354 lines (309 loc) 9.34 kB
/*jslint node: true*/ var toArray = require('lodash.toarray'); var emojiByName = require('./emoji.json'); "use strict"; /** * regex to parse emoji in a string - finds emoji, e.g. :coffee: */ var emojiNameRegex = /:([a-zA-Z0-9_\-\+]+):/g; var _emojiNameRegex = /(\:\w+\:|\>\:o|\(y\)|-_-|\<[\/\\]?3|3\:\)|\^_\^|o\:\)|[\(\)\\\D|\*\$][\-\^]?[\:\;\=]|[\:\;\=B8][\(-|\')\^]?[3DOPp\@\$\*\\\)\(\/\|])(?=\s|[\!\.\?\:\;]|$)/gi; /** * regex to trim whitespace * use instead of String.prototype.trim() for IE8 support */ var trimSpaceRegex = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; /** * Removes colons on either side * of the string if present * @param {string} str * @return {string} */ function stripColons(str) { var colonIndex = str.indexOf(':'); if (colonIndex > -1) { // :emoji: (http://www.emoji-cheat-sheet.com/) if (colonIndex === str.length - 1) { str = str.substring(0, colonIndex); return stripColons(str); } else { str = str.substr(colonIndex + 1); return stripColons(str); } } return str; } /** * Adds colons to either side * of the string * @param {string} str * @return {string} */ function wrapColons(str) { return (typeof str === 'string' && str.length > 0) ? ':' + str + ':' : str; } /** * Ensure that the word is wrapped in colons * by only adding them, if they are not there. * @param {string} str * @return {string} */ function ensureColons(str) { return (typeof str === 'string' && str[0] !== ':') ? wrapColons(str) : str; } // Non spacing mark, some emoticons have them. It's the 'Variant Form', // which provides more information so that emoticons can be rendered as // more colorful graphics. FE0E is a unicode text version, where as FE0F // should be rendered as a graphical version. The code gracefully degrades. var NON_SPACING_MARK = String.fromCharCode(65039); // 65039 - '️' - 0xFE0F; var nonSpacingRegex = new RegExp(NON_SPACING_MARK, 'g') // Remove the non-spacing-mark from the code, never send a stripped version // to the client, as it kills graphical emoticons. function stripNSB(code) { return code.replace(nonSpacingRegex, ''); }; // Reversed hash table, where as emojiByName contains a { heart: '❤' } // dictionary emojiByCode contains { ❤: 'heart' }. The codes are normalized // to the text version. var emojiByCode = Object.keys(emojiByName).reduce(function(h, k) { h[stripNSB(emojiByName[k])] = k; return h; }, {}); /** * Emoji namespace */ var Emoji = { emoji: emojiByName }; /** * get emoji code from name * @param {string} emoji * @return {string} */ Emoji._get = function _get(emoji, ignore) { emoji = emoji.toLowerCase().replace(':-', ':'); if (emojiByName.hasOwnProperty(emoji)) { return emojiByName[emoji]; } if (ignore) return emoji; return ensureColons(emoji); }; /** * get emoji code from :emoji: string or name * @param {string} emoji * @return {string} */ Emoji.get = function get(emoji) { if (emoji.split(emojiNameRegex).length > 1) { emoji = emoji.split(emojiNameRegex)[1]; } // emoji = stripColons(emoji); return Emoji._get(emoji); }; /** * find the emoji by either code or name * @param {string} nameOrCode The emoji to find, either `coffee`, `:coffee:` or `☕`; * @return {object} */ Emoji.find = function find(nameOrCode) { return Emoji.findByName(nameOrCode) || Emoji.findByCode(nameOrCode); }; /** * find the emoji by name * @param {string} name The emoji to find either `coffee` or `:coffee:`; * @return {object} */ Emoji.findByName = function findByName(name) { var stripped = stripColons(name); var emoji = emojiByName[stripped]; return emoji ? ({emoji: emoji, key: stripped}) : undefined; }; /** * find the emoji by code (emoji) * @param {string} code The emoji to find; for example `☕` or `☔` * @return {object} */ Emoji.findByCode = function findByCode(code) { var stripped = stripNSB(code); var name = emojiByCode[stripped]; // lookup emoji to ensure the Variant Form is returned return name ? ({emoji: emojiByName[name], key: name}) : undefined; }; /** * Check if an emoji is known by this library * @param {string} nameOrCode The emoji to validate, either `coffee`, `:coffee:` or `☕`; * @return {object} */ Emoji.hasEmoji = function hasEmoji(nameOrCode) { return Emoji.hasEmojiByName(nameOrCode) || Emoji.hasEmojiByCode(nameOrCode); }; /** * Check if an emoji with given name is known by this library * @param {string} name The emoji to validate either `coffee` or `:coffee:`; * @return {object} */ Emoji.hasEmojiByName = function hasEmojiByName(name) { var result = Emoji.findByName(name); return !!result && result.key === stripColons(name); }; /** * Check if a given emoji is known by this library * @param {string} code The emoji to validate; for example `☕` or `☔` * @return {object} */ Emoji.hasEmojiByCode = function hasEmojiByCode(code) { var result = Emoji.findByCode(code); return !!result && stripNSB(result.emoji) === stripNSB(code); }; /** * get emoji name from code * @param {string} emoji * @param {boolean} includeColons should the result include the :: * @return {string} */ Emoji.which = function which(emoji_code, includeColons) { var code = stripNSB(emoji_code); var word = emojiByCode[code]; return includeColons ? wrapColons(word) : word; }; /** * emojify a string (replace :emoji: with an emoji) * @param {string} str * @param {function} on_missing (gets emoji name without :: and returns a proper emoji if no emoji was found) * @param {function} format (wrap the returned emoji in a custom element) * @return {string} */ Emoji.emojify = function emojify(str, on_missing, format) { if (!str) return ''; // console.log(str.split(_emojiNameRegex)); let _data = str.split(emojiNameRegex). // parse emoji via regex map(function parseEmoji(s, i) { let _s = s; // every second element is an emoji, e.g. "test :fast_forward:" -> [ "test ", "fast_forward" ] if (i % 2 === 0) return _s; // if (s.split(emojiNameRegex).length > 1) { // _s = s.split(emojiNameRegex)[1]; // } // console.log(_s.match(emojiNameRegex)); var emoji = Emoji._get(_s); // console.log(emoji); var isMissing = emoji.indexOf(':') > -1; if (isMissing && typeof on_missing === 'function') { return on_missing(_s); } if (!isMissing && typeof format === 'function') { return format(emoji, _s); } return emoji; }).join(''); // convert back to string _data = _data.split(_emojiNameRegex).map(function parseEmoji(s, i) { let _s = s; // every second element is an emoji, e.g. "test :fast_forward:" -> [ "test ", "fast_forward" ] if (i % 2 === 0) return _s; // if (s.split(emojiNameRegex).length > 1) { // _s = s.split(emojiNameRegex)[1]; // } // console.log(_s.match(emojiNameRegex)); var emoji = Emoji._get(_s, true); // console.log(emoji); var isMissing = emoji.indexOf(':') > -1; if (isMissing && typeof on_missing === 'function') { return on_missing(_s); } if (!isMissing && typeof format === 'function') { return format(emoji, _s); } return emoji; }).join('') return _data; }; /** * return a random emoji * @return {string} */ Emoji.random = function random() { var emojiKeys = Object.keys(emojiByName); var randomIndex = Math.floor(Math.random() * emojiKeys.length); var key = emojiKeys[randomIndex]; var emoji = Emoji._get(key); return {key: key, emoji: emoji}; } /** * return an collection of potential emoji matches * @param {string} str * @return {Array.<Object>} */ Emoji.search = function search(str) { var emojiKeys = Object.keys(emojiByName); var matcher = stripColons(str) var matchingKeys = emojiKeys.filter(function(key) { return key.toString().indexOf(matcher) === 0; }); return matchingKeys.map(function(key) { return {key: key, emoji: Emoji._get(key)}; }); } /** * unemojify a string (replace emoji with :emoji:) * @param {string} str * @return {string} */ Emoji.unemojify = function unemojify(str) { if (!str) return ''; var words = toArray(str); return words.map(function(word) { return Emoji.which(word, true) || word; }).join(''); }; /** * replace emojis with replacement value * @param {string} str * @param {function|string} the string or callback function to replace the emoji with * @param {boolean} should trailing whitespaces be cleaned? Defaults false * @return {string} */ Emoji.replace = function replace(str, replacement, cleanSpaces) { if (!str) return ''; var replace = typeof replacement === 'function' ? replacement : function() { return replacement; }; var words = toArray(str); var replaced = words.map(function(word, idx) { var emoji = Emoji.findByCode(word); if (emoji && cleanSpaces && words[idx + 1] === ' ') { words[idx + 1] = ''; } return emoji ? replace(emoji) : word; }).join(''); return cleanSpaces ? replaced.replace(trimSpaceRegex, '') : replaced; }; /** * remove all emojis from a string * @param {string} str * @return {string} */ Emoji.strip = function strip(str) { return Emoji.replace(str, '', true); }; module.exports = Emoji;