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.
 
 
 

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