emoji-node
Version:
simple emoji support for node.js projects
354 lines (309 loc) • 9.34 kB
JavaScript
/*jslint node: true*/
var toArray = require('lodash.toarray');
var emojiByName = require('./emoji.json');
;
/**
* 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;