1
0

uiMessage.js 11 KB

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