Browse Source

[add] moved templates
[add][wip] ui settings dialog
[add] cgu page
[add] error pages
[add][wip] client config
[bugfix] check if dom exists before setting lang content
[add] block account creation from config
[refactor] moved controller in new folder and classes
[add] config data table
[wip] poll config
[quickfix] disable login with some services from srv config

B Thibault 8 years ago
parent
commit
d04783a78d

+ 36 - 2
Makefile

@@ -14,6 +14,7 @@ SRC=		srv/src/context.js			\
 			cli/confirmDialog.js		\
 			cli/resources.js			\
 			cli/ui.js					\
+			cli/uiSettings.js			\
 			cli/osmTile.js				\
 			cli/dom.js					\
 			cli/emojiBar.js				\
@@ -22,6 +23,7 @@ SRC=		srv/src/context.js			\
 			cli/workflow.js				\
 			cli/uiMessage.js			\
 			cli/utils.js				\
+			cli/config.js				\
 			\
 			cli/commands/core.js		\
 			cli/commands/sherlock.js
@@ -40,11 +42,27 @@ SRC_EMOJIONE_3=	cli/emoji/emojione_v3/data.js		\
 
 OUTPUT_EMOJIONE_3= srv/public/emojione_v3.sprites.js
 
+SRC_CGU=	cli/lang/core.js		\
+			cli/cgu/fr.js			\
+			cli/cgu/en.js			\
+			\
+			cli/cgu/main.js
+
+OUTPUT_CGU=	srv/public/cgu.min.js
+
+SRC_ERR=	cli/lang/core.js		\
+			cli/err/fr.js			\
+			cli/err/en.js			\
+			\
+			cli/err/main.js
+
+OUTPUT_ERR=	srv/public/err.min.js
+
 CLOSURE=	cli/closure-compiler-v20170521.jar
 
 JSHINT=		jshint
 
-all:	core emojione2.3 emojione3
+all:	core emojione2.3 emojione3 cgu
 
 core:
 	echo "\"use strict\";(function(){" > ${OUTPUT}
@@ -87,7 +105,23 @@ emojione3-debug: cli/emoji/emojione_v3/data.js
 emojione_3-clean:
 	$(RM) $(OUTPUT_EMOJIONE_3) cli/emoji/emojione_v3/data.js
 
-clean:	core-clean emojione_2.3-clean emojione_3-clean
+cgu:
+	echo "\"use strict\";(function(){" > ${OUTPUT_CGU}
+	java -jar ${CLOSURE} --compilation_level ADVANCED --language_in=ECMASCRIPT5_STRICT --warning_level=VERBOSE ${SRC_CGU} >> ${OUTPUT_CGU}
+	echo "})();" >> ${OUTPUT_CGU}
+
+cgu-clean:
+	$(RM) $(OUTPUT_CGU)
+
+err:
+	echo "\"use strict\";(function(){" > ${OUTPUT_ERR}
+	java -jar ${CLOSURE} --compilation_level ADVANCED --language_in=ECMASCRIPT5_STRICT --warning_level=VERBOSE ${SRC_ERR} >> ${OUTPUT_ERR}
+	echo "})();" >> ${OUTPUT_ERR}
+
+err-clean:
+	$(RM) $(OUTPUT_ERR)
+
+clean:	core-clean emojione_2.3-clean emojione_3-clean cgu-clean
 
 re:	clean all
 

+ 10 - 0
cli/cgu/en.js

@@ -0,0 +1,10 @@
+/* jshint sub: true */
+
+lang["en"] = {
+    dom: {
+        "cguHeader": "Please read carefully the following terms of services:",
+        "cguContent": "OSEF",
+        "cguButton": "I accept"
+    }
+};
+

+ 10 - 0
cli/cgu/fr.js

@@ -0,0 +1,10 @@
+/* jshint sub: true */
+
+lang["fr"] = {
+    dom: {
+        "cguHeader": "Merci de lire attentivement les Conditions Générales d'Utilisation suivantes:",
+        "cguContent": "OSEF",
+        "cguButton": "J'accepte"
+    }
+};
+

+ 20 - 0
cli/cgu/main.js

@@ -0,0 +1,20 @@
+
+initLang();
+
+document.getElementById("cguButton").addEventListener("click", function(e) {
+    e.preventDefault();
+    var xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = function(e) {
+        if (xhr.readyState === 4) {
+            if (xhr.status === 204) {
+                document.location.reload();
+            } else {
+                //FIXME error
+            }
+        }
+    };
+    xhr.open('PUT', 'account/cguAccept', true);
+    xhr.send(null);
+    return false;
+});
+

+ 24 - 0
cli/config.js

@@ -0,0 +1,24 @@
+
+/** @type Config */
+var CONFIG;
+
+/** @constructor */
+function Config(configData) {
+    this.deviceId = null;
+
+    this.services = [];
+
+    // Load global configurations
+    for (var i =0, nbConfig = configData.length; i < nbConfig; i++)
+        if (configData[i]["service"] === null && configData[i]["device"] === null)
+            this.mergeConfig(JSON.parse(configData[i]["config"]));
+}
+
+Config.prototype.mergeConfig = function(configData) {
+    if (configData["services"])
+        configData["services"].forEach(function(i) {
+            if (this.services.indexOf(i) === -1)
+                this.services.push(i);
+        }, this);
+};
+

+ 4 - 0
cli/data.js

@@ -115,6 +115,10 @@ SlackWrapper.prototype.update = function(data) {
     }
     if (data["static"] || typingUpdated)
         onTypingUpdated();
