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.

96 lines
3.2 KiB

  1. import { autoPlayGif } from 'flavours/glitch/util/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. const emojify = (str, customEmojis = {}) => {
  7. const tagCharsWithoutEmojis = '<&';
  8. const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
  9. let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
  10. for (;;) {
  11. let match, i = 0, tag;
  12. while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
  13. i += str.codePointAt(i) < 65536 ? 1 : 2;
  14. }
  15. let rend, replacement = '';
  16. if (i === str.length) {
  17. break;
  18. } else if (str[i] === ':') {
  19. if (!(() => {
  20. rend = str.indexOf(':', i + 1) + 1;
  21. if (!rend) return false; // no pair of ':'
  22. const lt = str.indexOf('<', i + 1);
  23. if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
  24. const shortname = str.slice(i, rend);
  25. // now got a replacee as ':shortname:'
  26. // if you want additional emoji handler, add statements below which set replacement and return true.
  27. if (shortname in customEmojis) {
  28. const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
  29. replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
  30. return true;
  31. }
  32. return false;
  33. })()) rend = ++i;
  34. } else if (tag >= 0) { // <, &
  35. rend = str.indexOf('>;'[tag], i + 1) + 1;
  36. if (!rend) {
  37. break;
  38. }
  39. if (tag === 0) {
  40. if (invisible) {
  41. if (str[i + 1] === '/') { // closing tag
  42. if (!--invisible) {
  43. tagChars = tagCharsWithEmojis;
  44. }
  45. } else if (str[rend - 2] !== '/') { // opening tag
  46. invisible++;
  47. }
  48. } else {
  49. if (str.startsWith('<span class="invisible">', i)) {
  50. // avoid emojifying on invisible text
  51. invisible = 1;
  52. tagChars = tagCharsWithoutEmojis;
  53. }
  54. }
  55. }
  56. i = rend;
  57. } else { // matched to unicode emoji
  58. const { filename, shortCode } = unicodeMapping[match];
  59. const title = shortCode ? `:${shortCode}:` : '';
  60. replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
  61. rend = i + match.length;
  62. }
  63. rtn += str.slice(0, i) + replacement;
  64. str = str.slice(rend);
  65. }
  66. return rtn + str;
  67. };
  68. export default emojify;
  69. export { unicodeMapping };
  70. export const buildCustomEmojis = (customEmojis) => {
  71. const emojis = [];
  72. customEmojis.forEach(emoji => {
  73. const shortcode = emoji.get('shortcode');
  74. const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
  75. const name = shortcode.replace(':', '');
  76. emojis.push({
  77. id: name,
  78. name,
  79. short_names: [name],
  80. text: '',
  81. emoticons: [],
  82. keywords: [name],
  83. imageUrl: url,
  84. custom: true,
  85. });
  86. });
  87. return emojis;
  88. };