function setChatWidth(width) { document.querySelector('body').style.setProperty("--chat-width", width + 'px'); } function setHighlightColor(color) { document.querySelector('body').style.setProperty("--highlight-color", color); } chrome.storage.onChanged.addListener((changes, areaName) => { if ('chatWidth' in changes) { setChatWidth(changes.chatWidth.newValue); } if ('highlightColor' in changes) { setHighlightColor(changes.highlightColor.newValue); } }); chrome.storage.sync.get(null, (settings) => { if ('chatWidth' in settings) { setChatWidth(settings.chatWidth); } if ('highlightColor' in settings) { setHighlightColor(settings.highlightColor); } }); class TabCompletion { constructor() { this.ownUsernameRegExp = null; this.usernames = []; } setOwnUsername(username) { this.ownUsernameRegExp = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi'); } addUsername(username) { if (!this.usernames.includes(username)) { this.usernames.push(username); } } complete(input, start, symbol, choices) { let partial = input.value.substring(start + 1, input.selectionStart).trim(); if (partial === this[`suggestion_${symbol}`]) { partial = this[`partial_${symbol}`]; } else { this[`partial_${symbol}`] = partial; } const possibilities = choices.filter((choice) => choice.toLowerCase().startsWith(partial.toLowerCase())); if (this[`completionIndex_${symbol}`] === undefined || ++this[`completionIndex_${symbol}`] >= possibilities.length) { this[`completionIndex_${symbol}`] = 0; } if (possibilities.length > 0) { this[`suggestion_${symbol}`] = possibilities[this[`completionIndex_${symbol}`]].trim(); input.value = input.value.substring(0, start + 1) + this[`suggestion_${symbol}`] + ' ' + input.value.substring(input.selectionStart); input.selectionStart = start + this[`suggestion_${symbol}`].length + 2; input.selectionEnd = input.selectionStart; } } attempt(input) { const at = input.value.lastIndexOf('@', input.selectionStart - 1); let colon = input.value.lastIndexOf(':', input.selectionStart - 1); // ignore colon at the end of emoji shortname if (colon !== -1) { const match = emojis.shortnameRegExp.exec(input.value); if (match !== null) { colon = match.index; } } if (at !== -1 && at > colon) { this.complete(input, at, '@', this.usernames); } else if (colon !== -1 && colon > at) { this.complete(input, colon, ':', emojis.shortnames.map((s => s + ':'))); } else { return false; } return true; } } const tabCompletion = new TabCompletion(); (new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { for (let node of mutation.addedNodes) { if (node.nodeType === 1 && (username = node.querySelector('span.user-name')) !== null) { (new MutationObserver((mutationsList, observer) => { for (let mutation of mutationsList) { tabCompletion.setOwnUsername(mutation.target.data.trim()); } })).observe(username, {characterData: true, subtree: true}); } if (node.nodeType === 1 && (chatInput = node.querySelector('textarea#input-chat')) !== null) { chatInput.addEventListener('keydown', (event) => { if (event.key == 'Tab' && tabCompletion.attempt(event.currentTarget)) { event.preventDefault(); } }); } if (node.nodeType === 1 && (username = node.querySelector('a.name')) !== null) { if (!username.text.trim().startsWith('{')) { tabCompletion.addUsername(username.text.trim()); } } if (node.nodeType === 1 && (messageContent = node.querySelector('div.message-content-main')) !== null) { if (tabCompletion.ownUsernameRegExp !== null) { const updated = messageContent.innerHTML.replace(tabCompletion.ownUsernameRegExp, '$&'); if (messageContent.innerHTML !== updated) { messageContent.innerHTML = updated; messageContent.parentNode.classList.add('highlighted'); } } } } } })).observe(document, {childList: true, subtree: true});