|
|
|
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;
|
|
|
|
|
|
|
|
input.dispatchEvent(new Event('change', {'bubbles': true}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
'<span style="font-weight: bold">$&</span>');
|
|
|
|
|
|
|
|
if (messageContent.innerHTML !== updated) {
|
|
|
|
messageContent.innerHTML = updated;
|
|
|
|
messageContent.parentNode.classList.add('highlighted');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})).observe(document, {childList: true, subtree: true});
|