Browse Source

[add][Refs #1] Get custom emojis from server and display them
[bugfix] Fix emoji links containing formatting chars

B Thibault 8 years ago
parent
commit
a5a9884811
6 changed files with 115 additions and 55 deletions
  1. 3 1
      cli/resources.js
  2. 78 39
      cli/ui.js
  3. 13 12
      srv/public/slack.min.js
  4. 1 0
      srv/public/style.css
  5. 16 3
      srv/src/slack.js
  6. 4 0
      srv/src/slackData.js

+ 3 - 1
cli/resources.js

@@ -30,7 +30,9 @@ var R = {
         ,unreadHi: "unreadHi"
         ,replyingTo: "replyingTo"
         ,emoji: {
-            small: "emoji-small"
+            emoji: "emoji"
+            ,small: "emoji-small"
+            ,custom: "emoji-custom"
         }
         ,chatList: {
             entry: "slack-context-room"

+ 78 - 39
cli/ui.js

@@ -59,9 +59,9 @@ function createImsListItem(ims) {
 }
 
 function onContextUpdated() {
-    var chanListFram = document.createDocumentFragment();
+    var chanListFram = document.createDocumentFragment()
+        ,sortedChans = SLACK.context.self ? Object.keys(SLACK.context.self.channels) : [];
 
-    var sortedChans = SLACK.context.self ? Object.keys(SLACK.context.self.channels) : [];
     sortedChans.sort(function(a, b) {
         if (a[0] !== b[0]) {
             return a[0] - b[0];
@@ -201,17 +201,48 @@ function formatDate(ts) {
     return (new Date(ts * 1000)).toLocaleTimeString();
 }
 
+/**
+ * Try to resolve emoji from customized context
+ * @param {string} emoji
+ * @return {Element|string}
+**/
+function tryGetCustomEmoji(emoji) {
+    var loop = {};
+
+    while (!loop[emoji]) {
+        var emojisrc= SLACK.context.emojis[emoji];
+        if (emojisrc) {
+            if (emojisrc.substr(0, 6) == "alias:") {
+                loop[emoji] = true;
+                emoji = emojisrc.substr(6);
+            } else {
+                var dom = document.createElement("span");
+                dom.className = R.klass.emoji.custom +' ' +R.klass.emoji.emoji;
+                dom.style.backgroundImage = "url('" +emojisrc +"')";
+                return dom;
+            }
+        }
+        return emoji; // Emoji not found, fallback to std emoji
+    }
+    return emoji; //loop detected, return first emoji
+}
+
+function getEmojiDom(emojiCode) {
+    var emoji = tryGetCustomEmoji(emojiCode);
+
+    if (typeof emoji === "string" && "makeEmoji" in window)
+        emoji = window['makeEmoji'](emoji);
+    return typeof emoji === "string" ? null : emoji;
+}
+
 /**
  * replace all :emoji: codes with corresponding image
  * @param {string} inputString
  * @return {string}
 **/
 function formatEmojis(inputString) {
-    if (!("makeEmoji" in window))
-        return inputString;
     return inputString.replace(/:(\w+):/g, function(returnFailed, emoji) {
-        var emojiDom = window['makeEmoji'](emoji);
-        console.log(emoji, emojiDom ? "found" : "not found");
+        var emojiDom = getEmojiDom(emoji);
         if (emojiDom) {
             var domParent = document.createElement("span");
             domParent.className = R.klass.emoji.small;
@@ -230,13 +261,45 @@ function formatSlackText(fullText) {
     var msgContents = fullText.split(/\r?\n/g);
 
     for (var msgContentIndex=0, nbMsgContents = msgContents.length; msgContentIndex < nbMsgContents; msgContentIndex++) {
-        var msgContent = formatEmojis(msgContents[msgContentIndex])
+        var msgContent = msgContents[msgContentIndex]
             ,_msgContent = ""
             ,currentMods = {}
             ,quote = false
             ,i =0
-            ,msgLength = msgContent.length;
 
+        msgContent = msgContent.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
+            function(matched, type, entity) {
+                var sub = entity.split('|');
+
+                if (type === '@') {
+                    if (!sub[1]) {
+                        var user = SLACK.context.getMember(sub[0]);
+                        sub[1] = user ? ('@' +user.name) : locale.unknownMember;
+                    } else if ('@' !== sub[1][0]) {
+                        sub[1] = '@' +sub[1];
+                    }
+                    sub[0] = '#' +sub[0];
+                    sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
+                } else if (type === '#') {
+                    if (!sub[1]) {
+                        var chan = SLACK.context.getChannel(sub[0]);
+                        sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
+                    } else if ('#' !== sub[1][0]) {
+                        sub[1] = '#' +sub[1];
+                    }
+                    sub[0] = '#' +sub[0];
+                    sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
+                } else if (sub[0].indexOf("://") !== -1) {
+                    if (!sub[1])
+                        sub[1] = sub[0];
+                    sub[2] = R.klass.msg.link;
+                } else {
+                    return matched;
+                }
+                return '<a href="' +sub[0] +'" class="' +sub[2] +'"' +(!type ? ' target="_blank"' : '') +'>' +sub[1] +'</a>';
+            });
+        msgContent = formatEmojis(msgContent);
+        var msgLength = msgContent.length;
         var checkEnd = function(str, pos, c) {
             while (str[pos]) {
                 if (str[pos] != ' ' && str[pos] != c && str[pos +1] == c) {
@@ -261,6 +324,13 @@ function formatSlackText(fullText) {
         for (; i < msgLength; i++) {
             var c = msgContent[i];
 
+            if (c === '<') {
+                do {
+                    _msgContent += msgContent[i++];
+                } while (msgContent[i -1] !== '>');
+                i--;
+                continue;
+            }
             if (!(currentMods[R.klass.msg.style.bold]) && c === '*' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
                 if (Object.keys(currentMods).length)
                     _msgContent += '</span>';
@@ -311,37 +381,6 @@ function formatSlackText(fullText) {
             // Should not append
             _msgContent += '</span>';
         }
-        _msgContent = _msgContent.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
-            function(matched, type, entity) {
-                var sub = entity.split('|');
-
-                if (type === '@') {
-                    if (!sub[1]) {
-                        var user = SLACK.context.getMember(sub[0]);
-                        sub[1] = user ? ('@' +user.name) : locale.unknownMember;
-                    } else if ('@' !== sub[1][0]) {
-                        sub[1] = '@' +sub[1];
-                    }
-                    sub[0] = '#' +sub[0];
-                    sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
-                } else if (type === '#') {
-                    if (!sub[1]) {
-                        var chan = SLACK.context.getChannel(sub[0]);
-                        sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
-                    } else if ('#' !== sub[1][0]) {
-                        sub[1] = '#' +sub[1];
-                    }
-                    sub[0] = '#' +sub[0];
-                    sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
-                } else if (sub[0].indexOf("://") !== -1) {
-                    if (!sub[1])
-                        sub[1] = sub[0];
-                    sub[2] = R.klass.msg.link;
-                } else {
-                    return matched;
-                }
-                return '<a href="' +sub[0] +'" class="' +sub[2] +'"' +(!type ? ' target="_blank"' : '') +'>' +sub[1] +'</a>';
-            });
         if (quote)
             msgContents[msgContentIndex] = '<span class="' +R.klass.msg.style.quote +'">' +_msgContent +'</span>';
         else

+ 13 - 12
srv/public/slack.min.js

@@ -1,7 +1,7 @@
 function h(a){this.id=a.id;this.name=a.name}function l(a,b){this.id=a.id;this.name=a.name;this.b=parseFloat(a.last_read);this.a={};if(a.members)for(var d=0,c=a.members.length;d<c;d++){var f=t(b,a.members[d]);this.a[f.id]=f;f.f[this.id]=this}}function u(a,b){var d=[];this.id=b.id;this.a={};for(var c=0,f=b.members.length;c<f;c++){var e=t(a,b.members[c]);this.a[b.members[c]]=e;e.f[this.id]=this;d.push(e.name)}this.name=d.join(", ");this.b=parseFloat(b.last_read)}
-function aa(a,b){this.id=b.id;this.c=a;this.b=parseFloat(b.last_read)}function ba(a){this.id=a.id;this.name=a.name;this.status=a.status;this.b={G:a.profile.image_24,H:a.profile.image_32,j:a.profile.image_48,B:a.profile.image_72,F:a.profile.image_192,J:a.profile.image_512};this.f={};this.a=null}function ca(a){this.id=a.id;this.name=a.name;this.b={I:a.icons.image_36,j:a.icons.image_48,B:a.icons.image_72};this.f={};this.a=null}
-function v(){this.o=null;this.f={};this.b={};this.m={};this.a={};this.c=null;this.g={}}function t(a,b){return a.a[b]||a.g[b]||null}function w(a,b){return a.f[b]||a.m[b]||a.b[b]||null}"undefined"!==typeof module&&(module.A.C=v);function da(a){this.g=a.user;this.b=parseFloat(a.ts);this.type=a.type;this.c=a.subtype;this.a=a}function x(a,b,d){this.id="string"===typeof a?a:a.id;this.a=[];this.b=b;d&&y(this,d)}function y(a,b){var d=0;b.forEach(function(a){d=Math.max(this.push(a),d)}.bind(a));ea(a)}x.prototype.push=function(a){for(var b=parseFloat(a.ts),d=0,c=this.a.length;d<c;d++)if(this.a[d].b===b)return b;for(this.a.push(new da(a));this.a.length>this.b;)this.a.shift();return b};
-function ea(a){a.a.sort(function(a,d){return a.b-d.b})}"undefined"!==typeof module&&(module.A.D=x);var B={},C;function fa(){var a;if(!a){for(var b=0,d=navigator.languages.length;b<d;b++)if(B.hasOwnProperty(navigator.languages[b])){a=navigator.languages[b];break}a||(a="en")}C=B[a];console.log("Loading language pack: "+a);if(C.h)for(b in C.h)document.getElementById(b).textContent=C.h[b]};B.fr={w:"Utilisateur inconnu",u:"Channel inconnu",s:"Nouveau message",h:{}};B.en={w:"Unknown member",u:"Unknown channel",s:"New message",h:{}};var F=null,G=0;
+function aa(a,b){this.id=b.id;this.c=a;this.b=parseFloat(b.last_read)}function ba(a){this.id=a.id;this.name=a.name;this.status=a.status;this.b={H:a.profile.image_24,I:a.profile.image_32,j:a.profile.image_48,C:a.profile.image_72,G:a.profile.image_192,K:a.profile.image_512};this.f={};this.a=null}function ca(a){this.id=a.id;this.name=a.name;this.b={J:a.icons.image_36,j:a.icons.image_48,C:a.icons.image_72};this.f={};this.a=null}
+function v(){this.s=null;this.f={};this.b={};this.m={};this.a={};this.c=null;this.g={};this.o={}}function t(a,b){return a.a[b]||a.g[b]||null}function w(a,b){return a.f[b]||a.m[b]||a.b[b]||null}"undefined"!==typeof module&&(module.B.D=v);function da(a){this.g=a.user;this.b=parseFloat(a.ts);this.type=a.type;this.c=a.subtype;this.a=a}function x(a,b,d){this.id="string"===typeof a?a:a.id;this.a=[];this.b=b;d&&y(this,d)}function y(a,b){var d=0;b.forEach(function(a){d=Math.max(this.push(a),d)}.bind(a));ea(a)}x.prototype.push=function(a){for(var b=parseFloat(a.ts),d=0,c=this.a.length;d<c;d++)if(this.a[d].b===b)return b;for(this.a.push(new da(a));this.a.length>this.b;)this.a.shift();return b};
+function ea(a){a.a.sort(function(a,d){return a.b-d.b})}"undefined"!==typeof module&&(module.B.F=x);var B={},C;function fa(){var a;if(!a){for(var b=0,d=navigator.languages.length;b<d;b++)if(B.hasOwnProperty(navigator.languages[b])){a=navigator.languages[b];break}a||(a="en")}C=B[a];console.log("Loading language pack: "+a);if(C.h)for(b in C.h)document.getElementById(b).textContent=C.h[b]};B.fr={A:"Utilisateur inconnu",w:"Channel inconnu",u:"Nouveau message",h:{}};B.en={A:"Unknown member",w:"Unknown channel",u:"New message",h:{}};var F=null,G=0;
 function ga(){var a=document.createDocumentFragment(),b=H.a.c?Object.keys(H.a.c.f):[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:(H.a.f[a]||H.a.b[a]).name.localeCompare((H.a.f[b]||H.a.b[b]).name)});b.forEach(function(b){b=H.a.f[b]||H.a.b[b];var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;"D"===b.id[0]?c.className="slack-context-room slack-ims":"G"===b.id[0]?c.className="slack-context-room slack-group":"C"===b.id[0]&&(c.className="slack-context-room slack-channel");d.textContent=
 b.name;c.appendChild(d);c&&a.appendChild(c)});b=H.a.a?Object.keys(H.a.a):[];b.sort(function(a,b){return H.a.a[a].name.localeCompare(H.a.a[b].name)});b.forEach(function(b){b=H.a.a[b].a;var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;c.className="slack-context-room";d.textContent=b.c.name;c.appendChild(d);c&&a.appendChild(c)});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a);I()}
 function K(a){a?document.body.classList.remove("no-network"):document.body.classList.add("no-network")}function L(){if(F){document.body.classList.add("replyingTo");var a=document.getElementById("replyToContainer"),b=document.createElement("a");b.addEventListener("click",function(){F=null;L()});b.className="replyto-close";b.textContent="x";a.textContent="";a.appendChild(b);a.appendChild(M("reply_"+N.id,F,!0))}else document.body.classList.remove("replyingTo")}
@@ -11,13 +11,14 @@ c=document.createElement("div"),d=document.createElement("div"),e=document.creat
 ("#"===a.color[0]?A=a.color[0]:"good"===a.color?A="#2fa44f":"warning"===a.color?A="#de9e31":"danger"===a.color&&(A="#d50200"));c.style.borderColor=A;d.className="slackmsg-attachment-pretext";a.pretext?d.innerHTML=Q(a.pretext):d.classList.add("hidden");e.target="_blank";a.title?(e.innerHTML=Q(a.title),a.title_link&&(e.href=a.title_link),e.className="slackmsg-attachment-title"):e.className="hidden slackmsg-attachment-title";k.target="_blank";f.className="slackmsg-author";a.author_name?(k.innerHTML=
 Q(a.author_name),k.href=a.author_link||"",k.className="slackmsg-author-name",g.className="slackmsg-author-img",a.author_icon?g.src=a.author_icon:g.classList.add("hidden")):f.classList.add("hidden");n.innerHTML=Q(a.text||"");n.a="slackmsg-attachment-text";p.className="slackmsg-attachment-thumb";a.thumb_url?p.src=a.thumb_url:p.classList.add("hidden");q.className="slackmsg-attachment-img";a.image_url?q.src=a.image_url:q.classList.add("hidden");r.className="slackmsg-attachment-footer";D.className="slackmsg-attachment-footer-text";
 z.className="slackmsg-attachment-footer-icon";a.footer?(D.innerHTML=Q(a.footer),a.footer_icon?z.src=a.footer_icon:z.classList.add("hidden")):(z.classList.add("hidden"),D.classList.add("hidden"));E.className="slackmsg-ts";a.ts?E.innerHTML=P(a.ts):E.classList.add("hidden");f.appendChild(g);f.appendChild(k);J.appendChild(n);J.appendChild(p);r.appendChild(z);r.appendChild(D);r.appendChild(E);c.appendChild(e);c.appendChild(f);c.appendChild(J);c.appendChild(q);c.appendChild(r);b.appendChild(d);b.appendChild(c);
-b&&m.appendChild(b)});c.appendChild(p);return c}function P(a){"string"!==typeof a&&(a=parseFloat(a));return(new Date(1E3*a)).toLocaleTimeString()}function ha(a){return"makeEmoji"in window?a.replace(/:(\w+):/g,function(a,d){var b=window.makeEmoji(d);console.log(d,b?"found":"not found");if(b){var f=document.createElement("span");f.className="emoji-small";f.appendChild(b);return f.outerHTML}return a}):a}
-function Q(a){a=a.split(/\r?\n/g);for(var b=0,d=a.length;b<d;b++){for(var c=ha(a[b]),f="",e={},k=!1,g=0,n=c.length,p=function(a,b,c){for(;a[b];){if(" "!=a[b]&&a[b]!=c&&a[b+1]==c)return!0;b++}return!1},r=function(a){return Object.keys(e).length?'<span class="'+Object.keys(a).join(" ")+'">':""};g<n&&(" "===c[g]||"\t"===c[g]);)g++;"&gt;"===c.substr(g,4)&&(k=!0,g+=4);for(;g<n;g++){var m=c[g];if(!e["slackmsg-style-bold"]&&"*"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-bold"]=
-!0,f+=r(e);else if(!e["slackmsg-style-strike"]&&"~"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-strike"]=!0,f+=r(e);else if(!e["slackmsg-style-code"]&&"`"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-code"]=!0,f+=r(e);else if(!e["slackmsg-style-italic"]&&"_"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-italic"]=!0,f+=r(e);else{var q=!1,f=f+m;do{if(e["slackmsg-style-bold"]&&"*"!==m&&"*"===c[g+1])delete e["slackmsg-style-bold"],
-q=!0;else if(e["slackmsg-style-strike"]&&"~"!==m&&"~"===c[g+1])delete e["slackmsg-style-strike"],q=!0;else if(e["slackmsg-style-code"]&&"`"!==m&&"`"===c[g+1])delete e["slackmsg-style-code"],q=!0;else if(e["slackmsg-style-italic"]&&"_"!==m&&"_"===c[g+1])delete e["slackmsg-style-italic"],q=!0;else break;m=c[++g]}while(g<n);q&&(f+="</span>"+r(e))}}e&&(f+="</span>");f=f.replace(RegExp("<([@#]?)([^>]*)>","g"),function(a,b,c){c=c.split("|");if("@"===b)c[1]?"@"!==c[1][0]&&(c[1]="@"+c[1]):(a=t(H.a,c[0]),
-c[1]=a?"@"+a.name:C.w),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-user";else if("#"===b)c[1]?"#"!==c[1][0]&&(c[1]="#"+c[1]):(a=w(H.a,c[0]),c[1]=a?"#"+a.name:C.u),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-chan";else if(-1!==c[0].indexOf("://"))c[1]||(c[1]=c[0]),c[2]="slackmsg-link";else return a;return'<a href="'+c[0]+'" class="'+c[2]+'"'+(b?"":' target="_blank"')+">"+c[1]+"</a>"});a[b]=k?'<span class="slackmsg-style-quote">'+f+"</span>":f}return a.join("<br/>")}
-function M(a,b,d){"me_message"===b.c?(a=O(a,b,d),a.classList.add("slackmsg-me_message")):a=O(a,b,d);return a}function R(){var a=0,b=0,d="",c;for(c in S)S.hasOwnProperty(c)&&(a+=S[c].l,b+=S[c].i);b?d="(!"+b+") - ":a&&(d="("+a+") - ");d+=H.a.o.name;document.title=d}
-function ia(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(G+3E4<a){var b=new Notification(C.s);G=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
+b&&m.appendChild(b)});c.appendChild(p);return c}function P(a){"string"!==typeof a&&(a=parseFloat(a));return(new Date(1E3*a)).toLocaleTimeString()}
+function ha(a){return a.replace(/:(\w+):/g,function(a,d){var b;a:{for(var f=d,e={};!e[f];){if(b=H.a.o[f])if("alias:"==b.substr(0,6))e[f]=!0,f=b.substr(6);else{f=document.createElement("span");f.className="emoji-custom emoji";f.style.backgroundImage="url('"+b+"')";b=f;break a}break}b=f}"string"===typeof b&&"makeEmoji"in window&&(b=window.makeEmoji(b));return(b="string"===typeof b?null:b)?(f=document.createElement("span"),f.className="emoji-small",f.appendChild(b),f.outerHTML):a})}
+function Q(a){a=a.split(/\r?\n/g);for(var b=0,d=a.length;b<d;b++){for(var c=a[b],f="",e={},k=!1,g=0,c=c.replace(RegExp("<([@#]?)([^>]*)>","g"),function(a,b,c){c=c.split("|");if("@"===b)c[1]?"@"!==c[1][0]&&(c[1]="@"+c[1]):(a=t(H.a,c[0]),c[1]=a?"@"+a.name:C.A),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-user";else if("#"===b)c[1]?"#"!==c[1][0]&&(c[1]="#"+c[1]):(a=w(H.a,c[0]),c[1]=a?"#"+a.name:C.w),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-chan";else if(-1!==c[0].indexOf("://"))c[1]||(c[1]=
+c[0]),c[2]="slackmsg-link";else return a;return'<a href="'+c[0]+'" class="'+c[2]+'"'+(b?"":' target="_blank"')+">"+c[1]+"</a>"}),c=ha(c),n=c.length,p=function(a,b,c){for(;a[b];){if(" "!=a[b]&&a[b]!=c&&a[b+1]==c)return!0;b++}return!1},r=function(a){return Object.keys(e).length?'<span class="'+Object.keys(a).join(" ")+'">':""};g<n&&(" "===c[g]||"\t"===c[g]);)g++;"&gt;"===c.substr(g,4)&&(k=!0,g+=4);for(;g<n;g++){var m=c[g];if("<"===m){do f+=c[g++];while(">"!==c[g-1]);g--}else if(!e["slackmsg-style-bold"]&&
+"*"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-bold"]=!0,f+=r(e);else if(!e["slackmsg-style-strike"]&&"~"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-strike"]=!0,f+=r(e);else if(!e["slackmsg-style-code"]&&"`"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-code"]=!0,f+=r(e);else if(!e["slackmsg-style-italic"]&&"_"===m&&c[g+1]&&p(c,g,m))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-italic"]=
+!0,f+=r(e);else{var q=!1,f=f+m;do{if(e["slackmsg-style-bold"]&&"*"!==m&&"*"===c[g+1])delete e["slackmsg-style-bold"],q=!0;else if(e["slackmsg-style-strike"]&&"~"!==m&&"~"===c[g+1])delete e["slackmsg-style-strike"],q=!0;else if(e["slackmsg-style-code"]&&"`"!==m&&"`"===c[g+1])delete e["slackmsg-style-code"],q=!0;else if(e["slackmsg-style-italic"]&&"_"!==m&&"_"===c[g+1])delete e["slackmsg-style-italic"],q=!0;else break;m=c[++g]}while(g<n);q&&(f+="</span>"+r(e))}}e&&(f+="</span>");a[b]=k?'<span class="slackmsg-style-quote">'+
+f+"</span>":f}return a.join("<br/>")}function M(a,b,d){"me_message"===b.c?(a=O(a,b,d),a.classList.add("slackmsg-me_message")):a=O(a,b,d);return a}function R(){var a=0,b=0,d="",c;for(c in S)S.hasOwnProperty(c)&&(a+=S[c].l,b+=S[c].i);b?d="(!"+b+") - ":a&&(d="("+a+") - ");d+=H.a.s.name;document.title=d}
+function ia(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(G+3E4<a){var b=new Notification(C.u);G=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
 function T(){var a=document.createDocumentFragment(),b=N.id;document.getElementById("chatWindow").textContent="";H.b[b]&&H.b[b].a.forEach(function(c){"message"===c.type&&(c=M(b,c),a.appendChild(c))});var d=document.getElementById("chatWindow");d.appendChild(a);d.scrollTop=d.scrollHeight-d.clientHeight}
 function ja(a){for(var b=a.target;b!==a.currentTarget&&b&&!b.classList.contains("slackmsg-hover");){if(b.classList.contains("slackmsg-hover-reply")){a:{for(b=b||a.target;b!==a.currentTarget&&b;){if(b.classList.contains("slackmsg-item")){a=b.id;break a}b=b.parentElement}a=void 0}if(a){a=parseFloat(a.split("_")[1]);for(var b=H.b[N.id].a,d=0,c=b.length;d<c&&b[d].b<=a;d++)if(b[d].b===a){F!==b[d]&&(F=b[d],L());break}}break}b=b.parentElement}}function U(){document.getElementById("msgInput").focus()}
 function I(){var a=document.location.hash.substr(1),b=w(H.a,a),a=t(H.a,a);b&&b!==N?V(b):a&&a.a&&V(a.a)}
@@ -26,7 +27,7 @@ document.addEventListener("DOMContentLoaded",function(){fa();document.getElement
 document.getElementById("attachFile").addEventListener("click",function(a){a.preventDefault();N&&document.getElementById("fileUploadContainer").classList.remove("hidden");return!1});document.getElementById("msgForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("msgInput");if(N&&a.value){var b=N,d=F,c=new XMLHttpRequest,f="api/msg?room="+b.id+"&text="+encodeURIComponent(a.value);if(d){var e=t(H.a,d.g),k="Message";"C"===b.id[0]?k="Channel message":"D"===b.id[0]?
 k="Direct message":"G"===b.id[0]&&(k="Group message");f+="&attachments="+encodeURIComponent(JSON.stringify([{fallback:d.a.text||"",author_name:"<@"+e.id+"|"+e.name+">",author_icon:e.b.j,text:d.a.text||"",footer:k,ts:d.b}]))}c.open("POST",f,!0);c.send(null);a.value="";F&&(F=null,L())}U();return!1});window.addEventListener("blur",function(){window.hasFocus=!1});window.addEventListener("focus",function(){window.hasFocus=!0;G=0;N&&W();U()});window.hasFocus=!0;X()});var H,S={};function la(a,b){if(a&&(a!==N||!window.hasFocus)){var d=new RegExp("<@"+H.a.c.id),c=!1,f=!1;S[a.id]||(S[a.id]={i:0,l:0});b.forEach(function(b){"message"===b.type&&b.text&&("D"===a.id[0]||b.text.match(d)?(f|=!S[a.id].i,S[a.id].i++,c=!0):S[a.id].l++)});R();document.getElementById(a.id).classList.add("unread");c&&document.getElementById(a.id).classList.add("unreadHi");f&&!window.hasFocus&&ia()}}
 function W(){var a=N;S[a.id]&&(S[a.id]={i:0,l:0},R());a=document.getElementById(a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}H=new function(){this.c=0;this.a=new v;this.b={}};var Y=0,N=null;function Z(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var d=null,c=2===Math.floor(b.status/100);if(c){Y&&(Y=0,K(!0));d=b.response;try{d=JSON.parse(d)}catch(f){d=null}}else Y?(Y+=Math.floor((Y||5)/2),Y=Math.min(60,Y)):(Y=5,K(!1));a(c,d)}else Y&&(Y=0,K(!0)),Z(a)};b.open("GET","api?v="+H.c,!0);b.send(null)}
-function ma(a,b){if(a){if(b){var d=H;b.v&&(d.c=b.v);if(b["static"]){for(var c=d.a,f=b["static"],e=0,k=f.bots.length;e<k;e++)c.g[f.bots[e].id]=new ca(f.bots[e]);e=0;for(k=f.users.length;e<k;e++)c.a[f.users[e].id]=new ba(f.users[e]);e=0;for(k=f.ims.length;e<k;e++){var g=t(c,f.ims[e].user);g&&(g.a=new aa(g,f.ims[e]),c.m[g.a.id]=g.a)}e=0;for(k=f.channels.length;e<k;e++)c.f[f.channels[e].id]=new l(f.channels[e],c);e=0;for(k=f.groups.length;e<k;e++)c.b[f.groups[e].id]=new u(c,f.groups[e]);c.o=new h(f.team);
-c.c=t(c,f.self.id);ga()}if(b.live){for(var n in b.live)(c=d.b[n])?y(c,b.live[n]):d.b[n]=new x(n,250,b.live[n]);for(var p in b.live)la(w(d.a,p),b.live[p]),N&&b.live[N.id]&&T()}}X()}else setTimeout(X,1E3*Y)}function X(){Z(ma)}
+function ma(a,b){if(a){if(b){var d=H;b.v&&(d.c=b.v);if(b["static"]){for(var c=d.a,f=b["static"],e=0,k=f.bots.length;e<k;e++)c.g[f.bots[e].id]=new ca(f.bots[e]);e=0;for(k=f.users.length;e<k;e++)c.a[f.users[e].id]=new ba(f.users[e]);e=0;for(k=f.ims.length;e<k;e++){var g=t(c,f.ims[e].user);g&&(g.a=new aa(g,f.ims[e]),c.m[g.a.id]=g.a)}e=0;for(k=f.channels.length;e<k;e++)c.f[f.channels[e].id]=new l(f.channels[e],c);e=0;for(k=f.groups.length;e<k;e++)c.b[f.groups[e].id]=new u(c,f.groups[e]);c.o=f.emojis;
+c.s=new h(f.team);c.c=t(c,f.self.id);ga()}if(b.live){for(var n in b.live)(c=d.b[n])?y(c,b.live[n]):d.b[n]=new x(n,250,b.live[n]);for(var p in b.live)la(w(d.a,p),b.live[p]),N&&b.live[N.id]&&T()}}X()}else setTimeout(X,1E3*Y)}function X(){Z(ma)}
 function V(a){N&&document.getElementById(N.id).classList.remove("selected");document.getElementById(a.id).classList.add("selected");document.body.classList.remove("no-room-selected");N=a;a=N.name||(N.c?N.c.name:void 0);if(!a){a=[];for(var b in N.a)a.push(N.a[b].name);a=a.join(", ")}document.getElementById("currentRoomTitle").textContent=a;T();U();document.getElementById("fileUploadContainer").classList.add("hidden");W();F&&(F=null,L());N.b&&!H.b[N.id]&&(b=new XMLHttpRequest,b.open("GET","api/hist?room="+
 N.id,!0),b.send(null))}function ka(a,b,d){var c=N;new FileReader;var f=new FormData,e=new XMLHttpRequest;f.append("file",b);f.append("filename",a);e.onreadystatechange=function(){4===e.readyState&&(204===e.status?d(null):d(e.statusText))};e.open("POST","api/file?room="+c.id);e.send(f)};

+ 1 - 0
srv/public/style.css

@@ -90,4 +90,5 @@ body {
 
 .emoji-small { position: relative; display: inline-block; width: 20px; height: 20px; vertical-align: middle; }
 .emoji-small .emoji { transform: scale(0.3); position: absolute; left: -24px; top: -24px; }
+.emoji-custom { text-indent: -9999em; image-rendering: optimizeQuality; font-size: inherit; height: 64px; width: 64px; top: -3px; position: relative; display: inline-block; margin: 0 .15em; line-height: normal; vertical-align: middle; background-repeat: no-repeat; background-size: contain; }
 

+ 16 - 3
srv/src/slack.js

@@ -20,6 +20,7 @@ const SLACK_ENDPOINT = "https://slack.com/api/"
         ,groupHistory: "groups.history"
         ,postMsg: "chat.postMessage"
         ,postFile: "files.upload"
+        ,emojiList: "emoji.list"
     }
     ,HISTORY_LENGTH = 35
 ;
@@ -95,9 +96,21 @@ Slack.prototype.connect = function(knownVersion) {
             _this.lastCb(_this);
             return;
         }
-        _this.data.updateStatic(body);
-        _this.connectRtm(body.url);
-        _this.waitForEvent(knownVersion);
+        _this.getEmojis((emojis) => {
+            body.emojis = emojis;
+            _this.data.updateStatic(body);
+            _this.connectRtm(body.url);
+            _this.waitForEvent(knownVersion);
+        });
+    });
+};
+
+Slack.prototype.getEmojis = function(cb) {
+    httpsRequest(SLACK_ENDPOINT +GETAPI.emojiList +"?token=" +this.token, (status, body) => {
+        if (!status || !body || !body.ok)
+            cb(null);
+        else
+            cb(body.emoji || {});
     });
 };
 

+ 4 - 0
srv/src/slackData.js

@@ -228,6 +228,8 @@ function SlackData(slack) {
     this.self = null;
     /** @type {Object.<string, SlackBot>} */
     this.bots = {};
+    /** @type {Object.<string, string>} */
+    this.emojis = {};
 
     /**
      * Node serv handler
@@ -390,6 +392,7 @@ SlackData.prototype.updateStatic = function(data) {
     for (var i =0, nbGroups = data["groups"].length; i < nbGroups; i++) {
         this.groups[data["groups"][i]["id"]] = new SlackGroup(this, data["groups"][i]);
     }
+    this.emojis = data["emojis"];
     this.team = new SlackTeam(data["team"]);
     this.staticV = parseFloat(data["latest_event_ts"]);
     this.self = this.getMember(data["self"]["id"]);
@@ -442,6 +445,7 @@ SlackData.prototype.buildStatic = function() {
         ,"self": {
             "id": this.self.id
         }
+        ,"emojis": this.emojis
     };
     for (var chanId in this.channels) {
         res["channels"].push(this.channels[chanId].toStatic());