You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

383 lines
8.5 KiB

  1. define([
  2. 'js/system/events',
  3. 'html!ui/templates/messages/template',
  4. 'html!ui/templates/messages/tplTab',
  5. 'css!ui/templates/messages/styles',
  6. 'ui/templates/messages/mobile',
  7. 'ui/templates/messages/channelPicker',
  8. 'js/input',
  9. 'js/system/client',
  10. 'js/config'
  11. ], function (
  12. events,
  13. template,
  14. tplTab,
  15. styles,
  16. messagesMobile,
  17. channelPicker,
  18. input,
  19. client,
  20. config
  21. ) {
  22. return {
  23. tpl: template,
  24. maxChatLength: 255,
  25. hoverItem: null,
  26. hoverFilter: false,
  27. currentChannel: 'global',
  28. currentSubChannel: null,
  29. privateChannels: [],
  30. lastPrivateChannel: null,
  31. customChannels: [],
  32. lastCustomChannel: null,
  33. postRender: function () {
  34. [
  35. 'onGetMessages',
  36. 'onDoWhisper',
  37. 'onJoinChannel',
  38. 'onLeaveChannel',
  39. 'onClickFilter',
  40. 'onGetCustomChatChannels',
  41. 'onKeyDown',
  42. 'onKeyUp'
  43. ].forEach(e => this.onEvent(e, this[e].bind(this)));
  44. this.find('.filter:not(.channel)').on('click', this.onClickFilter.bind(this));
  45. channelPicker.init.call(this);
  46. if (isMobile)
  47. messagesMobile.init.call(this);
  48. else {
  49. this.find('input')
  50. .on('keydown', this.sendChat.bind(this))
  51. .on('input', this.enforceMaxMsgLength.bind(this))
  52. .on('blur', this.toggle.bind(this, false, true));
  53. }
  54. },
  55. update: function () {
  56. if (isMobile)
  57. return;
  58. if (this.el.hasClass('typing'))
  59. return;
  60. const time = new Date();
  61. const hours = time.getUTCHours().toString().padStart(2, 0);
  62. const minutes = time.getUTCMinutes().toString().padStart(2, 0);
  63. let elTime = this.find('.time');
  64. const timeString = `[ ${hours}:${minutes} ]`;
  65. if (elTime.html() !== timeString)
  66. elTime.html(timeString);
  67. },
  68. enforceMaxMsgLength: function () {
  69. let textbox = this.find('input');
  70. let val = textbox.val();
  71. if (val.length <= this.maxChatLength)
  72. return;
  73. val = val.substr(0, this.maxChatLength);
  74. textbox.val(val);
  75. },
  76. onGetCustomChatChannels: function (channels) {
  77. channels.forEach(c => this.onJoinChannel(c));
  78. },
  79. onJoinChannel: function (channel) {
  80. const container = this.find('.filters');
  81. const channelName = channel.trim();
  82. this.customChannels.spliceWhere(c => c === channel);
  83. this.find(`[filter="${channelName}"]`).remove();
  84. this.customChannels.push(channel);
  85. $(tplTab)
  86. .appendTo(container)
  87. .addClass('channel')
  88. .attr('filter', channelName)
  89. .html(channelName)
  90. .on('mouseover', this.onFilterHover.bind(this, true))
  91. .on('mouseleave', this.onFilterHover.bind(this, false))
  92. .on('click', this.onClickFilter.bind(this));
  93. },
  94. onLeaveChannel: function (channel) {
  95. this.customChannels.spliceWhere(c => c === channel);
  96. this.find(`.filters div[filter="${channel}"]`).remove();
  97. },
  98. onFilterHover: function (hover) {
  99. this.hoverFilter = hover;
  100. },
  101. onClickFilter: function (e) {
  102. let el = $(e.target);
  103. el.toggleClass('active');
  104. let filter = el.attr('filter');
  105. let method = (el.hasClass('active') ? 'show' : 'hide');
  106. if (method === 'show')
  107. this.find('.list').addClass(filter);
  108. else
  109. this.find('.list').removeClass(filter);
  110. if (el.hasClass('channel'))
  111. this.find('.list .' + filter)[method]();
  112. },
  113. onKeyDown: function (key) {
  114. if (key === 'enter')
  115. this.toggle(true);
  116. else if (key === 'shift')
  117. this.showItemTooltip();
  118. else if (key === 'esc' && this.el.hasClass('typing'))
  119. this.toggle(false);
  120. },
  121. onKeyUp: function (key) {
  122. if (key === 'shift')
  123. this.showItemTooltip();
  124. },
  125. onDoWhisper: function (charName) {
  126. this.toggle(true);
  127. this.currentChannel = 'direct';
  128. this.currentSubChannel = charName;
  129. this.find('.channelPicker').html(charName);
  130. const elInput = this.find('input')
  131. .val('message')
  132. .focus();
  133. elInput[0].setSelectionRange(0, 7);
  134. },
  135. //Remember private and custom channels used
  136. trackHistory: function (msg) {
  137. const { subType, source, target, channel } = msg;
  138. if (subType === 'privateIn' || subType === 'privateOut') {
  139. const list = this.privateChannels;
  140. list.spliceWhere(l => l === source || l === target);
  141. //Newest sources are always at the end
  142. list.push(source || target);
  143. if (list.length > 5)
  144. list.splice(0, list.length - 5);
  145. if (subType === 'privateOut' && config.rememberChatChannel)
  146. this.lastPrivateChannel = target;
  147. } else if (subType === 'custom' && config.rememberChatChannel)
  148. this.lastCustomChannel = channel;
  149. },
  150. onGetMessages: function (e) {
  151. let messages = e.messages;
  152. if (!messages.length)
  153. messages = [messages];
  154. let container = this.find('.list');
  155. const [ { scrollHeight, clientHeight, scrollTop } ] = container;
  156. const isAtMaxScroll = scrollTop >= (scrollHeight - clientHeight);
  157. messages.forEach(m => {
  158. this.trackHistory(m);
  159. let message = m.message;
  160. if (m.source) {
  161. if (window.player.social.isPlayerBlocked(m.source))
  162. return;
  163. }
  164. if (m.item) {
  165. let source = message.split(':')[0];
  166. message = source + ': <span class="q' + (m.item.quality || 0) + '">' + message.replace(source + ': ', '') + '</span>';
  167. }
  168. let el = $('<div class="list-message ' + m.class + '">' + message + '</div>')
  169. .appendTo(container);
  170. if (m.has('type'))
  171. el.addClass(m.type);
  172. else
  173. el.addClass('info');
  174. if (m.has('channel'))
  175. el.addClass(m.channel);
  176. if (m.item) {
  177. let clickHander = () => {};
  178. let moveHandler = this.showItemTooltip.bind(this, el, m.item);
  179. if (isMobile)
  180. [clickHander, moveHandler] = [moveHandler, clickHander];
  181. el.find('span')
  182. .on('mousemove', moveHandler)
  183. .on('mousedown', clickHander)
  184. .on('mouseleave', this.hideItemTooltip.bind(this));
  185. }
  186. if (m.type) {
  187. let isChannel = (['info', 'chat', 'loot', 'rep'].indexOf(m.type) === -1);
  188. if (isChannel) {
  189. if (this.find('.filter[filter="' + m.type + '"]').hasClass('active'))
  190. el.show();
  191. }
  192. if (isMobile && ['loot', 'info'].indexOf(m.type) !== -1) {
  193. events.emit('onGetAnnouncement', {
  194. msg: m.message
  195. });
  196. }
  197. }
  198. });
  199. if (!this.el.hasClass('typing') || isAtMaxScroll)
  200. container.scrollTop(9999999);
  201. },
  202. hideItemTooltip: function () {
  203. if (this.dragEl) {
  204. this.hoverCell = null;
  205. return;
  206. }
  207. events.emit('onHideItemTooltip', this.hoverItem);
  208. this.hoverItem = null;
  209. },
  210. showItemTooltip: function (el, item, e) {
  211. if (item)
  212. this.hoverItem = item;
  213. else
  214. item = this.hoverItem;
  215. if (!item)
  216. return;
  217. let ttPos = null;
  218. if (el) {
  219. ttPos = {
  220. x: ~~(e.clientX + 32),
  221. y: ~~(e.clientY)
  222. };
  223. }
  224. let bottomAlign = !isMobile;
  225. events.emit('onShowItemTooltip', item, ttPos, true, bottomAlign);
  226. },
  227. toggle: function (show, isFake, e) {
  228. if (isFake && this.hoverFilter)
  229. return;
  230. input.resetKeys();
  231. this.el.removeClass('typing');
  232. let textbox = this.find('input');
  233. if (show) {
  234. this.el.addClass('typing');
  235. if (!config.rememberChatChannel) {
  236. this.currentChannel = 'global';
  237. this.currentSubChannel = null;
  238. }
  239. this.find('.channelPicker').html(this.currentSubChannel || this.currentChannel);
  240. textbox.focus();
  241. this.find('.list').scrollTop(9999999);
  242. } else {
  243. this.find('.channelOptions').removeClass('active');
  244. textbox.val('');
  245. this.el.removeClass('picking');
  246. if (['direct', 'custom'].includes(this.currentChannel) && (!this.currentSubChannel || ['join new', 'leave'].includes(this.currentSubChannel))) {
  247. this.currentSubChannel = null;
  248. this.currentChannel = 'global';
  249. }
  250. }
  251. if (e)
  252. e.stopPropagation();
  253. },
  254. sendChat: function (e) {
  255. let textbox = this.find('input');
  256. let msgConfig = {
  257. success: true,
  258. message: textbox.val(),
  259. event: e,
  260. cancel: false
  261. };
  262. this.processChat(msgConfig);
  263. if (msgConfig.cancel || this.el.hasClass('picking'))
  264. return false;
  265. const { which: charCode } = e;
  266. if ([9, 27].includes(charCode) || charCode !== 13) {
  267. if (charCode === 9) {
  268. e.preventDefault();
  269. textbox.val(`${textbox.val()} `);
  270. } else if (charCode === 27)
  271. this.toggle(false);
  272. return;
  273. }
  274. events.emit('onBeforeChat', msgConfig);
  275. let val = msgConfig.message
  276. .split('<').join('&lt;')
  277. .split('>').join('&gt;');
  278. if (!msgConfig.success) {
  279. this.toggle(false);
  280. return;
  281. }
  282. if (val.trim() === '') {
  283. this.toggle(false);
  284. return;
  285. }
  286. client.request({
  287. cpn: 'social',
  288. method: 'chat',
  289. data: {
  290. message: val,
  291. type: this.currentChannel,
  292. subType: this.currentSubChannel
  293. }
  294. });
  295. this.toggle();
  296. }
  297. };
  298. });