ui.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  1. var
  2. /** @type {SlackMessage|null} */
  3. REPLYING_TO = null
  4. /** @type {SlackMessage|null} */
  5. ,EDITING = null
  6. /**
  7. * Minimum time between 2 notifications (ms)
  8. * @const
  9. * @type {number}
  10. **/
  11. ,NOTIFICATION_COOLDOWN = 30 * 1000 //30 sec
  12. /**
  13. * Maximum time the notification will stay visible (ms)
  14. * @const
  15. * @type {number}
  16. **/
  17. ,NOTIFICATION_DELAY = 5 * 1000 // 5 sec
  18. /** @type {number} */
  19. ,lastNotificationSpawn = 0
  20. ;
  21. /**
  22. * @param {SlackChan|SlackGroup} chan
  23. * @return {Element}
  24. **/
  25. function createChanListItem(chan) {
  26. var dom = document.createElement("li")
  27. ,link = document.createElement("a");
  28. dom.id = chan.id;
  29. link.href = '#' +chan.id;
  30. if (chan.id[0] === 'D')
  31. dom.className = R.klass.chatList.entry + " " +R.klass.chatList.typeDirect;
  32. else if (chan.id[0] === 'G')
  33. dom.className = R.klass.chatList.entry + " " +R.klass.chatList.typeGroup;
  34. else if (chan.id[0] === 'C')
  35. dom.className = R.klass.chatList.entry + " " +R.klass.chatList.typeChannel;
  36. if (SELECTED_ROOM === chan)
  37. dom.classList.add(R.klass.selected);
  38. link.textContent = chan.name;
  39. dom.appendChild(link);
  40. return dom;
  41. }
  42. /**
  43. * @param {SlackIms} ims
  44. * @return {Element}
  45. **/
  46. function createImsListItem(ims) {
  47. var dom = document.createElement("li")
  48. ,link = document.createElement("a");
  49. dom.id = ims.id;
  50. link.href = '#' +ims.id;
  51. dom.className = R.klass.chatList.entry + " " +R.klass.chatList.typeDirect;
  52. link.textContent = ims.user.name;
  53. dom.appendChild(link);
  54. if (!ims.user.presence)
  55. dom.classList.add(R.klass.presenceAway);
  56. if (SELECTED_ROOM === ims)
  57. dom.classList.add(R.klass.selected);
  58. return dom;
  59. }
  60. function onContextUpdated() {
  61. var chanListFram = document.createDocumentFragment()
  62. ,sortedChans = SLACK.context.self ? Object.keys(SLACK.context.self.channels) : [];
  63. sortedChans.sort(function(a, b) {
  64. if (a[0] !== b[0]) {
  65. return a[0] - b[0];
  66. }
  67. return SLACK.context.getChannel(a).name.localeCompare(SLACK.context.getChannel(b).name);
  68. });
  69. sortedChans.forEach(function(chanId) {
  70. var chan =
  71. /**
  72. * SortedChan does not contains ims ids
  73. * @type {SlackChan|SlackGroup}
  74. **/
  75. (SLACK.context.getChannel(chanId));
  76. if (!chan.archived) {
  77. var chanListItem = createChanListItem(chan);
  78. if (chanListItem)
  79. chanListFram.appendChild(chanListItem);
  80. }
  81. });
  82. var sortedUsers = SLACK.context.users ? Object.keys(SLACK.context.users) : [];
  83. sortedUsers.sort(function(a, b) {
  84. return SLACK.context.users[a].name.localeCompare(SLACK.context.users[b].name);
  85. });
  86. sortedUsers.forEach(function(userId) {
  87. var user = SLACK.context.getMember(userId);
  88. if (!user.deleted) {
  89. var ims = user.ims
  90. ,imsListItem = createImsListItem(ims);
  91. if (imsListItem) {
  92. chanListFram.appendChild(imsListItem);
  93. }
  94. }
  95. });
  96. document.getElementById(R.id.chanList).textContent = "";
  97. document.getElementById(R.id.chanList).appendChild(chanListFram);
  98. setRoomFromHashBang();
  99. updateTitle();
  100. createContextBackground(function(imgData) {
  101. document.getElementById(R.id.context).style.backgroundImage = 'url(' +imgData +')';
  102. });
  103. }
  104. function onTypingUpdated() {
  105. var typing = SLACK.context.typing;
  106. for (var chanId in SLACK.context.self.channels) {
  107. if (!SLACK.context.self.channels[chanId].archived) {
  108. var dom = document.getElementById(chanId);
  109. if (typing[chanId])
  110. dom.classList.add(R.klass.chatList.typing);
  111. else
  112. dom.classList.remove(R.klass.chatList.typing);
  113. }
  114. }
  115. for (var userId in SLACK.context.users) {
  116. var ims = SLACK.context.users[userId].ims;
  117. if (ims && !ims.archived) {
  118. var dom = document.getElementById(ims.id);
  119. if (typing[ims.id])
  120. dom.classList.add(R.klass.chatList.typing);
  121. else
  122. dom.classList.remove(R.klass.chatList.typing);
  123. }
  124. }
  125. }
  126. function onNetworkStateUpdated(isNetworkWorking) {
  127. isNetworkWorking ? document.body.classList.remove(R.klass.noNetwork) : document.body.classList.add(R.klass.noNetwork);
  128. updateTitle();
  129. }
  130. function onRoomSelected() {
  131. var name = SELECTED_ROOM.name || (SELECTED_ROOM.user ? SELECTED_ROOM.user.name : undefined);
  132. if (!name) {
  133. var members = [];
  134. for (var i in SELECTED_ROOM.members) {
  135. members.push(SELECTED_ROOM.members[i].name);
  136. }
  137. name = members.join(", ");
  138. }
  139. var roomLi = document.getElementById(SELECTED_ROOM.id);
  140. document.getElementById(R.id.currentRoom.title).textContent = name;
  141. onRoomUpdated();
  142. focusInput();
  143. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  144. markRoomAsRead(SELECTED_ROOM);
  145. if (REPLYING_TO) {
  146. REPLYING_TO = null;
  147. onReplyingToUpdated();
  148. }
  149. if (EDITING) {
  150. EDITING = null;
  151. onReplyingToUpdated();
  152. }
  153. }
  154. function onReplyingToUpdated() {
  155. if (REPLYING_TO) {
  156. document.body.classList.add(R.klass.replyingTo);
  157. var domParent = document.getElementById(R.id.message.replyTo)
  158. ,closeLink = document.createElement("a");
  159. closeLink.addEventListener("click", function() {
  160. REPLYING_TO = null;
  161. onReplyingToUpdated();
  162. });
  163. closeLink.className = R.klass.msg.replyTo.close;
  164. closeLink.textContent = 'x';
  165. domParent.textContent = "";
  166. domParent.appendChild(closeLink);
  167. domParent.appendChild(createMessageDom("reply_" +SELECTED_ROOM.id, REPLYING_TO, true));
  168. focusInput();
  169. } else {
  170. document.body.classList.remove(R.klass.replyingTo);
  171. document.getElementById(R.id.message.replyTo).textContent = "";
  172. focusInput();
  173. }
  174. }
  175. function onEditingUpdated() {
  176. if (EDITING) {
  177. document.body.classList.add(R.klass.replyingTo);
  178. var domParent = document.getElementById(R.id.message.replyTo)
  179. ,closeLink = document.createElement("a");
  180. closeLink.addEventListener("click", function() {
  181. EDITING = null;
  182. onEditingUpdated();
  183. });
  184. closeLink.className = R.klass.msg.replyTo.close;
  185. closeLink.textContent = 'x';
  186. domParent.textContent = "";
  187. domParent.appendChild(closeLink);
  188. domParent.appendChild(createMessageDom("edit_" +SELECTED_ROOM.id, EDITING, true));
  189. document.getElementById(R.id.message.input).value = EDITING.text;
  190. focusInput();
  191. } else {
  192. document.body.classList.remove(R.klass.replyingTo);
  193. document.getElementById(R.id.message.replyTo).textContent = "";
  194. focusInput();
  195. }
  196. }
  197. /**
  198. * @param {string} chanId
  199. * @param {string} msgId
  200. * @param {string} reaction
  201. * @param {Array.<string>} users
  202. * @return {Element|null}
  203. **/
  204. function createReactionDom(chanId, msgId, reaction, users) {
  205. var emojiDom = makeEmojiDom(reaction);
  206. if (emojiDom) {
  207. var dom = document.createElement("li")
  208. ,a = document.createElement("a")
  209. ,emojiContainer = document.createElement("span")
  210. ,userList = document.createElement("span")
  211. ,userNames = [];
  212. for (var i =0, nbUser = users.length; i < nbUser; i++) {
  213. var user = SLACK.context.getMember(users[i]);
  214. if (user)
  215. userNames.push(user.name);
  216. }
  217. userNames.sort();
  218. userList.textContent = userNames.join(", ");
  219. emojiContainer.appendChild(emojiDom);
  220. emojiContainer.className = R.klass.emoji.small;
  221. a.href = "javascript:toggleReaction('" +chanId +"', '" +msgId +"', '" +reaction +"')";
  222. a.appendChild(emojiContainer);
  223. a.appendChild(userList);
  224. dom.className = R.klass.msg.reactions.item;
  225. dom.appendChild(a);
  226. return dom;
  227. }
  228. return null;
  229. }
  230. /**
  231. * @param {string} chanId
  232. * @param {string} msgId
  233. * @param {string} reaction
  234. **/
  235. window['toggleReaction'] = function(chanId, msgId, reaction) {
  236. var hist = SLACK.history[chanId];
  237. if (!hist)
  238. return;
  239. var msg = hist.getMessageById(msgId);
  240. if (msg) {
  241. if (msg.hasReactionForUser(reaction, SLACK.context.self.id)) {
  242. removeReaction(chanId, msgId, reaction);
  243. } else {
  244. addReaction(chanId, msgId, reaction);
  245. }
  246. }
  247. };
  248. /**
  249. * @param {string} channelId
  250. * @param {SlackMessage} msg
  251. * @param {boolean=} skipAttachment
  252. * @return {Element}
  253. **/
  254. function doCreateMessageDom(channelId, msg, skipAttachment) {
  255. var dom = document.createElement("div")
  256. ,msgBlock = document.createElement("div")
  257. ,ts = document.createElement("div")
  258. ,text = document.createElement("div")
  259. ,authorImg = document.createElement("img")
  260. ,authorName = document.createElement("span")
  261. ,hover = document.createElement("ul")
  262. ,hoverReply = document.createElement("li")
  263. ,attachments = document.createElement("ul")
  264. ,reactions = document.createElement("ul")
  265. ,sender = SLACK.context.getMember(msg.userId);
  266. dom.id = channelId +"_" +msg.ts;
  267. dom.className = R.klass.msg.item;
  268. ts.className = R.klass.msg.ts;
  269. text.className = R.klass.msg.msg;
  270. authorImg.className = R.klass.msg.authorAvatar;
  271. authorName.className = R.klass.msg.authorname;
  272. hover.className = R.klass.msg.hover.container;
  273. hoverReply.className = R.klass.msg.hover.reply;
  274. ts.innerHTML = locale.formatDate(msg.ts);
  275. text.innerHTML = formatSlackText(msg.text);
  276. authorName.textContent = sender ? sender.name : (msg.username || "?");
  277. authorImg.src = sender ? sender.icons.image_48 : "";
  278. hover.appendChild(hoverReply);
  279. if ('makeEmoji' in window) {
  280. var hoverReaction = document.createElement("li")
  281. ,domReply = window['makeEmoji']("arrow_heading_down")
  282. ,domReaction = window['makeEmoji']("smile")
  283. ,domEdit = window['makeEmoji']("pencil2")
  284. ,domRemove = window['makeEmoji']("x");
  285. hoverReaction.className = R.klass.msg.hover.reaction;
  286. if (domReaction) {
  287. hoverReaction.classList.add(R.klass.emoji.small);
  288. hoverReaction.appendChild(domReaction);
  289. } else {
  290. hoverReaction.style.backgroundImage = 'url("smile.svg")';
  291. }
  292. if (domReply) {
  293. hoverReply.classList.add(R.klass.emoji.small);
  294. hoverReply.appendChild(domReply);
  295. } else {
  296. hoverReply.style.backgroundImage = 'url("repl.svg")';
  297. }
  298. hover.appendChild(hoverReaction);
  299. if (msg.userId === SLACK.context.self.id) {
  300. var hoverEdit = document.createElement("li");
  301. hoverEdit.className = R.klass.msg.hover.edit;
  302. if (domEdit)
  303. hoverEdit.classList.add(R.klass.emoji.small);
  304. else
  305. hoverEdit.style.backgroundImage = 'url("edit.svg")';
  306. hoverEdit.appendChild(domEdit);
  307. hover.appendChild(hoverEdit);
  308. var hoverRemove = document.createElement("li");
  309. hoverRemove.className = R.klass.msg.hover.remove;
  310. if (domRemove)
  311. hoverRemove.classList.add(R.klass.emoji.small);
  312. else
  313. hoverRemove.style.backgroundImage = 'url("remove.svg")';
  314. hoverRemove.appendChild(domRemove);
  315. hover.appendChild(hoverRemove);
  316. }
  317. } else {
  318. hoverReply.style.backgroundImage = 'url("repl.svg")';
  319. if (msg.userId === SLACK.context.self.id) {
  320. var hoverEdit = document.createElement("li");
  321. hoverEdit.className = R.klass.msg.hover.edit;
  322. hoverEdit.style.backgroundImage = 'url("edit.svg")';
  323. hover.appendChild(hoverEdit);
  324. var hoverRemove = document.createElement("li")
  325. hoverRemove.className = R.klass.msg.hover.remove;
  326. hoverRemove.style.backgroundImage = 'url("remove.svg")';
  327. hover.appendChild(hoverRemove);
  328. }
  329. }
  330. dom.appendChild(authorImg);
  331. msgBlock.appendChild(authorName);
  332. msgBlock.appendChild(text);
  333. msgBlock.appendChild(ts);
  334. msgBlock.appendChild(attachments);
  335. if (msg.edited) {
  336. var edited = document.createElement("div");
  337. edited.textContent = locale.edited;
  338. edited.className = R.klass.msg.edited;
  339. msgBlock.appendChild(edited);
  340. }
  341. msgBlock.appendChild(reactions);
  342. msgBlock.className = R.klass.msg.content;
  343. attachments.className = R.klass.msg.attachment.list;
  344. reactions.className = R.klass.msg.reactions.container;
  345. if (skipAttachment !== true) {
  346. if (msg.reactions) for (var reaction in msg.reactions) {
  347. var reac = createReactionDom(channelId, msg.id, reaction, msg.reactions[reaction]);
  348. reac && reactions.appendChild(reac);
  349. }
  350. msg.attachments.forEach(function(attachment) {
  351. var domAttachment = createAttachmentDom(channelId, msg, attachment);
  352. if (domAttachment)
  353. attachments.appendChild(domAttachment);
  354. });
  355. }
  356. dom.appendChild(msgBlock);
  357. dom.appendChild(hover);
  358. return dom;
  359. }
  360. /**
  361. * Try to resolve emoji from customized context
  362. * @param {string} emoji
  363. * @return {Element|string}
  364. **/
  365. function tryGetCustomEmoji(emoji) {
  366. var loop = {};
  367. while (!loop[emoji]) {
  368. var emojisrc= SLACK.context.emojis[emoji];
  369. if (emojisrc) {
  370. if (emojisrc.substr(0, 6) == "alias:") {
  371. loop[emoji] = true;
  372. emoji = emojisrc.substr(6);
  373. } else {
  374. var dom = document.createElement("span");
  375. dom.className = R.klass.emoji.custom +' ' +R.klass.emoji.emoji;
  376. dom.style.backgroundImage = "url('" +emojisrc +"')";
  377. return dom;
  378. }
  379. }
  380. return emoji; // Emoji not found, fallback to std emoji
  381. }
  382. return emoji; //loop detected, return first emoji
  383. }
  384. function makeEmojiDom(emojiCode) {
  385. var emoji = tryGetCustomEmoji(emojiCode);
  386. if (typeof emoji === "string" && "makeEmoji" in window)
  387. emoji = window['makeEmoji'](emoji);
  388. return typeof emoji === "string" ? null : emoji;
  389. }
  390. /**
  391. * replace all :emoji: codes with corresponding image
  392. * @param {string} inputString
  393. * @return {string}
  394. **/
  395. function formatEmojis(inputString) {
  396. return inputString.replace(/:([^ \t:]+):/g, function(returnFailed, emoji) {
  397. var emojiDom = makeEmojiDom(emoji);
  398. if (emojiDom) {
  399. var domParent = document.createElement("span");
  400. domParent.className = returnFailed === inputString ? R.klass.emoji.medium : R.klass.emoji.small;
  401. domParent.appendChild(emojiDom);
  402. return domParent.outerHTML;
  403. }
  404. return returnFailed;
  405. });
  406. }
  407. /**
  408. * @param {string} fullText
  409. * @return {string}
  410. **/
  411. function formatSlackText(fullText) {
  412. var msgContents = fullText.split(/\r?\n/g);
  413. for (var msgContentIndex=0, nbMsgContents = msgContents.length; msgContentIndex < nbMsgContents; msgContentIndex++) {
  414. var msgContent = msgContents[msgContentIndex].trim()
  415. ,_msgContent = ""
  416. ,currentMods = {}
  417. ,quote = false
  418. ,i =0
  419. msgContent = msgContent.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
  420. function(matched, type, entity) {
  421. var sub = entity.split('|');
  422. if (type === '@') {
  423. if (!sub[1]) {
  424. var user = SLACK.context.getMember(sub[0]);
  425. sub[1] = user ? ('@' +user.name) : locale.unknownMember;
  426. } else if ('@' !== sub[1][0]) {
  427. sub[1] = '@' +sub[1];
  428. }
  429. sub[0] = '#' +sub[0];
  430. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
  431. } else if (type === '#') {
  432. if (!sub[1]) {
  433. var chan = SLACK.context.getChannel(sub[0]);
  434. sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
  435. } else if ('#' !== sub[1][0]) {
  436. sub[1] = '#' +sub[1];
  437. }
  438. sub[0] = '#' +sub[0];
  439. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
  440. } else if (sub[0].indexOf("://") !== -1) {
  441. if (!sub[1])
  442. sub[1] = sub[0];
  443. sub[2] = R.klass.msg.link;
  444. } else {
  445. return matched;
  446. }
  447. return '<a href="' +sub[0] +'" class="' +sub[2] +'"' +(!type ? ' target="_blank"' : '') +'>' +sub[1] +'</a>';
  448. });
  449. msgContent = formatEmojis(msgContent);
  450. var msgLength = msgContent.length;
  451. var isAlphadec = function(c) {
  452. return ((c >= 'A' && c <= 'Z') ||
  453. (c >= 'a' && c <= 'z') ||
  454. (c >= '0' && c <= '9') ||
  455. "àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
  456. }
  457. ,checkEnd = function(str, pos, c) {
  458. while (str[pos]) {
  459. if (isAlphadec(str[pos]) && str[pos] != c && str[pos +1] == c) {
  460. return true;
  461. }
  462. pos++;
  463. }
  464. return false;
  465. } ,appendMod = function(mods) {
  466. if (!Object.keys(currentMods).length)
  467. return "";
  468. return '<span class="' +Object.keys(mods).join(' ') +'">';
  469. };
  470. // Skip trailing
  471. while (i < msgLength && (msgContent[i] === ' ' || msgContent[i] === '\t'))
  472. i++;
  473. if (msgContent.substr(i, 4) === '&gt;') {
  474. quote = true;
  475. i += 4;
  476. }
  477. for (; i < msgLength; i++) {
  478. var c = msgContent[i];
  479. if (c === '<') {
  480. do {
  481. _msgContent += msgContent[i++];
  482. } while (msgContent[i -1] !== '>');
  483. i--;
  484. continue;
  485. }
  486. if (!(currentMods[R.klass.msg.style.bold]) && c === '*' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  487. if (Object.keys(currentMods).length)
  488. _msgContent += '</span>';
  489. currentMods[R.klass.msg.style.bold] = true;
  490. _msgContent += appendMod(currentMods);
  491. } else if (!(currentMods[R.klass.msg.style.strike]) && c === '~' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  492. if (Object.keys(currentMods).length)
  493. _msgContent += '</span>';
  494. currentMods[R.klass.msg.style.strike] = true;
  495. _msgContent += appendMod(currentMods);
  496. } else if (!(currentMods[R.klass.msg.style.code]) && c === '`' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  497. if (Object.keys(currentMods).length)
  498. _msgContent += '</span>';
  499. currentMods[R.klass.msg.style.code] = true;
  500. _msgContent += appendMod(currentMods);
  501. } else if (!(currentMods[R.klass.msg.style.italic]) && c === '_' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  502. if (Object.keys(currentMods).length)
  503. _msgContent += '</span>';
  504. currentMods[R.klass.msg.style.italic] = true;
  505. _msgContent += appendMod(currentMods);
  506. } else {
  507. var finalFound = false;
  508. _msgContent += c;
  509. do {
  510. if ((currentMods[R.klass.msg.style.bold]) && c !== '*' && msgContent[i +1] === '*') {
  511. delete currentMods[R.klass.msg.style.bold];
  512. finalFound = true;
  513. } else if ((currentMods[R.klass.msg.style.strike]) && c !== '~' && msgContent[i +1] === '~') {
  514. delete currentMods[R.klass.msg.style.strike];
  515. finalFound = true;
  516. } else if ((currentMods[R.klass.msg.style.code]) && c !== '`' && msgContent[i +1] === '`') {
  517. delete currentMods[R.klass.msg.style.code];
  518. finalFound = true;
  519. } else if ((currentMods[R.klass.msg.style.italic]) && c !== '_' && msgContent[i +1] === '_') {
  520. delete currentMods[R.klass.msg.style.italic];
  521. finalFound = true;
  522. } else {
  523. break;
  524. }
  525. c = msgContent[++i];
  526. } while (i < msgLength);
  527. if (finalFound)
  528. _msgContent += '</span>' +appendMod(currentMods);
  529. }
  530. }
  531. if (currentMods) {
  532. // Should not append
  533. _msgContent += '</span>';
  534. }
  535. if (quote)
  536. msgContents[msgContentIndex] = '<span class="' +R.klass.msg.style.quote +'">' +_msgContent +'</span>';
  537. else
  538. msgContents[msgContentIndex] = _msgContent;
  539. }
  540. return msgContents.join('<br/>');
  541. }
  542. /**
  543. * @param {string} channelId
  544. * @param {SlackMessage} msg
  545. * @param {*} attachment
  546. * @return {Element|null}
  547. **/
  548. function createAttachmentDom(channelId, msg, attachment) {
  549. var rootDom = document.createElement("li")
  550. ,attachmentBlock = document.createElement("div")
  551. ,pretext = document.createElement("div")
  552. ,titleBlock = document.createElement("a")
  553. ,authorBlock = document.createElement("div")
  554. ,authorImg = document.createElement("img")
  555. ,authorName = document.createElement("a")
  556. ,textBlock = document.createElement("div")
  557. ,textDom = document.createElement("div")
  558. ,thumbImgDom = document.createElement("img")
  559. ,imgDom = document.createElement("img")
  560. ,footerBlock = document.createElement("div")
  561. ,footerIcon = document.createElement("img")
  562. ,footerText = document.createElement("span")
  563. ,footerTs = document.createElement("span")
  564. ;
  565. rootDom.className = R.klass.msg.attachment.container;
  566. //Color
  567. var color = "#e3e4e6";
  568. if (attachment["color"]) {
  569. if (attachment["color"][0] === '#')
  570. color = attachment["color"][0];
  571. else if (attachment["color"] === "good")
  572. color = "#2fa44f";
  573. else if (attachment["color"] === "warning")
  574. color = "#de9e31";
  575. else if (attachment["color"] === "danger")
  576. color = "#d50200";
  577. }
  578. attachmentBlock.style.borderColor = color;
  579. //Pretext
  580. pretext.className = R.klass.msg.attachment.pretext;
  581. if (attachment["pretext"]) {
  582. pretext.innerHTML = formatSlackText(attachment["pretext"]);
  583. } else {
  584. pretext.classList.add(R.klass.hidden);
  585. }
  586. //Title
  587. titleBlock.target = "_blank";
  588. if (attachment["title"]) {
  589. titleBlock.innerHTML = formatSlackText(attachment["title"]);
  590. if (attachment["title_link"]) {
  591. titleBlock.href = attachment["title_link"];
  592. }
  593. titleBlock.className = R.klass.msg.attachment.title;
  594. } else {
  595. titleBlock.className = R.klass.hidden + " " +R.klass.msg.attachment.title;
  596. }
  597. //Author
  598. authorName.target = "_blank";
  599. authorBlock.className = R.klass.msg.author;
  600. if (attachment["author_name"]) {
  601. authorName.innerHTML = formatSlackText(attachment["author_name"]);
  602. authorName.href = attachment["author_link"] || "";
  603. authorName.className = R.klass.msg.authorname;
  604. authorImg.className = R.klass.msg.authorAvatar;
  605. if (attachment["author_icon"])
  606. authorImg.src = attachment["author_icon"];
  607. else
  608. authorImg.classList.add(R.klass.hidden);
  609. } else {
  610. authorBlock.classList.add(R.klass.hidden);
  611. }
  612. //Text
  613. textDom.innerHTML = formatSlackText(attachment["text"] || "");
  614. textDom.klassName = R.klass.msg.attachment.text;
  615. // Img (small one)
  616. thumbImgDom.className = R.klass.msg.attachment.thumbImg;
  617. if (attachment["thumb_url"])
  618. thumbImgDom.src = attachment["thumb_url"];
  619. else
  620. thumbImgDom.classList.add(R.klass.hidden);
  621. //Img (the big one)
  622. imgDom.className = R.klass.msg.attachment.img;
  623. if (attachment["image_url"])
  624. imgDom.src = attachment["image_url"];
  625. else
  626. imgDom.classList.add(R.klass.hidden);
  627. //Footer
  628. footerBlock.className = R.klass.msg.attachment.footer;
  629. footerText.className = R.klass.msg.attachment.footerText;
  630. footerIcon.className = R.klass.msg.attachment.footerIcon;
  631. if (attachment["footer"]) {
  632. footerText.innerHTML = formatSlackText(attachment["footer"]);
  633. if (attachment["footer_icon"])
  634. footerIcon.src = attachment["footer_icon"];
  635. else
  636. footerIcon.classList.add(R.klass.hidden);
  637. } else {
  638. footerIcon.classList.add(R.klass.hidden);
  639. footerText.classList.add(R.klass.hidden);
  640. }
  641. //Ts
  642. footerTs.className = R.klass.msg.ts;
  643. if (attachment["ts"])
  644. footerTs.innerHTML = locale.formatDate(attachment["ts"]);
  645. else
  646. footerTs.classList.add(R.klass.hidden);
  647. // TODO Field [ {title, value, short } ]
  648. // TODO actions (button stuff)
  649. authorBlock.appendChild(authorImg);
  650. authorBlock.appendChild(authorName);
  651. textBlock.appendChild(textDom);
  652. textBlock.appendChild(thumbImgDom);
  653. footerBlock.appendChild(footerIcon);
  654. footerBlock.appendChild(footerText);
  655. footerBlock.appendChild(footerTs);
  656. attachmentBlock.appendChild(titleBlock);
  657. attachmentBlock.appendChild(authorBlock);
  658. attachmentBlock.appendChild(textBlock);
  659. attachmentBlock.appendChild(imgDom);
  660. attachmentBlock.appendChild(footerBlock);
  661. rootDom.appendChild(pretext);
  662. rootDom.appendChild(attachmentBlock);
  663. return rootDom;
  664. }
  665. /**
  666. * @param {string} channelId
  667. * @param {SlackMessage} msg
  668. * @param {boolean=} skipAttachment
  669. * @return {Element}
  670. **/
  671. function doCreateMeMessageDom(channelId, msg, skipAttachment) {
  672. var dom = doCreateMessageDom(channelId, msg, skipAttachment);
  673. dom.classList.add(R.klass.msg.meMessage);
  674. return dom;
  675. }
  676. /**
  677. * @param {string} channelId
  678. * @param {SlackMessage} msg
  679. * @param {boolean=} skipAttachment
  680. * @return {Element}
  681. **/
  682. function createMessageDom(channelId, msg, skipAttachment) {
  683. var dom = (msg.isMeMessage ?
  684. doCreateMeMessageDom(channelId, msg, skipAttachment):
  685. doCreateMessageDom(channelId, msg, skipAttachment));
  686. if (msg.edited)
  687. dom.classList.add(R.klass.msg.edited);
  688. return dom;
  689. }
  690. /**
  691. * @param {number} unreadhi
  692. * @param {number} unread
  693. **/
  694. function setFavicon(unreadhi, unread) {
  695. if (!unreadhi && !unread)
  696. document.getElementById(R.id.favicon).href = "favicon_ok.png";
  697. else
  698. document.getElementById(R.id.favicon).href = "favicon.png?h="+unreadhi+"&m="+unread;
  699. }
  700. function setNetErrorFavicon() {
  701. document.getElementById(R.id.favicon).href = "favicon_err.png";
  702. }
  703. function updateTitle() {
  704. var hasUnread = 0
  705. ,hasHl = 0
  706. ,title = "";
  707. if (NEXT_RETRY) {
  708. title = '!' +locale.netErrorShort +' - ';
  709. setNetErrorFavicon();
  710. } else {
  711. for (var i in UNREAD_CHANS) {
  712. if (UNREAD_CHANS.hasOwnProperty(i)) {
  713. hasUnread += UNREAD_CHANS[i].unread;
  714. hasHl += UNREAD_CHANS[i].hl;
  715. }
  716. }
  717. if (hasHl)
  718. title = "(!" +hasHl +") - ";
  719. else if (hasUnread)
  720. title = "(" +hasUnread +") - ";
  721. setFavicon(hasHl, hasUnread);
  722. }
  723. title += SLACK.context.team.name;
  724. document.title = title;
  725. }
  726. function spawnNotification() {
  727. if (!("Notification" in window))
  728. {}
  729. else if (Notification.permission === "granted") {
  730. var now = Date.now();
  731. if (lastNotificationSpawn + NOTIFICATION_COOLDOWN < now) {
  732. var n = new Notification(locale.newMessage);
  733. lastNotificationSpawn = now;
  734. setTimeout(function() {
  735. n.close();
  736. }, NOTIFICATION_DELAY);
  737. }
  738. }
  739. else if (Notification.permission !== "denied")
  740. Notification.requestPermission();
  741. }
  742. function onRoomUpdated() {
  743. var chatFrag = document.createDocumentFragment()
  744. ,currentRoomId = SELECTED_ROOM.id
  745. ,prevMsg = null
  746. ,firstTsCombo = 0
  747. ,prevMsgDom = null;
  748. if (SLACK.history[currentRoomId])
  749. SLACK.history[currentRoomId].messages.forEach(function(msg) {
  750. if (!msg.removed) {
  751. var dom = createMessageDom(currentRoomId, msg);
  752. if (prevMsg && prevMsg.userId === msg.userId && msg.userId) {
  753. dom.classList.add(R.klass.msg.same.author);
  754. if (Math.abs(firstTsCombo -msg.ts) < 30)
  755. prevMsgDom.classList.add(R.klass.msg.same.ts);
  756. else
  757. firstTsCombo = msg.ts;
  758. } else
  759. firstTsCombo = msg.ts;
  760. prevMsg = msg;
  761. prevMsgDom = dom;
  762. chatFrag.appendChild(dom);
  763. }
  764. });
  765. var content = document.getElementById(R.id.currentRoom.content);
  766. content.textContent = "";
  767. content.appendChild(chatFrag);
  768. //TODO scroll lock
  769. content.scrollTop = content.scrollHeight -content.clientHeight;
  770. }
  771. function chatClickDelegate(e) {
  772. var target = e.target
  773. ,getMessageId = function(e, target) {
  774. target = target || e.target;
  775. while (target !== e.currentTarget && target) {
  776. if (target.classList.contains(R.klass.msg.item)) {
  777. return target.id;
  778. }
  779. target = target.parentElement;
  780. }
  781. };
  782. while (target !== e.currentTarget && target) {
  783. if (target.classList.contains(R.klass.msg.hover.container)) {
  784. return;
  785. } else if (target.parentElement && target.parentElement.classList.contains(R.klass.msg.hover.container)) {
  786. var messageId = getMessageId(e, target);
  787. if (messageId) {
  788. messageId = parseFloat(messageId.split("_")[1]);
  789. var msg = SLACK.history[SELECTED_ROOM.id].getMessage(messageId);
  790. if (msg && target.classList.contains(R.klass.msg.hover.reply)) {
  791. if (EDITING) {
  792. EDITING = null;
  793. onEditingUpdated();
  794. }
  795. if (REPLYING_TO !== msg) {
  796. REPLYING_TO = msg;
  797. onReplyingToUpdated();
  798. }
  799. } else if (msg && target.classList.contains(R.klass.msg.hover.reaction)) {
  800. EMOJI_BAR.spawn(document.body, function(emoji) {
  801. if (emoji)
  802. addReaction(SELECTED_ROOM.id, msg.id, emoji);
  803. });
  804. } else if (msg && target.classList.contains(R.klass.msg.hover.edit)) {
  805. if (REPLYING_TO) {
  806. REPLYING_TO = null;
  807. onReplyingToUpdated();
  808. }
  809. if (EDITING !== msg) {
  810. EDITING = msg;
  811. onEditingUpdated();
  812. }
  813. } else if (msg && target.classList.contains(R.klass.msg.hover.remove)) {
  814. //TODO promt confirm
  815. if (REPLYING_TO) {
  816. REPLYING_TO = null;
  817. onReplyingToUpdated();
  818. }
  819. if (EDITING) {
  820. EDITING = null;
  821. onEditingUpdated();
  822. }
  823. removeMsg(SELECTED_ROOM, msg);
  824. }
  825. }
  826. return;
  827. }
  828. target = target.parentElement;
  829. }
  830. }
  831. function focusInput() {
  832. document.getElementById(R.id.message.input).focus();
  833. }
  834. function setRoomFromHashBang() {
  835. var hashId = document.location.hash.substr(1)
  836. ,room = SLACK.context.getChannel(hashId)
  837. ,user = SLACK.context.getMember(hashId);
  838. if (room && room !== SELECTED_ROOM)
  839. selectRoom(room);
  840. else if (user && user.ims)
  841. selectRoom(user.ims);
  842. }
  843. document.addEventListener('DOMContentLoaded', function() {
  844. initLang();
  845. document.getElementById(R.id.currentRoom.content).addEventListener("click", chatClickDelegate);
  846. window.addEventListener("hashchange", function(e) {
  847. if (document.location.hash && document.location.hash[0] === '#') {
  848. setRoomFromHashBang();
  849. }
  850. });
  851. document.getElementById(R.id.message.file.cancel).addEventListener("click", function(e) {
  852. e.preventDefault();
  853. document.getElementById(R.id.message.file.error).classList.add(R.klass.hidden);
  854. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  855. document.getElementById(R.id.message.file.fileInput).value = "";
  856. return false;
  857. });
  858. document.getElementById(R.id.message.file.form).addEventListener("submit", function(e) {
  859. e.preventDefault();
  860. var fileInput = document.getElementById(R.id.message.file.fileInput)
  861. ,filename = fileInput.value;
  862. if (filename) {
  863. filename = filename.substr(filename.lastIndexOf('\\') +1);
  864. uploadFile(SELECTED_ROOM, filename, fileInput.files[0], function(errorMsg) {
  865. var error = document.getElementById(R.id.message.file.error);
  866. if (errorMsg) {
  867. error.textContent = errorMsg;
  868. error.classList.remove(R.klass.hidden);
  869. } else {
  870. error.classList.add(R.klass.hidden);
  871. document.getElementById(R.id.message.file.fileInput).value = "";
  872. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  873. }
  874. });
  875. }
  876. return false;
  877. });
  878. document.getElementById(R.id.message.file.bt).addEventListener("click", function(e) {
  879. e.preventDefault();
  880. if (SELECTED_ROOM) {
  881. document.getElementById(R.id.message.file.formContainer).classList.remove(R.klass.hidden);
  882. }
  883. return false;
  884. });
  885. document.getElementById(R.id.message.form).addEventListener("submit", function(e) {
  886. e.preventDefault();
  887. var input =document.getElementById(R.id.message.input);
  888. if (SELECTED_ROOM && input.value) {
  889. if (onTextEntered(input.value)) {
  890. input.value = "";
  891. if (REPLYING_TO) {
  892. REPLYING_TO = null;
  893. onReplyingToUpdated();
  894. }
  895. if (EDITING) {
  896. EDITING = null;
  897. onReplyingToUpdated();
  898. }
  899. }
  900. }
  901. focusInput();
  902. return false;
  903. });
  904. window.addEventListener('blur', function() {
  905. window.hasFocus = false;
  906. });
  907. window.addEventListener('focus', function() {
  908. window.hasFocus = true;
  909. lastNotificationSpawn = 0;
  910. if (SELECTED_ROOM)
  911. markRoomAsRead(SELECTED_ROOM);
  912. focusInput();
  913. });
  914. window.hasFocus = true;
  915. //Emoji closure
  916. (function() {
  917. var emojiButton = document.getElementById(R.id.message.emoji);
  918. if ('makeEmoji' in window) {
  919. var emojiDom = window['makeEmoji']('smile');
  920. if (emojiDom) {
  921. emojiButton.innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
  922. } else {
  923. emojiButton.style.backgroundImage = 'url("smile.svg")';
  924. }
  925. emojiDom = window['makeEmoji']('paperclip');
  926. if (emojiDom) {
  927. document.getElementById(R.id.message.file.bt).innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
  928. } else {
  929. document.getElementById(R.id.message.file.bt).style.backgroundImage = 'url("public/paperclip.svg")';
  930. }
  931. emojiButton.addEventListener("click", function() {
  932. EMOJI_BAR.spawn(document.body, function(e) {
  933. if (e) document.getElementById(R.id.message.input).value += ":"+e+":";
  934. focusInput();
  935. });
  936. });
  937. } else {
  938. emojiButton.classList.add(R.klass.hidden);
  939. }
  940. })();
  941. startPolling();
  942. });