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.
 
 
 

388 lines
8.6 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. if (!window.player)
  152. return;
  153. let messages = e.messages;
  154. if (!messages.length)
  155. messages = [messages];
  156. let container = this.find('.list');
  157. const [ { scrollHeight, clientHeight, scrollTop } ] = container;
  158. const isAtMaxScroll = scrollTop >= (scrollHeight - clientHeight);
  159. messages.forEach(m => {
  160. this.trackHistory(m);
  161. let message = m.message;
  162. if (m.source) {
  163. if (window.player.social.isPlayerBlocked(m.source))
  164. return;
  165. }
  166. if (m.item) {
  167. let source = message.split(':')[0];
  168. message = source + ': <span class="q' + (m.item.quality || 0) + '">' + message.replace(source + ': ', '') + '</span>';
  169. }
  170. let el = $('<div class="list-message ' + m.class + '">' + message + '</div>')
  171. .appendTo(container);
  172. if (m.has('type'))
  173. el.addClass(m.type);
  174. else
  175. el.addClass('info');
  176. if (m.has('channel'))
  177. el.addClass(m.channel);
  178. if (m.item) {
  179. let clickHander = () => {};
  180. let moveHandler = this.showItemTooltip.bind(this, el, m.item);
  181. if (isMobile)
  182. [clickHander, moveHandler] = [moveHandler, clickHander];
  183. el.find('span')
  184. .on('mousemove', moveHandler)
  185. .on('mousedown', clickHander)
  186. .on('mouseleave', this.hideItemTooltip.bind(this));
  187. }
  188. if (m.type) {
  189. let isChannel = (['info', 'chat', 'loot', 'rep'].indexOf(m.type) === -1);
  190. if (isChannel) {
  191. if (this.find('.filter[filter="' + m.type + '"]').hasClass('active'))
  192. el.show();
  193. }
  194. if (isMobile && ['loot', 'info'].indexOf(m.type) !== -1) {
  195. events.emit('onGetAnnouncement', {
  196. msg: m.message
  197. });
  198. }
  199. }
  200. });
  201. if (!this.el.hasClass('typing') || isAtMaxScroll)
  202. container.scrollTop(9999999);
  203. },
  204. hideItemTooltip: function () {
  205. if (this.dragEl) {
  206. this.hoverCell = null;
  207. return;
  208. }
  209. events.emit('onHideItemTooltip', this.hoverItem);
  210. this.hoverItem = null;
  211. },
  212. showItemTooltip: function (el, item, e) {
  213. if (item)
  214. this.hoverItem = item;
  215. else
  216. item = this.hoverItem;
  217. if (!item)
  218. return;
  219. let ttPos = null;
  220. if (el) {
  221. ttPos = {
  222. x: ~~(e.clientX + 32),
  223. y: ~~(e.clientY)
  224. };
  225. }
  226. let bottomAlign = !isMobile;
  227. events.emit('onShowItemTooltip', item, ttPos, true, bottomAlign);
  228. },
  229. toggle: function (show, isFake, e) {
  230. if (isFake && this.hoverFilter)
  231. return;
  232. input.resetKeys();
  233. this.el.removeClass('typing');
  234. this.el.find('.main').removeClass('hasBorderShadow');
  235. let textbox = this.find('input');
  236. if (show) {
  237. this.el.addClass('typing');
  238. this.el.find('.main').addClass('hasBorderShadow');
  239. if (!config.rememberChatChannel) {
  240. this.currentChannel = 'global';
  241. this.currentSubChannel = null;
  242. }
  243. this.find('.channelPicker').html(this.currentSubChannel || this.currentChannel);
  244. textbox.focus();
  245. this.find('.list').scrollTop(9999999);
  246. } else {
  247. this.find('.channelOptions').removeClass('active');
  248. textbox.val('');
  249. this.el.removeClass('picking');
  250. if (['direct', 'custom'].includes(this.currentChannel) && (!this.currentSubChannel || ['join new', 'leave'].includes(this.currentSubChannel))) {
  251. this.currentSubChannel = null;
  252. this.currentChannel = 'global';
  253. }
  254. }
  255. if (e)
  256. e.stopPropagation();
  257. },
  258. sendChat: function (e) {
  259. let textbox = this.find('input');
  260. let msgConfig = {
  261. success: true,
  262. message: textbox.val(),
  263. event: e,
  264. cancel: false
  265. };
  266. this.processChat(msgConfig);
  267. if (msgConfig.cancel || this.el.hasClass('picking'))
  268. return false;
  269. const { which: charCode } = e;
  270. if ([9, 27].includes(charCode) || charCode !== 13) {
  271. if (charCode === 9) {
  272. e.preventDefault();
  273. textbox.val(`${textbox.val()} `);
  274. } else if (charCode === 27)
  275. this.toggle(false);
  276. return;
  277. }
  278. events.emit('onBeforeChat', msgConfig);
  279. let val = msgConfig.message
  280. .split('<').join('&lt;')
  281. .split('>').join('&gt;');
  282. if (!msgConfig.success) {
  283. this.toggle(false);
  284. return;
  285. }
  286. if (val.trim() === '') {
  287. this.toggle(false);
  288. return;
  289. }
  290. client.request({
  291. cpn: 'social',
  292. method: 'chat',
  293. data: {
  294. message: val,
  295. type: this.currentChannel,
  296. subType: this.currentSubChannel
  297. }
  298. });
  299. this.toggle();
  300. }
  301. };
  302. });