+    if (data["config"]) {
+        CONFIG = new Config(data["config"]);
+        onConfigUpdated();
+    }
 };
 
 setInterval(function() {

+ 11 - 0
cli/err/en.js

@@ -0,0 +1,11 @@
+/* jshint sub: true */
+
+lang["en"] = {
+    dom: {
+        "err403Header": "Oopsie",
+        "err403Text": "You tried to reach a ressource you don't has access to",
+        "err404Header": "Oopsie",
+        "err404Text": "This page does not exists"
+    }
+};
+

+ 11 - 0
cli/err/fr.js

@@ -0,0 +1,11 @@
+/* jshint sub: true */
+
+lang["fr"] = {
+    dom: {
+        "err403Header": "Oopsie",
+        "err403Text": "Vous avez tenté d'acceder à une page non autorisée",
+        "err404Header": "Oopsie",
+        "err404Text": "Cette page n'existe pas"
+    }
+};
+

+ 3 - 0
cli/err/main.js

@@ -0,0 +1,3 @@
+
+initLang();
+

+ 5 - 2
cli/lang/core.js

@@ -20,8 +20,11 @@ function initLang(lg) {
     locale = lang[lg];
     console.log("Loading language pack: " +lg);
     if (locale.dom)
-        for (var domId in locale.dom)
-            document.getElementById(domId).textContent = locale.dom[domId];
+        for (var domId in locale.dom) {
+            var dom = document.getElementById(domId);
+            if (dom)
+                dom.textContent = locale.dom[domId];
+        }
     onLangInitialized.forEach(function(fnc) {
         fnc();
     });

+ 30 - 0
cli/uiSettings.js

@@ -0,0 +1,30 @@
+
+var Settings = (function() {
+    var displayed = false,
+
+    pages = {
+        services: {}
+    },
+
+    spawn = function() {
+    },
+
+    updatePage = function(page) {
+    };
+
+    return {
+        display: function(page) {
+            if (!displayed)
+                spawn();
+            updatePage(page);
+            return this;
+        },
+
+        setClosable: function(closable) {
+            return this;
+        },
+
+        pages: pages
+    };
+})();
+

+ 6 - 0
cli/workflow.js

@@ -50,6 +50,12 @@ function fetchHistory(room, cb) {
     xhr.send(null);
 }
 
+function onConfigUpdated() {
+    if (!CONFIG.services.length) {
+        Settings.setClosable(false).display(Settings.pages.services);
+    }
+}
+
 function poll(callback) {
     var xhr = new XMLHttpRequest();
     xhr.timeout = 1000 * 60 * 1; // 3 min timeout

+ 1 - 0
srv/config.js.exple

@@ -32,6 +32,7 @@ module.exports = {
     }
     ,rootUrl: rootUrl
     ,isDebug: true
+    ,allowNewAccounts: true
     ,port: 8084
 };
 

BIN
srv/public/btn_google_connect.png


BIN
srv/public/btn_slack_connect.png


+ 4 - 0
srv/public/cgu.min.js

@@ -0,0 +1,4 @@
+"use strict";(function(){
+var c={},d,f=[];c.fr={a:{cguHeader:"Merci de lire attentivement les Conditions G\u00e9n\u00e9rales d'Utilisation suivantes:",cguContent:"OSEF",cguButton:"J'accepte"}};c.en={a:{cguHeader:"Please read carefully the following terms of services:",cguContent:"OSEF",cguButton:"I accept"}};(function(a){if(!a){for(var b=0,g=navigator.languages.length;b<g;b++)if(c.hasOwnProperty(navigator.languages[b])){a=navigator.languages[b];break}a||(a="en")}d=c[a];console.log("Loading language pack: "+a);if(d.a)for(var e in d.a)if(a=document.getElementById(e))a.textContent=d.a[e];f.forEach(function(a){a()})})();
+document.getElementById("cguButton").addEventListener("click",function(a){a.preventDefault();var b=new XMLHttpRequest;b.onreadystatechange=function(){4===b.readyState&&204===b.status&&document.location.reload()};b.open("PUT","account/cguAccept",!0);b.send(null);return!1});
+})();

+ 3 - 0
srv/public/err.min.js

@@ -0,0 +1,3 @@
+"use strict";(function(){
+var b={},c,f=[];b.fr={a:{err403Header:"Oopsie",err403Text:"Vous avez tent\u00e9 d'acceder \u00e0 une page non autoris\u00e9e",err404Header:"Oopsie",err404Text:"Cette page n'existe pas"}};b.en={a:{err403Header:"Oopsie",err403Text:"You tried to reach a ressource you don't has access to",err404Header:"Oopsie",err404Text:"This page does not exists"}};(function(a){if(!a){for(var d=0,g=navigator.languages.length;d<g;d++)if(b.hasOwnProperty(navigator.languages[d])){a=navigator.languages[d];break}a||(a="en")}c=b[a];console.log("Loading language pack: "+a);if(c.a)for(var e in c.a)if(a=document.getElementById(e))a.textContent=c.a[e];f.forEach(function(a){a()})})();
+})();

+ 61 - 61
srv/public/slack.min.js

@@ -1,103 +1,103 @@
 "use strict";(function(){
-var q;function aa(a){this.id=a;this.version=0}aa.prototype.update=function(a,b){void 0!==a.name&&(this.name=a.name);this.version=Math.max(this.version,b)};function da(a){this.a=a.desc;this.name=a.name;this.type=a.type;this.usage=a.usage;this.R=a.category}function ea(){this.ia={};this.w=[];this.version=0}
-ea.prototype.update=function(a,b){a.emoji_use&&(this.ia=JSON.parse(a.emoji_use));a.highlight_words?this.w=(a.highlight_words||"").split(",").filter(function(a){return""!==a.trim()}):a.highlights&&(this.w=a.highlights);this.version=Math.max(this.version,b)};function fa(){this.b=null;this.l={};this.m={};this.self=null;this.a={version:0,data:{}};this.i={version:0,data:{}};this.s={};this.D=0}
+var q;function aa(a){this.id=a;this.version=0}aa.prototype.update=function(a,b){void 0!==a.name&&(this.name=a.name);this.version=Math.max(this.version,b)};function da(a){this.a=a.desc;this.name=a.name;this.type=a.type;this.usage=a.usage;this.R=a.category}function ea(){this.ja={};this.w=[];this.version=0}
+ea.prototype.update=function(a,b){a.emoji_use&&(this.ja=JSON.parse(a.emoji_use));a.highlight_words?this.w=(a.highlight_words||"").split(",").filter(function(a){return""!==a.trim()}):a.highlights&&(this.w=a.highlights);this.version=Math.max(this.version,b)};function fa(){this.b=null;this.l={};this.m={};this.self=null;this.a={version:0,data:{}};this.i={version:0,data:{}};this.s={};this.D=0}
 function ga(a,b,c){var d=d||"";b.team&&(a.b||(a.b=new aa(b.team.id)),a.b.update(b.team,c));if(b.users)for(var e=0,g=b.users.length;e<g;e++){var f=a.m[d+b.users[e].id];f||(f=a.m[d+b.users[e].id]=new ha(b.users[e].id));f.update(b.users[e],c)}if(b.channels)for(e=0,g=b.channels.length;e<g;e++){f=a.l[d+b.channels[e].id];if(!f){var f=a.l,n=d+b.channels[e].id;var h=b.channels[e];h=h.pv?new r(h.id,a.m[h.user]):new u(h.id);f=f[n]=h}f.update(b.channels[e],a,c,d)}b.emojis&&(a.a.data=b.emojis,a.a.version=c);
-if(void 0!==b.commands){a.i.data={};for(e in b.commands)a.i.data[e]=new da(b.commands[e]);a.i.version=c}b.self&&(a.self=a.m[d+b.self.id]||null,b.self.prefs&&(a.self.O||(a.self.O=new ea),a.self.O.update(b.self.prefs,c)));a.D=Math.max(a.D,c)}"undefined"!==typeof module&&(module.H.$a=fa,module.H.ab=aa,module.H.cb=da);function u(a){this.id=a;this.J=!1;this.A=0;this.m={};this.version=0}
-u.prototype.update=function(a,b,c,d){d=d||"";void 0!==a.name&&(this.name=a.name);void 0!==a.is_archived&&(this.Y=a.is_archived);void 0!==a.is_member&&(this.i=a.is_member);void 0!==a.last_read&&(this.A=Math.max(parseFloat(a.last_read),this.A));void 0!==a.last_msg&&(this.I=parseFloat(a.last_msg));void 0!==a.is_private&&(this.b=a.is_private);void 0!==a.is_starred&&(this.J=a.is_starred);if(a.members&&(this.m={},a.members))for(var e=0,g=a.members.length;e<g;e++){var f=b.m[d+a.members[e]];this.m[f.id]=
-f;f.l[this.id]=this}this.version=Math.max(this.version,c)};function r(a,b){u.call(this,a);this.a=b;this.name=this.a.name;this.b=!0;b.na=this}r.prototype=Object.create(u.prototype);r.prototype.constructor=r;"undefined"!==typeof module&&(module.H.jb=u,module.H.ib=r);function y(a,b){this.G=a.user;this.username=a.username;this.id=a.id||a.ts;this.j=parseFloat(a.ts);this.text="";this.o=[];this.i=this.C=this.J=!1;this.B={};this.version=b;this.update(a,b)}function A(a,b){y.call(this,a,b)}function C(a,b){y.call(this,a,b)}
-y.prototype.update=function(a,b){if(a){if(this.text=a.text||"",a.attachments&&(this.o=a.attachments),this.J=!!a.is_starred,this.C=void 0===a.edited?!1:a.edited,this.i=!!a.removed,a.reactions){var c={};a.reactions.forEach(function(a){c[a.name]=[];a.users.forEach(function(b){c[a.name].push(b)})});this.B=c}}else this.i=!0;this.version=b};function D(a,b,c,d){this.id="string"===typeof a?a:a.id;this.a=[];this.Na=0;this.i=b;c&&ia(this,c,d)}
+if(void 0!==b.commands){a.i.data={};for(e in b.commands)a.i.data[e]=new da(b.commands[e]);a.i.version=c}b.self&&(a.self=a.m[d+b.self.id]||null,b.self.prefs&&(a.self.O||(a.self.O=new ea),a.self.O.update(b.self.prefs,c)));a.D=Math.max(a.D,c)}"undefined"!==typeof module&&(module.H.cb=fa,module.H.eb=aa,module.H.gb=da);function u(a){this.id=a;this.J=!1;this.A=0;this.m={};this.version=0}
+u.prototype.update=function(a,b,c,d){d=d||"";void 0!==a.name&&(this.name=a.name);void 0!==a.is_archived&&(this.Z=a.is_archived);void 0!==a.is_member&&(this.i=a.is_member);void 0!==a.last_read&&(this.A=Math.max(parseFloat(a.last_read),this.A));void 0!==a.last_msg&&(this.I=parseFloat(a.last_msg));void 0!==a.is_private&&(this.b=a.is_private);void 0!==a.is_starred&&(this.J=a.is_starred);if(a.members&&(this.m={},a.members))for(var e=0,g=a.members.length;e<g;e++){var f=b.m[d+a.members[e]];this.m[f.id]=
+f;f.l[this.id]=this}this.version=Math.max(this.version,c)};function r(a,b){u.call(this,a);this.a=b;this.name=this.a.name;this.b=!0;b.oa=this}r.prototype=Object.create(u.prototype);r.prototype.constructor=r;"undefined"!==typeof module&&(module.H.mb=u,module.H.lb=r);function y(a,b){this.G=a.user;this.username=a.username;this.id=a.id||a.ts;this.j=parseFloat(a.ts);this.text="";this.o=[];this.i=this.C=this.J=!1;this.B={};this.version=b;this.update(a,b)}function A(a,b){y.call(this,a,b)}function C(a,b){y.call(this,a,b)}
+y.prototype.update=function(a,b){if(a){if(this.text=a.text||"",a.attachments&&(this.o=a.attachments),this.J=!!a.is_starred,this.C=void 0===a.edited?!1:a.edited,this.i=!!a.removed,a.reactions){var c={};a.reactions.forEach(function(a){c[a.name]=[];a.users.forEach(function(b){c[a.name].push(b)})});this.B=c}}else this.i=!0;this.version=b};function D(a,b,c,d){this.id="string"===typeof a?a:a.id;this.a=[];this.Oa=0;this.i=b;c&&ia(this,c,d)}
 function ia(a,b,c){var d=0;b.forEach(function(a){d=Math.max(this.push(a,c),d)}.bind(a));ja(a)}D.prototype.b=function(a,b){return!0===a.isMeMessage?new A(a,b):!0===a.isNotice?new C(a,b):new y(a,b)};D.prototype.push=function(a,b){for(var c,d=!1,e,g=0,f=this.a.length;g<f;g++)if(c=this.a[g],c.id===a.id){e=c.update(a,b);d=!0;break}d||(c=this.b(a,b),this.a.push(c),e=c.j);for(;this.a.length>this.i;)this.a.shift();return e||0};function ka(a){return a.a[a.a.length-1]}
-function la(a,b){for(var c=0,d=a.a.length;c<d;c++)if(a.a[c].id==b)return a.a[c];return null}function ja(a){a.a.sort(function(a,c){return a.j-c.j})}A.prototype=Object.create(y.prototype);A.prototype.constructor=A;C.prototype=Object.create(y.prototype);C.prototype.constructor=C;"undefined"!==typeof module&&(module.H={fb:y,eb:A,hb:C,kb:D});function ha(a){this.id=a;this.l={};this.na=this.O=null;this.version=0}ha.prototype.update=function(a,b){void 0!==a.name&&(this.name=a.name);void 0!==a.deleted&&(this.Da=a.deleted);void 0!==a.status&&(this.status=a.status);void 0!==a.presence&&(this.a="away"!==a.presence);void 0!==a.isPresent&&(this.a=a.isPresent);a.isBot&&(this.Ta=a.isBot);this.version=Math.max(this.version,b)};"undefined"!==typeof module&&(module.H.bb=ha);function ma(){this.a=[]}ma.prototype.push=function(a){this.a.push(a)};function na(a,b){for(var c=0,d=a.a.length;c<d;c++)if(b===oa(a.a[c]))return a.a[c];return null}function pa(a,b){for(var c=0,d=a.a.length;c<d;c++){var e=a.a[c],g;for(g in e.l)if(!0===b(e.l[g],g))return}}function qa(a){for(var b=E.context,c=0,d=b.a.length;c<d&&!0!==a(b.a[c]);c++);}function H(a,b){for(var c=0,d=a.a.length;c<d;c++)if(a.a[c].l[b])return a.a[c];return null}
-function I(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++){var e=b.a[c].l[a];if(e)return e}return null}function ra(a){for(var b=E.context,c=[],d=0,e=b.a.length;d<e;d++){var g=b.a[d].l,f;for(f in g)a&&!a(g[f],b.a[d],f)||c.push(f)}return c}function J(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++){var e=b.a[c].m[a];if(e)return e}return null}function sa(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++)if(b.a[c].self.id===a)return!0;return!1}"undefined"!==typeof module&&(module.H.gb=ma);var K={},L,ta=[];function ua(){if(!c){for(var a=0,b=navigator.languages.length;a<b;a++)if(K.hasOwnProperty(navigator.languages[a])){var c=navigator.languages[a];break}c||(c="en")}L=K[c];console.log("Loading language pack: "+c);if(L.c)for(var d in L.c)document.getElementById(d).textContent=L.c[d];ta.forEach(function(a){a()})};K.fr={Za:"Utilisateur inconnu",Ya:"Channel inconnu",Ia:"Nouveau message",message:"Message",Ha:"Reseau",Ja:"(visible seulement par vous)",J:"Favoris",l:"Discutions",La:"Discutions priv\u00e9es",Ma:"Partage sa position GPS",ok:"Ok",Ea:"Annuler",W:function(a){"string"!==typeof a&&(a=parseFloat(a));var b=new Date,c=new Date;a=new Date(a);b.setHours(0,0,0,0);c.setTime(b.getTime());c.setDate(c.getDate()-1);return a.getTime()>b.getTime()?a.toLocaleTimeString():a.getTime()>c.getTime()?"hier, "+a.toLocaleTimeString():
-a.toLocaleString()},c:{fileUploadCancel:"Annuler",neterror:"Impossible de se connecter au chat !"}};K.fr.C=function(a){return"(edit&eacute; "+K.fr.W(a)+")"};K.en={Za:"Unknown member",Ya:"Unknown channel",Ia:"New message",message:"Message",Ha:"Network",Ja:"(only visible to you)",J:"Starred",l:"Channels",La:"Direct messages",Ma:"Share your GPS location",ok:"Ok",Ea:"Cancel",W:function(a){"string"!==typeof a&&(a=parseFloat(a));var b=new Date,c=new Date;a=new Date(a);b.setHours(0,0,0,0);c.setTime(b.getTime());c.setDate(c.getDate()-1);return a.getTime()>b.getTime()?a.toLocaleTimeString():a.getTime()>c.getTime()?"yesterday, "+a.toLocaleTimeString():a.toLocaleString()},
-c:{fileUploadCancel:"Cancel",neterror:"Cannot connect to chat !"}};K.en.C=function(a){return"(edited "+K.en.W(a)+")"};var va=function(){function a(a){this.text="";this.g=a}function b(b,c,d){this.T=c;this.f=null;this.h=[];this.a=d||"";this.da="<"===this.a;this.oa="*"===this.a;this.ca="_"===this.a;this.ea="~"===this.a||"-"===this.a;this.i=">"===this.a||"&gt;"===this.a;this.X=":"===this.a;this.qa="`"===this.a;this.Ba="```"===this.a;this.ra="\n"===this.a;this.ba=void 0!==d&&-1!==m.w.indexOf(d);this.g=b;this.fa=null;this.b=this.ra||this.ba?c+d.length-1:!1;this.ba&&(this.f=new a(this),this.h.push(this.f),this.f.text=d)}
+function la(a,b){for(var c=0,d=a.a.length;c<d;c++)if(a.a[c].id==b)return a.a[c];return null}function ja(a){a.a.sort(function(a,c){return a.j-c.j})}A.prototype=Object.create(y.prototype);A.prototype.constructor=A;C.prototype=Object.create(y.prototype);C.prototype.constructor=C;"undefined"!==typeof module&&(module.H={ib:y,hb:A,kb:C,nb:D});function ha(a){this.id=a;this.l={};this.oa=this.O=null;this.version=0}ha.prototype.update=function(a,b){void 0!==a.name&&(this.name=a.name);void 0!==a.deleted&&(this.Ea=a.deleted);void 0!==a.status&&(this.status=a.status);void 0!==a.presence&&(this.a="away"!==a.presence);void 0!==a.isPresent&&(this.a=a.isPresent);a.isBot&&(this.Ua=a.isBot);this.version=Math.max(this.version,b)};"undefined"!==typeof module&&(module.H.fb=ha);function ma(){this.a=[]}ma.prototype.push=function(a){this.a.push(a)};function na(a,b){for(var c=0,d=a.a.length;c<d;c++)if(b===oa(a.a[c]))return a.a[c];return null}function pa(a,b){for(var c=0,d=a.a.length;c<d;c++){var e=a.a[c],g;for(g in e.l)if(!0===b(e.l[g],g))return}}function qa(a){for(var b=E.context,c=0,d=b.a.length;c<d&&!0!==a(b.a[c]);c++);}function H(a,b){for(var c=0,d=a.a.length;c<d;c++)if(a.a[c].l[b])return a.a[c];return null}
+function I(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++){var e=b.a[c].l[a];if(e)return e}return null}function ra(a){for(var b=E.context,c=[],d=0,e=b.a.length;d<e;d++){var g=b.a[d].l,f;for(f in g)a&&!a(g[f],b.a[d],f)||c.push(f)}return c}function J(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++){var e=b.a[c].m[a];if(e)return e}return null}function sa(a){for(var b=E.context,c=0,d=b.a.length;c<d;c++)if(b.a[c].self.id===a)return!0;return!1}"undefined"!==typeof module&&(module.H.jb=ma);var K={},L,ta=[];function ua(){if(!c){for(var a=0,b=navigator.languages.length;a<b;a++)if(K.hasOwnProperty(navigator.languages[a])){var c=navigator.languages[a];break}c||(c="en")}L=K[c];console.log("Loading language pack: "+c);if(L.c)for(var d in L.c)if(c=document.getElementById(d))c.textContent=L.c[d];ta.forEach(function(a){a()})};K.fr={bb:"Utilisateur inconnu",ab:"Channel inconnu",Ja:"Nouveau message",message:"Message",Ia:"Reseau",Ka:"(visible seulement par vous)",J:"Favoris",l:"Discutions",Ma:"Discutions priv\u00e9es",Na:"Partage sa position GPS",ok:"Ok",Fa:"Annuler",W:function(a){"string"!==typeof a&&(a=parseFloat(a));var b=new Date,c=new Date;a=new Date(a);b.setHours(0,0,0,0);c.setTime(b.getTime());c.setDate(c.getDate()-1);return a.getTime()>b.getTime()?a.toLocaleTimeString():a.getTime()>c.getTime()?"hier, "+a.toLocaleTimeString():
+a.toLocaleString()},c:{fileUploadCancel:"Annuler",neterror:"Impossible de se connecter au chat !"}};K.fr.C=function(a){return"(edit&eacute; "+K.fr.W(a)+")"};K.en={bb:"Unknown member",ab:"Unknown channel",Ja:"New message",message:"Message",Ia:"Network",Ka:"(only visible to you)",J:"Starred",l:"Channels",Ma:"Direct messages",Na:"Share your GPS location",ok:"Ok",Fa:"Cancel",W:function(a){"string"!==typeof a&&(a=parseFloat(a));var b=new Date,c=new Date;a=new Date(a);b.setHours(0,0,0,0);c.setTime(b.getTime());c.setDate(c.getDate()-1);return a.getTime()>b.getTime()?a.toLocaleTimeString():a.getTime()>c.getTime()?"yesterday, "+a.toLocaleTimeString():a.toLocaleString()},
+c:{fileUploadCancel:"Cancel",neterror:"Cannot connect to chat !"}};K.en.C=function(a){return"(edited "+K.en.W(a)+")"};var va=function(){function a(a){this.text="";this.g=a}function b(b,c,d){this.T=c;this.f=null;this.h=[];this.a=d||"";this.ea="<"===this.a;this.pa="*"===this.a;this.da="_"===this.a;this.fa="~"===this.a||"-"===this.a;this.i=">"===this.a||"&gt;"===this.a;this.X=":"===this.a;this.ra="`"===this.a;this.Ca="```"===this.a;this.sa="\n"===this.a;this.ca=void 0!==d&&-1!==m.w.indexOf(d);this.g=b;this.ga=null;this.b=this.sa||this.ca?c+d.length-1:!1;this.ca&&(this.f=new a(this),this.h.push(this.f),this.f.text=d)}
 function c(a){return"A"<=a&&"Z">=a||"a"<=a&&"z">=a||"0"<=a&&"9">=a||-1!=="\u00e0\u00e8\u00ec\u00f2\u00f9\u00c0\u00c8\u00cc\u00d2\u00d9\u00e1\u00e9\u00ed\u00f3\u00fa\u00fd\u00c1\u00c9\u00cd\u00d3\u00da\u00dd\u00e2\u00ea\u00ee\u00f4\u00fb\u00c2\u00ca\u00ce\u00d4\u00db\u00e3\u00f1\u00f5\u00c3\u00d1\u00d5\u00e4\u00eb\u00ef\u00f6\u00fc\u00ff\u00c4\u00cb\u00cf\u00d6\u00dc\u0178\u00e7\u00c7\u00df\u00d8\u00f8\u00c5\u00e5\u00c6\u00e6\u0153".indexOf(a)}function d(a){a=a||h;for(var c=0,e=a.h.length;c<e;c++){var l=
-a.h[c];if(l instanceof b)if(l.b){if(l=d(l))return l}else return l}return null}function e(a,c){a.g instanceof b&&(a.g.h.splice(a.g.h.indexOf(a)+(c?1:0)),a.g.f=a.g.h[a.g.h.length-1],e(a.g,!0))}function g(a){return a}function f(a){return{link:a,text:a,Ga:!1}}var n,h,m={w:[],V:g,aa:g,Z:f};b.prototype.ta=function(){return this.oa&&!!this.b||this.g instanceof b&&this.g.ta()};b.prototype.wa=function(){return this.ca&&!!this.b||this.g instanceof b&&this.g.wa()};b.prototype.xa=function(){return this.ea&&!!this.b||
-this.g instanceof b&&this.g.xa()};b.prototype.la=function(){return this.X&&!!this.b||this.g instanceof b&&this.g.la()};b.prototype.va=function(){return this.ba&&!!this.b||this.g instanceof b&&this.g.va()};b.prototype.ua=function(){return this.qa&&!!this.b||this.g instanceof b&&this.g.ua()};b.prototype.ka=function(){return this.Ba&&!!this.b||this.g instanceof b&&this.g.ka()};b.prototype.ya=function(){for(var a=0,c=this.h.length;a<c;a++)if(this.h[a]instanceof b&&(!this.h[a].b||this.h[a].ya()))return!0;
-return!1};b.prototype.za=function(a){if("<"===this.a&&">"===n[a])return!0;var b=c(n[a-1]);if(!this.i&&n.substr(a,this.a.length)===this.a){if(!b&&(this.oa||this.ca||this.ea))return!1;if(this.f&&this.ya())return this.f.Ca();if(this.Oa())return!0}return"\n"===n[a]&&this.i?!0:!1};b.prototype.Oa=function(){for(var a=this;a;){for(var c=0,d=a.h.length;c<d;c++)if(a.h[c]instanceof b||a.h[c].text.length)return!0;a=a.fa}return!1};b.prototype.Ca=function(){var a=new b(this.g,this.T,this.a);a.fa=this;this.f&&
-this.f instanceof b&&(a.f=this.f.Ca(),a.h=[a.f]);return a};b.prototype.Pa=function(a){return this.X&&(" "===n[a]||"\t"===n[a])||(this.X||this.da||this.oa||this.ca||this.ea||this.qa)&&"\n"===n[a]?!1:!0};b.prototype.Qa=function(b){if(this.qa||this.X||this.Ba||this.da)return null;if(!this.f||this.f.b||this.f instanceof a){var d=c(n[b-1]),e=c(n[b+1]);if("```"===n.substr(b,3))return"```";var f=h.ma();if(void 0===f||f){if("&gt;"===n.substr(b,4))return"&gt;";if(">"===n[b])return n[b]}if("`"===n[b]&&!d||
-"\n"===n[b]||!(-1===["*","~","-","_"].indexOf(n[b])||!e&&void 0!==n[b+1]&&-1==="*~-_<&".split("").indexOf(n[b+1])||d&&void 0!==n[b-1]&&-1==="*~-_<&".split("").indexOf(n[b-1]))||-1!==[":"].indexOf(n[b])&&e||-1!==["<"].indexOf(n[b]))return n[b];d=0;for(e=m.w.length;d<e;d++)if(f=m.w[d],n.substr(b,f.length)===f)return f}return null};a.prototype.ma=function(){if(""!==this.text.trim())return!1};b.prototype.ma=function(){for(var a=this.h.length-1;0<=a;a--){var b=this.h[a].ma();if(void 0!==b)return b}if(this.ra||
-this.i)return!0};a.prototype.D=function(a){this.text+=n[a];return 1};b.prototype.D=function(c){var d=this.f&&!this.f.b&&this.f.za?this.f.za(c):null;if(d){var e=this.f.a.length;this.f.sa(c);d instanceof b&&(this.f=d,this.h.push(d));return e}if(!this.f||this.f.b||this.f instanceof a||this.f.Pa(c)){if(d=this.Qa(c))return this.f=new b(this,c,d),this.h.push(this.f),this.f.a.length;if(!this.f||this.f.b)this.f=new a(this),this.h.push(this.f);return this.f.D(c)}d=this.f.T+1;h.ja(this.f.T);this.f=new a(this);
-this.f.D(d-1);this.h.pop();this.h.push(this.f);return d-c};b.prototype.sa=function(a){for(var b=this;b;)b.b=a,b=b.fa};b.prototype.ja=function(a){this.b&&this.b>=a&&(this.b=!1);this.h.forEach(function(c){c instanceof b&&c.ja(a)})};a.prototype.innerHTML=function(){if(this.g.la()){for(var a=this.g;a&&!a.X;)a=a.g;if(a){var a=a.a+this.text+a.a,b=m.V(a);return b?b:a}return(a=m.V(this.text))?a:this.text}if(this.g.ka()){if("undefined"!==typeof hljs)try{return a=this.text.match(/^\w+/),hljs.configure({useBR:!0,
-tabReplace:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"}),a&&hljs.getLanguage(a[0])?hljs.fixMarkup(hljs.highlight(a[0],this.text.substr(a[0].length)).value):hljs.fixMarkup(hljs.highlightAuto(this.text).value)}catch(p){console.error(p)}return this.text.replace(/\n/g,"<br/>")}return m.aa(this.text)};a.prototype.outerHTML=function(){var a="span",b=[],c="";if(this.g.ka()){a="pre";b.push("codeblock");var d=this.innerHTML()}else this.g.ua()?(b.push("code"),d=this.innerHTML()):(this.g.da&&(d=m.Z(this.text))?(a=
-"a",c=' href="'+d.link+'"',d.Ga||(c+=' target="_blank"'),d=m.aa(d.text)):d=this.innerHTML(),this.g.ta()&&b.push("bold"),this.g.wa()&&b.push("italic"),this.g.xa()&&b.push("strike"),this.g.la()&&b.push("emoji"),this.g.va()&&b.push("highlight"));return"<"+a+c+(b.length?' class="'+b.join(" ")+'"':"")+">"+d+"</"+a+">"};b.prototype.outerHTML=function(){var a="";this.i&&(a+='<span class="quote">');this.ra&&(a+="<br/>");this.h.forEach(function(b){a+=b.outerHTML()});this.i&&(a+="</span>");return a};b.prototype.Aa=
-function(a){this.i&&!this.b&&this.sa(a);this.h.forEach(function(c){c instanceof b&&c.Aa(a)})};return function(c,k){k||(k={});m.w=k.w||[];m.V=k.V||g;m.aa=k.aa||g;m.Z=k.Z||f;n=c;h=new b(this,0);k=0;c=n.length;do{for(;k<c;)k+=h.D(k);h.Aa(n.length);if(k=d()){e(k,!1);h.ja(k.T);var l=new a(k.g);l.D(k.T);k.g.h.push(l);k.g.f=l;k=k.T+1}else k=void 0}while(void 0!==k);return h.outerHTML()}}();"undefined"!==typeof module&&(module.H.u=va);function wa(a,b){this.i=a;this.content=b;this.c=xa(this);this.b=ya(this);this.a=[];this.D=[]}
+a.h[c];if(l instanceof b)if(l.b){if(l=d(l))return l}else return l}return null}function e(a,c){a.g instanceof b&&(a.g.h.splice(a.g.h.indexOf(a)+(c?1:0)),a.g.f=a.g.h[a.g.h.length-1],e(a.g,!0))}function g(a){return a}function f(a){return{link:a,text:a,Ha:!1}}var n,h,m={w:[],V:g,ba:g,$:f};b.prototype.ua=function(){return this.pa&&!!this.b||this.g instanceof b&&this.g.ua()};b.prototype.xa=function(){return this.da&&!!this.b||this.g instanceof b&&this.g.xa()};b.prototype.ya=function(){return this.fa&&!!this.b||
+this.g instanceof b&&this.g.ya()};b.prototype.ma=function(){return this.X&&!!this.b||this.g instanceof b&&this.g.ma()};b.prototype.wa=function(){return this.ca&&!!this.b||this.g instanceof b&&this.g.wa()};b.prototype.va=function(){return this.ra&&!!this.b||this.g instanceof b&&this.g.va()};b.prototype.la=function(){return this.Ca&&!!this.b||this.g instanceof b&&this.g.la()};b.prototype.za=function(){for(var a=0,c=this.h.length;a<c;a++)if(this.h[a]instanceof b&&(!this.h[a].b||this.h[a].za()))return!0;
+return!1};b.prototype.Aa=function(a){if("<"===this.a&&">"===n[a])return!0;var b=c(n[a-1]);if(!this.i&&n.substr(a,this.a.length)===this.a){if(!b&&(this.pa||this.da||this.fa))return!1;if(this.f&&this.za())return this.f.Da();if(this.Pa())return!0}return"\n"===n[a]&&this.i?!0:!1};b.prototype.Pa=function(){for(var a=this;a;){for(var c=0,d=a.h.length;c<d;c++)if(a.h[c]instanceof b||a.h[c].text.length)return!0;a=a.ga}return!1};b.prototype.Da=function(){var a=new b(this.g,this.T,this.a);a.ga=this;this.f&&
+this.f instanceof b&&(a.f=this.f.Da(),a.h=[a.f]);return a};b.prototype.Qa=function(a){return this.X&&(" "===n[a]||"\t"===n[a])||(this.X||this.ea||this.pa||this.da||this.fa||this.ra)&&"\n"===n[a]?!1:!0};b.prototype.Ra=function(b){if(this.ra||this.X||this.Ca||this.ea)return null;if(!this.f||this.f.b||this.f instanceof a){var d=c(n[b-1]),e=c(n[b+1]);if("```"===n.substr(b,3))return"```";var f=h.na();if(void 0===f||f){if("&gt;"===n.substr(b,4))return"&gt;";if(">"===n[b])return n[b]}if("`"===n[b]&&!d||
+"\n"===n[b]||!(-1===["*","~","-","_"].indexOf(n[b])||!e&&void 0!==n[b+1]&&-1==="*~-_<&".split("").indexOf(n[b+1])||d&&void 0!==n[b-1]&&-1==="*~-_<&".split("").indexOf(n[b-1]))||-1!==[":"].indexOf(n[b])&&e||-1!==["<"].indexOf(n[b]))return n[b];d=0;for(e=m.w.length;d<e;d++)if(f=m.w[d],n.substr(b,f.length)===f)return f}return null};a.prototype.na=function(){if(""!==this.text.trim())return!1};b.prototype.na=function(){for(var a=this.h.length-1;0<=a;a--){var b=this.h[a].na();if(void 0!==b)return b}if(this.sa||
+this.i)return!0};a.prototype.D=function(a){this.text+=n[a];return 1};b.prototype.D=function(c){var d=this.f&&!this.f.b&&this.f.Aa?this.f.Aa(c):null;if(d){var e=this.f.a.length;this.f.ta(c);d instanceof b&&(this.f=d,this.h.push(d));return e}if(!this.f||this.f.b||this.f instanceof a||this.f.Qa(c)){if(d=this.Ra(c))return this.f=new b(this,c,d),this.h.push(this.f),this.f.a.length;if(!this.f||this.f.b)this.f=new a(this),this.h.push(this.f);return this.f.D(c)}d=this.f.T+1;h.ka(this.f.T);this.f=new a(this);
+this.f.D(d-1);this.h.pop();this.h.push(this.f);return d-c};b.prototype.ta=function(a){for(var b=this;b;)b.b=a,b=b.ga};b.prototype.ka=function(a){this.b&&this.b>=a&&(this.b=!1);this.h.forEach(function(c){c instanceof b&&c.ka(a)})};a.prototype.innerHTML=function(){if(this.g.ma()){for(var a=this.g;a&&!a.X;)a=a.g;if(a){var a=a.a+this.text+a.a,b=m.V(a);return b?b:a}return(a=m.V(this.text))?a:this.text}if(this.g.la()){if("undefined"!==typeof hljs)try{return a=this.text.match(/^\w+/),hljs.configure({useBR:!0,
+tabReplace:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"}),a&&hljs.getLanguage(a[0])?hljs.fixMarkup(hljs.highlight(a[0],this.text.substr(a[0].length)).value):hljs.fixMarkup(hljs.highlightAuto(this.text).value)}catch(p){console.error(p)}return this.text.replace(/\n/g,"<br/>")}return m.ba(this.text)};a.prototype.outerHTML=function(){var a="span",b=[],c="";if(this.g.la()){a="pre";b.push("codeblock");var d=this.innerHTML()}else this.g.va()?(b.push("code"),d=this.innerHTML()):(this.g.ea&&(d=m.$(this.text))?(a=
+"a",c=' href="'+d.link+'"',d.Ha||(c+=' target="_blank"'),d=m.ba(d.text)):d=this.innerHTML(),this.g.ua()&&b.push("bold"),this.g.xa()&&b.push("italic"),this.g.ya()&&b.push("strike"),this.g.ma()&&b.push("emoji"),this.g.wa()&&b.push("highlight"));return"<"+a+c+(b.length?' class="'+b.join(" ")+'"':"")+">"+d+"</"+a+">"};b.prototype.outerHTML=function(){var a="";this.i&&(a+='<span class="quote">');this.sa&&(a+="<br/>");this.h.forEach(function(b){a+=b.outerHTML()});this.i&&(a+="</span>");return a};b.prototype.Ba=
+function(a){this.i&&!this.b&&this.ta(a);this.h.forEach(function(c){c instanceof b&&c.Ba(a)})};return function(c,k){k||(k={});m.w=k.w||[];m.V=k.V||g;m.ba=k.ba||g;m.$=k.$||f;n=c;h=new b(this,0);k=0;c=n.length;do{for(;k<c;)k+=h.D(k);h.Ba(n.length);if(k=d()){e(k,!1);h.ka(k.T);var l=new a(k.g);l.D(k.T);k.g.h.push(l);k.g.f=l;k=k.T+1}else k=void 0}while(void 0!==k);return h.outerHTML()}}();"undefined"!==typeof module&&(module.H.u=va);function wa(a,b){this.i=a;this.content=b;this.c=xa(this);this.b=ya(this);this.a=[];this.D=[]}
 function xa(a){var b=document.createElement("div"),c=document.createElement("header"),d=document.createElement("span"),e=document.createElement("span"),g=document.createElement("div"),f=document.createElement("footer");b.a=document.createElement("span");b.b=document.createElement("span");d.textContent=a.i;"string"==typeof a.content?g.innerHTML=a.content:g.appendChild(a.content);c.className=za;d.className=Aa;e.className=Ba;e.textContent="x";c.appendChild(d);c.appendChild(e);b.appendChild(c);g.className=
-Ca;b.appendChild(g);b.b.className=Da;b.b.textContent=L.Ea;b.b.addEventListener("click",function(){M(a,!1)});e.addEventListener("click",function(){M(a,!1)});b.a.addEventListener("click",function(){M(a,!0)});f.appendChild(b.b);b.a.className=Da;b.a.textContent=L.ok;f.appendChild(b.a);f.className=Ea+" "+Fa;b.appendChild(f);b.className=Ga;return b}function M(a,b){(b?a.a:a.D).forEach(function(a){a()});a.close()}
-function ya(a){var b=document.createElement("div");b.className=Ha;b.addEventListener("click",function(){M(this,!1)}.bind(a));return b}function Ia(a,b,c){a.c.a.textContent=b;a.c.b.textContent=c;return a}wa.prototype.$=function(a){a=a||document.body;a.appendChild(this.b);a.appendChild(this.c);return this};wa.prototype.close=function(){this.c.remove();this.b.remove();return this};function Ja(a,b){a.a.push(b);return a};var Da="button",Ea="button-container",Ga="dialog",Ha="dialog-overlay",za="dialog-title",Aa="dialog-title-label",Ba="dialog-title-close",Ca="dialog-body",Fa="dialog-footer";var Ka=[],La=0;
-function Ma(){var a=document.createDocumentFragment(),b=ra(function(a){return!a.Y&&!1!==a.i}),c=[],d=[],e=[],g=[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:I(a).name.localeCompare(I(b).name)});b.forEach(function(a){a=I(a);if(a instanceof r){var b;if(b=!a.a.Da){var f=document.createElement("li");b=document.createElement("a");f.id="room_"+a.id;b.href="#"+a.id;f.className="slack-context-room slack-ims";b.textContent=a.a.name;f.appendChild(Na());f.appendChild(b);a.a.a||f.classList.add("away");N===
+Ca;b.appendChild(g);b.b.className=Da;b.b.textContent=L.Fa;b.b.addEventListener("click",function(){M(a,!1)});e.addEventListener("click",function(){M(a,!1)});b.a.addEventListener("click",function(){M(a,!0)});f.appendChild(b.b);b.a.className=Da;b.a.textContent=L.ok;f.appendChild(b.a);f.className=Ea+" "+Fa;b.appendChild(f);b.className=Ga;return b}function M(a,b){(b?a.a:a.D).forEach(function(a){a()});a.close()}
+function ya(a){var b=document.createElement("div");b.className=Ha;b.addEventListener("click",function(){M(this,!1)}.bind(a));return b}function Ia(a,b,c){a.c.a.textContent=b;a.c.b.textContent=c;return a}wa.prototype.aa=function(a){a=a||document.body;a.appendChild(this.b);a.appendChild(this.c);return this};wa.prototype.close=function(){this.c.remove();this.b.remove();return this};function Ja(a,b){a.a.push(b);return a};var Da="button",Ea="button-container",Ga="dialog",Ha="dialog-overlay",za="dialog-title",Aa="dialog-title-label",Ba="dialog-title-close",Ca="dialog-body",Fa="dialog-footer";var Ka=[],La=0;
+function Ma(){var a=document.createDocumentFragment(),b=ra(function(a){return!a.Z&&!1!==a.i}),c=[],d=[],e=[],g=[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:I(a).name.localeCompare(I(b).name)});b.forEach(function(a){a=I(a);if(a instanceof r){var b;if(b=!a.a.Ea){var f=document.createElement("li");b=document.createElement("a");f.id="room_"+a.id;b.href="#"+a.id;f.className="slack-context-room slack-ims";b.textContent=a.a.name;f.appendChild(Na());f.appendChild(b);a.a.a||f.classList.add("away");N===
 a&&f.classList.add("selected");a.I>a.A&&(f.classList.add("unread"),f.classList.add("unreadHi"));b=f}b&&(a.J?c.push(f):g.push(f))}else f=document.createElement("li"),b=document.createElement("a"),f.id="room_"+a.id,b.href="#"+a.id,a.b?(f.className="slack-context-room slack-group",f.dataset.count=Object.keys(a.m||{}).length):f.className="slack-context-room slack-channel",N===a&&f.classList.add("selected"),b.textContent=a.name,f.appendChild(Na()),f.appendChild(b),a.I>a.A&&(f.classList.add("unread"),0<=
-O.indexOf(a)&&f.classList.add("unreadHi")),f&&(a.J?c.push(f):a.b?e.push(f):d.push(f))});c.length&&a.appendChild(Oa(L.J));c.forEach(function(b){a.appendChild(b)});d.length&&a.appendChild(Oa(L.l));d.forEach(function(b){a.appendChild(b)});e.forEach(function(b){a.appendChild(b)});g.length&&a.appendChild(Oa(L.La));g.forEach(function(b){a.appendChild(b)});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a);Pa();P();Q&&Qa(Q.b.id,Q.m,function(a){document.getElementById("slackCtx").style.backgroundImage=
-"url("+a+")"})}function Ra(){qa(function(a){var b=a.s,c;for(c in a.self.l)if(!a.self.l[c].Y){var d=document.getElementById("room_"+c);b[c]?d.classList.add("slack-context-typing"):d.classList.remove("slack-context-typing")}for(var e in a.m)(c=a.m[e].na)&&!c.Y&&(d=document.getElementById("room_"+c.id))&&(b[c.id]?d.classList.add("slack-context-typing"):d.classList.remove("slack-context-typing"))});Sa()}
+O.indexOf(a)&&f.classList.add("unreadHi")),f&&(a.J?c.push(f):a.b?e.push(f):d.push(f))});c.length&&a.appendChild(Oa(L.J));c.forEach(function(b){a.appendChild(b)});d.length&&a.appendChild(Oa(L.l));d.forEach(function(b){a.appendChild(b)});e.forEach(function(b){a.appendChild(b)});g.length&&a.appendChild(Oa(L.Ma));g.forEach(function(b){a.appendChild(b)});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a);Pa();P();Q&&Qa(Q.b.id,Q.m,function(a){document.getElementById("slackCtx").style.backgroundImage=
+"url("+a+")"})}function Ra(){qa(function(a){var b=a.s,c;for(c in a.self.l)if(!a.self.l[c].Z){var d=document.getElementById("room_"+c);b[c]?d.classList.add("slack-context-typing"):d.classList.remove("slack-context-typing")}for(var e in a.m)(c=a.m[e].oa)&&!c.Z&&(d=document.getElementById("room_"+c.id))&&(b[c.id]?d.classList.add("slack-context-typing"):d.classList.remove("slack-context-typing"))});Sa()}
 function Sa(){var a;document.getElementById("whoistyping").textContent="";if(Q&&N&&(a=Q.s[N.id])){var b=document.createDocumentFragment(),c=!1,d;for(d in a)(a=J(d))?b.appendChild(Ta(a)):c=!0;c&&(E.b=0);document.getElementById("whoistyping").appendChild(b)}}function Ua(a){a?document.body.classList.remove("no-network"):document.body.classList.add("no-network");P()}
 function Va(){var a=N.name||(N.a?N.a.name:void 0);if(!a){var b=[];N.m.forEach(function(a){b.push(a.name)});a=b.join(", ")}document.getElementById("currentRoomTitle").textContent=a;Wa();R();document.getElementById("fileUploadContainer").classList.add("hidden");Xa();T&&(T=null,U());V&&(V=null,U());Sa()}
 function U(){if(T){document.body.classList.add("replyingTo");var a=document.getElementById("replyToContainer"),b=document.createElement("a");b.addEventListener("click",function(){T=null;U()});b.className="replyto-close";b.textContent="x";a.textContent="";a.appendChild(b);a.appendChild(T.L())}else document.body.classList.remove("replyingTo"),document.getElementById("replyToContainer").textContent="";R()}
 function W(){if(V){document.body.classList.add("replyingTo");var a=document.getElementById("replyToContainer"),b=document.createElement("a");b.addEventListener("click",function(){V=null;W()});b.className="replyto-close";b.textContent="x";a.textContent="";a.appendChild(b);a.appendChild(V.L());document.getElementById("msgInput").value=V.text}else document.body.classList.remove("replyingTo"),document.getElementById("replyToContainer").textContent="";R()}
 window.toggleReaction=function(a,b,c){var d=E.a[a],e,g;(d=E.a[a])&&(e=la(d,b))&&(g=H(E.context,a))&&(e.B[c]&&-1!==e.B[c].indexOf(g.self.id)?(d=new XMLHttpRequest,d.open("DELETE","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(c),!0),d.send(null)):Ya(a,b,c))};
 function Za(a){a:{var b={};if(Q){var c;for(c=Q;!b[a];){if(c=c.a.data[a])if("alias:"==c.substr(0,6))b[a]=!0,a=c.substr(6);else{a=document.createElement("span");a.className="emoji-custom emoji";a.style.backgroundImage="url('"+c+"')";break a}break}}}"string"===typeof a&&"makeEmoji"in window&&(a=window.makeEmoji(a));return"string"===typeof a?null:a}function $a(a,b){document.getElementById("linkFavicon").href=a||b?"favicon.png?h="+a+"&m="+b:"favicon_ok.png"}
-function P(){var a=O.length,b="";if(X)b="!"+L.Ha+" - ",document.getElementById("linkFavicon").href="favicon_err.png";else if(a)b="(!"+a+") - ",$a(a,a);else{var c=0;pa(E.context,function(a){a.I>a.A&&c++});c&&(b="("+c+") - ");$a(0,c)}E.context.b&&(b+=E.context.b.name);document.title=b}
-function ab(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(La+3E4<a){var b=new Notification(L.Ia);La=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
+function P(){var a=O.length,b="";if(X)b="!"+L.Ia+" - ",document.getElementById("linkFavicon").href="favicon_err.png";else if(a)b="(!"+a+") - ",$a(a,a);else{var c=0;pa(E.context,function(a){a.I>a.A&&c++});c&&(b="("+c+") - ");$a(0,c)}E.context.b&&(b+=E.context.b.name);document.title=b}
+function ab(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(La+3E4<a){var b=new Notification(L.Ja);La=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
 function Wa(){var a=document.createDocumentFragment(),b=N.id,c=null,d=0,e=null,g;Ka=[];E.a[b]&&E.a[b].a.forEach(function(b){if(b.i)b.S();else{var f=b.M(),h=!1;c&&c.G===b.G&&b.G?30>Math.abs(d-b.j)&&!(b instanceof A)?e.classList.add("slackmsg-same-ts"):d=b.j:(d=b.j,h=!0);(!c||c.j<=N.A)&&b.j>N.A?f.classList.add("slackmsg-first-unread"):f.classList.remove("slackmsg-first-unread");if(b instanceof A)e=c=null,d=0,a.appendChild(f),g=null;else{if(h||!g){var h=J(b.G),m=b.username,l=document.createElement("div"),
 k=document.createElement("div"),p=document.createElement("span");l.U=document.createElement("img");l.U.className="slackmsg-author-img";p.className="slackmsg-author-name";h?(p.textContent=h.name,l.U.src="api/avatar?user="+h.id):(p.textContent=m||"?",l.U.src="");k.appendChild(l.U);k.appendChild(p);k.className="slackmsg-author";l.className="slackmsg-authorGroup";l.appendChild(k);l.content=document.createElement("div");l.content.className="slackmsg-author-messages";l.appendChild(l.content);g=l;Ka.push(g);
 a.appendChild(g)}c=b;e=f;g.content.appendChild(f)}}});b=document.getElementById("chatWindow");b.textContent="";b.appendChild(a);b.scrollTop=b.scrollHeight-b.clientHeight;window.hasFocus&&Xa()}
-function bb(a,b){a.classList.contains("slackmsg-hover-reply")?(V&&(V=null,W()),T!==b&&(T=b,U())):a.classList.contains("slackmsg-hover-reaction")?cb.$(document.body,{Xa:N.id,Va:b.id},function(a){a&&Ya(this.Xa,this.Va,a)}):a.classList.contains("slackmsg-hover-edit")?(T&&(T=null,U()),V!==b&&(V=b,W())):a.classList.contains("slackmsg-hover-remove")&&(T&&(T=null,U()),V&&(V=null,W()),db(b))}
+function bb(a,b){a.classList.contains("slackmsg-hover-reply")?(V&&(V=null,W()),T!==b&&(T=b,U())):a.classList.contains("slackmsg-hover-reaction")?cb.aa(document.body,{Za:N.id,Wa:b.id},function(a){a&&Ya(this.Za,this.Wa,a)}):a.classList.contains("slackmsg-hover-edit")?(T&&(T=null,U()),V!==b&&(V=b,W())):a.classList.contains("slackmsg-hover-remove")&&(T&&(T=null,U()),V&&(V=null,W()),db(b))}
 function eb(a){function b(a,b){for(b=b||a.target;b!==a.currentTarget&&b;){if(b.id&&b.classList.contains("slackmsg-item"))return b.id;b=b.parentElement}}for(var c=a.target;c!==a.currentTarget&&c&&!c.classList.contains("slackmsg-hover");){var d;if(c.parentElement&&c.classList.contains("slackmsg-attachment-actions-item")){var e=c.dataset.attachmentIndex,g=c.dataset.actionIndex;if((d=b(a,c))&&void 0!==e&&void 0!==g){d=d.substr(d.lastIndexOf("_")+1);(a=la(E.a[N.id],d))&&a.o[e]&&a.o[e].actions&&a.o[e].actions[g]&&
 fb(a,a.o[e],a.o[e].actions[g]);break}}if(c.parentElement&&c.parentElement.classList.contains("slackmsg-hover")){if(d=b(a,c))d=d.substr(d.lastIndexOf("_")+1),(a=la(E.a[N.id],d))&&bb(c,a);break}c=c.parentElement}}
-function fb(a,b,c){function d(){var d={actions:[c],attachment_id:b.id,callback_id:b.callback_id,channel_id:e,is_ephemeral:a instanceof C,message_ts:a.id},f=new XMLHttpRequest;f.open("POST","api/attachmentAction?serviceId="+a.G);f.send(JSON.stringify(d))}var e=N.id;c.confirm?Ja(Ia(new wa(c.confirm.title,c.confirm.text),c.confirm.ok_text,c.confirm.dismiss_text),d).$():d()}function R(){document.getElementById("msgInput").focus()}
+function fb(a,b,c){function d(){var d={actions:[c],attachment_id:b.id,callback_id:b.callback_id,channel_id:e,is_ephemeral:a instanceof C,message_ts:a.id},f=new XMLHttpRequest;f.open("POST","api/attachmentAction?serviceId="+a.G);f.send(JSON.stringify(d))}var e=N.id;c.confirm?Ja(Ia(new wa(c.confirm.title,c.confirm.text),c.confirm.ok_text,c.confirm.dismiss_text),d).aa():d()}function R(){document.getElementById("msgInput").focus()}
 function Pa(){var a=document.location.hash.substr(1),b=I(a);b&&b!==N?gb(b):(a=J(a))&&a.b&&gb(a.b)}function hb(){var a=document.getElementById("chatWindow").getBoundingClientRect().top;Ka.forEach(function(b){var c=b.U,d=c.clientHeight;b=b.getBoundingClientRect();c.style.top=Math.max(0,Math.min(a-b.top,b.height-d-d/2))+"px"})}
 document.addEventListener("DOMContentLoaded",function(){ua();ib();document.getElementById("chatWindow").addEventListener("click",eb);window.addEventListener("hashchange",function(){document.location.hash&&"#"===document.location.hash[0]&&Pa()});document.getElementById("fileUploadCancel").addEventListener("click",function(a){a.preventDefault();document.getElementById("fileUploadError").classList.add("hidden");document.getElementById("fileUploadContainer").classList.add("hidden");document.getElementById("fileUploadInput").value=
 "";return!1});document.getElementById("fileUploadForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("fileUploadInput");var b=a.value;b&&(b=b.substr(b.lastIndexOf("\\")+1),jb(b,a.files[0],function(a){var b=document.getElementById("fileUploadError");a?(b.textContent=a,b.classList.remove("hidden")):(b.classList.add("hidden"),document.getElementById("fileUploadInput").value="",document.getElementById("fileUploadContainer").classList.add("hidden"))}));return!1});
 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");N&&a.value&&kb(a.value)&&(a.value="",T&&(T=null,U()),V&&(V=null,U()),document.getElementById("slashList").textContent="");R();return!1});window.addEventListener("blur",function(){window.hasFocus=
-!1});window.addEventListener("focus",function(){window.hasFocus=!0;La=0;N&&Xa();R()});document.getElementById("chatWindow").addEventListener("scroll",hb);var a=0;document.getElementById("msgInput").addEventListener("input",function(){if(N){var b=Date.now();a+3E3<b&&(Q.self.a||N instanceof r)&&(lb(),a=b);var b=[],c=this.value;if("/"===this.value[0]){var d=c.indexOf(" "),e=-1!==d,d=-1===d?c.length:d,c=c.substr(0,d);if(e){var g=mb.Fa(c);g&&b.push(g)}else b=mb.Ra(c);var g=Q?Q.i.data:{};for(n in g){var f=
+!1});window.addEventListener("focus",function(){window.hasFocus=!0;La=0;N&&Xa();R()});document.getElementById("chatWindow").addEventListener("scroll",hb);var a=0;document.getElementById("msgInput").addEventListener("input",function(){if(N){var b=Date.now();a+3E3<b&&(Q.self.a||N instanceof r)&&(lb(),a=b);var b=[],c=this.value;if("/"===this.value[0]){var d=c.indexOf(" "),e=-1!==d,d=-1===d?c.length:d,c=c.substr(0,d);if(e){var g=mb.Ga(c);g&&b.push(g)}else b=mb.Sa(c);var g=Q?Q.i.data:{};for(n in g){var f=
 g[n];(!e&&f.name.substr(0,d)===c||e&&f.name===c)&&b.push(f)}}b.sort(function(a,b){return a.R.localeCompare(b.R)||a.name.localeCompare(b.name)});var n=document.getElementById("slashList");var d=document.createDocumentFragment();n.textContent="";e=0;for(c=b.length;e<c;e++){g=b[e];if(h!==g.R){var h=g.R;d.appendChild(nb(g.R))}d.appendChild(ob(g))}n.appendChild(d)}});window.hasFocus=!0;(function(){var a=document.getElementById("emojiButton");if("makeEmoji"in window){var c=window.makeEmoji("smile");c?a.innerHTML=
-"<span class='emoji-small'>"+c.outerHTML+"</span>":a.style.backgroundImage='url("smile.svg")';(c=window.makeEmoji("paperclip"))?document.getElementById("attachFile").innerHTML="<span class='emoji-small'>"+c.outerHTML+"</span>":document.getElementById("attachFile").style.backgroundImage='url("public/paperclip.svg")';a.addEventListener("click",function(){Q&&cb.$(document.body,Q,function(a){a&&(document.getElementById("msgInput").value+=":"+a+":");R()})})}else a.classList.add("hidden")})();pb()});function qb(a){if(void 0!==a.latitude&&void 0!==a.longitude&&-90<=a.latitude&&90>=a.latitude&&-180<=a.longitude&&180>=a.longitude){var b=0,c=function(a,b,c,d){return new Promise(function(e,f){var g=new Image;g.addEventListener("load",function(){d.N=g;e(d)});g.addEventListener("error",function(){console.warn("Error loading tile ",{zoom:a,x:b,y:c});f(g)});g.crossOrigin="anonymous";g.src="https://c.tile.openstreetmap.org/"+a+"/"+b+"/"+c+".png"})},d=document.createElement("canvas"),e=document.createElement("canvas");
+"<span class='emoji-small'>"+c.outerHTML+"</span>":a.style.backgroundImage='url("smile.svg")';(c=window.makeEmoji("paperclip"))?document.getElementById("attachFile").innerHTML="<span class='emoji-small'>"+c.outerHTML+"</span>":document.getElementById("attachFile").style.backgroundImage='url("public/paperclip.svg")';a.addEventListener("click",function(){Q&&cb.aa(document.body,Q,function(a){a&&(document.getElementById("msgInput").value+=":"+a+":");R()})})}else a.classList.add("hidden")})();pb()});var qb={display:function(){return this},$a:function(){return this},Xa:{Y:{}}};function rb(a){if(void 0!==a.latitude&&void 0!==a.longitude&&-90<=a.latitude&&90>=a.latitude&&-180<=a.longitude&&180>=a.longitude){var b=0,c=function(a,b,c,d){return new Promise(function(e,f){var g=new Image;g.addEventListener("load",function(){d.N=g;e(d)});g.addEventListener("error",function(){console.warn("Error loading tile ",{zoom:a,x:b,y:c});f(g)});g.crossOrigin="anonymous";g.src="https://c.tile.openstreetmap.org/"+a+"/"+b+"/"+c+".png"})},d=document.createElement("canvas"),e=document.createElement("canvas");
 d.height=d.width=e.height=e.width=300;var g=d.getContext("2d"),f=e.getContext("2d"),n=function(a,b,c){a=a*Math.PI/180;b=b*Math.PI/180;c=c*Math.PI/180;return Math.abs(6371E3*Math.acos(Math.pow(Math.sin(a),2)+Math.pow(Math.cos(a),2)*Math.cos(c-b)))},h=function(a,d,e,h){f.fillStyle="#808080";f.fillRect(0,0,300,300);g.fillStyle="#808080";g.fillRect(0,0,300,300);var k=Math.pow(2,a),l=(e+180)/360*k,m=(1-Math.log(Math.tan(d*Math.PI/180)+1/Math.cos(d*Math.PI/180))/Math.PI)/2*k,F=Math.floor(l),ba=Math.floor(m),
-ca=h?100*h/n(180/Math.PI*Math.atan(.5*(Math.exp(Math.PI-2*Math.PI*ba/k)-Math.exp(-(Math.PI-2*Math.PI*ba/k)))),F/k*360-180,(F+1)/k*360-180):0;d=b;for(e=0;3>e;e++)for(h=0;3>h;h++)c(a,F+e-1,ba+h-1,{Sa:e,Ua:h,Na:d}).then(function(a){if(a.Na===b){f.drawImage(a.N,100*a.Sa,100*a.Ua,100,100);a=l-F;var c=m-ba;a=100*a+100;c=100*c+100;g.putImageData(f.getImageData(0,0,300,300),0,0);void 0!==ca&&(g.beginPath(),g.arc(a,c,Math.max(ca,10),0,2*Math.PI,!1),g.lineWidth=2,g.fillStyle="rgba(244, 146, 66, 0.4)",g.strokeStyle=
+ca=h?100*h/n(180/Math.PI*Math.atan(.5*(Math.exp(Math.PI-2*Math.PI*ba/k)-Math.exp(-(Math.PI-2*Math.PI*ba/k)))),F/k*360-180,(F+1)/k*360-180):0;d=b;for(e=0;3>e;e++)for(h=0;3>h;h++)c(a,F+e-1,ba+h-1,{Ta:e,Va:h,Oa:d}).then(function(a){if(a.Oa===b){f.drawImage(a.N,100*a.Ta,100*a.Va,100,100);a=l-F;var c=m-ba;a=100*a+100;c=100*c+100;g.putImageData(f.getImageData(0,0,300,300),0,0);void 0!==ca&&(g.beginPath(),g.arc(a,c,Math.max(ca,10),0,2*Math.PI,!1),g.lineWidth=2,g.fillStyle="rgba(244, 146, 66, 0.4)",g.strokeStyle=
 "rgba(244, 146, 66, 0.8)",g.stroke(),g.fill());if(void 0===ca||25<ca)g.strokeStyle="rgba(244, 146, 66, 1)",g.beginPath(),g.moveTo(a-5,c-5),g.lineTo(a+5,c+5),g.stroke(),g.moveTo(a+5,c-5),g.lineTo(a-5,c+5),g.stroke()}})},m,l=function(c){c=Math.max(4,Math.min(19,c));m!==c&&(b++,m=c,h(m,Number(a.latitude),Number(a.longitude),Number(a.accuracy)))};l(12);var e=document.createElement("div"),k=document.createElement("div"),p=document.createElement("button"),t=document.createElement("button");e.className=
 "OSM-wrapper";d.className="OSM-canvas";k.className="OSM-controls";t.className="OSM-controls-zoomMin";p.className="OSM-controls-zoomPlus";t.addEventListener("click",function(){l(m-1)});p.addEventListener("click",function(){l(m+1)});k.appendChild(t);k.appendChild(p);e.appendChild(d);e.appendChild(k);return e}};function Na(){var a=document.createElement("span"),b=document.createElement("span"),c=document.createElement("span"),d=document.createElement("span");a.className="typing-container";b.className="typing-dot1";c.className="typing-dot2";d.className="typing-dot3";b.textContent=c.textContent=d.textContent=".";a.appendChild(b);a.appendChild(c);a.appendChild(d);return a}var Oa=function(){var a={};return function(b){var c=a[b];c||(c=a[b]=document.createElement("header"),c.textContent=b);return c}}();
-function rb(a){var b=a.b,c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),g=document.createElement("li");c.o=document.createElement("ul");c.B=document.createElement("ul");c.j=document.createElement("div");c.pa=document.createElement("div");c.ha=document.createElement("span");c.id=b+"_"+a.id;c.className="slackmsg-item";c.j.className="slackmsg-ts";c.pa.className="slackmsg-msg";c.ha.className="slackmsg-author-name";e.className="slackmsg-hover";g.className=
+function sb(a){var b=a.b,c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),g=document.createElement("li");c.o=document.createElement("ul");c.B=document.createElement("ul");c.j=document.createElement("div");c.qa=document.createElement("div");c.ia=document.createElement("span");c.id=b+"_"+a.id;c.className="slackmsg-item";c.j.className="slackmsg-ts";c.qa.className="slackmsg-msg";c.ia.className="slackmsg-author-name";e.className="slackmsg-hover";g.className=
 "slackmsg-hover-reply";e.appendChild(g);if("makeEmoji"in window){var f=document.createElement("li"),n=window.makeEmoji("arrow_heading_down"),h=window.makeEmoji("smile"),m=window.makeEmoji("pencil2"),b=window.makeEmoji("x");f.className="slackmsg-hover-reaction";h?(f.classList.add("emoji-small"),f.appendChild(h)):f.style.backgroundImage='url("smile.svg")';n?(g.classList.add("emoji-small"),g.appendChild(n)):g.style.backgroundImage='url("repl.svg")';e.appendChild(f);sa(a.G)&&(a=document.createElement("li"),
 a.className="slackmsg-hover-edit",m?a.classList.add("emoji-small"):a.style.backgroundImage='url("edit.svg")',a.appendChild(m),e.appendChild(a),a=document.createElement("li"),a.className="slackmsg-hover-remove",b?a.classList.add("emoji-small"):a.style.backgroundImage='url("remove.svg")',a.appendChild(b),e.appendChild(a))}else g.style.backgroundImage='url("repl.svg")',sa(a.G)&&(a=document.createElement("li"),a.className="slackmsg-hover-edit",a.style.backgroundImage='url("edit.svg")',e.appendChild(a),
-a=document.createElement("li"),a.className="slackmsg-hover-remove",a.style.backgroundImage='url("remove.svg")',e.appendChild(a));d.appendChild(c.ha);d.appendChild(c.pa);d.appendChild(c.j);d.appendChild(c.o);c.C=document.createElement("div");c.C.className="slackmsg-edited";d.appendChild(c.C);d.appendChild(c.B);d.className="slackmsg-content";c.o.className="slackmsg-attachments";c.B.className="slackmsg-reactions";c.appendChild(d);c.appendChild(e);return c}
-function sb(a){var b={good:"#2fa44f",warning:"#de9e31",danger:"#d50200"};if(a){if("#"===a[0])return a;if(b[a])return b[a]}return"#e3e4e6"}
-function tb(a,b,c){var d=document.createElement("li"),e=document.createElement("div"),g=document.createElement("div"),f=document.createElement("a"),n=document.createElement("div"),h=document.createElement("img"),m=document.createElement("a"),l=document.createElement("div"),k=document.createElement("div"),p=document.createElement("div"),t=document.createElement("img"),v=document.createElement("div");d.className="slackmsg-attachment";e.style.borderColor=sb(b.color||"");e.className="slackmsg-attachment-block";
+a=document.createElement("li"),a.className="slackmsg-hover-remove",a.style.backgroundImage='url("remove.svg")',e.appendChild(a));d.appendChild(c.ia);d.appendChild(c.qa);d.appendChild(c.j);d.appendChild(c.o);c.C=document.createElement("div");c.C.className="slackmsg-edited";d.appendChild(c.C);d.appendChild(c.B);d.className="slackmsg-content";c.o.className="slackmsg-attachments";c.B.className="slackmsg-reactions";c.appendChild(d);c.appendChild(e);return c}
+function tb(a){var b={good:"#2fa44f",warning:"#de9e31",danger:"#d50200"};if(a){if("#"===a[0])return a;if(b[a])return b[a]}return"#e3e4e6"}
+function ub(a,b,c){var d=document.createElement("li"),e=document.createElement("div"),g=document.createElement("div"),f=document.createElement("a"),n=document.createElement("div"),h=document.createElement("img"),m=document.createElement("a"),l=document.createElement("div"),k=document.createElement("div"),p=document.createElement("div"),t=document.createElement("img"),v=document.createElement("div");d.className="slackmsg-attachment";e.style.borderColor=tb(b.color||"");e.className="slackmsg-attachment-block";
 g.className="slackmsg-attachment-pretext";b.pretext?g.innerHTML=a.u(b.pretext):g.classList.add("hidden");f.target="_blank";b.title?(f.innerHTML=a.u(b.title),b.title_link&&(f.href=b.title_link),f.className="slackmsg-attachment-title"):f.className="hidden slackmsg-attachment-title";m.target="_blank";n.className="slackmsg-author";b.author_name&&(m.innerHTML=a.u(b.author_name),m.href=b.author_link||"",m.className="slackmsg-author-name",h.className="slackmsg-author-img",b.author_icon&&(h.src=b.author_icon,
-n.appendChild(h)),n.appendChild(m));p.className="slackmsg-attachment-thumb";b.thumb_url?(h=document.createElement("img"),h.src=b.thumb_url,p.appendChild(h),e.classList.add("has-thumb"),b.video_html&&(p.dataset.video=b.video_html)):p.classList.add("hidden");l.className="slackmsg-attachment-content";h=a.u(b.text||"");k.className="slackmsg-attachment-text";h&&""!=h?k.innerHTML=h:k.classList.add("hidden");l.appendChild(p);l.appendChild(k);b.geo&&(k=qb(b.geo))&&l.appendChild(k);t.className="slackmsg-attachment-img";
+n.appendChild(h)),n.appendChild(m));p.className="slackmsg-attachment-thumb";b.thumb_url?(h=document.createElement("img"),h.src=b.thumb_url,p.appendChild(h),e.classList.add("has-thumb"),b.video_html&&(p.dataset.video=b.video_html)):p.classList.add("hidden");l.className="slackmsg-attachment-content";h=a.u(b.text||"");k.className="slackmsg-attachment-text";h&&""!=h?k.innerHTML=h:k.classList.add("hidden");l.appendChild(p);l.appendChild(k);b.geo&&(k=rb(b.geo))&&l.appendChild(k);t.className="slackmsg-attachment-img";
 b.image_url?t.src=b.image_url:t.classList.add("hidden");v.className="slackmsg-attachment-footer";b.footer&&(k=document.createElement("span"),k.className="slackmsg-attachment-footer-text",k.innerHTML=a.u(b.footer),b.footer_icon&&(p=document.createElement("img"),p.src=b.footer_icon,p.className="slackmsg-attachment-footer-icon",v.appendChild(p)),v.appendChild(k));b.ts&&(k=document.createElement("span"),k.className="slackmsg-ts",k.innerHTML=L.W(b.ts),v.appendChild(k));e.appendChild(f);e.appendChild(n);
 e.appendChild(l);e.appendChild(t);if(b.fields&&b.fields.length){var w=document.createElement("ul");e.appendChild(w);w.className="slackmsg-attachment-fields";b.fields.forEach(function(b){var c=b.title||"",d=b.value||"";b=!!b["short"];var e=document.createElement("li"),g=document.createElement("div"),f=document.createElement("div");e.className="field";b||e.classList.add("field-long");g.className="field-title";g.textContent=c;f.className="field-text";f.innerHTML=a.u(d);e.appendChild(g);e.appendChild(f);
-e&&w.appendChild(e)})}if(b.actions&&b.actions.length)for(f=document.createElement("ul"),f.className="slackmsg-attachment-actions "+Ea,e.appendChild(f),n=0,l=b.actions.length;n<l;n++)(t=b.actions[n])&&(t=ub(c,n,t))&&f.appendChild(t);e.appendChild(v);d.appendChild(g);d.appendChild(e);return d}
-function ub(a,b,c){var d=document.createElement("li"),e=sb(c.style);d.textContent=c.text;e!==sb()&&(d.style.color=e);d.style.borderColor=e;d.dataset.attachmentIndex=a;d.dataset.actionIndex=b;d.className="slackmsg-attachment-actions-item "+Da;return d}function Ta(a){var b=document.createElement("li"),c=document.createElement("span");c.textContent=a.name;b.appendChild(Na());b.appendChild(c);return b}
+e&&w.appendChild(e)})}if(b.actions&&b.actions.length)for(f=document.createElement("ul"),f.className="slackmsg-attachment-actions "+Ea,e.appendChild(f),n=0,l=b.actions.length;n<l;n++)(t=b.actions[n])&&(t=vb(c,n,t))&&f.appendChild(t);e.appendChild(v);d.appendChild(g);d.appendChild(e);return d}
+function vb(a,b,c){var d=document.createElement("li"),e=tb(c.style);d.textContent=c.text;e!==tb()&&(d.style.color=e);d.style.borderColor=e;d.dataset.attachmentIndex=a;d.dataset.actionIndex=b;d.className="slackmsg-attachment-actions-item "+Da;return d}function Ta(a){var b=document.createElement("li"),c=document.createElement("span");c.textContent=a.name;b.appendChild(Na());b.appendChild(c);return b}
 function nb(a){var b=document.createElement("lh");b.textContent=a;b.className="slack-command-header";return b}
-function ob(a){var b=document.createElement("li"),c=document.createElement("span"),d=document.createElement("span"),e=document.createElement("span");c.textContent=a.name;d.textContent=a.usage;e.textContent=a.a;b.appendChild(c);b.appendChild(d);b.appendChild(e);b.className="slack-command-item";c.className="slack-command-name";d.className="slack-command-usage";e.className="slack-command-desc";return b};var cb=function(){function a(a,b){for(a=a.target;a!==h&&a&&"LI"!==a.nodeName;)a=a.parentElement;a&&"LI"===a.nodeName&&a.id&&"emojibar-"===a.id.substr(0,9)?b(a.id.substr(9)):b(null)}function b(){if(!c())return!1;G&&G(null);return!0}function c(){return h.parentElement?(h.parentElement.removeChild(m),h.parentElement.removeChild(h),!0):!1}function d(a){var b=0;a=void 0===a?t.value:a;if(n()){var c=0,d=window.searchEmojis(a),f=e(d,S.self.O.ia),h;for(l in v)v[l].visible&&(v[l].visible=!1,k.removeChild(v[l].c));
-var l=0;for(h=f.length;l<h;l++){var m=f[l].name,z=v[m];if(!z){var z=v,F=m;var G=m;var m=window.makeEmoji(d[m]),x=document.createElement("span");x.appendChild(m);x.className="emoji-medium";G=g(G,x);z=z[F]=G}z.visible||(z.visible=!0,k.appendChild(z.c));c++}b+=c}l=b;var c=0;for(B in w)w[B].visible&&(w[B].visible=!1,p.removeChild(w[B].c));d=e(S.a.data,S.self.O.ia);var B=0;for(b=d.length;B<b;B++)F=d[B].name,""!==a&&F.substr(0,a.length)!==a||"alias:"===S.a.data[F].substr(0,6)||(f=w[F],f||(f=w,z=h=F,F=S.a.data[F],
-G=document.createElement("span"),m=document.createElement("span"),G.className="emoji emoji-custom",G.style.backgroundImage='url("'+F+'")',m.appendChild(G),m.className="emoji-medium",z=g(z,m),f=f[h]=z),f.visible||(f.visible=!0,p.appendChild(f.c)),c++);return l+c}function e(a,b){var c=[],d;for(d in a){var e={name:d,Ka:0,count:0};if(a[d].names)for(var f=0,g=a[d].names.length;f<g;f++)e.count+=b[a[d].names[f]]||0;c.push(e)}return c=c.sort(function(a,b){var c=b.count-a.count;return c?c:a.Ka-b.Ka})}function g(a,
+function ob(a){var b=document.createElement("li"),c=document.createElement("span"),d=document.createElement("span"),e=document.createElement("span");c.textContent=a.name;d.textContent=a.usage;e.textContent=a.a;b.appendChild(c);b.appendChild(d);b.appendChild(e);b.className="slack-command-item";c.className="slack-command-name";d.className="slack-command-usage";e.className="slack-command-desc";return b};var cb=function(){function a(a,b){for(a=a.target;a!==h&&a&&"LI"!==a.nodeName;)a=a.parentElement;a&&"LI"===a.nodeName&&a.id&&"emojibar-"===a.id.substr(0,9)?b(a.id.substr(9)):b(null)}function b(){if(!c())return!1;G&&G(null);return!0}function c(){return h.parentElement?(h.parentElement.removeChild(m),h.parentElement.removeChild(h),!0):!1}function d(a){var b=0;a=void 0===a?t.value:a;if(n()){var c=0,d=window.searchEmojis(a),f=e(d,S.self.O.ja),h;for(l in v)v[l].visible&&(v[l].visible=!1,k.removeChild(v[l].c));
+var l=0;for(h=f.length;l<h;l++){var m=f[l].name,z=v[m];if(!z){var z=v,F=m;var G=m;var m=window.makeEmoji(d[m]),x=document.createElement("span");x.appendChild(m);x.className="emoji-medium";G=g(G,x);z=z[F]=G}z.visible||(z.visible=!0,k.appendChild(z.c));c++}b+=c}l=b;var c=0;for(B in w)w[B].visible&&(w[B].visible=!1,p.removeChild(w[B].c));d=e(S.a.data,S.self.O.ja);var B=0;for(b=d.length;B<b;B++)F=d[B].name,""!==a&&F.substr(0,a.length)!==a||"alias:"===S.a.data[F].substr(0,6)||(f=w[F],f||(f=w,z=h=F,F=S.a.data[F],
+G=document.createElement("span"),m=document.createElement("span"),G.className="emoji emoji-custom",G.style.backgroundImage='url("'+F+'")',m.appendChild(G),m.className="emoji-medium",z=g(z,m),f=f[h]=z),f.visible||(f.visible=!0,p.appendChild(f.c)),c++);return l+c}function e(a,b){var c=[],d;for(d in a){var e={name:d,La:0,count:0};if(a[d].names)for(var f=0,g=a[d].names.length;f<g;f++)e.count+=b[a[d].names[f]]||0;c.push(e)}return c=c.sort(function(a,b){var c=b.count-a.count;return c?c:a.La-b.La})}function g(a,
 b){var c=document.createElement("li");c.appendChild(b);c.className="emojibar-list-item";c.id="emojibar-"+a;return{visible:!1,c:c}}function f(a){var b=document.createElement("img"),c=document.createElement("div");b.src=a;c.appendChild(b);c.className="emojibar-header";return c}function n(){return"searchEmojis"in window}var h=document.createElement("div"),m=document.createElement("div"),l=document.createElement("div"),k=document.createElement("ul"),p=document.createElement("ul"),t=document.createElement("input"),
 v={},w={},x=document.createElement("div"),B=document.createElement("span"),z=document.createElement("span"),G,S;m.addEventListener("click",function(a){var c=h.getBoundingClientRect();(a.screenY<c.top||a.screenY>c.bottom||a.screenX<c.left||a.screenX>c.right)&&b()});m.className="emojibar-overlay";h.className="emojibar";l.className="emojibar-emojis";x.className="emojibar-detail";B.className="emojibar-detail-img";z.className="emojibar-detail-name";k.className=p.className="emojibar-list";t.className="emojibar-search";
 x.appendChild(B);x.appendChild(z);l.appendChild(f(window.emojiProviderHeader));l.appendChild(k);l.appendChild(f("emojicustom.png"));l.appendChild(p);h.appendChild(l);h.appendChild(x);h.appendChild(t);t.addEventListener("keyup",function(){d()});h.addEventListener("mousemove",function(b){a(b,function(a){var b=a?v[a]||w[a]:null;b?(B.innerHTML=b.c.outerHTML,z.textContent=":"+a+":"):(B.textContent="",z.textContent="")})});h.addEventListener("click",function(b){a(b,function(a){a&&c()&&G&&G(a)})});return{isSupported:n,
-$:function(a,b,c){return n()?(S=b,G=c,a.appendChild(m),a.appendChild(h),t.value="",d(),t.focus(),!0):!1},search:d,close:b}}();var E,O=[];function vb(){fa.call(this)}vb.prototype=Object.create(fa.prototype);vb.prototype.constructor=vb;function oa(a){return a.b?a.b.id:null}function wb(){this.b=0;this.context=new ma;this.a={}}
-wb.prototype.update=function(a){var b=Date.now();a.v&&(this.b=a.v);if(a["static"])for(e in a["static"]){var c=na(this.context,e);c||(c=new vb,this.context.push(c));ga(c,a["static"][e],b)}pa(this.context,function(a){a.I===a.A&&(a=O.indexOf(a),-1!==a&&O.splice(a,1))});if(a.live){for(e in a.live)(c=this.a[e])?ia(c,a.live[e],b):c=this.a[e]=new Y(e,250,a.live[e],b);for(var d in a.live){var e=H(this.context,d);(c=e.l[d])?(this.a[d].a.length&&(c.I=Math.max(c.I,ka(this.a[d]).j)),c.Y||(xb(e,c,a.live[d]),N&&
-a.live[N.id]&&Wa())):E.b=0}}a["static"]&&Ma();var g=!1;a.typing&&this.context.a.forEach(function(c){var d=g,e=a.typing,f=!1;if(c.s)for(var l in c.s)e[l]||(delete c.s[l],f=!0);if(e)for(l in e)if(c.l[l]){c.s[l]||(c.s[l]={});for(var k in e[l])c.s[l][k]||(f=!0),c.s[l][k]=b}g=d|f},this);(a["static"]||g)&&Ra()};
+aa:function(a,b,c){return n()?(S=b,G=c,a.appendChild(m),a.appendChild(h),t.value="",d(),t.focus(),!0):!1},search:d,close:b}}();var E,O=[];function wb(){fa.call(this)}wb.prototype=Object.create(fa.prototype);wb.prototype.constructor=wb;function oa(a){return a.b?a.b.id:null}function xb(){this.b=0;this.context=new ma;this.a={}}
+xb.prototype.update=function(a){var b=Date.now();a.v&&(this.b=a.v);if(a["static"])for(e in a["static"]){var c=na(this.context,e);c||(c=new wb,this.context.push(c));ga(c,a["static"][e],b)}pa(this.context,function(a){a.I===a.A&&(a=O.indexOf(a),-1!==a&&O.splice(a,1))});if(a.live){for(e in a.live)(c=this.a[e])?ia(c,a.live[e],b):c=this.a[e]=new Y(e,250,a.live[e],b);for(var d in a.live){var e=H(this.context,d);(c=e.l[d])?(this.a[d].a.length&&(c.I=Math.max(c.I,ka(this.a[d]).j)),c.Z||(yb(e,c,a.live[d]),N&&
+a.live[N.id]&&Wa())):E.b=0}}a["static"]&&Ma();var g=!1;a.typing&&this.context.a.forEach(function(c){var d=g,e=a.typing,f=!1;if(c.s)for(var l in c.s)e[l]||(delete c.s[l],f=!0);if(e)for(l in e)if(c.l[l]){c.s[l]||(c.s[l]={});for(var k in e[l])c.s[l][k]||(f=!0),c.s[l][k]=b}g=d|f},this);(a["static"]||g)&&Ra();a.config&&(zb=new Ab(a.config),zb.Y.length||qb.$a(!1).display(qb.Xa.Y))};
 setInterval(function(){var a=!1,b=Date.now();qa(function(c){var d=!1,e;for(e in c.s){var g=!0,f;for(f in c.s[e])c.s[e][f]+3E3<b?(delete c.s[e][f],d=!0):g=!1;g&&(delete c.s[e],d=!0)}d&&(a=!0)});a&&Ra()},1E3);
-function xb(a,b,c){if(b!==N||!window.hasFocus){var d=new RegExp("<@"+a.self.id),e=!1,g=!1,f=!1;c.forEach(function(c){if(!(parseFloat(c.ts)<=b.A)){g=!0;var h;if(!(h=b instanceof r)&&(h=c.text)&&!(h=c.text.match(d)))a:{h=a.self.O.w;for(var m=0,l=h.length;m<l;m++)if(-1!==c.text.indexOf(h[m])){h=!0;break a}h=!1}h&&(-1===O.indexOf(b)&&(f=!0,O.push(b)),e=!0)}});if(g){P();if(c=document.getElementById("room_"+b.id))c.classList.add("unread"),e&&c.classList.add("unreadHi");f&&!window.hasFocus&&ab()}}}
-function Xa(){var a=N,b=O.indexOf(a);if(a.I>a.A){var c=E.a[a.id];if(c&&(c=c.a[c.a.length-1])){var d=new XMLHttpRequest;d.open("POST","api/markread?room="+a.id+"&id="+c.id+"&ts="+c.j,!0);d.send(null);a.A=c.j}}0<=b&&(O.splice(b,1),P());a=document.getElementById("room_"+a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}E=new wb;var Qa=function(){function a(a,c){c.sort(function(){return Math.random()-.5});for(var d=0,e=20;e<m-40;e+=k)for(var g=0;g+k<=l;g+=k)f(a,c[d],e,g),d++,d===c.length&&(c.sort(b),d=0)}function b(a,b){return a.N?b.N?Math.random()-.5:-1:1}function c(a,b){for(var e=0,g=a.length;e<g;e++)if(void 0===a[e].N){d(a[e].src,function(d){a[e].N=d;c(a,b)});return}var f=[];a.forEach(function(a){a.N&&f.push(a.N)});b(f)}function d(a,b){var c=new XMLHttpRequest;c.responseType="blob";c.onreadystatechange=function(){if(4===
+function yb(a,b,c){if(b!==N||!window.hasFocus){var d=new RegExp("<@"+a.self.id),e=!1,g=!1,f=!1;c.forEach(function(c){if(!(parseFloat(c.ts)<=b.A)){g=!0;var h;if(!(h=b instanceof r)&&(h=c.text)&&!(h=c.text.match(d)))a:{h=a.self.O.w;for(var m=0,l=h.length;m<l;m++)if(-1!==c.text.indexOf(h[m])){h=!0;break a}h=!1}h&&(-1===O.indexOf(b)&&(f=!0,O.push(b)),e=!0)}});if(g){P();if(c=document.getElementById("room_"+b.id))c.classList.add("unread"),e&&c.classList.add("unreadHi");f&&!window.hasFocus&&ab()}}}
+function Xa(){var a=N,b=O.indexOf(a);if(a.I>a.A){var c=E.a[a.id];if(c&&(c=c.a[c.a.length-1])){var d=new XMLHttpRequest;d.open("POST","api/markread?room="+a.id+"&id="+c.id+"&ts="+c.j,!0);d.send(null);a.A=c.j}}0<=b&&(O.splice(b,1),P());a=document.getElementById("room_"+a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}E=new xb;var Qa=function(){function a(a,c){c.sort(function(){return Math.random()-.5});for(var d=0,e=20;e<m-40;e+=k)for(var g=0;g+k<=l;g+=k)f(a,c[d],e,g),d++,d===c.length&&(c.sort(b),d=0)}function b(a,b){return a.N?b.N?Math.random()-.5:-1:1}function c(a,b){for(var e=0,g=a.length;e<g;e++)if(void 0===a[e].N){d(a[e].src,function(d){a[e].N=d;c(a,b)});return}var f=[];a.forEach(function(a){a.N&&f.push(a.N)});b(f)}function d(a,b){var c=new XMLHttpRequest;c.responseType="blob";c.onreadystatechange=function(){if(4===
 c.readyState)if(c.response){var a=new Image;a.onload=function(){var c=document.createElement("canvas");c.height=c.width=v;c=c.getContext("2d");c.drawImage(a,0,0,v,v);var c=c.getImageData(0,0,v,v),d=0,e;for(e=0;e<c.width*c.height*4;e+=4)c.data[e]=c.data[e+1]=c.data[e+2]=(c.data[e]+c.data[e+1]+c.data[e+2])/3,c.data[e+3]=50,d+=c.data[e];if(50>d/(c.height*c.width))for(e=0;e<c.width*c.height*4;e+=4)c.data[e]=c.data[e+1]=c.data[e+2]=255-c.data[e];b(c)};a.onerror=function(){b(null)};a.src=window.URL.createObjectURL(c.response)}else b(null)};
 c.open("GET",a,!0);c.send(null)}function e(){var a=h.createLinearGradient(0,0,0,l);a.addColorStop(0,"#4D394B");a.addColorStop(1,"#201820");h.fillStyle=a;h.fillRect(0,0,m,l);return h.getImageData(0,0,m,l)}function g(a,b){for(var c=(a.height-b.height)/2,d=0;d<b.height;d++)for(var e=0;e<b.width;e++){var g=b.data[4*(d*b.width+e)]/255,f=4*((d+c)*a.width+e+c);a.data[f]*=g;a.data[f+1]*=g;a.data[f+2]*=g}return a}function f(a,b,c,d){var e=Math.floor(d);a=[a.data[e*m*4+0],a.data[e*m*4+1],a.data[e*m*4+2]];h.fillStyle=
 "#"+(1.1*a[0]<<16|1.1*a[1]<<8|1.1*a[2]).toString(16);h.beginPath();h.moveTo(c+k/2,d+p);h.lineTo(c-p+k,d+k/2);h.lineTo(c+k/2,d-p+k);h.lineTo(c+p,d+k/2);h.closePath();h.fill();h.putImageData(g(h.getImageData(c+p,d+p,t,t),b),c+p,d+p)}var n=document.createElement("canvas"),h=n.getContext("2d"),m=n.width=250,l=n.height=290,k=(m-40)/3,p=.1*k,t=Math.floor(k-2*p),v=.5*t,w={},x={},B={};return function(b,d,g){if(w[b])g(w[b]);else if(B[b])x[b]?x[b].push(g):x[b]=[g];else{var f=e(),h=[];B[b]=!0;x[b]?x[b].push(g):
-x[b]=[g];for(var k in d)d[k].Da||d[k].Ta||h.push({src:"api/avatar?user="+d[k].id});c(h,function(c){a(f,c);w[b]=n.toDataURL();x[b].forEach(function(a){a(w[b])})})}}}();var X=0,N=null,Q=null,T=null,V=null;function ib(){var a=new XMLHttpRequest;a.timeout=6E4;a.onreadystatechange=function(){if(4===a.readyState){var b=document.createElement("script");b.innerHTML=a.response;b.language="text/javascript";document.head.innerHTML+='<link href="hljs-androidstudio.css" rel="stylesheet"/>';document.body.appendChild(b)}};a.open("GET","highlight.pack.js",!0);a.send(null)}function yb(){var a=new XMLHttpRequest;a.open("GET","api/hist?room="+N.id,!0);a.send(null)}
-function zb(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var c=null,d=2===Math.floor(b.status/100);if(d){X&&(X=0,Ua(!0));c=b.response;try{c=JSON.parse(c)}catch(e){c=null}}else X?(X+=Math.floor((X||5)/2),X=Math.min(60,X)):(X=5,Ua(!1));a(d,c)}else X&&(X=0,Ua(!0)),zb(a)};b.open("GET","api?v="+E.b,!0);b.send(null)}function lb(){var a=new XMLHttpRequest;a.open("POST","api/typing?room="+N.id,!0);a.send(null)}
-function Ab(a,b){a?(b&&E.update(b),pb()):setTimeout(pb,1E3*X)}function pb(){zb(Ab)}function gb(a){N&&document.getElementById("room_"+N.id).classList.remove("selected");document.getElementById("room_"+a.id).classList.add("selected");document.body.classList.remove("no-room-selected");N=a;Q=H(E.context,a.id);Va();Qa(Q.b.id,Q.m,function(a){document.getElementById("slackCtx").style.backgroundImage="url("+a+")"});N.I&&!E.a[N.id]&&yb()}
+x[b]=[g];for(var k in d)d[k].Ea||d[k].Ua||h.push({src:"api/avatar?user="+d[k].id});c(h,function(c){a(f,c);w[b]=n.toDataURL();x[b].forEach(function(a){a(w[b])})})}}}();var X=0,N=null,Q=null,T=null,V=null;function ib(){var a=new XMLHttpRequest;a.timeout=6E4;a.onreadystatechange=function(){if(4===a.readyState){var b=document.createElement("script");b.innerHTML=a.response;b.language="text/javascript";document.head.innerHTML+='<link href="hljs-androidstudio.css" rel="stylesheet"/>';document.body.appendChild(b)}};a.open("GET","highlight.pack.js",!0);a.send(null)}function Bb(){var a=new XMLHttpRequest;a.open("GET","api/hist?room="+N.id,!0);a.send(null)}
+function Cb(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var c=null,d=2===Math.floor(b.status/100);if(d){X&&(X=0,Ua(!0));c=b.response;try{c=JSON.parse(c)}catch(e){c=null}}else X?(X+=Math.floor((X||5)/2),X=Math.min(60,X)):(X=5,Ua(!1));a(d,c)}else X&&(X=0,Ua(!0)),Cb(a)};b.open("GET","api?v="+E.b,!0);b.send(null)}function lb(){var a=new XMLHttpRequest;a.open("POST","api/typing?room="+N.id,!0);a.send(null)}
+function Db(a,b){a?(b&&E.update(b),pb()):setTimeout(pb,1E3*X)}function pb(){Cb(Db)}function gb(a){N&&document.getElementById("room_"+N.id).classList.remove("selected");document.getElementById("room_"+a.id).classList.add("selected");document.body.classList.remove("no-room-selected");N=a;Q=H(E.context,a.id);Va();Qa(Q.b.id,Q.m,function(a){document.getElementById("slackCtx").style.backgroundImage="url("+a+")"});N.I&&!E.a[N.id]&&Bb()}
 function jb(a,b,c){var d=N;new FileReader;var e=new FormData,g=new XMLHttpRequest;e.append("file",b);e.append("filename",a);g.onreadystatechange=function(){4===g.readyState&&(204===g.status?c(null):c(g.statusText))};g.open("POST","api/file?room="+d.id);g.send(e)}
-function Bb(a,b,c){var d=new XMLHttpRequest;b="api/msg?room="+a.id+"&text="+encodeURIComponent(b);c&&(b+="&attachments="+encodeURIComponent(JSON.stringify([{fallback:c.text,author_name:J(c.G).name,text:c.text,footer:a.b?L.message:a.name,ts:c.j}])));d.open("POST",b,!0);d.send(null)}
-function kb(a){if(V){var b=new XMLHttpRequest;b.open("PUT","api/msg?room="+N.id+"&ts="+V.id+"&text="+encodeURIComponent(a),!0);b.send(null);return!0}if("/"===a[0]){var c=a.indexOf(" "),b=a.substr(0,-1===c?void 0:c);a=-1===c?"":a.substr(c);var c=Q,d=mb.Fa(b);return d?(d.exec(c,N,a.trim()),!0):c&&(b=c.i.data[b])?(c=new XMLHttpRequest,c.open("POST","api/cmd?room="+N.id+"&cmd="+encodeURIComponent(b.name.substr(1))+"&args="+encodeURIComponent(a.trim()),!0),c.send(null),!0):!1}Bb(N,a,T);return!0}
-function db(a){var b=new XMLHttpRequest;b.open("DELETE","api/msg?room="+N.id+"&ts="+a.id,!0);b.send(null)}function Ya(a,b,c){var d=new XMLHttpRequest;d.open("POST","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(c),!0);d.send(null)};function Y(a,b,c,d){D.call(this,a,b,c,d)}Y.prototype=Object.create(D.prototype);Y.prototype.constructor=Y;Y.prototype.b=function(a,b){return!0===a.isMeMessage?new Cb(this.id,a,b):!0===a.isNotice?new Db(this.id,a,b):new Eb(this.id,a,b)};
-var Z=function(){function a(a,d){return va(d,{w:a.context.self.O.w,V:function(a){":"===a[0]&&":"===a[a.length-1]&&(a=a.substr(1,a.length-2));if(a=Za(a)){var b=document.createElement("span");b.className="emoji-small";b.appendChild(a);return b.outerHTML}return null},Z:function(c){return b(a,c)}})}function b(a,b){var c=b.indexOf("|");if(-1===c)var d=b;else{d=b.substr(0,c);var f=b.substr(c+1)}if("@"===d[0])if(d=oa(a.context)+"|"+d.substr(1),f=J(d))a=!0,d="#"+f.na.id,f="@"+f.name;else return null;else if("#"===
-d[0])if(d=oa(a.context)+"|"+d.substr(1),f=I(d))a=!0,d="#"+d,f="#"+f.name;else return null;else{if(!d.match(/^(https?|mailto):\/\//i))return null;a=!1}return{link:d,text:f||d,Ga:a}}return{F:function(a){a.P=!0;return a},S:function(a){a.c&&a.c.parentElement&&(a.c.remove(),delete a.c);return a},M:function(a){a.c?a.P&&(a.P=!1,a.K()):a.ga().K();return a.c},K:function(b){var c=J(b.G);b.c.j.innerHTML=L.W(b.j);b.c.pa.innerHTML=a(b,b.text);b.c.ha.textContent=c?c.name:b.username||"?";for(var c=document.createDocumentFragment(),
-e=0,g=b.o.length;e<g;e++){var f=b.o[e];f&&(f=tb(b,f,e))&&c.appendChild(f)}b.c.o.textContent="";b.c.o.appendChild(c);c=b.b;e=document.createDocumentFragment();if(b.B)for(var n in b.B){var g=c,f=b.id,h=n,m=b.B[n],l=Za(h);if(l){for(var k=document.createElement("li"),p=document.createElement("a"),t=document.createElement("span"),v=document.createElement("span"),w=[],x=0,B=m.length;x<B;x++){var z=J(m[x]);z&&w.push(z.name)}w.sort();v.textContent=w.join(", ");t.appendChild(l);t.className="emoji-small";p.href=
+function Eb(a,b,c){var d=new XMLHttpRequest;b="api/msg?room="+a.id+"&text="+encodeURIComponent(b);c&&(b+="&attachments="+encodeURIComponent(JSON.stringify([{fallback:c.text,author_name:J(c.G).name,text:c.text,footer:a.b?L.message:a.name,ts:c.j}])));d.open("POST",b,!0);d.send(null)}
+function kb(a){if(V){var b=new XMLHttpRequest;b.open("PUT","api/msg?room="+N.id+"&ts="+V.id+"&text="+encodeURIComponent(a),!0);b.send(null);return!0}if("/"===a[0]){var c=a.indexOf(" "),b=a.substr(0,-1===c?void 0:c);a=-1===c?"":a.substr(c);var c=Q,d=mb.Ga(b);return d?(d.exec(c,N,a.trim()),!0):c&&(b=c.i.data[b])?(c=new XMLHttpRequest,c.open("POST","api/cmd?room="+N.id+"&cmd="+encodeURIComponent(b.name.substr(1))+"&args="+encodeURIComponent(a.trim()),!0),c.send(null),!0):!1}Eb(N,a,T);return!0}
+function db(a){var b=new XMLHttpRequest;b.open("DELETE","api/msg?room="+N.id+"&ts="+a.id,!0);b.send(null)}function Ya(a,b,c){var d=new XMLHttpRequest;d.open("POST","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(c),!0);d.send(null)};function Y(a,b,c,d){D.call(this,a,b,c,d)}Y.prototype=Object.create(D.prototype);Y.prototype.constructor=Y;Y.prototype.b=function(a,b){return!0===a.isMeMessage?new Fb(this.id,a,b):!0===a.isNotice?new Gb(this.id,a,b):new Hb(this.id,a,b)};
+var Z=function(){function a(a,d){return va(d,{w:a.context.self.O.w,V:function(a){":"===a[0]&&":"===a[a.length-1]&&(a=a.substr(1,a.length-2));if(a=Za(a)){var b=document.createElement("span");b.className="emoji-small";b.appendChild(a);return b.outerHTML}return null},$:function(c){return b(a,c)}})}function b(a,b){var c=b.indexOf("|");if(-1===c)var d=b;else{d=b.substr(0,c);var f=b.substr(c+1)}if("@"===d[0])if(d=oa(a.context)+"|"+d.substr(1),f=J(d))a=!0,d="#"+f.oa.id,f="@"+f.name;else return null;else if("#"===
+d[0])if(d=oa(a.context)+"|"+d.substr(1),f=I(d))a=!0,d="#"+d,f="#"+f.name;else return null;else{if(!d.match(/^(https?|mailto):\/\//i))return null;a=!1}return{link:d,text:f||d,Ha:a}}return{F:function(a){a.P=!0;return a},S:function(a){a.c&&a.c.parentElement&&(a.c.remove(),delete a.c);return a},M:function(a){a.c?a.P&&(a.P=!1,a.K()):a.ha().K();return a.c},K:function(b){var c=J(b.G);b.c.j.innerHTML=L.W(b.j);b.c.qa.innerHTML=a(b,b.text);b.c.ia.textContent=c?c.name:b.username||"?";for(var c=document.createDocumentFragment(),
+e=0,g=b.o.length;e<g;e++){var f=b.o[e];f&&(f=ub(b,f,e))&&c.appendChild(f)}b.c.o.textContent="";b.c.o.appendChild(c);c=b.b;e=document.createDocumentFragment();if(b.B)for(var n in b.B){var g=c,f=b.id,h=n,m=b.B[n],l=Za(h);if(l){for(var k=document.createElement("li"),p=document.createElement("a"),t=document.createElement("span"),v=document.createElement("span"),w=[],x=0,B=m.length;x<B;x++){var z=J(m[x]);z&&w.push(z.name)}w.sort();v.textContent=w.join(", ");t.appendChild(l);t.className="emoji-small";p.href=
 "javascript:toggleReaction('"+g+"', '"+f+"', '"+h+"')";p.appendChild(t);p.appendChild(v);k.className="slackmsg-reaction-item";k.appendChild(p);g=k}else console.warn("Reaction id not found: "+h),g=null;g&&e.appendChild(g)}b.c.B.textContent="";b.c.B.appendChild(e);b.C&&(b.c.C.innerHTML=L.C(b.C),b.c.classList.add("edited"));return b},L:function(a){return a.c.cloneNode(!0)},u:function(b,d){return a(b,d)}}}();
-function Cb(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.c=Z.c;this.P=Z.P}Cb.prototype=Object.create(A.prototype);q=Cb.prototype;q.constructor=Cb;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){return Z.S(this)};q.M=function(){return Z.M(this)};q.ga=function(){this.c=rb(this);this.c.classList.add("slackmsg-me_message");return this};q.L=function(){return Z.L(this)};q.K=function(){Z.K(this);return this};
-q.update=function(a,b){A.prototype.update.call(this,a,b);this.F()};function Eb(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.c=Z.c;this.P=Z.P}Eb.prototype=Object.create(y.prototype);q=Eb.prototype;q.constructor=Eb;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){return Z.S(this)};q.M=function(){return Z.M(this)};q.ga=function(){this.c=rb(this);return this};q.L=function(){return Z.L(this)};q.K=function(){Z.K(this);return this};
+function Fb(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.c=Z.c;this.P=Z.P}Fb.prototype=Object.create(A.prototype);q=Fb.prototype;q.constructor=Fb;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){return Z.S(this)};q.M=function(){return Z.M(this)};q.ha=function(){this.c=sb(this);this.c.classList.add("slackmsg-me_message");return this};q.L=function(){return Z.L(this)};q.K=function(){Z.K(this);return this};
+q.update=function(a,b){A.prototype.update.call(this,a,b);this.F()};function Hb(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.c=Z.c;this.P=Z.P}Hb.prototype=Object.create(y.prototype);q=Hb.prototype;q.constructor=Hb;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){return Z.S(this)};q.M=function(){return Z.M(this)};q.ha=function(){this.c=sb(this);return this};q.L=function(){return Z.L(this)};q.K=function(){Z.K(this);return this};
 q.update=function(a,b){y.prototype.update.call(this,a,b);this.F();if(a=this.text.match(/^<?https:\/\/www\.openstreetmap\.org\/\?mlat=(-?[0-9\.]+)(&amp;|&)mlon=(-?[0-9\.]+)(&amp;|&)macc=([0-9\.]+)[^\s]*/))this.text=this.text.substr(0,a.index)+this.text.substr(a.index+a[0].length).trim(),this.o.unshift({color:"#008000",text:a[0],footer:"Open Street Map",footer_icon:"https://www.openstreetmap.org/assets/favicon-32x32-36d06d8a01933075bc7093c9631cffd02d49b03b659f767340f256bb6839d990.png",geo:{latitude:a[1],
-longitude:a[3],accuracy:a[5]}})};function Db(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.a=null;this.P=!0}Db.prototype=Object.create(C.prototype);q=Db.prototype;q.constructor=Db;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){this.a&&this.a.parentElement&&(this.a.remove(),delete this.a);this.c&&delete this.c;return this};q.M=function(){Z.M(this);return this.a};q.L=function(){return this.a.cloneNode(!0)};
-q.ga=function(){this.c=rb(this);this.a=document.createElement("span");this.c.classList.add("slackmsg-notice");this.a.className="slackmsg-notice";this.a.textContent=L.Ja;this.a.appendChild(this.c);return this};q.K=function(){Z.K(this);return this};q.update=function(a,b){C.prototype.update.call(this,a,b);this.F()};var mb=function(){var a=[];return{Fa:function(b){for(var c=0,d=a.length;c<d;c++)if(-1!==a[c].names.indexOf(b))return a[c];return null},Ra:function(b){var c=[];a.forEach(function(a){for(var d=0,g=a.names.length;d<g;d++)if(a.names[d].substr(0,b.length)===b){c.push(a);break}});return c},Wa:function(b){b.R="client";b.exec=b.exec.bind(b);a.push(b)}}}();function Fb(){return new Promise(function(a,b){"geolocation"in window.navigator?navigator.geolocation.getCurrentPosition(function(c){c?a(c):b("denied")}):b("geolocation not available")})}
-ta.push(function(){mb.Wa({name:"/sherlock",names:["/sherlock","/sharelock"],usage:"",description:L.Ma,exec:function(a,b){Fb().then(function(a){var c=a.coords.latitude,e=a.coords.longitude;Bb(b,"https://www.openstreetmap.org/?mlat="+c+"&mlon="+e+"&macc="+a.coords.accuracy+"#map=17/"+c+"/"+e)}).catch(function(a){console.error("Error: ",a)})}})});
+longitude:a[3],accuracy:a[5]}})};function Gb(a,b,c){y.call(this,b,c);this.context=H(E.context,a);this.b=a;this.a=null;this.P=!0}Gb.prototype=Object.create(C.prototype);q=Gb.prototype;q.constructor=Gb;q.F=function(){return Z.F(this)};q.u=function(a){return Z.u(this,a)};q.S=function(){this.a&&this.a.parentElement&&(this.a.remove(),delete this.a);this.c&&delete this.c;return this};q.M=function(){Z.M(this);return this.a};q.L=function(){return this.a.cloneNode(!0)};
+q.ha=function(){this.c=sb(this);this.a=document.createElement("span");this.c.classList.add("slackmsg-notice");this.a.className="slackmsg-notice";this.a.textContent=L.Ka;this.a.appendChild(this.c);return this};q.K=function(){Z.K(this);return this};q.update=function(a,b){C.prototype.update.call(this,a,b);this.F()};var zb;function Ab(a){this.Y=[];for(var b=0,c=a.length;b<c;b++)null===a[b].service&&null===a[b].device&&Ib(this,JSON.parse(a[b].config))}function Ib(a,b){b.services&&b.services.forEach(function(a){-1===this.Y.indexOf(a)&&this.Y.push(a)},a)};var mb=function(){var a=[];return{Ga:function(b){for(var c=0,d=a.length;c<d;c++)if(-1!==a[c].names.indexOf(b))return a[c];return null},Sa:function(b){var c=[];a.forEach(function(a){for(var d=0,g=a.names.length;d<g;d++)if(a.names[d].substr(0,b.length)===b){c.push(a);break}});return c},Ya:function(b){b.R="client";b.exec=b.exec.bind(b);a.push(b)}}}();function Jb(){return new Promise(function(a,b){"geolocation"in window.navigator?navigator.geolocation.getCurrentPosition(function(c){c?a(c):b("denied")}):b("geolocation not available")})}
+ta.push(function(){mb.Ya({name:"/sherlock",names:["/sherlock","/sharelock"],usage:"",description:L.Na,exec:function(a,b){Jb().then(function(a){var c=a.coords.latitude,e=a.coords.longitude;Eb(b,"https://www.openstreetmap.org/?mlat="+c+"&mlon="+e+"&macc="+a.coords.accuracy+"#map=17/"+c+"/"+e)}).catch(function(a){console.error("Error: ",a)})}})});
 })();

+ 11 - 0
srv/public/style.css

@@ -187,3 +187,14 @@ body { display: flex; margin: 0; padding: 0; font-family: Lato, sans-serif; heig
 .OSM-wrapper .OSM-controls-zoomMin { }
 .OSM-wrapper .OSM-controls-zoomPlus { }
 
+.full-width { display: block; width: 100%; }
+
+.maci-wrapper { position: fixed; z-index: 5000; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: auto; }
+.maci-wrapper.fixed-width { width: 500px; }
+.maci-content { margin: 15px; }
+.maci-content footer { text-align: right; }
+
+@media screen and (max-width: 500px) {
+    .maci-wrapper { position: fixed; z-index: 5000; top: 50%; left: 0; width: 100%; transform: translate(0, -50%); }
+}
+

+ 19 - 0
srv/src/controller/accountController.js

@@ -0,0 +1,19 @@
+/* jshint esversion: 6 */
+
+const config = require('../../config.js'),
+        accountManager = require('../models/accounts.js').accountManager;
+
+module.exports.AccountController = {
+    onRequest: function(req, res, srv) {
+        if (req.urlObj.match(["account", "cguAccept"]) && req.method === "PUT") {
+            req.account.dirty = true;
+            req.account.cguReadAndAccepted = true;
+            accountManager.save(req.account, () => {});
+            res.writeHeader("204", "No Content");
+            res.end();
+        } else {
+            srv.execTemplate(require('../template/_404.js'), req, res);
+        }
+    }
+};
+

+ 275 - 0
srv/src/controller/apiController.js

@@ -0,0 +1,275 @@
+/* jshint esversion: 6 */
+
+const config = require("../../config.js"),
+    sessionManager = require("../session.js").SessionManager,
+    accountConfigManager = require("../models/accountConfig.js").accountConfigManager;
+
+function recursiveGet(url, cb, redirectLoop) {
+    var getFnc = http.get;
+
+    if (url.substr(0, 8) === "https://")
+        getFnc = https.get;
+    getFnc(url, (d) => {
+        if (d.statusCode >= 300 && d.statusCode < 400 && d.headers["location"]) {
+            if (!redirectLoop)
+                redirectLoop = [];
+
+            if (redirectLoop.indexOf(d.headers["location"]) === -1 && redirectLoop.length < 5) {
+                redirectLoop.push(d.headers["location"]);
+                recursiveGet(d.headers["location"], cb, redirectLoop);
+                return;
+            }
+        }
+        cb(d);
+    });
+};
+
+module.exports.ApiController = {
+    onRequest: function(req, res, srv) {
+        if (req.urlObj.match(["api", "hist"])) {
+            if (!req.urlObj.queryTokens.room) {
+                res.writeHeader("400", "Bad request");
+            } else {
+                let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+                if (ctx) {
+                    ctx.fetchHistory(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]);
+                    res.writeHeader("204", "No Content");
+                } else {
+                    res.writeHeader("404", "Channel not found");
+                }
+            }
+            sessionManager.saveSession(req.session);
+            res.end();
+        } else if (req.urlObj.match(["api", "typing"])) {
+            if (!req.urlObj.queryTokens.room) {
+                res.writeHeader("400", "Bad request");
+                res.end();
+            } else {
+                let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+
+                if (!ctx) {
+                    res.writeHeader("404", "Chan not found");
+                } else {
+                    ctx.sendTyping(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]);
+                    res.writeHeader("204", "No Content");
+                }
+                res.end();
+            }
+        } else if (req.urlObj.match(["api", "cmd"])) {
+            if (!req.urlObj.queryTokens.room ||
+                !req.urlObj.queryTokens.cmd) {
+                res.writeHeader("400", "Bad request");
+                res.end();
+            } else {
+                let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]),
+                    cmd = ctx.getChatContext().commands.data['/' +req.urlObj.queryTokens.cmd[0]];
+
+                if (!ctx) {
+                    res.writeHeader("404", "Chan not found");
+                } else if (!cmd) {
+                    res.writeHeader("404", "No such command");
+                } else {
+                    let args = req.urlObj.queryTokens.args ? req.urlObj.queryTokens.args[0] : "";
+                    if (args === true)
+                        args = "";
+                    ctx.sendCommand(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], cmd, args);
+                    res.writeHeader("204", "No Content");
+                }
+                res.end();
+            }
+        } else if (req.urlObj.match(["api", "markread"])) {
+            if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.id || !req.urlObj.queryTokens.ts) {
+                res.writeHeader("400", "Bad request");
+                res.end();
+            } else {
+                let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]),
+                    id = req.urlObj.queryTokens.id[0],
+                    ts = req.urlObj.queryTokens.ts[0];
+
+                if (!ctx) {
+                    res.writeHeader("404", "Chan Not Found");
+                } else {
+                    ctx.markRead(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], id, ts);
+                    res.writeHeader("204", "No Content");
+                }
+                res.end();
+            }
+            sessionManager.saveSession(req.session);
+        } else if (req.urlObj.match(["api", "avatar"])) {
+            if (!req.urlObj.queryTokens.user) {
+                res.writeHeader("400", "Bad request");
+                res.end();
+            } else {
+                let user = res.chatContext.getUser(req.urlObj.queryTokens.user[0]);
+                if (!user) {
+                    res.writeHeader("404", "User Not Found");
+                    res.end();
+                } else {
+                    let url = (req.urlObj.queryTokens.size && req.urlObj.queryTokens.size[0] === 'l') ? user.getLargeIcon() : user.getSmallIcon();
+                    if (!config.isDebug)
+                        res.setHeader('Cache-Control', 'private, max-age=' +60 * 60); // 1 hour cache for avatars
+                    recursiveGet(url, (d) => {
+                        d.pipe(res, { end: true });
+                    });
+                }
+            }
+            sessionManager.saveSession(req.session);
+        } else if (req.urlObj.match(["api", "msg"])) {
+            if (req.method === 'POST') {
+                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.text) {
+                    res.writeHeader("400", "Bad request");
+                } else {
+                    let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+
+                    if (ctx) {
+                        let attachments = null;
+                        if (req.urlObj.queryTokens.attachments) {
+                            try { attachments = JSON.parse(decodeURIComponent(req.urlObj.queryTokens.attachments[0])); }
+                            catch (e) {}
+                        }
+                        if (req.urlObj.queryTokens.me)
+                            ctx.sendMeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text);
+                        else
+                            ctx.sendMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text, attachments);
+                        res.writeHeader("204", "No Content");
+                    } else {
+                        res.writeHeader("404", "Channel not found");
+                    }
+                }
+            } else if (req.method === "DELETE") {
+                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts) {
+                    res.writeHeader("400", "Bad request");
+                } else {
+                    let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+                    if (ctx) {
+                        ctx.removeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0]);
+                        res.writeHeader("204", "No Content");
+                    } else {
+                        res.writeHeader("404", "Channel not found");
+                    }
+                }
+            } else if (req.method === "PUT") {
+                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts || !req.urlObj.queryTokens.text) {
+                    res.writeHeader("400", "Bad request");
+                } else {
+                    let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+                    if (ctx) {
+                        ctx.editMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0], req.urlObj.queryTokens.text);
+                        res.writeHeader("204", "No Content");
+                    } else {
+                        res.writeHeader("404", "Channel not found");
+                    }
+                }
+            } else {
+                res.writeHeader("400", "Bad request");
+            }
+            sessionManager.saveSession(req.session);
+            res.end();
+        } else if (req.urlObj.match(["api", "reaction"])) {
+            const chanId = req.urlObj.queryTokens.room ? req.urlObj.queryTokens.room[0] : undefined,
+                msgId = req.urlObj.queryTokens.msg ? req.urlObj.queryTokens.msg[0] : undefined,
+                reaction = req.urlObj.queryTokens.reaction ? req.urlObj.queryTokens.reaction[0] : undefined;
+
+            if (chanId && msgId && reaction) {
+                let ctx = res.chatContext.getChannelContext(chanId);
+                if (!ctx) {
+                    res.writeHeader("404", "Channel Not Found");
+                } else if (req.method === 'POST') {
+                    res.writeHeader("204", "No Content");
+                    ctx.addReaction(ctx.getChatContext().channels[chanId], msgId, reaction);
+                } else if (req.method === 'DELETE') {
+                    res.writeHeader("204", "No Content");
+                    ctx.removeReaction(ctx.getChatContext().channels[chanId], msgId, reaction);
+                } else {
+                    res.writeHeader("405", "Method not allowed");
+                }
+            } else {
+                res.writeHeader("400", "Missing Parameter");
+            }
+            sessionManager.saveSession(req.session);
+            res.end();
+        } else if (req.urlObj.match(["api", "file"])) {
+            sessionManager.saveSession(req.session);
+            if (req.urlObj.queryTokens.room) {
+                let ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
+                if (ctx) {
+                    let uploadRequest = ctx.openUploadFileStream(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.headers["content-type"], (errorMsg) => {
+                        if (!errorMsg)
+                            res.writeHeader("204", "No Content");
+                        else
+                            res.writeHeader("500", errorMsg);
+                        res.end();
+                    });
+                    req.on('end', () => {
+                        uploadRequest.end();
+                    });
+                    req.pipe(uploadRequest);
+                } else {
+                    res.writeHeader("404", "Channel Not Found");
+                    res.end();
+                }
+            } else {
+                res.writeHeader("400", "Bad Request");
+                res.end();
+            }
+        } else if (req.urlObj.match(["api", "attachmentAction"])) {
+            if (!req.urlObj.queryTokens.serviceId) {
+                res.writeHeader("400", "Bad Request");
+                res.end();
+            } else {
+                let serviceId = req.urlObj.queryTokens.serviceId[0],
+                    ctx = res.chatContext.getUserContext(serviceId);
+
+                if (ctx) {
+                    let body = [];
+                    req.on('data', (chunk) => { body.push(chunk); });
+                    req.once('end', ()=> {
+                        let payload;
+
+                        try {
+                            payload = JSON.parse(Buffer.concat(body).toString());
+                        } catch(e) {
+                            res.writeHeader("400", "Bad Request");
+                            res.end();
+                            return;
+                        }
+                        if (!ctx.sendAction(serviceId, payload, (result) => {
+                                if (result !== false) {
+                                    res.writeHeader(200);
+                                    res.end(JSON.stringify(result));
+                                } else {
+                                    res.writeHeader(503, "Service unavailable");
+                                    res.end();
+                                }
+                            })) {
+                            // Error re-interpreting payload
+                            res.writeHeader("400", "Bad Request");
+                            res.end();
+                        }
+                    });
+                } else {
+                    res.writeHeader("404", "Service not found");
+                    res.end();
+                }
+            }
+            sessionManager.saveSession(req.session);
+        } else if (req.urlObj.match(["api"])) {
+            res.chatContext.poll(
+                (req.urlObj.queryTokens.v ? parseInt(req.urlObj.queryTokens.v[0], 10) : 0) || 0, (newData) => {
+                if (!res.ended) {
+                    try {
+                        res.writeHeader("200", {
+                            "Content-Type": "application/json"
+                        });
+                        res.end(JSON.stringify(newData));
+                    } catch (e) {}
+                }
+                sessionManager.saveSession(req.session);
+            }, (knownVersion, cb) => {
+                accountConfigManager.fromAccountIdAndVersion(req.account.id, knownVersion, cb);
+            });
+        } else {
+            srv.execTemplate(require('../template/_404.js'), req, res);
+        }
+    }
+};

