@svgmoji/core
Version:
Utilities forked from emojibase for working with svgmoji
720 lines (594 loc) β’ 19.2 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var _classPrivateFieldGet = require('@babel/runtime/helpers/classPrivateFieldGet');
var emojibase = require('emojibase');
var matchSorter = require('match-sorter');
var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
var idbKeyval = require('idb-keyval');
const SpriteCollection = {
/**
* A larger bundle size with all the available emoji in one package.
*/
All: 'all',
/**
* Break the emoji down into 10 separate groups.
*/
Group: 'group',
/**
* Further break the emoji down into even smaller sub groups.
*/
Subgroup: 'sub-group',
/**
* Get the direct url for the emoji
*/
Individual: 'individual'
};
function isMinifiedEmoji(value) {
if (typeof value !== 'object' || value == null) {
return false;
}
const keys = Object.keys(value);
return keys.includes('h') && keys.includes('e');
}
function isFlatEmoji(value) {
if (typeof value !== 'object' || value == null) {
return false;
}
const keys = Object.keys(value);
return keys.includes('hexcode') && keys.includes('emoji');
}
function isMinifiedEmojiList(list) {
return isMinifiedEmoji(list[0]);
}
function isFlatEmojiList(list) {
return isFlatEmoji(list[0]);
}
/**
* Minify emoji which can be useful for reducing the json bundlesize.
*/
function minifyEmoji(emojis) {
return emojis.map(emoji => omitUndefined({
a: emoji.annotation,
e: emoji.emoji,
u: emoji.emoticon,
g: emoji.gender,
b: emoji.group,
h: emoji.hexcode,
o: emoji.order,
s: emoji.shortcodes,
k: emoji.skins,
c: emoji.subgroup,
t: emoji.tags,
d: emoji.text,
f: emoji.tone,
i: emoji.type,
v: emoji.version
}));
}
/**
* Remove the undefined values from an object.
*/
function omitUndefined(object) {
return JSON.parse(JSON.stringify(object));
}
/**
* Populate the minified emoji into a readable format.
*/
function populateMinifiedEmoji(minified) {
return minified.map(emoji => omitUndefined({
annotation: emoji.a,
emoji: emoji.e,
emoticon: emoji.u,
gender: emoji.g,
group: emoji.b,
hexcode: emoji.h,
order: emoji.o,
shortcodes: emoji.s,
skins: emoji.k,
subgroup: emoji.c,
tags: emoji.t,
text: emoji.d,
tone: emoji.f,
type: emoji.i,
version: emoji.v
}));
}
const groups = ([{
"0": "smileys-emotion",
"1": "people-body",
"2": "component",
"3": "animals-nature",
"4": "food-drink",
"5": "travel-places",
"6": "activities",
"7": "objects",
"8": "symbols",
"9": "flags"
}][0]);
const subgroups = ([{
"0": "face-smiling",
"1": "face-affection",
"2": "face-tongue",
"3": "face-hand",
"4": "face-neutral-skeptical",
"5": "face-sleepy",
"6": "face-unwell",
"7": "face-hat",
"8": "face-glasses",
"9": "face-concerned",
"10": "face-negative",
"11": "face-costume",
"12": "cat-face",
"13": "monkey-face",
"14": "emotion",
"15": "hand-fingers-open",
"16": "hand-fingers-partial",
"17": "hand-single-finger",
"18": "hand-fingers-closed",
"19": "hands",
"20": "hand-prop",
"21": "body-parts",
"22": "person",
"23": "person-gesture",
"24": "person-role",
"25": "person-fantasy",
"26": "person-activity",
"27": "person-sport",
"28": "person-resting",
"29": "family",
"30": "person-symbol",
"31": "skin-tone",
"32": "hair-style",
"33": "animal-mammal",
"34": "animal-bird",
"35": "animal-amphibian",
"36": "animal-reptile",
"37": "animal-marine",
"38": "animal-bug",
"39": "plant-flower",
"40": "plant-other",
"41": "food-fruit",
"42": "food-vegetable",
"43": "food-prepared",
"44": "food-asian",
"45": "food-marine",
"46": "food-sweet",
"47": "drink",
"48": "dishware",
"49": "place-map",
"50": "place-geographic",
"51": "place-building",
"52": "place-religious",
"53": "place-other",
"54": "transport-ground",
"55": "transport-water",
"56": "transport-air",
"57": "hotel",
"58": "time",
"59": "sky-weather",
"60": "event",
"61": "award-medal",
"62": "sport",
"63": "game",
"64": "arts-crafts",
"65": "clothing",
"66": "sound",
"67": "music",
"68": "musical-instrument",
"69": "phone",
"70": "computer",
"71": "light-video",
"72": "book-paper",
"73": "money",
"74": "mail",
"75": "writing",
"76": "office",
"77": "lock",
"78": "tool",
"79": "science",
"80": "medical",
"81": "household",
"82": "other-object",
"83": "transport-sign",
"84": "warning",
"85": "arrow",
"86": "religion",
"87": "zodiac",
"88": "av-symbol",
"89": "gender",
"90": "math",
"91": "punctuation",
"92": "currency",
"93": "other-symbol",
"94": "keycap",
"95": "alphanum",
"96": "geometric",
"97": "flag",
"98": "country-flag",
"99": "subdivision-flag"
}][0]);
var _findCache = /*#__PURE__*/new WeakMap();
class Moji {
/**
* The name of the svgmoji.
*/
/**
* The version to retrieve from the cdn.
*/
/**
* All the available emoji.
*/
/**
* Only data without tones included.
*/
/**
* The type of sprite to load.
*/
/**
* The fallback emoji to use when none can be found.
*/
/**
* A list of popular emoji that can be presented when an empty string is provided as the query.
*/
/**
* Cache the results for finding an emoji.
*/
get cdn() {
return "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji@".concat(this.version, "/packages/svgmoji__").concat(this.name);
}
get fallbackUrl() {
return "".concat(this.cdn, "/svg/").concat(this.fallback.hexcode, ".svg");
}
/**
* @param data - data which is used to lookup the groups and subgroups for the emoji instance
* @param fallback - the default hexcode to use when none can be found.
*/
constructor(_ref) {
let {
data,
type,
fallback = '1F44D',
popular = DEFAULT_POPULAR_EMOJI
} = _ref;
_findCache.set(this, {
writable: true,
value: new Map()
});
this.type = type;
this.data = isMinifiedEmojiList(data) ? populateMinifiedEmoji(data) : data;
this.tonelessData = this.data.filter(emoji => !emoji.tone);
this.popularEmoji = this.populatePopularEmoji(popular);
const fallbackEmoji = this.find(fallback);
if (!fallbackEmoji) {
throw new Error("\u274C No emoji exists for the provided fallback value: '".concat(fallback, "'"));
}
this.fallback = fallbackEmoji;
}
/**
* Get the CDN url from the provided emoji hexcode, emoticon or unicode string.
*/
url(code) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
fallback = true
} = options;
const emoji = isFlatEmoji(code) ? code : this.find(code);
const fallbackUrl = fallback ? this.fallbackUrl : undefined;
if (!emoji) {
return fallbackUrl;
}
if (this.type === SpriteCollection.All) {
return "".concat(this.cdn, "/sprites/all.svg#").concat(emoji.hexcode);
}
if (this.type === SpriteCollection.Individual) {
return "".concat(this.cdn, "/svg/").concat(emoji.hexcode, ".svg");
}
if (this.type === SpriteCollection.Group && emoji.group != null) {
var _groups$emoji$group;
const name = (_groups$emoji$group = groups[emoji.group]) !== null && _groups$emoji$group !== void 0 ? _groups$emoji$group : 'other';
return "".concat(this.cdn, "/sprites/group/").concat(name, ".svg#").concat(emoji.hexcode);
}
if (this.type === SpriteCollection.Subgroup && emoji.subgroup != null) {
var _subgroups$emoji$subg;
const name = (_subgroups$emoji$subg = subgroups[emoji.subgroup]) !== null && _subgroups$emoji$subg !== void 0 ? _subgroups$emoji$subg : 'other';
return "".concat(this.cdn, "/sprites/subgroup/").concat(name, ".svg#").concat(emoji.hexcode);
}
return fallbackUrl;
}
/**
* Get an the emoji object of a value by it's hexcode, emoticon or unicode string.
*/
find(code) {
if (_classPrivateFieldGet(this, _findCache).has(code)) {
return _classPrivateFieldGet(this, _findCache).get(code);
}
for (const emoji of this.data) {
if (emojiMatchesCode(code, emoji)) {
_classPrivateFieldGet(this, _findCache).set(code, emoji);
return emoji;
}
} // No matches were found in the data.
// eslint-disable-next-line unicorn/no-useless-undefined
_classPrivateFieldGet(this, _findCache).set(code, undefined);
return;
}
/**
* Search for the nearest emoji using the `match-sorter` algorithm.
*/
search(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
excludeTone,
maxResults
} = _objectSpread(_objectSpread({}, DEFAULT_OPTIONS), options);
const data = excludeTone ? this.tonelessData : this.data;
if (!query) {
return take(this.popularEmoji, maxResults);
}
return take(matchSorter.matchSorter(data, query, {
threshold: matchSorter.rankings.WORD_STARTS_WITH,
keys: [{
threshold: matchSorter.rankings.STARTS_WITH,
key: 'shortcodes'
}, item => {
var _item$shortcodes$map, _item$shortcodes;
return (_item$shortcodes$map = (_item$shortcodes = item.shortcodes) === null || _item$shortcodes === void 0 ? void 0 : _item$shortcodes.map(shortcode => shortcode.split('_').join(' '))) !== null && _item$shortcodes$map !== void 0 ? _item$shortcodes$map : [];
}, 'annotation', 'tags', item => {
var _subgroups$item$subgr, _subgroups$item$subgr2;
return item.subgroup ? (_subgroups$item$subgr = (_subgroups$item$subgr2 = subgroups[item.subgroup]) === null || _subgroups$item$subgr2 === void 0 ? void 0 : _subgroups$item$subgr2.split('-').join(' ')) !== null && _subgroups$item$subgr !== void 0 ? _subgroups$item$subgr : '' : '';
}, item => {
var _groups$item$group$sp, _groups$item$group;
return item.group ? (_groups$item$group$sp = (_groups$item$group = groups[item.group]) === null || _groups$item$group === void 0 ? void 0 : _groups$item$group.split('-').join(' ')) !== null && _groups$item$group$sp !== void 0 ? _groups$item$group$sp : '' : '';
}]
}), maxResults);
}
/**
* Get skins from emoji
*/
getTones(emoji) {
const skins = [];
for (const skin of (_emoji$skins = emoji.skins) !== null && _emoji$skins !== void 0 ? _emoji$skins : []) {
var _emoji$skins;
const skinEmoji = this.find(skin);
if (skinEmoji) {
skins.push();
}
}
return skins;
}
/**
* Populate the popular emoji codes.
*/
populatePopularEmoji(codes) {
const popularEmoji = [];
for (const code of codes) {
const emoji = this.find(code);
if (emoji) {
popularEmoji.push(emoji);
}
}
return popularEmoji;
}
}
const DEFAULT_OPTIONS = {
excludeTone: false,
maxResults: 20
};
/**
* Takes a number of elements from the provided array starting from the
* zero-index.
*
* Set count to `-1` to include all results.
*
* @param array - the array to take from
* @param count - the number of items to take
*
*/
function take(array, count) {
count = Math.max(Math.min(0, count), count === -1 ? array.length : count);
return array.slice(0, count);
}
/**
* Check if the emoji matches the provided code.
*/
function emojiMatchesCode(code, emoji) {
var _emoji$shortcodes, _emoji$shortcodes2;
if ( // This is a native emoji match
emoji.emoji === code || // This uses the underlying text representation of the emoji
emoji.text === code || // This is a hexcode match.
emoji.hexcode === code || // There is a match for the shortcode
(_emoji$shortcodes = emoji.shortcodes) !== null && _emoji$shortcodes !== void 0 && _emoji$shortcodes.includes(code) || // There is a match for the shortcode, but with surrounding braces.
(_emoji$shortcodes2 = emoji.shortcodes) !== null && _emoji$shortcodes2 !== void 0 && _emoji$shortcodes2.map(shortcode => ":".concat(shortcode, ":")).includes(code) || // The provided code matches the emoticon.
emoji.emoticon && emojibase.generateEmoticonPermutations(emoji.emoticon).includes(code)) {
return true;
}
return false;
}
/**
* A list of some of the most popular emoji.
*
* Derived from https://home.unicode.org/emoji/emoji-frequency/
*/
const DEFAULT_POPULAR_EMOJI = ['π', 'β€οΈ', 'π', 'π€£', 'π', 'π', 'π', 'π', 'π', 'π', 'π
', 'π', 'π', 'π₯', 'π', 'π', 'π’', 'π€', 'π', 'π', 'πͺ', 'π', 'βΊοΈ', 'π', 'π€', 'π', 'π', 'π', 'πΉ', 'π€¦', 'π', 'π', 'βοΈ', 'β¨', 'π€·', 'π±', 'π', 'πΈ', 'π', 'π', 'π', 'π', 'π€©', 'π', 'π', 'π', 'π―', 'π', 'π', 'πΆ', 'π', 'π€', 'β£οΈ', 'β', 'π', 'π', 'π', 'πͺ', 'π', 'π₯', 'π', 'π', 'π©', 'π‘', 'π€ͺ', 'π', 'βοΈ', 'π₯', 'π€€', 'π', 'π', 'π³', 'β', 'π', 'π', 'π΄', 'π', 'π¬', 'π', 'π', 'π·', 'π»', 'π', 'β', 'β
', 'π', 'π', 'π€', 'π¦', 'βοΈ', 'π£', 'π', 'π', 'βΉοΈ', 'π', 'π', 'π ', 'βοΈ', 'π', 'πΊ'];
const _excluded$2 = ["version"];
async function runInBrowser(callback) {
if (typeof document === 'undefined') {
return;
}
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return callback(...args);
}
async function fetchFromCDN(path) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
version = 'latest'
} = options,
opts = _objectWithoutProperties(options, _excluded$2);
if (process.env.NODE_ENV === 'dev') {
if (!path || path.slice(-5) !== '.json') {
throw new Error('A valid JSON dataset is required to fetch.');
}
if (!version) {
throw new Error('A valid release version is required.');
}
}
const cacheKey = "svgmoji/".concat(version, "/").concat(path);
const cachedData = await runInBrowser(idbKeyval.get, cacheKey); // Check the cache first
if (cachedData) {
return Promise.resolve(cachedData);
}
const response = await fetch("https://cdn.jsdelivr.net/npm/emojibase-data@".concat(version, "/").concat(path), _objectSpread({
credentials: 'omit',
mode: 'cors',
redirect: 'error'
}, opts));
if (!response.ok) {
throw new Error('Failed to load Emojibase dataset.');
}
const data = await response.json();
try {
await runInBrowser(idbKeyval.set, cacheKey, data);
} catch (_unused) {// Do nothing.
}
return data;
}
const ALIASES = {
discord: 'joypixels',
slack: 'iamcal'
};
function fetchShortcodes(locale, preset, options) {
var _ALIASES$preset;
if (preset === 'cldr-native' && !emojibase.NON_LATIN_LOCALES.includes(locale)) {
return Promise.resolve({});
}
return fetchFromCDN("".concat(locale, "/shortcodes/").concat((_ALIASES$preset = ALIASES[preset]) !== null && _ALIASES$preset !== void 0 ? _ALIASES$preset : preset, ".json"), options);
}
function joinShortcodesToEmoji(emoji, shortcodeDatasets) {
if (shortcodeDatasets.length === 0) {
return emoji;
}
const list = new Set(emoji.shortcodes);
for (const dataset of shortcodeDatasets) {
const shortcodes = dataset[emoji.hexcode];
if (Array.isArray(shortcodes)) {
shortcodes.forEach(code => list.add(code));
} else if (shortcodes) {
list.add(shortcodes);
}
}
emoji.shortcodes = [...list];
if (!emoji.skins) {
return emoji;
}
for (const skin of emoji.skins) {
joinShortcodesToEmoji(skin, shortcodeDatasets);
}
return emoji;
}
const _excluded$1 = ["skins", "tone"],
_excluded2 = ["tone"];
/**
* Throws an error if the tone is undefined.
*/
function getTone(tone) {
if (!tone) {
throw new Error('A tone is required when using `getTone`');
}
return Array.isArray(tone) ? [tone[0], tone[1]] : [tone];
}
function createFlatEmoji(base, skins, tone) {
const flatEmoji = _objectSpread({}, base);
if (tone) {
flatEmoji.tone = getTone(tone);
}
if (skins) {
flatEmoji.skins = skins.map(skin => skin.hexcode);
}
return flatEmoji;
}
function flattenEmojiData(data) {
let shortcodeDatasets = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
const emojis = [];
for (const emoji of data) {
const emojiWithShortcodes = joinShortcodesToEmoji(emoji, shortcodeDatasets);
const {
skins,
tone
} = emojiWithShortcodes,
base = _objectWithoutProperties(emojiWithShortcodes, _excluded$1);
emojis.push(createFlatEmoji(base, skins, tone));
if (!skins) {
continue;
}
for (const skin of skins) {
const _skin = _objectSpread({}, skin),
{
tone
} = _skin,
baseSkin = _objectWithoutProperties(_skin, _excluded2);
if (base.tags) {
baseSkin.tags = [...base.tags];
}
emojis.push(createFlatEmoji(baseSkin, undefined, tone));
}
}
return emojis;
}
const _excluded = ["shortcodes"];
async function fetchEmojis(locale) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
shortcodes: presets = []
} = options,
opts = _objectWithoutProperties(options, _excluded);
const emojis = await fetchFromCDN("".concat(locale, "/data.json"), opts);
let shortcodes = [];
if (presets.length > 0) {
shortcodes = await Promise.all(presets.map(preset => {
let promise;
if (preset.includes('/')) {
const [customLocale, customPreset] = preset.split('/');
promise = fetchShortcodes(customLocale, customPreset, opts);
} else {
promise = fetchShortcodes(locale, preset, opts);
} // Ignore as the primary dataset should still load
return promise.catch(() => ({}));
}));
}
return flattenEmojiData(emojis, shortcodes);
}
Object.defineProperty(exports, 'fromUnicodeToHexcode', {
enumerable: true,
get: function () {
return emojibase.fromUnicodeToHexcode;
}
});
Object.defineProperty(exports, 'generateEmoticonPermutations', {
enumerable: true,
get: function () {
return emojibase.generateEmoticonPermutations;
}
});
Object.defineProperty(exports, 'stripHexcode', {
enumerable: true,
get: function () {
return emojibase.stripHexcode;
}
});
exports.DEFAULT_POPULAR_EMOJI = DEFAULT_POPULAR_EMOJI;
exports.Moji = Moji;
exports.SpriteCollection = SpriteCollection;
exports.fetchEmojis = fetchEmojis;
exports.fetchFromCDN = fetchFromCDN;
exports.flattenEmojiData = flattenEmojiData;
exports.isFlatEmoji = isFlatEmoji;
exports.isFlatEmojiList = isFlatEmojiList;
exports.isMinifiedEmoji = isMinifiedEmoji;
exports.isMinifiedEmojiList = isMinifiedEmojiList;
exports.joinShortcodesToEmoji = joinShortcodesToEmoji;
exports.minifyEmoji = minifyEmoji;
exports.omitUndefined = omitUndefined;
exports.populateMinifiedEmoji = populateMinifiedEmoji;