bigbuffet-rw/app/soapbox/features/emoji/emoji.js

104 lines
3.4 KiB
JavaScript
Raw Normal View History

2020-03-27 13:59:38 -07:00
import Trie from 'substring-trie';
import { joinPublicPath } from 'soapbox/utils/static';
2022-01-10 14:01:24 -08:00
import unicodeMapping from './emoji_unicode_mapping_light';
2020-03-27 13:59:38 -07:00
const trie = new Trie(Object.keys(unicodeMapping));
2020-04-17 15:00:25 -07:00
const emojify = (str, customEmojis = {}, autoplay = false) => {
2020-03-27 13:59:38 -07:00
const tagCharsWithoutEmojis = '<&';
const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
for (;;) {
let match, i = 0, tag;
while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
i += str.codePointAt(i) < 65536 ? 1 : 2;
}
let rend, replacement = '';
if (i === str.length) {
break;
} else if (str[i] === ':') {
// FIXME: This is insane.
/* eslint-disable no-loop-func */
2020-03-27 13:59:38 -07:00
if (!(() => {
rend = str.indexOf(':', i + 1) + 1;
if (!rend) return false; // no pair of ':'
const lt = str.indexOf('<', i + 1);
if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
const shortname = str.slice(i, rend);
// now got a replacee as ':shortname:'
// if you want additional emoji handler, add statements below which set replacement and return true.
if (shortname in customEmojis) {
2020-04-17 15:00:25 -07:00
const filename = autoplay ? customEmojis[shortname].url : customEmojis[shortname].static_url;
2020-03-27 13:59:38 -07:00
replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
return true;
}
return false;
})()) rend = ++i;
/* eslint-enable no-loop-func */
2020-03-27 13:59:38 -07:00
} else if (tag >= 0) { // <, &
rend = str.indexOf('>;'[tag], i + 1) + 1;
if (!rend) {
break;
}
if (tag === 0) {
if (invisible) {
if (str[i + 1] === '/') { // closing tag
if (!--invisible) {
tagChars = tagCharsWithEmojis;
}
} else if (str[rend - 2] !== '/') { // opening tag
invisible++;
}
} else {
if (str.startsWith('<span class="invisible">', i)) {
// avoid emojifying on invisible text
invisible = 1;
tagChars = tagCharsWithoutEmojis;
}
}
}
i = rend;
} else { // matched to unicode emoji
const { filename, shortCode } = unicodeMapping[match];
const title = shortCode ? `:${shortCode}:` : '';
const src = joinPublicPath(`packs/emoji/${filename}.svg`);
replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${src}" />`;
2020-03-27 13:59:38 -07:00
rend = i + match.length;
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
if (str.codePointAt(rend) === 65038) {
rend += 1;
}
}
rtn += str.slice(0, i) + replacement;
str = str.slice(rend);
}
return rtn + str;
};
export default emojify;
2020-04-17 15:00:25 -07:00
export const buildCustomEmojis = (customEmojis, autoplay = false) => {
2020-03-27 13:59:38 -07:00
const emojis = [];
customEmojis.forEach(emoji => {
const shortcode = emoji.get('shortcode');
2020-04-17 15:00:25 -07:00
const url = autoplay ? emoji.get('url') : emoji.get('static_url');
2020-03-27 13:59:38 -07:00
const name = shortcode.replace(':', '');
emojis.push({
id: name,
name,
short_names: [name],
text: '',
emoticons: [],
keywords: [name],
imageUrl: url,
custom: true,
});
});
return emojis;
};