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(); });