about summary refs log tree commit diff
path: root/app/javascript/mastodon/emoji.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/mastodon/emoji.js')
-rw-r--r--app/javascript/mastodon/emoji.js100
1 files changed, 61 insertions, 39 deletions
diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js
index 865b85b61..d75f6f598 100644
--- a/app/javascript/mastodon/emoji.js
+++ b/app/javascript/mastodon/emoji.js
@@ -3,48 +3,70 @@ import Trie from 'substring-trie';
 
 const trie = new Trie(Object.keys(unicodeMapping));
 
+const assetHost = process.env.CDN_HOST || '';
+
 const emojify = (str, customEmojis = {}) => {
-  // This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
-  // and replacing valid unicode strings
-  // that _aren't_ within tags with an <img> version.
-  // The goal is to be the same as an emojione.regUnicode replacement, but faster.
-  let i = -1;
-  let insideTag = false;
-  let insideShortname = false;
-  let shortnameStartIndex = -1;
-  let match;
-  while (++i < str.length) {
-    const char = str.charAt(i);
-    if (insideShortname && char === ':') {
-      const shortname = str.substring(shortnameStartIndex, i + 1);
-      if (shortname in customEmojis) {
-        const replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${customEmojis[shortname]}" />`;
-        str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1);
-        i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string
-      } else {
-        i--;
-      }
-      insideShortname = false;
-    } else if (insideTag && char === '>') {
-      insideTag = false;
-    } else if (char === '<') {
-      insideTag = true;
-      insideShortname = false;
-    } else if (!insideTag && char === ':') {
-      insideShortname = true;
-      shortnameStartIndex = i;
-    } else if (!insideTag && (match = trie.search(str.substring(i)))) {
-      const unicodeStr = match;
-      if (unicodeStr in unicodeMapping) {
-        const [filename, shortCode] = unicodeMapping[unicodeStr];
-        const alt      = unicodeStr;
-        const replacement =  `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
-        str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
-        i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
-      }
+  let rtn = '';
+  for (;;) {
+    let match, i = 0, tag;
+    while (i < str.length && (tag = '<&'.indexOf(str[i])) === -1 && str[i] !== ':' && !(match = trie.search(str.slice(i)))) {
+      i += str.codePointAt(i) < 65536 ? 1 : 2;
+    }
+    if (i === str.length)
+      break;
+    else if (tag >= 0) {
+      const tagend = str.indexOf('>;'[tag], i + 1) + 1;
+      if (!tagend)
+        break;
+      rtn += str.slice(0, tagend);
+      str = str.slice(tagend);
+    } else if (str[i] === ':') {
+      try {
+        // if replacing :shortname: succeed, exit this block with "continue"
+        const closeColon = str.indexOf(':', i + 1) + 1;
+        if (!closeColon) throw null; // no pair of ':'
+        const lt = str.indexOf('<', i + 1);
+        if (!(lt === -1 || lt >= closeColon)) throw null; // tag appeared before closing ':'
+        const shortname = str.slice(i, closeColon);
+        if (shortname in customEmojis) {
+          rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${customEmojis[shortname]}" />`;
+          str = str.slice(closeColon);
+          continue;
+        }
+      } catch (e) {}
+      // replacing :shortname: failed
+      rtn += str.slice(0, i + 1);
+      str = str.slice(i + 1);
+    } else {
+      const [filename, shortCode] = unicodeMapping[match];
+      rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="${assetHost}/emoji/${filename}.svg" />`;
+      str = str.slice(i + match.length);
     }
   }
-  return str;
+  return rtn + str;
 };
 
 export default emojify;
+
+export const buildCustomEmojis = customEmojis => {
+  const emojis = [];
+
+  customEmojis.forEach(emoji => {
+    const shortcode = emoji.get('shortcode');
+    const url       = emoji.get('url');
+    const name      = shortcode.replace(':', '');
+
+    emojis.push({
+      id: name,
+      name,
+      short_names: [name],
+      text: '',
+      emoticons: [],
+      keywords: [name],
+      imageUrl: url,
+      custom: true,
+    });
+  });
+
+  return emojis;
+};