]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/features/emoji/emoji.js
359bb7ffd44700b28eb21682eff00d57beda8dcc
[mastodon.git] / app / javascript / mastodon / features / emoji / emoji.js
1 import { autoPlayGif } from '../../initial_state';
2 import unicodeMapping from './emoji_unicode_mapping_light';
3 import Trie from 'substring-trie';
4
5 const trie = new Trie(Object.keys(unicodeMapping));
6
7 const assetHost = process.env.CDN_HOST || '';
8
9 const emojify = (str, customEmojis = {}) => {
10 const tagCharsWithoutEmojis = '<&';
11 const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
12 let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
13 for (;;) {
14 let match, i = 0, tag;
15 while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
16 i += str.codePointAt(i) < 65536 ? 1 : 2;
17 }
18 let rend, replacement = '';
19 if (i === str.length) {
20 break;
21 } else if (str[i] === ':') {
22 if (!(() => {
23 rend = str.indexOf(':', i + 1) + 1;
24 if (!rend) return false; // no pair of ':'
25 const lt = str.indexOf('<', i + 1);
26 if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
27 const shortname = str.slice(i, rend);
28 // now got a replacee as ':shortname:'
29 // if you want additional emoji handler, add statements below which set replacement and return true.
30 if (shortname in customEmojis) {
31 const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
32 replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
33 return true;
34 }
35 return false;
36 })()) rend = ++i;
37 } else if (tag >= 0) { // <, &
38 rend = str.indexOf('>;'[tag], i + 1) + 1;
39 if (!rend) {
40 break;
41 }
42 if (tag === 0) {
43 if (invisible) {
44 if (str[i + 1] === '/') { // closing tag
45 if (!--invisible) {
46 tagChars = tagCharsWithEmojis;
47 }
48 } else if (str[rend - 2] !== '/') { // opening tag
49 invisible++;
50 }
51 } else {
52 if (str.startsWith('<span class="invisible">', i)) {
53 // avoid emojifying on invisible text
54 invisible = 1;
55 tagChars = tagCharsWithoutEmojis;
56 }
57 }
58 }
59 i = rend;
60 } else { // matched to unicode emoji
61 const { filename, shortCode } = unicodeMapping[match];
62 const title = shortCode ? `:${shortCode}:` : '';
63 replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
64 rend = i + match.length;
65 // If the matched character was followed by VS15 (for selecting text presentation), skip it.
66 if (str.codePointAt(rend) === 65038) {
67 rend += 1;
68 }
69 }
70 rtn += str.slice(0, i) + replacement;
71 str = str.slice(rend);
72 }
73 return rtn + str;
74 };
75
76 export default emojify;
77
78 export const buildCustomEmojis = (customEmojis) => {
79 const emojis = [];
80
81 customEmojis.forEach(emoji => {
82 const shortcode = emoji.get('shortcode');
83 const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
84 const name = shortcode.replace(':', '');
85
86 emojis.push({
87 id: name,
88 name,
89 short_names: [name],
90 text: '',
91 emoticons: [],
92 keywords: [name],
93 imageUrl: url,
94 custom: true,
95 customCategory: emoji.get('category'),
96 });
97 });
98
99 return emojis;
100 };
101
102 export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set());
This page took 0.093555 seconds and 2 git commands to generate.