var
/**
* Minimum time between 2 notifications (ms)
* @const
* @type {number}
**/
NOTIFICATION_COOLDOWN = 30 * 1000 //30 sec
/**
* Maximum time the notification will stay visible (ms)
* @const
* @type {number}
**/
,NOTIFICATION_DELAY = 5 * 1000 // 5 sec
/** @type {number} */
,lastNotificationSpawn = 0
;
function onContextUpdated() {
var chanListFram = document.createDocumentFragment()
,sortedChans = SLACK.context.self ? Object.keys(SLACK.context.self.channels) : [];
sortedChans.sort(function(a, b) {
if (a[0] !== b[0]) {
return a[0] - b[0];
}
return SLACK.context.getChannel(a).name.localeCompare(SLACK.context.getChannel(b).name);
});
sortedChans.forEach(function(chanId) {
var chan =
/**
* SortedChan does not contains ims ids
* @type {SlackChan|SlackGroup}
**/
(SLACK.context.getChannel(chanId));
if (!chan.archived) {
var chanListItem = createChanListItem(chan);
if (chanListItem)
chanListFram.appendChild(chanListItem);
}
});
var sortedUsers = SLACK.context.users ? Object.keys(SLACK.context.users) : [];
sortedUsers.sort(function(a, b) {
return SLACK.context.users[a].name.localeCompare(SLACK.context.users[b].name);
});
sortedUsers.forEach(function(userId) {
var user = SLACK.context.getMember(userId);
if (!user.deleted) {
var ims = user.ims
,imsListItem = createImsListItem(ims);
if (imsListItem) {
chanListFram.appendChild(imsListItem);
}
}
});
document.getElementById(R.id.chanList).textContent = "";
document.getElementById(R.id.chanList).appendChild(chanListFram);
setRoomFromHashBang();
updateTitle();
createContextBackground(function(imgData) {
document.getElementById(R.id.context).style.backgroundImage = 'url(' +imgData +')';
});
}
function onTypingUpdated() {
var typing = SLACK.context.typing;
for (var chanId in SLACK.context.self.channels) {
if (!SLACK.context.self.channels[chanId].archived) {
var dom = document.getElementById(chanId);
if (typing[chanId])
dom.classList.add(R.klass.chatList.typing);
else
dom.classList.remove(R.klass.chatList.typing);
}
}
for (var userId in SLACK.context.users) {
var ims = SLACK.context.users[userId].ims;
if (ims && !ims.archived) {
var dom = document.getElementById(ims.id);
if (typing[ims.id])
dom.classList.add(R.klass.chatList.typing);
else
dom.classList.remove(R.klass.chatList.typing);
}
}
updateTypingChat();
}
function updateTypingChat() {
var typing = SLACK.context.typing;
if (SELECTED_ROOM && typing[SELECTED_ROOM.id]) {
var typingUserNames = [], isOutOfSync = false;
for (var i in typing[SELECTED_ROOM.id]) {
var member = SLACK.context.getMember(i);
if (member)
typingUserNames.push(member.name);
else
isOutOfSync = true;
}
if (isOutOfSync)
outOfSync();
document.getElementById(R.id.typing).textContent = locale.areTyping(typingUserNames);
} else {
document.getElementById(R.id.typing).textContent = "";
}
}
function onNetworkStateUpdated(isNetworkWorking) {
isNetworkWorking ? document.body.classList.remove(R.klass.noNetwork) : document.body.classList.add(R.klass.noNetwork);
updateTitle();
}
function onRoomSelected() {
var name = SELECTED_ROOM.name || (SELECTED_ROOM.user ? SELECTED_ROOM.user.name : undefined);
if (!name) {
var members = [];
for (var i in SELECTED_ROOM.members) {
members.push(SELECTED_ROOM.members[i].name);
}
name = members.join(", ");
}
var roomLi = document.getElementById(SELECTED_ROOM.id);
document.getElementById(R.id.currentRoom.title).textContent = name;
onRoomUpdated();
focusInput();
document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
markRoomAsRead(SELECTED_ROOM);
if (REPLYING_TO) {
REPLYING_TO = null;
onReplyingToUpdated();
}
if (EDITING) {
EDITING = null;
onReplyingToUpdated();
}
updateTypingChat();
}
function onReplyingToUpdated() {
if (REPLYING_TO) {
document.body.classList.add(R.klass.replyingTo);
var domParent = document.getElementById(R.id.message.replyTo)
,closeLink = document.createElement("a");
closeLink.addEventListener("click", function() {
REPLYING_TO = null;
onReplyingToUpdated();
});
closeLink.className = R.klass.msg.replyTo.close;
closeLink.textContent = 'x';
domParent.textContent = "";
domParent.appendChild(closeLink);
domParent.appendChild(createMessageDom("reply_" +SELECTED_ROOM.id, REPLYING_TO, true));
focusInput();
} else {
document.body.classList.remove(R.klass.replyingTo);
document.getElementById(R.id.message.replyTo).textContent = "";
focusInput();
}
}
function onEditingUpdated() {
if (EDITING) {
document.body.classList.add(R.klass.replyingTo);
var domParent = document.getElementById(R.id.message.replyTo)
,closeLink = document.createElement("a");
closeLink.addEventListener("click", function() {
EDITING = null;
onEditingUpdated();
});
closeLink.className = R.klass.msg.replyTo.close;
closeLink.textContent = 'x';
domParent.textContent = "";
domParent.appendChild(closeLink);
domParent.appendChild(createMessageDom("edit_" +SELECTED_ROOM.id, EDITING, true));
document.getElementById(R.id.message.input).value = EDITING.text;
focusInput();
} else {
document.body.classList.remove(R.klass.replyingTo);
document.getElementById(R.id.message.replyTo).textContent = "";
focusInput();
}
}
/**
* @param {string} chanId
* @param {string} msgId
* @param {string} reaction
**/
window['toggleReaction'] = function(chanId, msgId, reaction) {
var hist = SLACK.history[chanId];
if (!hist)
return;
var msg = hist.getMessageById(msgId);
if (msg) {
if (msg.hasReactionForUser(reaction, SLACK.context.self.id)) {
removeReaction(chanId, msgId, reaction);
} else {
addReaction(chanId, msgId, reaction);
}
}
};
/**
* Try to resolve emoji from customized context
* @param {string} emoji
* @return {Element|string}
**/
function tryGetCustomEmoji(emoji) {
var loop = {};
while (!loop[emoji]) {
var emojisrc= SLACK.context.emojis[emoji];
if (emojisrc) {
if (emojisrc.substr(0, 6) == "alias:") {
loop[emoji] = true;
emoji = emojisrc.substr(6);
} else {
var dom = document.createElement("span");
dom.className = R.klass.emoji.custom +' ' +R.klass.emoji.emoji;
dom.style.backgroundImage = "url('" +emojisrc +"')";
return dom;
}
}
return emoji; // Emoji not found, fallback to std emoji
}
return emoji; //loop detected, return first emoji
}
function makeEmojiDom(emojiCode) {
var emoji = tryGetCustomEmoji(emojiCode);
if (typeof emoji === "string" && "makeEmoji" in window)
emoji = window['makeEmoji'](emoji);
return typeof emoji === "string" ? null : emoji;
}
/**
* replace all :emoji: codes with corresponding image
* @param {string} inputString
* @return {string}
**/
function formatEmojis(inputString) {
return inputString.replace(/:([^ \t:]+):/g, function(returnFailed, emoji) {
var emojiDom = makeEmojiDom(emoji);
if (emojiDom) {
var domParent = document.createElement("span");
domParent.className = returnFailed === inputString ? R.klass.emoji.medium : R.klass.emoji.small;
domParent.appendChild(emojiDom);
return domParent.outerHTML;
}
return returnFailed;
});
}
/**
* @param {string} fullText
* @return {string}
**/
function formatSlackText(fullText) {
var msgContents = fullText.split(/\r?\n/g);
for (var msgContentIndex=0, nbMsgContents = msgContents.length; msgContentIndex < nbMsgContents; msgContentIndex++) {
var msgContent = msgContents[msgContentIndex].trim()
,_msgContent = ""
,currentMods = {}
,quote = false
,i =0
msgContent = msgContent.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
function(matched, type, entity) {
var sub = entity.split('|');
if (type === '@') {
if (!sub[1]) {
var user = SLACK.context.getMember(sub[0]);
sub[1] = user ? ('@' +user.name) : locale.unknownMember;
} else if ('@' !== sub[1][0]) {
sub[1] = '@' +sub[1];
}
sub[0] = '#' +sub[0];
sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
} else if (type === '#') {
if (!sub[1]) {
var chan = SLACK.context.getChannel(sub[0]);
sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
} else if ('#' !== sub[1][0]) {
sub[1] = '#' +sub[1];
}
sub[0] = '#' +sub[0];
sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
} else if (sub[0].indexOf("://") !== -1) {
if (!sub[1])
sub[1] = sub[0];
sub[2] = R.klass.msg.link;
} else {
return matched;
}
return '' +sub[1] +'';
});
msgContent = formatEmojis(msgContent);
var msgLength = msgContent.length;
var isAlphadec = function(c) {
return ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
"àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
}
,checkEnd = function(str, pos, c) {
while (str[pos]) {
if (isAlphadec(str[pos]) && str[pos] != c && str[pos +1] == c) {
return true;
}
pos++;
}
return false;
} ,appendMod = function(mods) {
if (!Object.keys(currentMods).length)
return "";
return '';
};
// Skip trailing
while (i < msgLength && (msgContent[i] === ' ' || msgContent[i] === '\t'))
i++;
if (msgContent.substr(i, 4) === '>') {
quote = true;
i += 4;
}
for (; i < msgLength; i++) {
var c = msgContent[i];
if (c === '<') {
do {
_msgContent += msgContent[i++];
} while (msgContent[i -1] !== '>');
i--;
continue;
}
if (!(currentMods[R.klass.msg.style.bold]) && c === '*' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
if (Object.keys(currentMods).length)
_msgContent += '';
currentMods[R.klass.msg.style.bold] = true;
_msgContent += appendMod(currentMods);
} else if (!(currentMods[R.klass.msg.style.strike]) && c === '~' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
if (Object.keys(currentMods).length)
_msgContent += '';
currentMods[R.klass.msg.style.strike] = true;
_msgContent += appendMod(currentMods);
} else if (!(currentMods[R.klass.msg.style.code]) && c === '`' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
if (Object.keys(currentMods).length)
_msgContent += '';
currentMods[R.klass.msg.style.code] = true;
_msgContent += appendMod(currentMods);
} else if (!(currentMods[R.klass.msg.style.italic]) && c === '_' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
if (Object.keys(currentMods).length)
_msgContent += '';
currentMods[R.klass.msg.style.italic] = true;
_msgContent += appendMod(currentMods);
} else {
var finalFound = false;
_msgContent += c;
do {
if ((currentMods[R.klass.msg.style.bold]) && c !== '*' && msgContent[i +1] === '*') {
delete currentMods[R.klass.msg.style.bold];
finalFound = true;
} else if ((currentMods[R.klass.msg.style.strike]) && c !== '~' && msgContent[i +1] === '~') {
delete currentMods[R.klass.msg.style.strike];
finalFound = true;
} else if ((currentMods[R.klass.msg.style.code]) && c !== '`' && msgContent[i +1] === '`') {
delete currentMods[R.klass.msg.style.code];
finalFound = true;
} else if ((currentMods[R.klass.msg.style.italic]) && c !== '_' && msgContent[i +1] === '_') {
delete currentMods[R.klass.msg.style.italic];
finalFound = true;
} else {
break;
}
c = msgContent[++i];
} while (i < msgLength);
if (finalFound)
_msgContent += '' +appendMod(currentMods);
}
}
if (currentMods) {
// Should not append
_msgContent += '';
}
if (quote)
msgContents[msgContentIndex] = '' +_msgContent +'';
else
msgContents[msgContentIndex] = _msgContent;
}
return msgContents.join('
');
}
/**
* @param {string} channelId
* @param {SlackMessage} msg
* @param {boolean=} skipAttachment
* @return {Element}
**/
function doCreateMeMessageDom(channelId, msg, skipAttachment) {
var dom = doCreateMessageDom(channelId, msg, skipAttachment);
dom.classList.add(R.klass.msg.meMessage);
return dom;
}
/**
* @param {string} channelId
* @param {SlackMessage} msg
* @param {boolean=} skipAttachment
* @return {Element}
**/
function createMessageDom(channelId, msg, skipAttachment) {
var dom = (msg.isMeMessage ?
doCreateMeMessageDom(channelId, msg, skipAttachment):
doCreateMessageDom(channelId, msg, skipAttachment));
if (msg.edited)
dom.classList.add(R.klass.msg.edited);
return dom;
}
/**
* @param {number} unreadhi
* @param {number} unread
**/
function setFavicon(unreadhi, unread) {
if (!unreadhi && !unread)
document.getElementById(R.id.favicon).href = "favicon_ok.png";
else
document.getElementById(R.id.favicon).href = "favicon.png?h="+unreadhi+"&m="+unread;
}
function setNetErrorFavicon() {
document.getElementById(R.id.favicon).href = "favicon_err.png";
}
function updateTitle() {
var hasUnread = 0
,hasHl = 0
,title = "";
if (NEXT_RETRY) {
title = '!' +locale.netErrorShort +' - ';
setNetErrorFavicon();
} else {
for (var i in UNREAD_CHANS) {
if (UNREAD_CHANS.hasOwnProperty(i)) {
hasUnread += UNREAD_CHANS[i].unread;
hasHl += UNREAD_CHANS[i].hl;
}
}
if (hasHl)
title = "(!" +hasHl +") - ";
else if (hasUnread)
title = "(" +hasUnread +") - ";
setFavicon(hasHl, hasUnread);
}
title += SLACK.context.team.name;
document.title = title;
}
function spawnNotification() {
if (!("Notification" in window))
{}
else if (Notification.permission === "granted") {
var now = Date.now();
if (lastNotificationSpawn + NOTIFICATION_COOLDOWN < now) {
var n = new Notification(locale.newMessage);
lastNotificationSpawn = now;
setTimeout(function() {
n.close();
}, NOTIFICATION_DELAY);
}
}
else if (Notification.permission !== "denied")
Notification.requestPermission();
}
function onRoomUpdated() {
var chatFrag = document.createDocumentFragment()
,currentRoomId = SELECTED_ROOM.id
,prevMsg = null
,firstTsCombo = 0
,prevMsgDom = null;
if (SLACK.history[currentRoomId])
SLACK.history[currentRoomId].messages.forEach(function(msg) {
if (!msg.removed) {
var dom = createMessageDom(currentRoomId, msg);
if (prevMsg && prevMsg.userId === msg.userId && msg.userId) {
dom.classList.add(R.klass.msg.same.author);
if (Math.abs(firstTsCombo -msg.ts) < 30)
prevMsgDom.classList.add(R.klass.msg.same.ts);
else
firstTsCombo = msg.ts;
} else
firstTsCombo = msg.ts;
prevMsg = msg;
prevMsgDom = dom;
chatFrag.appendChild(dom);
}
});
var content = document.getElementById(R.id.currentRoom.content);
content.textContent = "";
content.appendChild(chatFrag);
//TODO scroll lock
content.scrollTop = content.scrollHeight -content.clientHeight;
}
function chatClickDelegate(e) {
var target = e.target
,getMessageId = function(e, target) {
target = target || e.target;
while (target !== e.currentTarget && target) {
if (target.classList.contains(R.klass.msg.item)) {
return target.id;
}
target = target.parentElement;
}
};
while (target !== e.currentTarget && target) {
if (target.classList.contains(R.klass.msg.hover.container)) {
return;
} else if (target.parentElement && target.parentElement.classList.contains(R.klass.msg.hover.container)) {
var messageId = getMessageId(e, target);
if (messageId) {
messageId = parseFloat(messageId.split("_")[1]);
var msg = SLACK.history[SELECTED_ROOM.id].getMessage(messageId);
if (msg && target.classList.contains(R.klass.msg.hover.reply)) {
if (EDITING) {
EDITING = null;
onEditingUpdated();
}
if (REPLYING_TO !== msg) {
REPLYING_TO = msg;
onReplyingToUpdated();
}
} else if (msg && target.classList.contains(R.klass.msg.hover.reaction)) {
EMOJI_BAR.spawn(document.body, function(emoji) {
if (emoji)
addReaction(SELECTED_ROOM.id, msg.id, emoji);
});
} else if (msg && target.classList.contains(R.klass.msg.hover.edit)) {
if (REPLYING_TO) {
REPLYING_TO = null;
onReplyingToUpdated();
}
if (EDITING !== msg) {
EDITING = msg;
onEditingUpdated();
}
} else if (msg && target.classList.contains(R.klass.msg.hover.remove)) {
//TODO promt confirm
if (REPLYING_TO) {
REPLYING_TO = null;
onReplyingToUpdated();
}
if (EDITING) {
EDITING = null;
onEditingUpdated();
}
removeMsg(SELECTED_ROOM, msg);
}
}
return;
}
target = target.parentElement;
}
}
function focusInput() {
document.getElementById(R.id.message.input).focus();
}
function setRoomFromHashBang() {
var hashId = document.location.hash.substr(1)
,room = SLACK.context.getChannel(hashId)
,user = SLACK.context.getMember(hashId);
if (room && room !== SELECTED_ROOM)
selectRoom(room);
else if (user && user.ims)
selectRoom(user.ims);
}
document.addEventListener('DOMContentLoaded', function() {
initLang();
document.getElementById(R.id.currentRoom.content).addEventListener("click", chatClickDelegate);
window.addEventListener("hashchange", function(e) {
if (document.location.hash && document.location.hash[0] === '#') {
setRoomFromHashBang();
}
});
document.getElementById(R.id.message.file.cancel).addEventListener("click", function(e) {
e.preventDefault();
document.getElementById(R.id.message.file.error).classList.add(R.klass.hidden);
document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
document.getElementById(R.id.message.file.fileInput).value = "";
return false;
});
document.getElementById(R.id.message.file.form).addEventListener("submit", function(e) {
e.preventDefault();
var fileInput = document.getElementById(R.id.message.file.fileInput)
,filename = fileInput.value;
if (filename) {
filename = filename.substr(filename.lastIndexOf('\\') +1);
uploadFile(SELECTED_ROOM, filename, fileInput.files[0], function(errorMsg) {
var error = document.getElementById(R.id.message.file.error);
if (errorMsg) {
error.textContent = errorMsg;
error.classList.remove(R.klass.hidden);
} else {
error.classList.add(R.klass.hidden);
document.getElementById(R.id.message.file.fileInput).value = "";
document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
}
});
}
return false;
});
document.getElementById(R.id.message.file.bt).addEventListener("click", function(e) {
e.preventDefault();
if (SELECTED_ROOM) {
document.getElementById(R.id.message.file.formContainer).classList.remove(R.klass.hidden);
}
return false;
});
document.getElementById(R.id.message.form).addEventListener("submit", function(e) {
e.preventDefault();
var input =document.getElementById(R.id.message.input);
if (SELECTED_ROOM && input.value) {
if (onTextEntered(input.value)) {
input.value = "";
if (REPLYING_TO) {
REPLYING_TO = null;
onReplyingToUpdated();
}
if (EDITING) {
EDITING = null;
onReplyingToUpdated();
}
document.getElementById(R.id.message.slashComplete).textContent = '';
}
}
focusInput();
return false;
});
window.addEventListener('blur', function() {
window.hasFocus = false;
});
window.addEventListener('focus', function() {
window.hasFocus = true;
lastNotificationSpawn = 0;
if (SELECTED_ROOM)
markRoomAsRead(SELECTED_ROOM);
focusInput();
});
var lastKeyDown = 0;
document.getElementById(R.id.message.input).addEventListener('input', function() {
if (SELECTED_ROOM) {
var now = Date.now();
if (lastKeyDown + 3000 < now) {
sendTyping(SELECTED_ROOM);
lastKeyDown = now;
}
var commands = []
,input = this.value;
if (this.value[0] === '/') {
var endCmd = input.indexOf(' ')
,inputFinished = endCmd !== -1;
endCmd = endCmd === -1 ? input.length : endCmd;
var inputCmd = input.substr(0, endCmd);
for (var i in SLACK.context.commands.data) {
var currentCmd = SLACK.context.commands.data[i]
if ((!inputFinished && currentCmd.name.substr(0, endCmd) === inputCmd) ||
(inputFinished && currentCmd.name === inputCmd))
commands.push(currentCmd);
}
}
if (commands.length > 5) commands = []; // TODO Do not display to mush at once
commands.sort(function(a, b) { return a.name.localeCompare(b.name); });
var slashDom = document.getElementById(R.id.message.slashComplete)
,slashFrag = document.createDocumentFragment();
slashDom.textContent = '';
for (var i =0, nbCmd = commands.length; i < nbCmd; i++)
slashFrag.appendChild(createSlashAutocompleteDom(commands[i]));
slashDom.appendChild(slashFrag);
}
});
window.hasFocus = true;
//Emoji closure
(function() {
var emojiButton = document.getElementById(R.id.message.emoji);
if ('makeEmoji' in window) {
var emojiDom = window['makeEmoji']('smile');
if (emojiDom) {
emojiButton.innerHTML = "" +emojiDom.outerHTML +"";
} else {
emojiButton.style.backgroundImage = 'url("smile.svg")';
}
emojiDom = window['makeEmoji']('paperclip');
if (emojiDom) {
document.getElementById(R.id.message.file.bt).innerHTML = "" +emojiDom.outerHTML +"";
} else {
document.getElementById(R.id.message.file.bt).style.backgroundImage = 'url("public/paperclip.svg")';
}
emojiButton.addEventListener("click", function() {
EMOJI_BAR.spawn(document.body, function(e) {
if (e) document.getElementById(R.id.message.input).value += ":"+e+":";
focusInput();
});
});
} else {
emojiButton.classList.add(R.klass.hidden);
}
})();
startPolling();
});