uiMessage.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /**
  2. * @constructor
  3. * @extends {RoomHistory}
  4. * @param {Room|string} room or roomId
  5. * @param {number} keepMessages number of messages to keep in memory
  6. * @param {Array|undefined} evts
  7. * @param {number|undefined} now
  8. **/
  9. function UiRoomHistory(room, keepMessages, evts, now) {
  10. RoomHistory.call(this, room, keepMessages, evts, now);
  11. }
  12. UiRoomHistory.prototype = Object.create(RoomHistory.prototype);
  13. UiRoomHistory.prototype.constructor = UiRoomHistory;
  14. UiRoomHistory.prototype.messageFactory = function(ev, ts) {
  15. if (ev["isMeMessage"] === true)
  16. return new UiMeMessage(this.id, ev, ts);
  17. if (ev["isNotice"] === true)
  18. return new UiNoticeMessage(this.id, ev, ts);
  19. return new UiMessage(this.id, ev, ts);
  20. }
  21. /** @interface */
  22. function IUiMessage() {};
  23. /**
  24. * @return {Element}
  25. **/
  26. IUiMessage.prototype.getDom = function() {};
  27. /**
  28. * @return {IUiMessage}
  29. **/
  30. IUiMessage.prototype.removeDom = function() {};
  31. /**
  32. * @return {IUiMessage}
  33. **/
  34. IUiMessage.prototype.invalidate = function() {};
  35. /**
  36. * @return {IUiMessage}
  37. **/
  38. IUiMessage.prototype.createDom = function() {};
  39. /**
  40. * @return {IUiMessage}
  41. **/
  42. IUiMessage.prototype.updateDom = function() {};
  43. /**
  44. * @return {Element}
  45. **/
  46. IUiMessage.prototype.duplicateDom = function() {};
  47. /** @const */
  48. var AbstractUiMessage = (function() {
  49. var updateReactions = function(_this, channelId) {
  50. var reactionFrag = document.createDocumentFragment();
  51. if (_this.reactions) {
  52. for (var reaction in _this.reactions) {
  53. var reac = createReactionDom(channelId, _this.id, reaction, _this.reactions[reaction]);
  54. if (reac)
  55. reactionFrag.appendChild(reac);
  56. }
  57. }
  58. _this.dom.reactions.textContent = '';
  59. _this.dom.reactions.appendChild(reactionFrag);
  60. },
  61. _linkFilter = function(msgContext, str) {
  62. var sep = str.indexOf('|')
  63. ,link
  64. ,text
  65. ,isInternal = false;
  66. if (sep === -1) {
  67. link = str;
  68. } else {
  69. link = str.substr(0, sep);
  70. text = str.substr(sep +1);
  71. }
  72. if (link[0] === '@') {
  73. //FIXME pointer back from message to team to prepend team id to remote userId
  74. var newLink = msgContext.context.getId() +'|' +link.substr(1)
  75. ,user = DATA.context.getUser(newLink);
  76. if (user) {
  77. isInternal = true;
  78. link = newLink;
  79. text = '@' +user.name;
  80. } else {
  81. return null;
  82. }
  83. } else if (link[0] === '#') {
  84. var newLink = msgContext.context.getId() +'|' +link.substr(1)
  85. ,chan = DATA.context.getChannel(newLink);
  86. if (chan) {
  87. isInternal = true;
  88. link = newLink;
  89. text = '#' +chan.name;
  90. } else {
  91. return null;
  92. }
  93. } else {
  94. isInternal = false;
  95. // TODO check javascript:// injections
  96. }
  97. return {
  98. link: link,
  99. text: text || link,
  100. isInternal: isInternal
  101. };
  102. },
  103. _formatText = function(_this, text) {
  104. return formatText(text, {
  105. highlights: _this.context.self.prefs.highlights,
  106. emojiFormatFunction: function(emoji) {
  107. if (emoji[0] === ':' && emoji[emoji.length -1] === ':')
  108. emoji = emoji.substr(1, emoji.length -2);
  109. var emojiDom = makeEmojiDom(emoji);
  110. if (emojiDom) {
  111. var domParent = document.createElement("span");
  112. domParent.className = R.klass.emoji.small;
  113. domParent.appendChild(emojiDom);
  114. return domParent.outerHTML;
  115. }
  116. return null;
  117. },
  118. linkFilter: function(link) {
  119. return _linkFilter(_this, link);
  120. }
  121. });
  122. },
  123. updateAttachments = function(_this, channelId) {
  124. var attachmentFrag = document.createDocumentFragment();
  125. for (var i =0, nbAttachments = _this.attachments.length; i < nbAttachments; i++) {
  126. var attachment = _this.attachments[i];
  127. if (attachment) {
  128. var domAttachment = createAttachmentDom(channelId, _this, attachment, i);
  129. if (domAttachment)
  130. attachmentFrag.appendChild(domAttachment);
  131. }
  132. }
  133. _this.dom.attachments.textContent = '';
  134. _this.dom.attachments.appendChild(attachmentFrag);
  135. },
  136. updateCommon = function(_this, sender) {
  137. _this.dom.ts.innerHTML = locale.formatDate(_this.ts);
  138. _this.dom.textDom.innerHTML = _formatText(_this, _this.text);
  139. _this.dom.authorName.textContent = sender ? sender.name : (_this.username || "?");
  140. };
  141. return {
  142. /** @type {Element|null} *
  143. dom: null,
  144. /** @type {boolean} *
  145. uiNeedRefresh: true,
  146. */
  147. /** @param {IUiMessage} _this */
  148. invalidate: function(_this) {
  149. _this.uiNeedRefresh = true;
  150. return _this;
  151. },
  152. /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
  153. removeDom: function(_this) {
  154. if (_this.dom && _this.dom.parentElement) {
  155. _this.dom.remove();
  156. delete(_this.dom);
  157. }
  158. return _this;
  159. },
  160. /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
  161. getDom: function(_this) {
  162. if (!_this.dom) {
  163. _this.createDom().updateDom();
  164. } else if (_this.uiNeedRefresh) {
  165. _this.uiNeedRefresh = false;
  166. _this.updateDom();
  167. }
  168. return _this.dom;
  169. },
  170. /** @param {UiMessage|UiMeMessage|UiNoticeMessage} _this */
  171. updateDom: function(_this) {
  172. var sender = DATA.context.getUser(_this.userId);
  173. updateCommon(_this, sender);
  174. updateAttachments(_this, _this.channelId);
  175. updateReactions(_this, _this.channelId);
  176. if (_this.edited) {
  177. _this.dom.edited.innerHTML = locale.edited(_this.edited);
  178. _this.dom.classList.add(R.klass.msg.editedStatus);
  179. }
  180. return _this;
  181. },
  182. duplicateDom: function(_this) {
  183. return _this.dom.cloneNode(true);
  184. },
  185. formatText: function(_this, text) {
  186. return _formatText(_this, text);
  187. }
  188. };
  189. })();
  190. /**
  191. * @constructor
  192. * @implements {IUiMessage}
  193. * @extends {MeMessage}
  194. * @param {*} ev
  195. * @param {number} ts
  196. **/
  197. function UiMeMessage(channelId, ev, ts) {
  198. // Extends AbstractUiMessage and MeMessage
  199. MeMessage.call(this, ev, ts);
  200. /** @const @type {ChatContext} */
  201. this.context = DATA.context.getChannelContext(channelId).getChatContext();
  202. /** @type {string} @const */
  203. this.channelId = channelId;
  204. this.dom = AbstractUiMessage.dom;
  205. this.uiNeedRefresh = AbstractUiMessage.uiNeedRefresh;
  206. }
  207. UiMeMessage.prototype = Object.create(MeMessage.prototype);
  208. UiMeMessage.prototype.constructor = UiMeMessage;
  209. UiMeMessage.prototype.invalidate = function() {
  210. return AbstractUiMessage.invalidate(this);
  211. };
  212. UiMeMessage.prototype.formatText = function(text) {
  213. return AbstractUiMessage.formatText(this, text);
  214. };
  215. UiMeMessage.prototype.removeDom = function() {
  216. return AbstractUiMessage.removeDom(this);
  217. };
  218. UiMeMessage.prototype.getDom = function() {
  219. return AbstractUiMessage.getDom(this);
  220. };
  221. UiMeMessage.prototype.createDom = function() {
  222. this.dom = doCreateMessageDom(this, false);
  223. this.dom.classList.add(R.klass.msg.meMessage);
  224. return this;
  225. };
  226. UiMeMessage.prototype.duplicateDom = function() {
  227. return AbstractUiMessage.duplicateDom(this);
  228. };
  229. UiMeMessage.prototype.updateDom = function() {
  230. AbstractUiMessage.updateDom(this);
  231. return this;
  232. };
  233. UiMeMessage.prototype.update = function(ev, ts) {
  234. MeMessage.prototype.update.call(this, ev, ts);
  235. this.invalidate();
  236. };
  237. /**
  238. * @constructor
  239. * @implements {IUiMessage}
  240. * @extends {Message}
  241. * @param {*} ev
  242. * @param {number} ts
  243. **/
  244. function UiMessage(channelId, ev, ts) {
  245. // Extends AbstractUiMessage and Message
  246. Message.call(this, ev, ts);
  247. /** @const @type {ChatContext} */
  248. this.context = DATA.context.getChannelContext(channelId).getChatContext();
  249. /** @type {string} @const */
  250. this.channelId = channelId;
  251. /** @type {Element} */
  252. this.dom = AbstractUiMessage.dom;
  253. /** @type {boolean} */
  254. this.uiNeedRefresh = AbstractUiMessage.uiNeedRefresh;
  255. }
  256. UiMessage.prototype = Object.create(Message.prototype);
  257. UiMessage.prototype.constructor = UiMessage;
  258. UiMessage.prototype.invalidate = function() {
  259. return AbstractUiMessage.invalidate(this);
  260. };
  261. UiMessage.prototype.formatText = function(text) {
  262. return AbstractUiMessage.formatText(this, text);
  263. };
  264. UiMessage.prototype.removeDom = function() {
  265. return AbstractUiMessage.removeDom(this);
  266. };
  267. UiMessage.prototype.getDom = function() {
  268. return AbstractUiMessage.getDom(this);
  269. };
  270. UiMessage.prototype.createDom = function() {
  271. this.dom = doCreateMessageDom(this, false);
  272. return this;
  273. };
  274. UiMessage.prototype.duplicateDom = function() {
  275. return AbstractUiMessage.duplicateDom(this);
  276. };
  277. UiMessage.prototype.updateDom = function() {
  278. AbstractUiMessage.updateDom(this);
  279. return this;
  280. };
  281. UiMessage.prototype.update = function(ev, ts) {
  282. Message.prototype.update.call(this, ev, ts);
  283. this.invalidate();
  284. };
  285. /**
  286. * @constructor
  287. * @implements {IUiMessage}
  288. * @extends {NoticeMessage}
  289. * @param {*} ev
  290. * @param {number} ts
  291. **/
  292. function UiNoticeMessage(channelId, ev, ts) {
  293. // Extends AbstractUiMessage and NoticeMessage
  294. NoticeMessage.call(this, ev, ts);
  295. /** @const @type {ChatContext} */
  296. this.context = DATA.context.getChannelContext(channelId).getChatContext();
  297. /** @type {string} @const */
  298. this.channelId = channelId;
  299. /** @type {Element} */
  300. this.dom;
  301. /** @type {Element} */
  302. this.domWrapper = null;
  303. /** @type {boolean} */
  304. this.uiNeedRefresh = true;
  305. }
  306. UiNoticeMessage.prototype = Object.create(NoticeMessage.prototype);
  307. UiNoticeMessage.prototype.constructor = UiNoticeMessage;
  308. UiNoticeMessage.prototype.invalidate = function() {
  309. return AbstractUiMessage.invalidate(this);
  310. };
  311. UiNoticeMessage.prototype.formatText = function(text) {
  312. return AbstractUiMessage.formatText(this, text);
  313. };
  314. UiNoticeMessage.prototype.removeDom = function() {
  315. if (this.domWrapper && this.domWrapper.parentElement) {
  316. this.domWrapper.remove();
  317. delete(this.domWrapper);
  318. }
  319. if (this.dom)
  320. delete(this.dom);
  321. return this;
  322. };
  323. UiNoticeMessage.prototype.getDom = function() {
  324. AbstractUiMessage.getDom(this);
  325. return this.domWrapper;
  326. };
  327. UiNoticeMessage.prototype.duplicateDom = function() {
  328. return this.domWrapper.cloneNode(true);
  329. };
  330. UiNoticeMessage.prototype.createDom = function() {
  331. this.dom = doCreateMessageDom(this, false);
  332. this.domWrapper = document.createElement("span");
  333. this.dom.classList.add(R.klass.msg.notice);
  334. this.domWrapper.className = R.klass.msg.notice;
  335. this.domWrapper.textContent = locale.onlyVisible;
  336. this.domWrapper.appendChild(this.dom);
  337. return this;
  338. };
  339. UiNoticeMessage.prototype.updateDom = function() {
  340. AbstractUiMessage.updateDom(this);
  341. return this;
  342. };
  343. UiNoticeMessage.prototype.update = function(ev, ts) {
  344. NoticeMessage.prototype.update.call(this, ev, ts);
  345. this.invalidate();
  346. };