ui.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. var
  2. /**
  3. * Minimum time between 2 notifications (ms)
  4. * @const
  5. * @type {number}
  6. **/
  7. NOTIFICATION_COOLDOWN = 30 * 1000 //30 sec
  8. /**
  9. * Maximum time the notification will stay visible (ms)
  10. * @const
  11. * @type {number}
  12. **/
  13. ,NOTIFICATION_DELAY = 5 * 1000 // 5 sec
  14. ,MSG_GROUPS = []
  15. /** @type {number} */
  16. ,lastNotificationSpawn = 0
  17. ;
  18. function onContextUpdated() {
  19. var chanListFram = document.createDocumentFragment()
  20. ,sortedChans = Object.keys(SLACK.context.channels || {})
  21. ,starred = []
  22. ,channels = []
  23. ,privs = []
  24. ,priv = [];
  25. sortedChans.sort(function(a, b) {
  26. if (a[0] !== b[0]) {
  27. return a[0] - b[0];
  28. }
  29. return SLACK.context.channels[a].name.localeCompare(SLACK.context.channels[b].name);
  30. });
  31. sortedChans.forEach(function(chanId) {
  32. var chan = SLACK.context.channels[chanId];
  33. if (!chan.archived) {
  34. if (chan instanceof PrivateMessageRoom) {
  35. var chanListItem = createImsListItem(chan);
  36. if (chanListItem) {
  37. if (chan.starred)
  38. starred.push(chanListItem);
  39. else
  40. priv.push(chanListItem);
  41. }
  42. } else {
  43. var chanListItem = createChanListItem(chan);
  44. if (chanListItem) {
  45. if (chan.starred)
  46. starred.push(chanListItem);
  47. else if (chan.isPrivate)
  48. privs.push(chanListItem);
  49. else
  50. channels.push(chanListItem);
  51. }
  52. }
  53. }
  54. });
  55. if (starred.length)
  56. chanListFram.appendChild(createChanListHeader(locale.starred));
  57. starred.forEach(function(dom) {
  58. chanListFram.appendChild(dom);
  59. });
  60. if (channels.length)
  61. chanListFram.appendChild(createChanListHeader(locale.channels));
  62. channels.forEach(function(dom) {
  63. chanListFram.appendChild(dom);
  64. });
  65. privs.forEach(function(dom) {
  66. chanListFram.appendChild(dom);
  67. });
  68. if (priv.length)
  69. chanListFram.appendChild(createChanListHeader(locale.privateMessageRoom));
  70. priv.forEach(function(dom) {
  71. chanListFram.appendChild(dom);
  72. });
  73. document.getElementById(R.id.chanList).textContent = "";
  74. document.getElementById(R.id.chanList).appendChild(chanListFram);
  75. setRoomFromHashBang();
  76. updateTitle();
  77. createContextBackground(function(imgData) {
  78. document.getElementById(R.id.context).style.backgroundImage = 'url(' +imgData +')';
  79. });
  80. }
  81. function onTypingUpdated() {
  82. var typing = SLACK.context.typing;
  83. for (var chanId in SLACK.context.self.channels) {
  84. if (!SLACK.context.self.channels[chanId].archived) {
  85. var dom = document.getElementById("room_" +chanId);
  86. if (typing[chanId])
  87. dom.classList.add(R.klass.chatList.typing);
  88. else
  89. dom.classList.remove(R.klass.chatList.typing);
  90. }
  91. }
  92. for (var userId in SLACK.context.users) {
  93. var ims = SLACK.context.users[userId].privateRoom;
  94. if (ims && !ims.archived) {
  95. var dom = document.getElementById("room_" +ims.id);
  96. if (typing[ims.id])
  97. dom.classList.add(R.klass.chatList.typing);
  98. else
  99. dom.classList.remove(R.klass.chatList.typing);
  100. }
  101. }
  102. updateTypingChat();
  103. }
  104. function updateTypingChat() {
  105. var typing = SLACK.context.typing;
  106. document.getElementById(R.id.typing).textContent = "";
  107. if (SELECTED_ROOM && typing[SELECTED_ROOM.id]) {
  108. var areTyping = document.createDocumentFragment()
  109. ,isOutOfSync = false;
  110. for (var i in typing[SELECTED_ROOM.id]) {
  111. var member = SLACK.context.users[i];
  112. if (member)
  113. areTyping.appendChild(makeUserIsTypingDom(member));
  114. else
  115. isOutOfSync = true;
  116. }
  117. if (isOutOfSync)
  118. outOfSync();
  119. document.getElementById(R.id.typing).appendChild(areTyping);
  120. }
  121. }
  122. function onNetworkStateUpdated(isNetworkWorking) {
  123. isNetworkWorking ? document.body.classList.remove(R.klass.noNetwork) : document.body.classList.add(R.klass.noNetwork);
  124. updateTitle();
  125. }
  126. function onRoomSelected() {
  127. var name = SELECTED_ROOM.name || (SELECTED_ROOM.user ? SELECTED_ROOM.user.name : undefined);
  128. if (!name) {
  129. /** @type {Array.<string>} */
  130. var members = [];
  131. SELECTED_ROOM.users.forEach(function(i) {
  132. members.push(i.name);
  133. });
  134. name = members.join(", ");
  135. }
  136. var roomLi = document.getElementById("room_" +SELECTED_ROOM.id);
  137. document.getElementById(R.id.currentRoom.title).textContent = name;
  138. onRoomUpdated();
  139. focusInput();
  140. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  141. markRoomAsRead(SELECTED_ROOM);
  142. if (REPLYING_TO) {
  143. REPLYING_TO = null;
  144. onReplyingToUpdated();
  145. }
  146. if (EDITING) {
  147. EDITING = null;
  148. onReplyingToUpdated();
  149. }
  150. updateTypingChat();
  151. }
  152. function onReplyingToUpdated() {
  153. if (REPLYING_TO) {
  154. document.body.classList.add(R.klass.replyingTo);
  155. var domParent = document.getElementById(R.id.message.replyTo)
  156. ,closeLink = document.createElement("a");
  157. closeLink.addEventListener("click", function() {
  158. REPLYING_TO = null;
  159. onReplyingToUpdated();
  160. });
  161. closeLink.className = R.klass.msg.replyTo.close;
  162. closeLink.textContent = 'x';
  163. domParent.textContent = "";
  164. domParent.appendChild(closeLink);
  165. domParent.appendChild(createMessageDom("reply_" +SELECTED_ROOM.id, REPLYING_TO, true));
  166. focusInput();
  167. } else {
  168. document.body.classList.remove(R.klass.replyingTo);
  169. document.getElementById(R.id.message.replyTo).textContent = "";
  170. focusInput();
  171. }
  172. }
  173. function onEditingUpdated() {
  174. if (EDITING) {
  175. document.body.classList.add(R.klass.replyingTo);
  176. var domParent = document.getElementById(R.id.message.replyTo)
  177. ,closeLink = document.createElement("a");
  178. closeLink.addEventListener("click", function() {
  179. EDITING = null;
  180. onEditingUpdated();
  181. });
  182. closeLink.className = R.klass.msg.replyTo.close;
  183. closeLink.textContent = 'x';
  184. domParent.textContent = "";
  185. domParent.appendChild(closeLink);
  186. domParent.appendChild(createMessageDom("edit_" +SELECTED_ROOM.id, EDITING, true));
  187. document.getElementById(R.id.message.input).value = EDITING.text;
  188. focusInput();
  189. } else {
  190. document.body.classList.remove(R.klass.replyingTo);
  191. document.getElementById(R.id.message.replyTo).textContent = "";
  192. focusInput();
  193. }
  194. }
  195. /**
  196. * @param {string} chanId
  197. * @param {string} msgId
  198. * @param {string} reaction
  199. **/
  200. window['toggleReaction'] = function(chanId, msgId, reaction) {
  201. var hist = SLACK.history[chanId];
  202. if (!hist)
  203. return;
  204. var msg = hist.getMessageById(msgId);
  205. if (msg) {
  206. if (msg.hasReactionForUser(reaction, SLACK.context.self.id)) {
  207. removeReaction(chanId, msgId, reaction);
  208. } else {
  209. addReaction(chanId, msgId, reaction);
  210. }
  211. }
  212. };
  213. /**
  214. * Try to resolve emoji from customized context
  215. * @param {string} emoji
  216. * @return {Element|string}
  217. **/
  218. function tryGetCustomEmoji(emoji) {
  219. var loop = {};
  220. while (!loop[emoji]) {
  221. var emojisrc= SLACK.context.emojis.data[emoji];
  222. if (emojisrc) {
  223. if (emojisrc.substr(0, 6) == "alias:") {
  224. loop[emoji] = true;
  225. emoji = emojisrc.substr(6);
  226. } else {
  227. var dom = document.createElement("span");
  228. dom.className = R.klass.emoji.custom +' ' +R.klass.emoji.emoji;
  229. dom.style.backgroundImage = "url('" +emojisrc +"')";
  230. return dom;
  231. }
  232. }
  233. return emoji; // Emoji not found, fallback to std emoji
  234. }
  235. return emoji; //loop detected, return first emoji
  236. }
  237. function makeEmojiDom(emojiCode) {
  238. var emoji = tryGetCustomEmoji(emojiCode);
  239. if (typeof emoji === "string" && "makeEmoji" in window)
  240. emoji = window['makeEmoji'](emoji);
  241. return typeof emoji === "string" ? null : emoji;
  242. }
  243. /**
  244. * replace all :emoji: codes with corresponding image
  245. * @param {string} inputString
  246. * @return {string}
  247. **/
  248. function formatEmojis(inputString) {
  249. return inputString.replace(/:([^ \t:]+):/g, function(returnFailed, emoji) {
  250. var emojiDom = makeEmojiDom(emoji);
  251. if (emojiDom) {
  252. var domParent = document.createElement("span");
  253. domParent.className = returnFailed === inputString ? R.klass.emoji.medium : R.klass.emoji.small;
  254. domParent.appendChild(emojiDom);
  255. return domParent.outerHTML;
  256. }
  257. return returnFailed;
  258. });
  259. }
  260. /**
  261. * @param {string} fullText
  262. * @return {string}
  263. **/
  264. function formatText(fullText) {
  265. var msgContents = fullText.split(/\r?\n/g);
  266. for (var msgContentIndex=0, nbMsgContents = msgContents.length; msgContentIndex < nbMsgContents; msgContentIndex++) {
  267. var msgContent = msgContents[msgContentIndex].trim()
  268. ,_msgContent = ""
  269. ,currentMods = {}
  270. ,quote = false
  271. ,i =0
  272. msgContent = msgContent.replace(new RegExp('<([@#]?)([^>]*)>', 'g'),
  273. function(matched, type, entity) {
  274. var sub = entity.split('|');
  275. if (type === '@') {
  276. if (!sub[1]) {
  277. var user = SLACK.context.users[sub[0]];
  278. sub[1] = user ? ('@' +user.name) : locale.unknownMember;
  279. } else if ('@' !== sub[1][0]) {
  280. sub[1] = '@' +sub[1];
  281. }
  282. sub[0] = '#' +sub[0];
  283. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkuser;
  284. } else if (type === '#') {
  285. if (!sub[1]) {
  286. var chan = SLACK.context.channels[sub[0]];
  287. sub[1] = chan ? ('#' +chan.name) : locale.unknownChannel;
  288. } else if ('#' !== sub[1][0]) {
  289. sub[1] = '#' +sub[1];
  290. }
  291. sub[0] = '#' +sub[0];
  292. sub[2] = R.klass.msg.link +' ' +R.klass.msg.linkchan;
  293. } else if (sub[0].indexOf("://") !== -1) {
  294. if (!sub[1])
  295. sub[1] = sub[0];
  296. sub[2] = R.klass.msg.link;
  297. } else {
  298. return matched;
  299. }
  300. return '<a href="' +sub[0] +'" class="' +sub[2] +'"' +(!type ? ' target="_blank"' : '') +'>' +sub[1] +'</a>';
  301. });
  302. msgContent = formatEmojis(msgContent);
  303. var msgLength = msgContent.length;
  304. var isAlphadec = function(c) {
  305. return ((c >= 'A' && c <= 'Z') ||
  306. (c >= 'a' && c <= 'z') ||
  307. (c >= '0' && c <= '9') ||
  308. "àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇߨøÅ寿œ".indexOf(c) !== -1);
  309. }
  310. ,checkEnd = function(str, pos, c) {
  311. while (str[pos]) {
  312. if (isAlphadec(str[pos]) && str[pos] != c && str[pos +1] == c) {
  313. return true;
  314. }
  315. pos++;
  316. }
  317. return false;
  318. } ,appendMod = function(mods) {
  319. if (!Object.keys(currentMods).length)
  320. return "";
  321. return '<span class="' +Object.keys(mods).join(' ') +'">';
  322. };
  323. // Skip trailing
  324. while (i < msgLength && (msgContent[i] === ' ' || msgContent[i] === '\t'))
  325. i++;
  326. if (msgContent.substr(i, 4) === '&gt;') {
  327. quote = true;
  328. i += 4;
  329. }
  330. for (; i < msgLength; i++) {
  331. var c = msgContent[i];
  332. if (c === '<') {
  333. do {
  334. _msgContent += msgContent[i++];
  335. } while (msgContent[i -1] !== '>');
  336. i--;
  337. continue;
  338. }
  339. if (!(currentMods[R.klass.msg.style.bold]) && c === '*' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  340. if (Object.keys(currentMods).length)
  341. _msgContent += '</span>';
  342. currentMods[R.klass.msg.style.bold] = true;
  343. _msgContent += appendMod(currentMods);
  344. } else if (!(currentMods[R.klass.msg.style.strike]) && c === '~' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  345. if (Object.keys(currentMods).length)
  346. _msgContent += '</span>';
  347. currentMods[R.klass.msg.style.strike] = true;
  348. _msgContent += appendMod(currentMods);
  349. } else if (!(currentMods[R.klass.msg.style.code]) && c === '`' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  350. if (Object.keys(currentMods).length)
  351. _msgContent += '</span>';
  352. currentMods[R.klass.msg.style.code] = true;
  353. _msgContent += appendMod(currentMods);
  354. } else if (!(currentMods[R.klass.msg.style.italic]) && c === '_' && msgContent[i +1] && checkEnd(msgContent, i, c)) {
  355. if (Object.keys(currentMods).length)
  356. _msgContent += '</span>';
  357. currentMods[R.klass.msg.style.italic] = true;
  358. _msgContent += appendMod(currentMods);
  359. } else {
  360. var finalFound = false;
  361. _msgContent += c;
  362. do {
  363. if ((currentMods[R.klass.msg.style.bold]) && c !== '*' && msgContent[i +1] === '*') {
  364. delete currentMods[R.klass.msg.style.bold];
  365. finalFound = true;
  366. } else if ((currentMods[R.klass.msg.style.strike]) && c !== '~' && msgContent[i +1] === '~') {
  367. delete currentMods[R.klass.msg.style.strike];
  368. finalFound = true;
  369. } else if ((currentMods[R.klass.msg.style.code]) && c !== '`' && msgContent[i +1] === '`') {
  370. delete currentMods[R.klass.msg.style.code];
  371. finalFound = true;
  372. } else if ((currentMods[R.klass.msg.style.italic]) && c !== '_' && msgContent[i +1] === '_') {
  373. delete currentMods[R.klass.msg.style.italic];
  374. finalFound = true;
  375. } else {
  376. break;
  377. }
  378. c = msgContent[++i];
  379. } while (i < msgLength);
  380. if (finalFound)
  381. _msgContent += '</span>' +appendMod(currentMods);
  382. }
  383. }
  384. if (currentMods) {
  385. // Should not append
  386. _msgContent += '</span>';
  387. }
  388. if (quote)
  389. msgContents[msgContentIndex] = '<span class="' +R.klass.msg.style.quote +'">' +_msgContent +'</span>';
  390. else
  391. msgContents[msgContentIndex] = _msgContent;
  392. }
  393. return msgContents.join('<br/>');
  394. }
  395. /**
  396. * @param {string} channelId
  397. * @param {Message} msg
  398. * @param {boolean=} skipAttachment
  399. * @return {Element}
  400. **/
  401. function doCreateMeMessageDom(channelId, msg, skipAttachment) {
  402. var dom = doCreateMessageDom(channelId, msg, skipAttachment);
  403. dom.classList.add(R.klass.msg.meMessage);
  404. return dom;
  405. }
  406. /**
  407. * @param {string} channelId
  408. * @param {Message} msg
  409. * @param {boolean=} skipAttachment
  410. * @return {Element}
  411. **/
  412. function createMessageDom(channelId, msg, skipAttachment) {
  413. var dom = (msg instanceof MeMessage) ?
  414. doCreateMeMessageDom(channelId, msg, skipAttachment):
  415. doCreateMessageDom(channelId, msg, skipAttachment);
  416. if (msg.edited)
  417. dom.classList.add(R.klass.msg.edited);
  418. if (msg instanceof NoticeMessage)
  419. dom.classList.add(R.klass.msg.notice);
  420. return dom;
  421. }
  422. /**
  423. * @param {number} unreadhi
  424. * @param {number} unread
  425. **/
  426. function setFavicon(unreadhi, unread) {
  427. if (!unreadhi && !unread)
  428. document.getElementById(R.id.favicon).href = "favicon_ok.png";
  429. else
  430. document.getElementById(R.id.favicon).href = "favicon.png?h="+unreadhi+"&m="+unread;
  431. }
  432. function setNetErrorFavicon() {
  433. document.getElementById(R.id.favicon).href = "favicon_err.png";
  434. }
  435. function updateTitle() {
  436. var hasHl = HIGHLIGHTED_CHANS.length
  437. ,title = "";
  438. if (NEXT_RETRY) {
  439. title = '!' +locale.netErrorShort +' - ';
  440. setNetErrorFavicon();
  441. } else if (hasHl) {
  442. title = "(!" +hasHl +") - ";
  443. setFavicon(hasHl, hasHl);
  444. } else {
  445. var hasUnread = 0;
  446. for (var chanId in SLACK.context.channels) {
  447. var i = SLACK.context.channels[chanId];
  448. if (i.lastMsg > i.lastRead)
  449. hasUnread++;
  450. }
  451. if (hasUnread)
  452. title = "(" +hasUnread +") - ";
  453. setFavicon(0, hasUnread);
  454. }
  455. if (SLACK.context.team)
  456. title += SLACK.context.team.name;
  457. document.title = title;
  458. }
  459. function spawnNotification() {
  460. if (!("Notification" in window))
  461. {}
  462. else if (Notification.permission === "granted") {
  463. var now = Date.now();
  464. if (lastNotificationSpawn + NOTIFICATION_COOLDOWN < now) {
  465. var n = new Notification(locale.newMessage);
  466. lastNotificationSpawn = now;
  467. setTimeout(function() {
  468. n.close();
  469. }, NOTIFICATION_DELAY);
  470. }
  471. }
  472. else if (Notification.permission !== "denied")
  473. Notification.requestPermission();
  474. }
  475. function onRoomUpdated() {
  476. var chatFrag = document.createDocumentFragment()
  477. ,currentRoomId = SELECTED_ROOM.id
  478. ,prevMsg = null
  479. ,firstTsCombo = 0
  480. ,prevMsgDom = null
  481. ,currentMsgGroupDom;
  482. MSG_GROUPS = [];
  483. if (SLACK.history[currentRoomId])
  484. SLACK.history[currentRoomId].messages.forEach(function(msg) {
  485. if (!msg.removed) {
  486. var dom = createMessageDom(currentRoomId, msg)
  487. ,newGroupDom = false;
  488. if (prevMsg && prevMsg.userId === msg.userId && msg.userId) {
  489. if (Math.abs(firstTsCombo -msg.ts) < 30 && !(msg instanceof MeMessage))
  490. prevMsgDom.classList.add(R.klass.msg.sameTs);
  491. else
  492. firstTsCombo = msg.ts;
  493. } else {
  494. firstTsCombo = msg.ts;
  495. newGroupDom = true;
  496. }
  497. if ((!prevMsg || prevMsg.ts <= SELECTED_ROOM.lastRead) && msg.ts > SELECTED_ROOM.lastRead)
  498. dom.classList.add(R.klass.msg.firstUnread);
  499. if (msg instanceof MeMessage) {
  500. prevMsg = null;
  501. prevMsgDom = null;
  502. firstTsCombo = 0;
  503. newGroupDom = true;
  504. chatFrag.appendChild(dom);
  505. currentMsgGroupDom = null;
  506. } else {
  507. if (newGroupDom || !currentMsgGroupDom) {
  508. currentMsgGroupDom = createMessageGroupDom(SLACK.context.users[msg.userId], msg.username);
  509. MSG_GROUPS.push(currentMsgGroupDom);
  510. chatFrag.appendChild(currentMsgGroupDom);
  511. }
  512. prevMsg = msg;
  513. prevMsgDom = dom;
  514. currentMsgGroupDom.content.appendChild(dom);
  515. }
  516. }
  517. });
  518. var content = document.getElementById(R.id.currentRoom.content);
  519. //TODO lazy add dom if needed
  520. content.textContent = "";
  521. content.appendChild(chatFrag);
  522. //TODO scroll lock
  523. content.scrollTop = content.scrollHeight -content.clientHeight;
  524. if (window.hasFocus)
  525. markRoomAsRead(SELECTED_ROOM);
  526. }
  527. function chatClickDelegate(e) {
  528. var target = e.target
  529. ,getMessageId = function(e, target) {
  530. target = target || e.target;
  531. while (target !== e.currentTarget && target) {
  532. if (target.classList.contains(R.klass.msg.item)) {
  533. return target.id;
  534. }
  535. target = target.parentElement;
  536. }
  537. };
  538. while (target !== e.currentTarget && target) {
  539. if (target.classList.contains(R.klass.msg.hover.container)) {
  540. return;
  541. } else if (target.parentElement && target.parentElement.classList.contains(R.klass.msg.hover.container)) {
  542. var messageId = getMessageId(e, target);
  543. if (messageId) {
  544. messageId = parseFloat(messageId.split("_")[1]);
  545. var msg = SLACK.history[SELECTED_ROOM.id].getMessage(messageId);
  546. if (msg && target.classList.contains(R.klass.msg.hover.reply)) {
  547. if (EDITING) {
  548. EDITING = null;
  549. onEditingUpdated();
  550. }
  551. if (REPLYING_TO !== msg) {
  552. REPLYING_TO = msg;
  553. onReplyingToUpdated();
  554. }
  555. } else if (msg && target.classList.contains(R.klass.msg.hover.reaction)) {
  556. EMOJI_BAR.spawn(document.body, function(emoji) {
  557. if (emoji)
  558. addReaction(SELECTED_ROOM.id, msg.id, emoji);
  559. });
  560. } else if (msg && target.classList.contains(R.klass.msg.hover.edit)) {
  561. if (REPLYING_TO) {
  562. REPLYING_TO = null;
  563. onReplyingToUpdated();
  564. }
  565. if (EDITING !== msg) {
  566. EDITING = msg;
  567. onEditingUpdated();
  568. }
  569. } else if (msg && target.classList.contains(R.klass.msg.hover.remove)) {
  570. //TODO promt confirm
  571. if (REPLYING_TO) {
  572. REPLYING_TO = null;
  573. onReplyingToUpdated();
  574. }
  575. if (EDITING) {
  576. EDITING = null;
  577. onEditingUpdated();
  578. }
  579. removeMsg(SELECTED_ROOM, msg);
  580. }
  581. }
  582. return;
  583. }
  584. target = target.parentElement;
  585. }
  586. }
  587. function focusInput() {
  588. document.getElementById(R.id.message.input).focus();
  589. }
  590. function setRoomFromHashBang() {
  591. var hashId = document.location.hash.substr(1)
  592. ,room = SLACK.context.channels[hashId];
  593. if (room && room !== SELECTED_ROOM)
  594. selectRoom(room);
  595. else {
  596. var user = SLACK.context.users[hashId];
  597. if (user && user.ims)
  598. selectRoom(user.ims);
  599. }
  600. }
  601. function updateAuthorAvatarImsOffset() {
  602. var chatDom = document.getElementById(R.id.currentRoom.content)
  603. ,chatTop = chatDom.getBoundingClientRect().top;
  604. MSG_GROUPS.forEach(function(group) {
  605. var imgDom = group.authorImg
  606. ,imgSize = imgDom.clientHeight
  607. ,domRect = group.getBoundingClientRect()
  608. ,_top = 0;
  609. imgDom.style.top = Math.max(0, Math.min(chatTop -domRect.top, domRect.height -imgSize -(imgSize /2))) +"px";
  610. });
  611. }
  612. document.addEventListener('DOMContentLoaded', function() {
  613. initLang();
  614. document.getElementById(R.id.currentRoom.content).addEventListener("click", chatClickDelegate);
  615. window.addEventListener("hashchange", function(e) {
  616. if (document.location.hash && document.location.hash[0] === '#') {
  617. setRoomFromHashBang();
  618. }
  619. });
  620. document.getElementById(R.id.message.file.cancel).addEventListener("click", function(e) {
  621. e.preventDefault();
  622. document.getElementById(R.id.message.file.error).classList.add(R.klass.hidden);
  623. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  624. document.getElementById(R.id.message.file.fileInput).value = "";
  625. return false;
  626. });
  627. document.getElementById(R.id.message.file.form).addEventListener("submit", function(e) {
  628. e.preventDefault();
  629. var fileInput = document.getElementById(R.id.message.file.fileInput)
  630. ,filename = fileInput.value;
  631. if (filename) {
  632. filename = filename.substr(filename.lastIndexOf('\\') +1);
  633. uploadFile(SELECTED_ROOM, filename, fileInput.files[0], function(errorMsg) {
  634. var error = document.getElementById(R.id.message.file.error);
  635. if (errorMsg) {
  636. error.textContent = errorMsg;
  637. error.classList.remove(R.klass.hidden);
  638. } else {
  639. error.classList.add(R.klass.hidden);
  640. document.getElementById(R.id.message.file.fileInput).value = "";
  641. document.getElementById(R.id.message.file.formContainer).classList.add(R.klass.hidden);
  642. }
  643. });
  644. }
  645. return false;
  646. });
  647. document.getElementById(R.id.message.file.bt).addEventListener("click", function(e) {
  648. e.preventDefault();
  649. if (SELECTED_ROOM) {
  650. document.getElementById(R.id.message.file.formContainer).classList.remove(R.klass.hidden);
  651. }
  652. return false;
  653. });
  654. document.getElementById(R.id.message.form).addEventListener("submit", function(e) {
  655. e.preventDefault();
  656. var input =document.getElementById(R.id.message.input);
  657. if (SELECTED_ROOM && input.value) {
  658. if (onTextEntered(input.value)) {
  659. input.value = "";
  660. if (REPLYING_TO) {
  661. REPLYING_TO = null;
  662. onReplyingToUpdated();
  663. }
  664. if (EDITING) {
  665. EDITING = null;
  666. onReplyingToUpdated();
  667. }
  668. document.getElementById(R.id.message.slashComplete).textContent = '';
  669. }
  670. }
  671. focusInput();
  672. return false;
  673. });
  674. window.addEventListener('blur', function() {
  675. window.hasFocus = false;
  676. });
  677. window.addEventListener('focus', function() {
  678. window.hasFocus = true;
  679. lastNotificationSpawn = 0;
  680. if (SELECTED_ROOM)
  681. markRoomAsRead(SELECTED_ROOM);
  682. focusInput();
  683. });
  684. document.getElementById(R.id.currentRoom.content).addEventListener('scroll', updateAuthorAvatarImsOffset);
  685. var lastKeyDown = 0;
  686. document.getElementById(R.id.message.input).addEventListener('input', function() {
  687. if (SELECTED_ROOM) {
  688. var now = Date.now();
  689. if (lastKeyDown + 3000 < now && (SLACK.context.self.presence || (SELECTED_ROOM instanceof PrivateMessageRoom))) {
  690. sendTyping(SELECTED_ROOM);
  691. lastKeyDown = now;
  692. }
  693. var commands = []
  694. ,input = this.value;
  695. if (this.value[0] === '/') {
  696. var endCmd = input.indexOf(' ')
  697. ,inputFinished = endCmd !== -1;
  698. endCmd = endCmd === -1 ? input.length : endCmd;
  699. var inputCmd = input.substr(0, endCmd);
  700. for (var i in SLACK.context.commands.data) {
  701. var currentCmd = SLACK.context.commands.data[i]
  702. if ((!inputFinished && currentCmd.name.substr(0, endCmd) === inputCmd) ||
  703. (inputFinished && currentCmd.name === inputCmd))
  704. commands.push(currentCmd);
  705. }
  706. }
  707. commands.sort(function(a, b) {
  708. return a.category.localeCompare(b.category) || a.name.localeCompare(b.name);
  709. });
  710. var slashDom = document.getElementById(R.id.message.slashComplete)
  711. ,slashFrag = document.createDocumentFragment()
  712. ,prevService;
  713. slashDom.textContent = '';
  714. for (var i =0, nbCmd = commands.length; i < nbCmd; i++) {
  715. var command = commands[i];
  716. if (prevService !== command.category) {
  717. prevService = command.category;
  718. slashFrag.appendChild(createSlashAutocompleteHeader(command.category));
  719. }
  720. slashFrag.appendChild(createSlashAutocompleteDom(command));
  721. }
  722. slashDom.appendChild(slashFrag);
  723. }
  724. });
  725. window.hasFocus = true;
  726. //Emoji closure
  727. (function() {
  728. var emojiButton = document.getElementById(R.id.message.emoji);
  729. if ('makeEmoji' in window) {
  730. var emojiDom = window['makeEmoji']('smile');
  731. if (emojiDom) {
  732. emojiButton.innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
  733. } else {
  734. emojiButton.style.backgroundImage = 'url("smile.svg")';
  735. }
  736. emojiDom = window['makeEmoji']('paperclip');
  737. if (emojiDom) {
  738. document.getElementById(R.id.message.file.bt).innerHTML = "<span class='" +R.klass.emoji.small +"'>" +emojiDom.outerHTML +"</span>";
  739. } else {
  740. document.getElementById(R.id.message.file.bt).style.backgroundImage = 'url("public/paperclip.svg")';
  741. }
  742. emojiButton.addEventListener("click", function() {
  743. EMOJI_BAR.spawn(document.body, function(e) {
  744. if (e) document.getElementById(R.id.message.input).value += ":"+e+":";
  745. focusInput();
  746. });
  747. });
  748. } else {
  749. emojiButton.classList.add(R.klass.hidden);
  750. }
  751. })();
  752. startPolling();
  753. });