+ 7 - 5
srv/src/database.js

@@ -1,5 +1,6 @@
-const sqlite3 = require('sqlite3')
-    ,createAccountTable = require('./accounts.js').createTable;
+const sqlite3 = require('sqlite3'),
+    createAccountTable = require('./models/accounts.js').createTable,
+    createAccountConfigTable = require('./models/accountConfig.js').createTable;
 
 const DB_PATH = __dirname +"/../database.sqlite";
 
@@ -18,7 +19,8 @@ Database.prototype.initDb = function(cb) {
                     self.db = new sqlite3.Database(DB_PATH, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
                         if (!err) {
                             self.createAllTables([
-                                createAccountTable
+                                createAccountTable,
+                                createAccountConfigTable
                             ], 0, cb);
                         } else {
                             cb(err);
@@ -96,12 +98,12 @@ Database.prototype.insert = function(table, data, cb) {
     });
 };
 
-Database.prototype.update = function(table, id, data, cb) {
+Database.prototype.update = function(table, id, fields, cb) {
     var args = [];
     for (var i in fields)
         args.push(fields[i]);
     args.push(id);
-    this.db.run("UPDATE " +table +" SET `" +Object.keys(data).join("`=?,") +"`=? WHERE id=?", args, (err) => {
+    this.db.run("UPDATE " +table +" SET `" +Object.keys(fields).join("`=?,`") +"`=? WHERE id=?", args, (err) => {
         if (err) {
             console.error(err);
         }

+ 9 - 270
srv/src/httpServ.js

@@ -5,8 +5,10 @@ const http = require("http")
     ,Url = require("./url.js").Url
     ,config = require("../config.js")
     ,sessionManager = require("./session.js").SessionManager
+    ,AccountController = require("./controller/accountController.js").AccountController
+    ,ApiController = require("./controller/apiController.js").ApiController
     ,MultiChatManager = require("./multichatManager.js").MultiChatManager
-    ,AccountManager = require("./accounts.js").accountManager
+    ,AccountManager = require("./models/accounts.js").accountManager
     ,Slack = require("./slack.js").Slack
     ,slackManager = require("./slackManager.js").SlackManager
     ,FaviconWriter = require("./faviconWriter.js").FaviconWriter;
@@ -65,7 +67,7 @@ Server.prototype.onListen = function() {
 };
 
 Server.prototype.execTemplate = function(template, req, res) {
-    var resp = template.exec(req, res);
+    var resp = template.exec(req, res, this);
     if (resp && resp.body) {
         if (resp.status)
             res.writeHeader(resp.status);
@@ -74,26 +76,6 @@ Server.prototype.execTemplate = function(template, req, res) {
     // else something is running asynchronously, let template close request later..
 };
 
-function recursiveGet(url, cb, redirectLoop) {
-    var getFnc = http.get;
-
-    if (url.substr(0, 8) === "https://")
-        getFnc = https.get;
-    getFnc(url, (d) => {
-        if (d.statusCode >= 300 && d.statusCode < 400 && d.headers["location"]) {
-            if (!redirectLoop)
-                redirectLoop = [];
-
-            if (redirectLoop.indexOf(d.headers["location"]) === -1 && redirectLoop.length < 5) {
-                redirectLoop.push(d.headers["location"]);
-                recursiveGet(d.headers["location"], cb, redirectLoop);
-                return;
-            }
-        }
-        cb(d);
-    });
-};
-
 Server.prototype.execRequest = function(req, res) {
     if (req.urlObj.isTemplate() && req.urlObj.template.needLogin === false) {
         return this.execTemplate(req.urlObj.template, req, res);
@@ -128,255 +110,12 @@ Server.prototype.execRequest = function(req, res) {
         res.chatContext = new MultiChatManager();
         res.chatContext.push(slackManager.lazyGet(req.session, req.reqT));
 
-        if (req.urlObj.match(["api", "hist"])) {
-            if (!req.urlObj.queryTokens.room) {
-                res.writeHeader("400", "Bad request");
-            } else {
-                var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-                if (ctx) {
-                    ctx.fetchHistory(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]);
-                    res.writeHeader("204", "No Content");
-                } else {
-                    res.writeHeader("404", "Channel not found");
-                }
-            }
-            sessionManager.saveSession(req.session);
-            res.end();
-        } else if (req.urlObj.match(["api", "typing"])) {
-            if (!req.urlObj.queryTokens.room) {
-                res.writeHeader("400", "Bad request");
-                res.end();
-            } else {
-                var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-
-                if (!ctx) {
-                    res.writeHeader("404", "Chan not found");
-                } else {
-                    ctx.sendTyping(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]]);
-                    res.writeHeader("204", "No Content");
-                }
-                res.end();
-            }
-        } else if (req.urlObj.match(["api", "cmd"])) {
-            if (!req.urlObj.queryTokens.room ||
-                !req.urlObj.queryTokens.cmd) {
-                res.writeHeader("400", "Bad request");
-                res.end();
-            } else {
-                var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0])
-                    ,cmd = ctx.getChatContext().commands.data['/' +req.urlObj.queryTokens.cmd[0]];
-
-                if (!ctx) {
-                    res.writeHeader("404", "Chan not found");
-                } else if (!cmd) {
-                    res.writeHeader("404", "No such command");
-                } else {
-                    var args = req.urlObj.queryTokens.args ? req.urlObj.queryTokens.args[0] : "";
-                    if (args === true)
-                        args = "";
-                    ctx.sendCommand(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], cmd, args);
-                    res.writeHeader("204", "No Content");
-                }
-                res.end();
-            }
-        } else if (req.urlObj.match(["api", "markread"])) {
-            if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.id || !req.urlObj.queryTokens.ts) {
-                res.writeHeader("400", "Bad request");
-                res.end();
-            } else {
-                var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0])
-                    ,id = req.urlObj.queryTokens.id[0]
-                    ,ts = req.urlObj.queryTokens.ts[0];
-
-                if (!ctx) {
-                    res.writeHeader("404", "Chan Not Found");
-                } else {
-                    ctx.markRead(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], id, ts);
-                    res.writeHeader("204", "No Content");
-                }
-                res.end();
-            }
-            sessionManager.saveSession(req.session);
-        } else if (req.urlObj.match(["api", "avatar"])) {
-            if (!req.urlObj.queryTokens.user) {
-                res.writeHeader("400", "Bad request");
-                res.end();
-            } else {
-                var user = res.chatContext.getUser(req.urlObj.queryTokens.user[0]);
-                if (!user) {
-                    res.writeHeader("404", "User Not Found");
-                    res.end();
-                } else {
-                    var url = req.urlObj.queryTokens.size && req.urlObj.queryTokens.size[0] === 'l'
-                            ? user.getLargeIcon()
-                            : user.getSmallIcon();
-                    if (!config.isDebug)
-                        res.setHeader('Cache-Control', 'private, max-age=' +60 * 60); // 1 hour cache for avatars
-                    recursiveGet(url, (d) => {
-                        d.pipe(res, { end: true });
-                    });
-                }
-            }
-            sessionManager.saveSession(req.session);
-        } else if (req.urlObj.match(["api", "msg"])) {
-            if (req.method === 'POST') {
-                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.text) {
-                    res.writeHeader("400", "Bad request");
-                } else {
-                    var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-
-                    if (ctx) {
-                        var attachments = null;
-                        if (req.urlObj.queryTokens.attachments) {
-                            try { attachments = JSON.parse(decodeURIComponent(req.urlObj.queryTokens.attachments[0])); }
-                            catch (e) {}
-                        }
-                        if (req.urlObj.queryTokens.me)
-                            ctx.sendMeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text);
-                        else
-                            ctx.sendMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.text, attachments);
-                        res.writeHeader("204", "No Content");
-                    } else {
-                        res.writeHeader("404", "Channel not found");
-                    }
-                }
-            } else if (req.method === "DELETE") {
-                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts) {
-                    res.writeHeader("400", "Bad request");
-                } else {
-                    var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-                    if (ctx) {
-                        ctx.removeMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0]);
-                        res.writeHeader("204", "No Content");
-                    } else {
-                        res.writeHeader("404", "Channel not found");
-                    }
-                }
-            } else if (req.method === "PUT") {
-                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.ts || !req.urlObj.queryTokens.text) {
-                    res.writeHeader("400", "Bad request");
-                } else {
-                    var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-                    if (ctx) {
-                        ctx.editMsg(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.urlObj.queryTokens.ts[0], req.urlObj.queryTokens.text);
-                        res.writeHeader("204", "No Content");
-                    } else {
-                        res.writeHeader("404", "Channel not found");
-                    }
-                }
-            } else {
-                res.writeHeader("400", "Bad request");
-            }
-            sessionManager.saveSession(req.session);
-            res.end();
-        } else if (req.urlObj.match(["api", "reaction"])) {
-            var chanId = req.urlObj.queryTokens["room"] ? req.urlObj.queryTokens["room"][0] : undefined
-                ,msgId = req.urlObj.queryTokens["msg"] ? req.urlObj.queryTokens["msg"][0] : undefined
-                ,reaction = req.urlObj.queryTokens["reaction"] ? req.urlObj.queryTokens["reaction"][0] : undefined;
-
-            if (chanId && msgId && reaction) {
-                var ctx = res.chatContext.getChannelContext(chanId);
-                if (!ctx) {
-                    res.writeHeader("404", "Channel Not Found");
-                } else if (req.method === 'POST') {
-                    res.writeHeader("204", "No Content");
-                    ctx.addReaction(ctx.getChatContext().channels[chanId], msgId, reaction);
-                } else if (req.method === 'DELETE') {
-                    res.writeHeader("204", "No Content");
-                    ctx.removeReaction(ctx.getChatContext().channels[chanId], msgId, reaction);
-                } else {
-                    res.writeHeader("405", "Method not allowed");
-                }
-            } else {
-                res.writeHeader("400", "Missing Parameter");
-            }
-            sessionManager.saveSession(req.session);
-            res.end();
-        } else if (req.urlObj.match(["api", "file"])) {
-            sessionManager.saveSession(req.session);
-            if (req.urlObj.queryTokens.room) {
-                var ctx = res.chatContext.getChannelContext(req.urlObj.queryTokens.room[0]);
-                if (ctx) {
-                    var uploadRequest = ctx.openUploadFileStream(ctx.getChatContext().channels[req.urlObj.queryTokens.room[0]], req.headers["content-type"], (errorMsg) => {
-                        if (!errorMsg)
-                            res.writeHeader("204", "No Content");
-                        else
-                            res.writeHeader("500", errorMsg);
-                        res.end();
-                    });
-                    req.on('end', () => {
-                        uploadRequest.end();
-                    });
-                    req.pipe(uploadRequest);
-                } else {
-                    res.writeHeader("404", "Channel Not Found");
-                    res.end();
-                }
-            } else {
-                res.writeHeader("400", "Bad Request");
-                res.end();
-            }
-        } else if (req.urlObj.match(["api", "attachmentAction"])) {
-            if (!req.urlObj.queryTokens.serviceId) {
-                res.writeHeader("400", "Bad Request");
-                res.end();
-            } else {
-                var serviceId = req.urlObj.queryTokens.serviceId[0]
-                    ,ctx = res.chatContext.getUserContext(serviceId);
-
-                if (ctx) {
-                    var body = [];
-                    req.on('data', (chunk) => { body.push(chunk); });
-                    req.once('end', ()=> {
-                        var payload;
-
-                        try {
-                            payload = JSON.parse(Buffer.concat(body).toString());
-                        } catch(e) {
-                            res.writeHeader("400", "Bad Request");
-                            res.end();
-                            return;
-                        }
-                        if (!ctx.sendAction(serviceId, payload, (result) => {
-                                if (result !== false) {
-                                    res.writeHeader(200);
-                                    res.end(JSON.stringify(result));
-                                } else {
-                                    res.writeHeader(503, "Service unavailable");
-                                    res.end();
-                                }
-                            })) {
-                            // Error re-interpreting payload
-                            res.writeHeader("400", "Bad Request");
-                            res.end();
-                        }
-                    });
-                } else {
-                    res.writeHeader("404", "Service not found");
-                    res.end();
-                }
-            }
-            sessionManager.saveSession(req.session);
-        } else if (req.urlObj.match(["api"])) {
-            res.chatContext.poll(
-                (req.urlObj.queryTokens.v ? parseInt(req.urlObj.queryTokens.v[0], 10) : 0) || 0
-                , (newData) => {
-                if (!res.ended) {
-                    try {
-                        res.writeHeader("200", {
-                            "Content-Type": "application/json"
-                        });
-                        res.end(JSON.stringify(newData));
-                    } catch (e) {}
-                }
-                sessionManager.saveSession(req.session);
-            });
+        if (req.urlObj.urlParts[0] === "account") {
+            AccountController.onRequest(req, res, this);
+        } else if (req.urlObj.urlParts[0] === "api") {
+            ApiController.onRequest(req, res, this);
         } else {
-            console.log(JSON.stringify(req.session));
-            console.log(JSON.stringify(req.urlObj));
-            console.log(JSON.stringify(req.cookies));
-            res.writeHeader("404", "Not Found");
-            res.end();
+            this.execTemplate(require('./template/_404.js'), req, res);
         }
     }
 };

