2019-03-15 00:44:49 -04:00
|
|
|
import {
|
|
|
|
aliases,
|
2020-12-01 13:31:26 -05:00
|
|
|
emojis,
|
2019-03-21 04:11:33 -04:00
|
|
|
replacements,
|
2019-03-15 00:44:49 -04:00
|
|
|
searchAliases,
|
2019-03-21 04:11:33 -04:00
|
|
|
tonableEmojis,
|
|
|
|
translations,
|
2019-03-15 00:44:49 -04:00
|
|
|
} from "pretty-text/emoji/data";
|
|
|
|
import { IMAGE_VERSION } from "pretty-text/emoji/version";
|
2019-03-13 08:02:56 -04:00
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
const extendedEmojiMap = new Map();
|
2019-03-13 08:02:56 -04:00
|
|
|
|
2020-03-30 14:16:10 -04:00
|
|
|
export function registerEmoji(code, url, group) {
|
2019-03-13 08:02:56 -04:00
|
|
|
code = code.toLowerCase();
|
2022-03-05 12:58:49 -05:00
|
|
|
extendedEmojiMap.set(code, { url, group });
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function extendedEmojiList() {
|
2022-03-05 12:58:49 -05:00
|
|
|
return extendedEmojiMap;
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
const emojiMap = new Map();
|
2019-03-13 08:02:56 -04:00
|
|
|
|
2021-04-22 02:43:06 -04:00
|
|
|
// https://github.com/mathiasbynens/emoji-regex/blob/main/text.js
|
|
|
|
export const emojiReplacementRegex =
|
|
|
|
"\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])
|
2019-12-03 11:32:33 -05:00
|
|
|
|
2021-04-22 02:43:06 -04:00
|
|
|
function textEmojiRegex(inlineEmoji) {
|
|
|
|
return inlineEmoji ? /:[^\s:]+(?::t\d)?:?/g : /\B:[^\s:]+(?::t\d)?:?\B/g;
|
2019-12-03 11:32:33 -05:00
|
|
|
}
|
2019-03-28 17:31:27 -04:00
|
|
|
|
2019-03-13 08:02:56 -04:00
|
|
|
// add all default emojis
|
2022-03-05 12:58:49 -05:00
|
|
|
emojis.forEach((code) => emojiMap.set(code, true));
|
2019-03-13 08:02:56 -04:00
|
|
|
|
|
|
|
// and their aliases
|
2022-03-05 12:58:49 -05:00
|
|
|
const aliasMap = new Map();
|
|
|
|
Object.entries(aliases).forEach(([name, list]) => {
|
|
|
|
list.forEach((alias) => aliasMap.set(alias, name));
|
2019-03-13 08:02:56 -04:00
|
|
|
});
|
|
|
|
|
2019-12-03 11:32:33 -05:00
|
|
|
function isReplacableInlineEmoji(string, index, inlineEmoji) {
|
2020-09-22 10:28:28 -04:00
|
|
|
if (inlineEmoji) {
|
|
|
|
return true;
|
|
|
|
}
|
2019-12-03 11:32:33 -05:00
|
|
|
|
|
|
|
// index depends on regex; when `inlineEmoji` is false, the regex starts
|
|
|
|
// with a `\B` character, so there's no need to subtract from the index
|
2020-05-07 10:25:23 -04:00
|
|
|
const beforeEmoji = string.slice(0, index);
|
2019-12-03 11:32:33 -05:00
|
|
|
|
|
|
|
return (
|
|
|
|
beforeEmoji.length === 0 ||
|
|
|
|
/(?:\s|[>.,\/#!$%^&*;:{}=\-_`~()])$/.test(beforeEmoji) ||
|
2021-04-22 02:43:06 -04:00
|
|
|
new RegExp(`(?:${emojiReplacementRegex})$`, "g").test(beforeEmoji)
|
2019-12-03 11:32:33 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-03-13 08:02:56 -04:00
|
|
|
export function performEmojiUnescape(string, opts) {
|
2020-03-30 14:16:10 -04:00
|
|
|
if (!string) {
|
2019-03-15 00:44:49 -04:00
|
|
|
return;
|
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
|
2020-05-27 14:11:52 -04:00
|
|
|
const allTranslations = Object.assign(
|
|
|
|
{},
|
|
|
|
translations,
|
|
|
|
opts.customEmojiTranslation || {}
|
|
|
|
);
|
2019-12-03 11:32:33 -05:00
|
|
|
|
2021-04-22 02:43:06 -04:00
|
|
|
const replacementFunction = (m, index) => {
|
2020-05-27 14:11:52 -04:00
|
|
|
const isEmoticon = opts.enableEmojiShortcuts && !!allTranslations[m];
|
2019-03-21 04:11:33 -04:00
|
|
|
const isUnicodeEmoticon = !!replacements[m];
|
|
|
|
let emojiVal;
|
2021-04-22 02:43:06 -04:00
|
|
|
|
2019-03-21 04:11:33 -04:00
|
|
|
if (isEmoticon) {
|
2020-05-27 14:11:52 -04:00
|
|
|
emojiVal = allTranslations[m];
|
2019-03-21 04:11:33 -04:00
|
|
|
} else if (isUnicodeEmoticon) {
|
|
|
|
emojiVal = replacements[m];
|
|
|
|
} else {
|
|
|
|
emojiVal = m.slice(1, m.length - 1);
|
|
|
|
}
|
|
|
|
const hasEndingColon = m.lastIndexOf(":") === m.length - 1;
|
|
|
|
const url = buildEmojiUrl(emojiVal, opts);
|
2020-08-24 08:20:51 -04:00
|
|
|
let classes = isCustomEmoji(emojiVal, opts)
|
2019-03-21 04:11:33 -04:00
|
|
|
? "emoji emoji-custom"
|
|
|
|
: "emoji";
|
|
|
|
|
2020-08-24 08:20:51 -04:00
|
|
|
if (opts.class) {
|
2022-03-05 12:58:49 -05:00
|
|
|
classes += ` ${opts.class}`;
|
2020-08-24 08:20:51 -04:00
|
|
|
}
|
|
|
|
|
2019-12-03 11:32:33 -05:00
|
|
|
const isReplacable =
|
|
|
|
(isEmoticon || hasEndingColon || isUnicodeEmoticon) &&
|
2021-04-22 02:43:06 -04:00
|
|
|
isReplacableInlineEmoji(string, index, opts.inlineEmoji);
|
2019-12-03 11:32:33 -05:00
|
|
|
|
|
|
|
return url && isReplacable
|
2020-09-28 07:23:46 -04:00
|
|
|
? `<img width="20" height="20" src='${url}' ${
|
|
|
|
opts.skipTitle ? "" : `title='${emojiVal}'`
|
|
|
|
} ${
|
2020-08-24 08:20:51 -04:00
|
|
|
opts.lazy ? "loading='lazy' " : ""
|
|
|
|
}alt='${emojiVal}' class='${classes}'>`
|
2019-03-21 04:11:33 -04:00
|
|
|
: m;
|
2021-04-22 02:43:06 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
return string
|
|
|
|
.replace(new RegExp(emojiReplacementRegex, "g"), replacementFunction)
|
|
|
|
.replace(textEmojiRegex(opts.inlineEmoji), replacementFunction);
|
2019-03-21 04:11:33 -04:00
|
|
|
}
|
|
|
|
|
2019-05-21 10:56:51 -04:00
|
|
|
export function performEmojiEscape(string, opts) {
|
2020-05-27 14:11:52 -04:00
|
|
|
const allTranslations = Object.assign(
|
|
|
|
{},
|
|
|
|
translations,
|
|
|
|
opts.customEmojiTranslation || {}
|
|
|
|
);
|
2019-12-03 11:32:33 -05:00
|
|
|
|
2021-04-22 02:43:06 -04:00
|
|
|
const replacementFunction = (m, index) => {
|
|
|
|
if (isReplacableInlineEmoji(string, index, opts.inlineEmoji)) {
|
2020-05-27 14:11:52 -04:00
|
|
|
if (!!allTranslations[m]) {
|
|
|
|
return opts.emojiShortcuts ? `:${allTranslations[m]}:` : m;
|
2019-12-03 11:32:33 -05:00
|
|
|
} else if (!!replacements[m]) {
|
|
|
|
return `:${replacements[m]}:`;
|
|
|
|
}
|
2019-03-21 04:11:33 -04:00
|
|
|
}
|
2019-12-03 11:32:33 -05:00
|
|
|
|
|
|
|
return m;
|
2021-04-22 02:43:06 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
return string
|
|
|
|
.replace(new RegExp(emojiReplacementRegex, "g"), replacementFunction)
|
|
|
|
.replace(textEmojiRegex(opts.inlineEmoji), replacementFunction);
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function isCustomEmoji(code, opts) {
|
|
|
|
code = code.toLowerCase();
|
2022-03-05 12:58:49 -05:00
|
|
|
return extendedEmojiMap.has(code) || opts?.customEmoji?.hasOwnProperty(code);
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function buildEmojiUrl(code, opts) {
|
|
|
|
let url;
|
|
|
|
code = String(code).toLowerCase();
|
2022-03-05 12:58:49 -05:00
|
|
|
|
|
|
|
if (extendedEmojiMap.has(code)) {
|
|
|
|
url = extendedEmojiMap.get(code).url;
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
if (opts.customEmoji?.[code]) {
|
2020-03-30 14:16:10 -04:00
|
|
|
url = opts.customEmoji[code].url || opts.customEmoji[code];
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const noToneMatch = code.match(/([^:]+):?/);
|
2021-03-02 14:04:16 -05:00
|
|
|
|
|
|
|
let emojiBasePath = "/images/emoji";
|
|
|
|
if (opts.emojiCDNUrl) {
|
|
|
|
emojiBasePath = opts.emojiCDNUrl;
|
|
|
|
}
|
|
|
|
|
2019-03-15 00:44:49 -04:00
|
|
|
if (
|
2022-03-05 12:58:49 -05:00
|
|
|
opts.getURL &&
|
|
|
|
opts.emojiSet &&
|
2019-03-15 00:44:49 -04:00
|
|
|
noToneMatch &&
|
|
|
|
!url &&
|
2022-03-05 12:58:49 -05:00
|
|
|
(emojiMap.has(noToneMatch[1]) || aliasMap.has(noToneMatch[1]))
|
2019-03-15 00:44:49 -04:00
|
|
|
) {
|
|
|
|
url = opts.getURL(
|
2021-03-02 14:04:16 -05:00
|
|
|
`${emojiBasePath}/${opts.emojiSet}/${code.replace(/:t/, "/")}.png`
|
2019-03-15 00:44:49 -04:00
|
|
|
);
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (url) {
|
2022-03-05 12:58:49 -05:00
|
|
|
url = `${url}?v=${IMAGE_VERSION}`;
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function emojiExists(code) {
|
|
|
|
code = code.toLowerCase();
|
2022-03-05 12:58:49 -05:00
|
|
|
return extendedEmojiMap.has(code) || emojiMap.has(code) || aliasMap.has(code);
|
2019-03-15 00:44:49 -04:00
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
|
|
|
|
let toSearch;
|
|
|
|
export function emojiSearch(term, options) {
|
2022-03-05 12:58:49 -05:00
|
|
|
const maxResults = options?.maxResults;
|
|
|
|
const diversity = options?.diversity;
|
2019-03-15 00:44:49 -04:00
|
|
|
if (maxResults === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
if (!toSearch) {
|
|
|
|
toSearch = [
|
|
|
|
...emojiMap.keys(),
|
|
|
|
...extendedEmojiMap.keys(),
|
|
|
|
...aliasMap.keys(),
|
2020-09-01 14:45:44 -04:00
|
|
|
].sort();
|
2022-03-05 12:58:49 -05:00
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
function addResult(t) {
|
2022-03-05 12:58:49 -05:00
|
|
|
const val = aliasMap.get(t) || t;
|
|
|
|
if (!results.includes(val)) {
|
2021-02-01 11:36:35 -05:00
|
|
|
if (diversity && diversity > 1 && isSkinTonableEmoji(val)) {
|
|
|
|
results.push(`${val}:t${diversity}`);
|
|
|
|
} else {
|
|
|
|
results.push(val);
|
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if term matches from beginning
|
2022-03-05 12:58:49 -05:00
|
|
|
for (const item of toSearch) {
|
2020-09-22 10:28:28 -04:00
|
|
|
if (item.indexOf(term) === 0) {
|
|
|
|
addResult(item);
|
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
2022-02-01 16:36:48 -05:00
|
|
|
for (const [key, value] of Object.entries(searchAliases)) {
|
|
|
|
if (key.startsWith(term)) {
|
|
|
|
for (const emoji of value) {
|
|
|
|
addResult(emoji);
|
|
|
|
}
|
2022-01-10 20:34:22 -05:00
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
for (const item of toSearch) {
|
2020-09-22 10:28:28 -04:00
|
|
|
if (item.indexOf(term) > 0) {
|
|
|
|
addResult(item);
|
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
|
2022-03-05 12:58:49 -05:00
|
|
|
if (maxResults) {
|
2019-03-13 08:02:56 -04:00
|
|
|
return results.slice(0, maxResults);
|
2022-03-05 12:58:49 -05:00
|
|
|
} else {
|
|
|
|
return results;
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
2019-03-15 00:44:49 -04:00
|
|
|
}
|
2019-03-13 08:02:56 -04:00
|
|
|
|
|
|
|
export function isSkinTonableEmoji(term) {
|
2020-09-01 16:47:22 -04:00
|
|
|
const match = term.split(":").filter(Boolean)[0];
|
2019-03-13 08:02:56 -04:00
|
|
|
if (match) {
|
2022-03-05 12:58:49 -05:00
|
|
|
return tonableEmojis.includes(match);
|
2019-03-13 08:02:56 -04:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|