You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
4.4 KiB

  1. import { autoPlayGif } from '../../initial_state';
  2. import unicodeMapping from './emoji_unicode_mapping_light';
  3. import Trie from 'substring-trie';
  4. const trie = new Trie(Object.keys(unicodeMapping));
  5. const assetHost = process.env.CDN_HOST || '';
  6. // Emoji requiring extra borders depending on theme
  7. const darkEmoji = '🎱🐜⚫🖤⬛◼️◾◼️✒️▪️💣🎳📷📸♣️🕶️✴️🔌💂‍♀️📽️🍳🦍💂🔪🕳️🕹️🕋🖊️🖋️💂‍♂️🎤🎓🎥🎼♠️🎩🦃📼📹🎮🐃🏴';
  8. const lightEmoji = '👽⚾🐔☁️💨🕊️👀🍥👻🐐❕❔⛸️🌩️🔊🔇📃🌧️🐏🍚🍙🐓🐑💀☠️🌨️🔉🔈💬💭🏐🏳️⚪⬜◽◻️▫️';
  9. const emojiFilename = (filename, match) => {
  10. const borderedEmoji = document.body.classList.contains('theme-mastodon-light') ? lightEmoji : darkEmoji;
  11. return borderedEmoji.includes(match) ? (filename + '_border') : filename;
  12. };
  13. const emojify = (str, customEmojis = {}) => {
  14. const tagCharsWithoutEmojis = '<&';
  15. const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
  16. let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
  17. for (;;) {
  18. let match, i = 0, tag;
  19. while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
  20. i += str.codePointAt(i) < 65536 ? 1 : 2;
  21. }
  22. let rend, replacement = '';
  23. if (i === str.length) {
  24. break;
  25. } else if (str[i] === ':') {
  26. if (!(() => {
  27. rend = str.indexOf(':', i + 1) + 1;
  28. if (!rend) return false; // no pair of ':'
  29. const lt = str.indexOf('<', i + 1);
  30. if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
  31. const shortname = str.slice(i, rend);
  32. // now got a replacee as ':shortname:'
  33. // if you want additional emoji handler, add statements below which set replacement and return true.
  34. if (shortname in customEmojis) {
  35. const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
  36. 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}" />`;
  37. return true;
  38. }
  39. return false;
  40. })()) rend = ++i;
  41. } else if (tag >= 0) { // <, &
  42. rend = str.indexOf('>;'[tag], i + 1) + 1;
  43. if (!rend) {
  44. break;
  45. }
  46. if (tag === 0) {
  47. if (invisible) {
  48. if (str[i + 1] === '/') { // closing tag
  49. if (!--invisible) {
  50. tagChars = tagCharsWithEmojis;
  51. }
  52. } else if (str[rend - 2] !== '/') { // opening tag
  53. invisible++;
  54. }
  55. } else {
  56. if (str.startsWith('<span class="invisible">', i)) {
  57. // avoid emojifying on invisible text
  58. invisible = 1;
  59. tagChars = tagCharsWithoutEmojis;
  60. }
  61. }
  62. }
  63. i = rend;
  64. } else { // matched to unicode emoji
  65. const { filename, shortCode } = unicodeMapping[match];
  66. const title = shortCode ? `:${shortCode}:` : '';
  67. replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename, match)}.svg" />`;
  68. rend = i + match.length;
  69. // If the matched character was followed by VS15 (for selecting text presentation), skip it.
  70. if (str.codePointAt(rend) === 65038) {
  71. rend += 1;
  72. }
  73. }
  74. rtn += str.slice(0, i) + replacement;
  75. str = str.slice(rend);
  76. }
  77. return rtn + str;
  78. };
  79. export default emojify;
  80. export const buildCustomEmojis = (customEmojis) => {
  81. const emojis = [];
  82. customEmojis.forEach(emoji => {
  83. const shortcode = emoji.get('shortcode');
  84. const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
  85. const name = shortcode.replace(':', '');
  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. return emojis;
  99. };
  100. export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));