+ 162 - 0
srv/src/models/accountConfig.js

@@ -0,0 +1,162 @@
+const db = require('../database.js');
+
+const TABLE_NAME = "accountConfig";
+
+function AccountConfigManager() {
+}
+
+AccountConfigManager.prototype.fromAccountId = function(id, cb) {
+    var self = this;
+    db.database.query(TABLE_NAME, { accountId: id }, (err, result) => {
+        if (!result)
+            cb(null);
+        let accConfigs = [];
+        result.forEach((res) => {
+            accConfigs.push(self.fromDb(res));
+        });
+        cb(accConfigs);
+    });
+};
+
+AccountConfigManager.prototype.fromAccountIdAndVersion = function(id, v, cb) {
+    var self = this;
+    db.database.query(TABLE_NAME, { accountId: id }, (err, result) => {
+        if (!result)
+            cb(null);
+        for (var i =0, nbConfig = result.length; i < nbConfig; i++) {
+            if (result[i].modified > v) {
+                var configs = [];
+                for (i =0; i < nbConfig; i++) {
+                    configs.push(self.fromDb(result[i]));
+                }
+                cb(configs);
+                return;
+            }
+        }
+        cb(null);
+    });
+};
+
+AccountConfigManager.prototype.newConfigFor = function(account) {
+    var conf = new AccountConfigWrapper();
+    conf.dirty = true;
+    conf.accountId = account.id;
+    return conf;
+};
+
+AccountConfigManager.prototype.fromId = function(id, cb) {
+    var self = this;
+    db.database.queryFirst(TABLE_NAME, { id: id }, (err, result) => {
+        cb(self.fromDb(result));
+    });
+};
+
+AccountConfigManager.prototype.fromDb = function(dbResult) {
+    if (!dbResult)
+        return null;
+    return new AccountConfigWrapper(dbResult);
+};
+
+function AccountConfigWrapper(dbResult) {
+    this.id;
+
+    this.accountId;
+    this.serviceId; // Can be null, or a serviceId
+    this.deviceId; // Can be null, or a deviceId
+
+    this.config = new AccountConfig();
+    this.modified; // modified ts
+
+    if (dbResult) {
+        this.id = dbResult.id;
+
+        this.accountId = dbResult.accountId;
+        this.serviceId = dbResult.serviceId;
+        this.deviceId = dbResult.deviceId;
+
+        this.config.fromDb(dbResult.config);
+        this.modified = dbResult.modified;
+        this.dirty = false;
+    } else {
+        this.id = null;
+
+        this.accountId = null;
+        this.serviceId = null;
+        this.deviceId = null;
+
+        this.modified = Date.now();
+
+        this.dirty = true;
+    }
+}
+
+AccountConfigWrapper.prototype.toDb = function() {
+    return {
+        accountId: this.accountId,
+        serviceId: this.serviceId,
+        deviceId: this.deviceId,
+        config: this.config.toDb(),
+        modified: this.modified
+    };
+};
+
+AccountConfigManager.prototype.save = function(account, cb) {
+    if (!account.id) {
+        // insert and save id
+        var data = account.toDb();
+        data.id = null;
+        db.database.insert(TABLE_NAME, data, (newId) => {
+            account.id = newId;
+            account.dirty = false;
+            cb(account);
+        });
+    } else if (account.dirty) {
+        // update
+        db.database.update(TABLE_NAME, account.id, account.toDb(), () => {
+            account.dirty = false;
+            cb(account);
+        });
+    } else {
+        // not changed
+        cb(account);
+    }
+};
+
+AccountConfigWrapper.prototype.expose = function() {
+    return {
+        service: this.serviceId,
+        device: this.deviceId,
+        config: this.config.toDb()
+    };
+};
+
+function AccountConfig() {
+    this.services = [];
+}
+
+AccountConfig.prototype.toDb = function() {
+    return JSON.stringify({
+        services: this.services
+    });
+};
+
+AccountConfig.prototype.fromDb = function(dbObj) {
+    dbObj = JSON.parse(dbObj);
+    this.services = dbObj.services || [];
+};
+
+module.exports.accountConfigManager = new AccountConfigManager();
+
+module.exports.createTable = function(dbObject, cb) {
+    console.info("Creating table " +TABLE_NAME);
+    dbObject.run('CREATE TABLE IF NOT EXISTS `' +TABLE_NAME +'` ('
+        +"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+        +"`accountId` STRING NOT NULL,"
+        +"`serviceId` STRING,"
+        +"`deviceId` STRING,"
+        +"`config` STRING NOT NULL,"
+        +"`modified` INT NOT NULL"
+        +');CREATE INDEX accconfigbyaccount ON ' +TABLE_NAME +'(accountId); CREATE UNIQUE INDEX accconfigUniq ON ' +TABLE_NAME +'(accountId, serviceId, deviceId)', cb);
+    //TODO permanent login token array
+};
+

