ui.js 35 KB

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