uiMessage.js 13 KB

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