+ 2 - 2
srv/src/accounts.js → srv/src/models/accounts.js

@@ -1,4 +1,4 @@
-const db = require('./database.js');
+const db = require('../database.js');
 
 const TABLE_NAME = "accounts";
 
@@ -106,7 +106,7 @@ AccountManager.prototype.save = function(account, cb) {
         });
     } else if (account.dirty) {
         // update
-        db.database.update(TABLE_NAME, this.id, account.toDb(), () => {
+        db.database.update(TABLE_NAME, account.id, account.toDb(), () => {
             account.dirty = false;
             cb(account);
         });

+ 27 - 2
srv/src/multichatManager.js

@@ -229,17 +229,23 @@ MultiChatManager.prototype.isMe = function(userId) {
     return false;
 };
 
-MultiChatManager.prototype.poll = function(knownVersion, callback) {
+/**
+ * @param {number} knownVersion
+ * @param {Function} callback
+ * @param {(function(number, function((Array<{expose:Function, modified: number}>|null))))=} checkConfigUpdate
+**/
+MultiChatManager.prototype.poll = function(knownVersion, callback, checkConfigUpdate) {
     this.contexts.forEach(function(ctx) {
         ctx.onRequest();
     });
+    var configFeed = { config: null };
     setTimeout((function() {
         //FIXME polling
         var liveFeed
             ,staticFeed
             ,allTyping
             ,v = 0
-            ,updated = false
+            ,updated = !!configFeed
             ,now = Date.now();
 
         this.contexts.forEach(function(ctx) {
@@ -270,10 +276,15 @@ MultiChatManager.prototype.poll = function(knownVersion, callback) {
                 }
             }
         });
+        if (configFeed.config) {
+            v = Math.max(v, configFeed.v);
+            updated = true;
+        }
         if (updated)
             callback({
                 "live": liveFeed,
                 "static": staticFeed,
+                "config": configFeed.config || undefined,
                 "typing": allTyping,
                 "v": Math.max(v, knownVersion)
             });
@@ -282,6 +293,20 @@ MultiChatManager.prototype.poll = function(knownVersion, callback) {
                 "v": knownVersion
             });
     }).bind(this), 2000);
+    if (checkConfigUpdate)
+        checkConfigUpdate(knownVersion, function(config) {
+            if (config) {
+                var exposedConfig = [],
+                    v = 0;
+
+                config.forEach(function(conf) {
+                    exposedConfig.push(conf.expose());
+                    v = Math.max(v, conf.modified);
+                });
+                configFeed.config = exposedConfig;
+                configFeed.v = v;
+            }
+        });
 };
 
 /** @suppress {undefinedVars,checkTypes} */

+ 6 - 5
srv/src/slack.js

@@ -85,14 +85,15 @@ Slack.prototype.connect = function(cb) {
 
     this.connected = undefined;
     httpsRequest(SLACK_ENDPOINT +GETAPI.rtmStart +"?token=" +this.token, (status, body) => {
-        if (status !== 200) {
-            _this.error = body.error;
+        if (!body || !body.ok) {
+            _this.error = "Slack API error";
             _this.connected = false;
-            console.error("Slack api responded " +status);
+            console.error("Slack api responded " +status +" with body " +JSON.stringify(body));
             cb && cb(_this);
-        } else if (!body || !body.ok) {
-            _this.error = "Slack API error";
+        } else if (status !== 200) {
+            _this.error = body.error;
             _this.connected = false;
+            console.error("Slack api responded " +status);
             cb && cb(_this);
         } else {
             _this.data.updateStatic({

+ 16 - 0
srv/src/template/_403.js

@@ -0,0 +1,16 @@
+
+const templates = require('./_templates.js');
+
+module.exports.exec = function(req) {
+    return {
+        status: 403,
+        body: templates.header("Mimou - Forbidden", ["style.css"])
+            + `<section class="maci-wrapper fixed-width"><div class="maci-content">
+            <header id="err403Header"></header>
+            <div id="err403Text"></div>
+            <footer id="err403Footer"></footer>
+            </div></section>`
+            +templates.footer(["err.min.js"])
+    };
+};
+

+ 16 - 0
srv/src/template/_404.js

@@ -0,0 +1,16 @@
+
+const templates = require('./_templates.js');
+
+module.exports.exec = function(req) {
+    return {
+        status: 404,
+        body: templates.header("Mimou - Lost", ["style.css"])
+            + `<section class="maci-wrapper fixed-width"><div class="maci-content">
+            <header id="err404Header"></header>
+            <div id="err404Text"></div>
+            <footer id="err404Footer"></footer>
+            </div></section>`
+            +templates.footer(["err.min.js"])
+    };
+};
+

+ 0 - 0
srv/template/_model.js → srv/src/template/_model.js


+ 4 - 1
srv/template/_templates.js → srv/src/template/_templates.js

@@ -12,11 +12,14 @@ module.exports = {
         });
         return res +`</head><body>`;
     }
-    ,footer: function(jsFiles) {
+    ,footer: function(jsFiles, rawJs) {
         var res = "";
         (jsFiles || []).forEach(function(i) {
             res += '<script src="' +config.rootUrl +i +'"></script>';
         });
+        if (rawJs) {
+            res += '<script>' +rawJs +'</script>';
+        }
         return res +`</body></html>`;
     }
 };

+ 72 - 0
srv/src/template/index.js

@@ -0,0 +1,72 @@
+
+const templates = require('./_templates.js'),
+        config = require('../config.js');
+
+module.exports.exec = function(req, res) {
+    if (!req.account.cguReadAndAccepted) {
+        if (config.allowNewAccounts)
+            return {
+                status: 200,
+                body: templates.header("Mimou - CGU", ["style.css"])
+                    + `<section class="maci-wrapper fixed-width"><div class="maci-content">
+                    <header id="cguHeader">Merci d'accepter les Conditions Generales d'Utilisation suivantes:</header>
+                    <textarea id="cguContent" class="full-width"></textarea>
+                    <footer><button id="cguButton">J'accepte les Conditions Generales d'Utilisation</button></footer>
+                    </div></section>`
+                    +templates.footer(["cgu.min.js"])
+            };
+        else
+            return require('./_403.js').exec(req, res);
+    }
+    return {
+        status: 200
+        ,body: templates.header("Mimou", ["style.css", "emojione_v2.3.sprites.css"], ['<link href="favicon_err.png" type="image/png" rel="icon" id="linkFavicon" />'])
+        +`<aside class="slack-context" id="slackCtx">
+              <nav class="slack-context-menu"></nav>
+              <div class="slack-context-rooms" id="chatList">
+                  <ul class="slack-context-channellist" id="chanList"></ul>
+                  <div class="slack-context-imlist"></div>
+              </div>
+              <div class="slack-context-roominfo hidden"></div>
+          </aside>
+          <section id="settings" class="maci-setting hidden">
+              <header>
+                  <div id="settingTitle"></div>
+                  <div id="settingDiscardClose"></div>
+              </header>
+              <div>
+                  <aside class="settingNav">
+                  </aside>
+                  <div class="settingContent">
+                  </div>
+              </div>
+              <footer class="settingFooter">
+                  <button id="settingCommit"></button>
+              </footer>
+          </section>
+          <div class="slack-chat-container">
+              <div class="slack-chat-title" id="currentRoomTitle"></div>
+              <div class="slack-chat-content" id="chatWindow"></div>
+              <ul class="slack-chat-whoistyping" id="whoistyping"></ul>
+              <div class="slack-chat-control">
+                  <div id="replyToContainer" class="replyto-container"></div>
+                  <ul id="slashList" class="slack-command-list"></ul>
+                  <form id="msgForm" class="msgform">
+                      <input type="text" id="msgInput" class="msgform-input" autocomplete="off" />
+                      <a id="emojiButton" class="button"/></a>
+                      <a id="attachFile" href="#!" class="button"><img src="paperclip.svg" alt="Send file" class="attach-file-icon" /></a>
+                      <input type="submit" class="button" value=">" />
+                  </form>
+              </div>
+          </div>
+          <div class="hidden file-upload-container" id="fileUploadContainer"><form id="fileUploadForm" enctype="multipart/form-data">
+              <input type="file" id="fileUploadInput" />
+              <div id="fileUploadError" class="file-upload-error hidden"></div>
+              <a id="fileUploadCancel" class="button"/></a>
+              <input type="submit" class="button"/>
+          </form></div>
+          <div class="error" id="neterror"></div>`
+        +templates.footer(["emojione_v2.3.sprites.js", "openpgp.min.js", "slack.min.js"])
+    };
+};
+

+ 36 - 18
srv/template/login.js → srv/src/template/login.js

@@ -4,7 +4,8 @@ const config = require("../config.js")
     ,GoogleOAuth = require("../src/googleOAuth.js").GoogleOAuth
     ,FacebookOAuth = require("../src/facebookOAuth.js").FacebookOAuth
     ,slackManager = require("../src/slackManager.js").SlackManager
-    ,accountManager = require("../src/accounts.js").accountManager
+    ,accountManager = require("../src/models/accounts.js").accountManager
+    ,accountConfigManager = require("../src/models/accountConfig.js").accountConfigManager
     ,sessionManager = require("../src/session.js").SessionManager
     ,templates = require('./_templates.js');
 
@@ -17,10 +18,15 @@ function checkTokens(service, req, cb) {
                         accountManager.fromSlackIdAuth(id, (account) => {
                             if (account) {
                                 cb(account);
-                            } else {
+                            } else if (config.allowNewAccounts) {
                                 var account = accountManager.createAccount();
                                 account.authSlackUserEmailAndTeam = id;
-                                accountManager.save(account, () => { cb(account); });
+                                accountManager.save(account, () => {
+                                    cb(account);
+                                    accountConfigManager.save(accountConfigManager.newConfigFor(account), () => {});
+                                });
+                            } else {
+                                cb(false);
                             }
                         });
                     } else {
@@ -39,10 +45,15 @@ function checkTokens(service, req, cb) {
                         accountManager.fromGoogleIdAuth(id, (account) => {
                             if (account) {
                                 cb(account);
-                            } else {
+                            } else if (config.allowNewAccounts) {
                                 var account = accountManager.createAccount();
                                 account.authGoogleUserId = id;
-                                accountManager.save(account, () => { cb(account); });
+                                accountManager.save(account, () => {
+                                    cb(account);
+                                    accountConfigManager.save(accountConfigManager.newConfigFor(account), () => {});
+                                });
+                            } else {
+                                cb(false);
                             }
                         });
                     } else {
@@ -61,10 +72,15 @@ function checkTokens(service, req, cb) {
                         accountManager.fromFacebookIdAuth(id, (account) => {
                             if (account) {
                                 cb(account);
-                            } else {
+                            } else if (config.allowNewAccounts) {
                                 var account = accountManager.createAccount();
                                 account.authFacebookUserId = id;
-                                accountManager.save(account, () => { cb(account); });
+                                accountManager.save(account, () => {
+                                    cb(account);
+                                    accountConfigManager.save(accountConfigManager.newConfigFor(account), () => {});
+                                });
+                            } else {
+                                cb(false);
                             }
                         });
                     } else {
@@ -84,24 +100,24 @@ function checkTokens(service, req, cb) {
 
 function makeLoginPage() {
     const
-    slackUri = config.login.slack.requestLoginUri
+    slackUri = config.login.slack ? (config.login.slack.requestLoginUri
         +"?client_id=" +config.login.slack.clientId
         +"&scope=" +slackManager.getAuthScope().join(',')
-        +"&redirect_uri=" +config.login.slack.redirect_uri,
-    googleUri = config.login.google.requestLoginUri
+        +"&redirect_uri=" +config.login.slack.redirect_uri) : "",
+    googleUri = config.login.google ? (config.login.google.requestLoginUri
         +"?client_id=" +config.login.google.clientId
         +"&scope=" +(["openid", "email", "profile"]).join("%20")
         +"&redirect_uri=" +config.login.google.redirect_uri
-        +"&response_type=code"
-    facebookUri = config.login.facebook.requestLoginUri
+        +"&response_type=code") : "",
+    facebookUri = config.login.facebook ? (config.login.facebook.requestLoginUri
         +"?client_id=" +config.login.facebook.clientId
-        +"&redirect_uri=" +config.login.facebook.redirect_uri;
+        +"&redirect_uri=" +config.login.facebook.redirect_uri) : null;
 
     return templates.header("Mimou - login", ["login.css"])
         +`<div class="services"><h1>Login</h1>`
-        +`<a href="${googleUri}"><img src="https://developers.google.com/identity/images/btn_google_signin_light_normal_web.png" alt="Sign in with Google" class="attempt-right"></a>`
-        +`<a href="${facebookUri}"><img src="${config.rootUrl}btn_facebook_connect.png" alt="Log in with facebook"/></a>`
-        +`<a href="${slackUri}"><img src="https://platform.slack-edge.com/img/sign_in_with_slack.png" srcset="https://platform.slack-edge.com/img/sign_in_with_slack.png 1x, https://platform.slack-edge.com/img/sign_in_with_slack@2x.png 2x" /></a>`
+        +(googleUri ? `<a href="${googleUri}"><img src="${config.rootUrl}btn_google_connect.png"></a>` : "")
+        +(facebookUri ? `<a href="${facebookUri}"><img src="${config.rootUrl}btn_facebook_connect.png"></a>` : "")
+        +(slackUri ? `<a href="${slackUri}"><img src="${config.rootUrl}btn_slack_connect.png"></a>` : "")
         +`</div>`
         +templates.footer();
 }
@@ -115,7 +131,7 @@ module.exports.match = function(url) {
     return false;
 };
 
-module.exports.exec = function(req, res) {
+module.exports.exec = function(req, res, srv) {
     if (!req.urlObj.urlParts[1]) {
         res.end(makeLoginPage());
     } else {
@@ -130,8 +146,10 @@ module.exports.exec = function(req, res) {
                 });
                 sessionManager.saveSession(req.session);
                 res.end();
-            } else {
+            } else if (account === null) {
                 res.end(makeLoginPage());
+            } else {
+                srv.execTemplate(require("./_403.js"), req, res);
             }
         });
     }

+ 0 - 0
srv/template/register.js → srv/src/template/register.js


+ 2 - 2
srv/src/url.js

@@ -70,9 +70,9 @@ function tryLoadTemplate() {
     var template;
 
     if (this.urlParts.length === 0) {
-        template = tryLoadTemplateFile(__dirname +'/../template/index.js');
+        template = tryLoadTemplateFile(__dirname +'/template/index.js');
     } else if (this.urlParts[0][0] !== '_') {
-        template = tryLoadTemplateFile(__dirname +'/../template/' +this.urlParts[0] +'.js');
+        template = tryLoadTemplateFile(__dirname +'/template/' +this.urlParts[0] +'.js');
     }
     if (!template || (template.match && !template.match(this)) || (!template.match && this.urlParts.length && this.urlParts[0].length > 1)) {
         return false;

+ 0 - 41
srv/template/index.js

@@ -1,41 +0,0 @@
-
-const templates = require('./_templates.js');
-
-module.exports.exec = function() {
-    return {
-        status: 200
-        ,body: templates.header("Mimou", ["style.css", "emojione.sprites.css"], ['<link href="favicon_err.png" type="image/png" rel="icon" id="linkFavicon" />'])
-        +`<aside class="slack-context" id="slackCtx">
-                    <nav class="slack-context-menu"></nav>
-                    <div class="slack-context-rooms" id="chatList">
-                        <ul class="slack-context-channellist" id="chanList"></ul>
-                        <div class="slack-context-imlist"></div>
-                    </div>
-                    <div class="slack-context-roominfo hidden"></div>
-                </aside>
-                <div class="slack-chat-container">
-                    <div class="slack-chat-title" id="currentRoomTitle"></div>
-                    <div class="slack-chat-content" id="chatWindow"></div>
-                    <ul class="slack-chat-whoistyping" id="whoistyping"></ul>
-                    <div class="slack-chat-control">
-                        <div id="replyToContainer" class="replyto-container"></div>
-                        <ul id="slashList" class="slack-command-list"></ul>
-                        <form id="msgForm" class="msgform">
-                            <input type="text" id="msgInput" class="msgform-input" autocomplete="off" />
-                            <a id="emojiButton" class="button"/></a>
-                            <a id="attachFile" href="#!" class="button"><img src="paperclip.svg" alt="Send file" class="attach-file-icon" /></a>
-                            <input type="submit" class="button" value=">" />
-                        </form>
-                    </div>
-                </div>
-                <div class="hidden file-upload-container" id="fileUploadContainer"><form id="fileUploadForm" enctype="multipart/form-data">
-                    <input type="file" id="fileUploadInput" />
-                    <div id="fileUploadError" class="file-upload-error hidden"></div>
-                    <a id="fileUploadCancel" class="button"/></a>
-                    <input type="submit" class="button"/>
-                </form></div>
-                <div class="error" id="neterror"></div>`
-            +templates.footer(["emojione.sprites.js", "openpgp.min.js", "slack.min.js"])
-    };
-};
-