- // ==UserScript==
- // @name Power Chat
- // @author commander
- // @description Redesigned chatbox for power users — and for those that just want a refresh
- // @namespace https://github.com/asger-finding/tanktrouble-userscripts
- // @version 0.1.4
- // @license GPL-3.0
- // @match https://tanktrouble.com/*
- // @match https://beta.tanktrouble.com/*
- // @exclude *://classic.tanktrouble.com/
- // @run-at document-end
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @require https://cdn.jsdelivr.net/npm/match-sorter@6/dist/match-sorter.umd.min.js
- // @require https://update.greasyfork.org/scripts/482092/1309109/TankTrouble%20Development%20Library.js
- // @noframes
- // ==/UserScript==
-
- GM_addStyle(`
- #chat {
- /* Disable drop shadow filter */
- filter: none;
-
- /* Transform chat location to bottom left */
- inset: calc(100% - 30px) auto auto 34px !important;
- }
-
- /* Reverse chat messages flow */
- #chat,
- #chat .content,
- #chat .body {
- display: flex;
- flex-direction: column-reverse;
- }
-
- #chat .body {
- align-items: end;
- background: #00000014;
- border-image: linear-gradient(90deg, rgb(0 0 0 / 20%), #0000) 4 7 3 / 0 0 1pt 0 / 0;
- border-radius: 3px;
- direction: rtl;
- margin-bottom: 4px;
- margin-top: 0 !important;
- mask-image: linear-gradient(to top, rgb(0 0 0) 70%, rgb(0 0 0 / 11%));
- overflow: hidden;
- padding-right: 10px;
- pointer-events: visible;
-
- /* Scrollbar */
- scrollbar-gutter: stable;
- top: 0 !important;
- }
-
- #chat .content {
- position: relative;
- width: fit-content !important;
- }
-
- #chat .status.button {
- cursor: initial;
- transform: translate(7px, -18px);
- z-index: 1;
- }
-
- #chat form {
- background: #ececec;
- border-image: linear-gradient(90deg, rgb(0 0 0 / 20%), #0000) 4 7 3 / 0 0 1pt 0 / 1pt;
- margin-left: 20px;
- width: 200px;
- }
-
- /* Disable chat message sending animation */
- #chat form[style*="repeating-linear-gradient"] {
- background: #d0d0d0 !important;
- }
-
- #chat:not(.open) form {
- display: none;
- }
-
- #chat textarea {
- font-family: Arial, verdana;
- left: 5px;
- transition: width 0s !important;
- width: calc(100% - 12px);
- }
-
- #chat .body .chatMessage svg {
- border-left: 2px dotted rgb(170 170 170);
- padding: 2px 4px 1px;
- }
-
- #chat .body.dragging {
- border: none !important;
- margin-left: 20px !important;
- }
-
- /* Rotate and align the handle to top-right */
- .handle.ui-resizable-ne[src*="resizeHandleBottomRight.png"] {
- height: 12px !important;
- position: absolute;
- right: 0;
- top: 0;
- transform: rotate(-90deg);
- width: 12px;
- }
-
- body:has(#chat .body.ui-resizable-resizing) .ui-resizable-handle.handle.ui-resizable-ne {
- display: none !important;
- }
-
- #chat .body:hover {
- overflow-y: scroll;
- }
-
- #chat .body .chatMessage {
- margin-left: ${(/Chrome.*Safari/u).test(navigator.userAgent) ? '3px' : '5px'};
- direction: ltr;
- }
-
- #chat .body::-webkit-scrollbar {
- width: 3px;
- }
-
- #chat .body::-webkit-scrollbar-track {
- background: transparent;
- }
-
- #chat .body::-webkit-scrollbar-thumb {
- background: rgb(170 170 170);
- }
-
- #chat form .autocomplete-dropdown {
- background-color: #00ff02;
- border-radius: 3px;
- bottom: 0;
- filter: drop-shadow(0 0 3px rgb(0 0 0 / 70%));
- font-family: Arial, verdana;
- margin-bottom: 25px;
- max-height: 120px;
- max-width: 200px;
- min-width: 120px;
- overflow-y: scroll;
- padding: 4px 2px;
- position: absolute;
- scrollbar-color: #00a902 transparent;
- scrollbar-gutter: stable;
- scrollbar-width: thin;
- white-space: nowrap;
- z-index: 999;
- }
-
- #chat form .autocomplete-dropdown div {
- border-bottom: 1pt dotted #00a902;
- cursor: pointer;
- display: none;
- margin-bottom: 2px;
- overflow: hidden;
- padding: 0 8px 2px 4px;
- text-overflow: ellipsis;
- }
-
- #chat form .autocomplete-dropdown .match {
- display: block;
- }
-
- #chat form .autocomplete-dropdown .match:not(:has(~ .match)) {
- border-bottom: none;
- padding: 0 8px 0 4px;
- }
-
- #chat form .autocomplete-dropdown .highlight {
- font-weight: bold;
- }
-
- #chat form .autocomplete-dropdown:hover .highlight {
- font-weight: normal;
- }
-
- #chat form .autocomplete-dropdown div:hover {
- font-weight: bold !important;
- }
-
- #chat form .autocomplete-dropdown:has(div:not(.highlight):hover) > .highlight {
- font-weight: normal;
- }
-
- #chat form .autocomplete-caret-mirror {
- background: transparent;
- color: transparent;
- font-family: Arial, verdana;
- font-size: inherit;
- font-weight: bold;
- height: 0;
- margin: 0 0 0 5px;
- opacity: 0;
- padding: 1px 2px;
- pointer-events: none;
- z-index: -2147483647;
- }
- `);
-
- /**
- * Reconfigure the chat handle to be dragging
- * from the south-east direction (down)
- * to the north-east direction (up)
- */
- const changeHandleDirection = async() => {
- const { resizable } = $.fn;
-
- // Use a regular function to keep context
- $.fn.resizable = function(...args) {
- const [config] = args;
-
- // Reassign the chat handle to be north-east facing
- if (config.handles) {
- const handle = config.handles.se;
- if (handle === TankTrouble.ChatBox.chatBodyResizeHandle) {
- handle.removeClass('ui-resizable-se')
- .addClass('ui-resizable-ne');
-
- config.handles.ne = handle;
- delete config.handles.se;
-
- // Set a taller chat maxHeight
- config.maxHeight = 650;
- }
- }
-
- return resizable.call(this, config);
- };
-
- await Loader.whenContentInitialized();
-
- TankTrouble.ChatBox.chatBodyResizeHandle.detach().insertAfter(TankTrouble.ChatBox.chatBody);
- };
-
- /**
- * Hook message render functions to disable jquery .show() animation and scroll to bottom
- * This fixes chat messages not showing up in the reversed chat order or overflowed messages being cleared
- */
- const fixChatRendering = () => {
- Loader.interceptFunction(TankTrouble.ChatBox, '_renderChatMessage', (original, ...args) => {
- TankTrouble.ChatBox.chatBody.scrollTop(TankTrouble.ChatBox.chatBody.height());
-
- // Set animateHeight to false
- args[9] = false;
- original(...args);
- });
-
- Loader.interceptFunction(TankTrouble.ChatBox, '_renderSystemMessage', (original, ...args) => {
- TankTrouble.ChatBox.chatBody.scrollTop(TankTrouble.ChatBox.chatBody.height());
-
- // Set animateHeight to false
- args[3] = false;
- original(...args);
- });
- };
-
- /**
- * Prevent TankTrouble from clearing the chat when the client disconnects
- * Print message to chat when client switches server to separate conversations
- */
- const preventServerChangeChatClear = () => {
- Loader.interceptFunction(TankTrouble.ChatBox, '_clearChat', (original, ...args) => {
- const isUnconnected = ClientManager.getClient().getState() === TTClient.STATES.UNCONNECTED;
-
- // Void the call if the client is unconnected
- // when the function is invoked
- if (isUnconnected) return null;
-
- return original(...args);
- });
-
- Loader.interceptFunction(TankTrouble.ChatBox, '_updateStatusMessageAndAvailability', (original, ...args) => {
- const [systemMessageText, guestPlayerIds] = args;
-
- // Check for a welcome message. If match.
- // print a different system message
- if (systemMessageText === 'Welcome to TankTrouble Comms § § ') {
- const newServer = ClientManager.getAvailableServers()[ClientManager.multiplayerServerId];
- return original(`Connected to ${ newServer.name } ${ guestPlayerIds.length ? '§ ' : '' }`, guestPlayerIds);
- }
-
- return original(...args);
- });
- };
-
- /**
- * Write the chat savestate to storage and return
- * @returns Promise for last savestate
- */
- const initChatSavestate = async() => {
- // Initialize dynamic stylesheet
- // for user-defined chat width
- const inputWidth = new CSSStyleSheet();
- inputWidth.insertRule('#chat form { padding-right: 12px !important; }', 0);
- inputWidth.insertRule('#chat form, #chat textarea { width: 208px !important; }', 1);
- document.adoptedStyleSheets = [inputWidth];
-
- // Savestate hooks
- Loader.interceptFunction(TankTrouble.ChatBox, 'open', (original, ...args) => {
- GM_setValue('chat-open', true);
- original(...args);
- });
- Loader.interceptFunction(TankTrouble.ChatBox, 'close', (original, ...args) => {
- GM_setValue('chat-open', false);
- original(...args);
- });
- Loader.interceptFunction(TankTrouble.ChatBox, '_refreshChat', (original, ...args) => {
- original(...args);
- GM_setValue('chat-width', TankTrouble.ChatBox.chatBody[0].clientWidth);
- });
-
- // Get savestate
- const shouldOpen = await GM_getValue('chat-open', true);
- const initialWidth = await GM_getValue('chat-width', 0);
-
- Loader.whenContentInitialized().then(() => {
- /* eslint-disable prefer-destructuring */
- const chatBody = TankTrouble.ChatBox.chatBody[0];
- const chatInput = TankTrouble.ChatBox.chatInput[0];
- /* eslint-enable prefer-destructuring*/
-
- if (shouldOpen) TankTrouble.ChatBox.open();
- if (initialWidth !== 0) chatBody.style.width = `${initialWidth}px`;
-
- // Create a mutation observer that looks for
- // changes in the chatBody's attributes
- new MutationObserver(() => {
- const width = Number(chatBody.offsetWidth || 220);
-
- inputWidth.deleteRule(1);
- inputWidth.insertRule(`#chat form, #chat form textarea { width: ${width - 12}px !important; }`, 1);
-
- chatInput.dispatchEvent(new InputEvent('input'));
- }).observe(chatBody, {
- attributes: true,
- characterData: false
- });
- });
- };
-
- /**
- * Add up/down history for sent messages
- * @param chatInput Input to target
- */
- const addInputHistory = chatInput => {
- const messages = [];
- let currentInputValue = chatInput.value;
-
- // Create and initialize chat messages history iterator
- let i = messages.length;
- const iterator = (function* chatsIterator() {
- while (true) {
- const incOrDec = (yield messages[i]) === 'prev' ? -1 : 1;
- i = Math.min(Math.max(i + incOrDec, 0), messages.length);
- }
- }(messages));
-
- // Initialize the generator
- iterator.next();
-
- /**
- * Check whether or not the input has an empty selection range
- * @returns Selection range is 0
- */
- const isSelectionEmpty = () => chatInput.selectionStart === chatInput.selectionEnd;
-
- /** Handle the user triggering a submit keydown event */
- const handleSubmit = () => {
- if (!chatInput.value) return;
-
- messages.push(chatInput.value);
- currentInputValue = '';
-
- i = messages.length;
- };
-
- /** Handle the user triggering an arrow up keydown event */
- const handleArrowUp = () => {
- if (isSelectionEmpty() && chatInput.selectionStart === 0) {
- const { value } = iterator.next('prev');
- chatInput.value = typeof value === 'undefined' ? '' : value;
-
- chatInput.setSelectionRange(chatInput.value.length, chatInput.value.length);
- chatInput.dispatchEvent(new InputEvent('input', { isComposing: true }));
- }
- };
-
- /** Handle the user triggering an arrow down keydown event */
- const handleArrowDown = () => {
- if (isSelectionEmpty() && chatInput.selectionStart === chatInput.value.length) {
- const { value } = iterator.next();
- chatInput.value = typeof value === 'undefined' ? currentInputValue : value;
-
- chatInput.setSelectionRange(chatInput.value.length, chatInput.value.length);
- chatInput.dispatchEvent(new InputEvent('input', { isComposing: true }));
- }
- };
-
- // If the user is at the top of the history,
- // save the chat input value as the "current"
- // message whenever there is a change
- chatInput.addEventListener('input', ({ inputType }) => {
- const isAtEndOfHistory = i === messages.length;
- const hasValueChanged = typeof inputType !== 'undefined';
- if (isAtEndOfHistory && hasValueChanged) currentInputValue = chatInput.value;
- });
-
- // Listen for keydown events
- // and trigger handlers
- chatInput.addEventListener('keydown', ({ key }) => {
- switch (key) {
- case 'Enter':
- handleSubmit();
- break;
- case 'ArrowUp':
- handleArrowUp();
- break;
- case 'ArrowDown':
- handleArrowDown();
- break;
- default:
- break;
- }
- });
- };
-
- /**
- * Add auto-complete for user mentions when typing @ in the chat input
- * @param chatInput Chat input instance
- */
- const addMentionAutocomplete = chatInput => {
- class Dropdown {
-
- options = new Map();
-
- matches = [];
-
- /**
- * Setup the dropdown class
- * @param input Input to attach to
- * @param config Dropdown configuration (allow multiple of the same value, expiry time)
- */
- constructor(input, config) {
- this.input = $(input);
- this.wrapper = $('<div class="autocomplete-dropdown" tabindex="-1"></div>').insertAfter(this.input);
- this.textareaMirror = $('<div class="autocomplete-caret-mirror"></div>').appendTo(this.wrapper.parent());
- this.textareaMirrorInline = $('<span></span>').appendTo(this.textareaMirror);
-
- Object.assign(this, {
- allowRepeats: false,
- autofillLifetime: 10 * 60 * 100,
- inputHeight: 18,
- ...config
- });
-
- this.wrapper.insertAfter(this.input);
-
-
- this.hide();
- }
-
- #searchTerm = -1;
-
- /**
- * Filter the dropdown elements when searchterm is set
- * @param term String term to search the dropdown registry for
- * @returns term
- */
- set searchTerm(term) {
- if (this.#searchTerm !== term) {
- this.#removeExpired();
-
- const allSymbols = Array.from(this.options.keys());
- this.matches = matchSorter.matchSorter(allSymbols, term, { keys: [symbol => symbol.description] });
- for (const symbol of allSymbols) {
- const element = this.options.get(symbol).value;
-
- element.classList[this.matches.includes(symbol) ? 'add' : 'remove']('match');
- }
- for (const symbol of this.matches) this.wrapper.append(this.options.get(symbol).value);
-
- this.#resetToFirst();
- }
-
- this.#searchTerm = term;
- return term;
- }
-
- /**
- * Getter for `searchTerm`
- * @returns `searchTerm`
- */
- get searchTerm() {
- return this.#searchTerm;
- }
-
- iterator = (function* (options, that) {
- let i = 0;
- while (true) {
- const symbol = that.matches[i];
-
- const change = (yield [symbol, options.get(symbol)]) || 0;
-
- i = (i = (i + change) % Math.max(that.matches.length, 1)) < 0
- ? i + that.matches.length
- : i;
- }
- }(this.options, this));
-
- /** Render the dropdown if not already visible */
- show() {
- if (this.isShowing()) return;
-
- this.#resetToFirst();
-
- this.wrapper.show();
- this.wrapper.scrollTop(0);
- }
-
- /** Hide the dropdown */
- hide() {
- this.wrapper.hide();
- }
-
- /**
- * Check if the dropdown is visible
- * @returns Is the dropdown showing?
- */
- isShowing() {
- return this.wrapper.is(':visible');
- }
-
- /**
- * Compute dropdown x-shift to textarea value.
- *
- * Should be called when value changes in the input field
- */
- update() {
- const transformed = this.input.val()
- .substr(0, this.input[0].selectionStart);
- this.textareaMirrorInline.html(transformed);
-
- const rects = this.textareaMirrorInline[0].getBoundingClientRect();
- const left = rects.right - rects.x;
- this.left = left
- + Dropdown.#toNumeric(this.input.css('left'))
- + Dropdown.#toNumeric(this.input.css('margin-left'))
- + Dropdown.#toNumeric(this.input.css('padding-left'));
-
- const isWordWrapped = this.#isWordWrapped();
- const leftShift = isWordWrapped ? 0 : Math.max(0, this.left - (this.wrapper.width() / 2));
- const bottomShift = this.input.outerHeight() - this.inputHeight;
- this.wrapper.css('margin-left', `${leftShift}px`);
- this.wrapper.css('margin-bottom', `${bottomShift + 25}px`);
-
- if (!this.isShowing()) this.show();
- }
-
- /**
- * Get data for the current position
- * @returns Identifier and data for the current dropdown position
- */
- getCurrent() {
- return this.iterator.next(0).value;
- }
-
- /**
- * Add an autocomplete option to the dropdown
- * @param option Option as string
- * @param submitCallback Event handler for mouseup
- * @returns Success in adding option?
- */
- addOption(option, submitCallback) {
- const overrideSymbol = !this.allowRepeats
- && Array.from(this.options.keys())
- .find(({ description }) => description === option);
- const symbolExists = typeof overrideSymbol === 'symbol';
-
- if (symbolExists) return false;
-
- const symbol = Symbol(option);
-
- const element = document.createElement('div');
- element.innerText = option;
- element.addEventListener('mouseup', evt => submitCallback(evt, evt.target.innerText));
-
- const insert = [
- symbol,
- {
- inserted: Date.now(),
- lifetime: this.autofillLifetime,
- value: element
- }
- ];
-
- this.options.set(...insert);
-
- return true;
- }
-
- /**
- * Add an array of text options to the dropdown
- * @param options Options as string[]
- * @param submitCallback Generalized event handler for mouseup for all options
- */
- addOptions(options, submitCallback) {
- for (const option of options) this.addOption(option, submitCallback);
- }
-
- /**
- * Remove option and corresponding HTMLElement from DOM
- * @param symbol Symbol for element to remove
- * @returns Was the option deleted?
- */
- removeOption(symbol) {
- this.options.get(symbol)?.value.remove();
- this.matches = this.matches.filter(toRemove => toRemove !== symbol);
- return this.options.delete(symbol);
- }
-
- /**
- * Clear all options from the dropdown
- * @returns Did options clear?
- */
- clearOptions() {
- for (const symbol of this.options.keys()) this.removeOption(symbol);
-
- return this.options.size === 0
- && this.wrapper.children().length === 0;
- }
-
- /**
- * Navigate position in the dropdown up/down
- * @param direction Up/down shift as number
- * @returns Identifier for where we navigated to
- */
- navigate(direction) {
- this.wrapper.children().removeClass('highlight');
-
- const [symbol, data] = this.iterator.next(direction).value;
- if (!symbol) return null;
-
- data.value.classList.add('highlight');
- data.value.scrollIntoView(false);
-
- return symbol;
- }
-
- /**
- * Check if the input wraps to newline
- * @returns Whether the input is one or multiple lines
- */
- #isWordWrapped() {
- return this.input.outerHeight() <= this.inputHeight;
- }
-
- /**
- * Reset the position to the
- * first item in the dropdown
- */
- #resetToFirst() {
- const symbols = this.matches;
- const [currentSymbol] = this.iterator.next(0).value;
- const dist = symbols.indexOf(currentSymbol);
-
- this.navigate(-dist);
- }
-
- /**
- * Remove expired entries
- */
- #removeExpired() {
- for (const [symbol, value] of this.options.entries()) {
- const expiry = value.inserted + value.autofillLifetime;
- if (Date.now() > expiry) this.removeOption(symbol);
- }
- }
-
- /**
- * Remove all non-numbers from string and return string as number
- * @param str String to parse
- * @returns String in number format
- */
- static #toNumeric = str => Number(str.replace(/[^0-9.]/ug, ''));
-
- }
-
- const dropdown = new Dropdown(chatInput);
-
- /**
- * Get the word and start/end indexies of the input selectionEnd
- * @returns Object with word and range start/end
- */
- const getIndexiesOfWordInCurrentSelection = () => {
- // Separate string by whitespace and
- // list indexies for each word in array
- const tokenizedQuery = chatInput.value.split(/[\s\n]/u).reduce((acc, word, index) => {
- const previous = acc[index - 1];
- const start = index === 0 ? index : previous.range[1] + 1;
- const end = start + word.length;
-
- return acc.concat([ { word, range: [start, end] } ]);
- }, []);
-
- const currentWord = tokenizedQuery.find(({ range }) => range[0] < chatInput.selectionEnd && range[1] >= chatInput.selectionEnd);
-
- return currentWord;
- };
-
- /**
- * Returns the user that the selection is over, from the input value, if prefixed by a @
- * @returns Mention username or null
- */
- const getUserFocusIfMention = () => {
- const currentWord = getIndexiesOfWordInCurrentSelection();
- const [mentions] = chatInput.value.split(/\s+(?=[^@])/u);
- const isUserChat = mentions.startsWith('@');
-
- if (currentWord && isUserChat) {
- const [, end] = currentWord.range;
- return end <= mentions.length ? currentWord : null;
- }
-
- return null;
- };
-
- /**
- * Handle a dropdown submit event (enter, tab or click)
- * by autofilling the value to the input field
- * @param evt Event object
- * @param username Username to autofill
- */
- const handleSubmit = (evt, username = dropdown.getCurrent()[0].description) => {
- const mention = getUserFocusIfMention();
- if (mention === null) return;
-
- const [start, end] = mention.range;
- if (username) {
- const before = chatInput.value.slice(0, start);
- const after = chatInput.value.substring(end, chatInput.value.length);
-
- const insertSpaceAfter = !after.startsWith(' ');
-
- const beforeValue = `${ before }@${ username }${ insertSpaceAfter ? ' ' : '' }`;
- const cursorPosition = [beforeValue.length + 1, beforeValue.length + 1];
- chatInput.value = `${ beforeValue }${ after }`;
-
- chatInput.setSelectionRange(...cursorPosition);
- }
-
- evt.preventDefault();
-
- chatInput.dispatchEvent(new InputEvent('input'));
- };
-
- /**
- * Event handler for TTClient.EVENTS.GAME_LIST_CHANGED
- */
- const handleGameListChanged = () => {
- const gameStates = ClientManager.getClient().getAvailableGameStates();
-
- for (const gameState of gameStates) {
- const playerStates = gameState.getPlayerStates();
-
- for (const player of playerStates) {
- const playerId = player.getPlayerId();
-
- Backend.getInstance().getPlayerDetails(result => {
- if (typeof result === 'object') dropdown.addOption(result.getUsername(), handleSubmit);
- }, () => {}, () => {}, playerId, Caches.getPlayerDetailsCache());
- }
- }
- };
-
- /**
- * Event handler for received chat messages
- * @param data Event data
- */
- const handleNewChatMessage = data => {
- const involvedPlayerIds = data.involvedPlayerIds || [...data.getFrom() || [], ...data.getTo() || []];
- const loggedIn = Users.getAllPlayerIds();
- const foreignPlayerIds = involvedPlayerIds.filter(playerId => !loggedIn.includes(playerId));
-
- for (const playerId of foreignPlayerIds) {
- Backend.getInstance().getPlayerDetails(result => {
- if (typeof result === 'object') dropdown.addOption(result.getUsername(), handleSubmit);
- }, () => {}, () => {}, playerId, Caches.getPlayerDetailsCache());
- }
- };
-
- chatInput.addEventListener('input', ({ isComposing }) => {
- if (isComposing) return;
-
- const userFocus = getUserFocusIfMention();
- if (userFocus === null) {
- dropdown.hide();
- return;
- }
-
- dropdown.searchTerm = userFocus.word.replace(/^@/u, '');
- if (!dropdown.matches.length) {
- dropdown.hide();
- return;
- }
-
- // Show UI
- dropdown.show();
- dropdown.update();
- });
-
- // eslint-disable-next-line complexity
- chatInput.addEventListener('keydown', evt => {
- const userFocus = getUserFocusIfMention();
- if (userFocus === null) return;
-
- dropdown.searchTerm = userFocus.word.replace(/^@/u, '');
- if (!dropdown.matches.length) return;
-
- switch (evt.key) {
- case 'Enter':
- case 'Tab':
- handleSubmit(evt);
- break;
- case 'ArrowUp':
- dropdown.navigate(-1);
- evt.preventDefault();
- break;
- case 'ArrowDown':
- dropdown.navigate(1);
- evt.preventDefault();
- break;
- default:
- break;
- }
- }, false);
-
- /**
- * State change event handler
- * @param _self Self reference
- * @param _oldState Old client state
- * @param newState New client state
- */
- const clientStateEventHandler = (_self, _oldState, newState) => {
- switch (newState) {
- case TTClient.STATES.UNCONNECTED:
- dropdown.clearOptions();
- break;
- default:
- break;
- }
- };
-
- /**
- * Event handler for new chat messages
- * @param _self Self reference
- * @param evt Event type
- * @param data Event data
- */
- // eslint-disable-next-line complexity
- const clientEventHandler = (_self, evt, data) => {
- switch (evt) {
- case TTClient.EVENTS.GAME_LIST_CHANGED:
- handleGameListChanged();
- break;
- case TTClient.EVENTS.USER_CHAT_POSTED:
- if (data) handleNewChatMessage(data);
- break;
- case TTClient.EVENTS.GLOBAL_CHAT_POSTED:
- case TTClient.EVENTS.CHAT_POSTED:
- if (data) handleNewChatMessage(data);
- break;
- case TTClient.EVENTS.SYSTEM_CHAT_POSTED:
- case TTClient.EVENTS.PLAYERS_BANNED:
- case TTClient.EVENTS.PLAYERS_UNBANNED:
- if (data) handleNewChatMessage(data);
- break;
- default:
- break;
- }
- };
-
- ClientManager.getClient().addStateChangeListener(clientStateEventHandler, this);
- ClientManager.getClient().addEventListener(clientEventHandler, this);
- };
-
- changeHandleDirection();
- fixChatRendering();
- initChatSavestate();
-
- Loader.whenContentInitialized().then(() => {
- // eslint-disable-next-line prefer-destructuring
- const chatInput = TankTrouble.ChatBox.chatInput[0];
-
- preventServerChangeChatClear();
- addMentionAutocomplete(chatInput);
- addInputHistory(chatInput);
-
- // Allow more characters in the chat input
- chatInput.setAttribute('maxlength', '255');
- });