blob: 67bd409f26a07a5620edd7c92dca776a7ba2ffee [file] [log] [blame]
var mutationObserver, intersectionObserver, intersectionOptions, options,
authuser;
function removeChildNodes(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
function getNParent(node, n) {
if (n <= 0) return node;
if (!('parentNode' in node)) return null;
return getNParent(node.parentNode, n - 1);
}
function createExtBadge() {
var badge = document.createElement('div');
badge.classList.add('TWPT-badge');
badge.setAttribute(
'title', chrome.i18n.getMessage('inject_extension_badge_helper', [
chrome.i18n.getMessage('appName')
]));
var badgeI = document.createElement('i');
badgeI.classList.add('material-icon-i', 'material-icons-extended');
badgeI.textContent = 'repeat';
badge.append(badgeI);
return badge;
}
function addProfileHistoryLink(node, type, query) {
var urlpart = encodeURIComponent('query=' + query);
var authuserpart =
(authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
var container = document.createElement('div');
container.style.margin = '3px 0';
var link = document.createElement('a');
link.setAttribute(
'href',
'https://support.google.com/s/community/search/' + urlpart +
authuserpart);
link.innerText = chrome.i18n.getMessage('inject_previousposts_' + type);
container.appendChild(link);
node.appendChild(container);
}
function applyDragAndDropFix(node) {
console.debug('Adding link drag&drop fix to ', node);
node.addEventListener('drop', e => {
if (e.dataTransfer.types.includes('text/uri-list')) {
e.stopImmediatePropagation();
console.debug('Stopping link drop event propagation.');
}
}, true);
}
function nodeIsReadToggleBtn(node) {
return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
node.getAttribute('debugid') !== null &&
(node.getAttribute('debugid') == 'mark-read-button' ||
node.getAttribute('debugid') == 'mark-unread-button') &&
('parentNode' in node) && node.parentNode !== null &&
('parentNode' in node.parentNode) &&
node.parentNode.querySelector('[debugid="batchlock"]') === null &&
node.parentNode.parentNode !== null &&
('tagName' in node.parentNode.parentNode) &&
node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
}
function injectDarkModeButton(rightControl) {
var darkThemeSwitch = document.createElement('material-button');
darkThemeSwitch.classList.add('TWPT-dark-theme', 'TWPT-btn--with-badge');
darkThemeSwitch.setAttribute('button', '');
darkThemeSwitch.setAttribute(
'title', chrome.i18n.getMessage('inject_ccdarktheme_helper'));
darkThemeSwitch.addEventListener('click', e => {
chrome.storage.sync.get(null, currentOptions => {
currentOptions.ccdarktheme_switch_status =
!options.ccdarktheme_switch_status;
chrome.storage.sync.set(currentOptions, _ => {
location.reload();
});
});
});
var switchContent = document.createElement('div');
switchContent.classList.add('content');
var icon = document.createElement('material-icon');
var i = document.createElement('i');
i.classList.add('material-icon-i', 'material-icons-extended');
i.textContent = 'brightness_4';
icon.appendChild(i);
switchContent.appendChild(icon);
darkThemeSwitch.appendChild(switchContent);
var badgeContent = createExtBadge();
darkThemeSwitch.appendChild(badgeContent);
rightControl.style.width =
(parseInt(window.getComputedStyle(rightControl).width) + 58) + 'px';
rightControl.insertAdjacentElement('afterbegin', darkThemeSwitch);
}
function addBatchLockBtn(readToggle) {
var clone = readToggle.cloneNode(true);
clone.setAttribute('debugid', 'batchlock');
clone.classList.add('TWPT-btn--with-badge');
clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
clone.querySelector('material-icon').setAttribute('icon', 'lock');
clone.querySelector('i.material-icon-i').textContent = 'lock';
var badge = createExtBadge();
clone.append(badge);
clone.addEventListener('click', function() {
var modal = document.querySelector('.pane[pane-id="default-1"]');
var dialog = document.createElement('material-dialog');
dialog.setAttribute('role', 'dialog');
dialog.setAttribute('aria-modal', 'true');
dialog.classList.add('TWPT-dialog');
var header = document.createElement('header');
header.setAttribute('role', 'presentation');
header.classList.add('TWPT-dialog-header');
var title = document.createElement('div');
title.classList.add('TWPT-dialog-header--title', 'title');
title.textContent = chrome.i18n.getMessage('inject_lockbtn');
header.append(title);
var main = document.createElement('main');
main.setAttribute('role', 'presentation');
main.classList.add('TWPT-dialog-main');
var p = document.createElement('p');
p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
main.append(p);
dialog.append(header, main);
var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
for (var i = 0; i < footers.length; ++i) {
var footer = document.createElement('footer');
footer.setAttribute('role', 'presentation');
footer.classList.add('TWPT-dialog-footer');
footer.setAttribute('data-footer-id', i);
if (i > 0) footer.classList.add('is-hidden');
footers[i].forEach(action => {
var btn = document.createElement('material-button');
btn.setAttribute('role', 'button');
btn.classList.add('TWPT-dialog-footer-btn');
if (i == 1) btn.classList.add('is-disabled');
switch (action) {
case 'lock':
case 'unlock':
btn.addEventListener('click', _ => {
if (btn.classList.contains('is-disabled')) return;
var message = {
action,
prefix: 'TWPT-batchlock',
};
window.postMessage(message, '*');
});
break;
case 'cancel':
case 'close':
btn.addEventListener('click', _ => {
if (btn.classList.contains('is-disabled')) return;
modal.classList.remove('visible');
modal.style.display = 'none';
removeChildNodes(modal);
});
break;
case 'reload':
btn.addEventListener('click', _ => {
if (btn.classList.contains('is-disabled')) return;
window.location.reload()
});
break;
}
var content = document.createElement('div');
content.classList.add('content', 'TWPT-dialog-footer-btn--content');
content.textContent =
chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
btn.append(content);
footer.append(btn);
});
var clear = document.createElement('div');
clear.style.clear = 'both';
footer.append(clear);
dialog.append(footer);
}
removeChildNodes(modal);
modal.append(dialog);
modal.classList.add('visible', 'modal');
modal.style.display = 'flex';
});
readToggle.parentNode.insertBefore(
clone, (readToggle.nextSibling || readToggle));
}
function injectPreviousPostsLinks(nameElement) {
var mainCardContent = getNParent(nameElement, 3);
if (mainCardContent === null) {
console.error(
'[previousposts] Couldn\'t find |.main-card-content| element.');
return;
}
var forumId = location.href.split('/forum/')[1].split('/')[0] || '0';
var name = escapeUsername(nameElement.textContent);
var query1 = encodeURIComponent(
'(creator:"' + name + '" | replier:"' + name + '") forum:' + forumId);
var query2 = encodeURIComponent(
'(creator:"' + name + '" | replier:"' + name + '") forum:any');
var container = document.createElement('div');
container.classList.add('TWPT-previous-posts');
var badge = createExtBadge();
container.appendChild(badge);
var linkContainer = document.createElement('div');
linkContainer.classList.add('TWPT-previous-posts--links');
addProfileHistoryLink(linkContainer, 'forum', query1);
addProfileHistoryLink(linkContainer, 'all', query2);
container.appendChild(linkContainer);
mainCardContent.appendChild(container);
}
const watchedNodesSelectors = [
// App container (used to set up the intersection observer and inject the dark
// mode button)
'ec-app',
// Load more bar (for the "load more"/"load all" buttons)
'.load-more-bar',
// Username span inside ec-user (user profile view)
'ec-user .main-card .header > .name > span',
// Rich text editor
'ec-movable-dialog',
'ec-rich-text-editor',
// Read/unread bulk action in the list of thread, for the batch lock feature
'ec-bulk-actions material-button[debugid="mark-read-button"]',
'ec-bulk-actions material-button[debugid="mark-unread-button"]',
];
function handleCandidateNode(node) {
if (typeof node.classList !== 'undefined') {
if (('tagName' in node) && node.tagName == 'EC-APP') {
// Set up the intersectionObserver
if (typeof intersectionObserver === 'undefined') {
var scrollableContent = node.querySelector('.scrollable-content');
if (scrollableContent !== null) {
intersectionOptions = {
root: scrollableContent,
rootMargin: '0px',
threshold: 1.0,
};
intersectionObserver = new IntersectionObserver(
intersectionCallback, intersectionOptions);
}
}
// Inject the dark mode button
if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
var rightControl = node.querySelector('header .right-control');
if (rightControl !== null) injectDarkModeButton(rightControl);
}
}
// Start the intersectionObserver for the "load more"/"load all" buttons
// inside a thread
if ((options.thread || options.threadall) &&
node.classList.contains('load-more-bar')) {
if (typeof intersectionObserver !== 'undefined') {
if (options.thread)
intersectionObserver.observe(node.querySelector('.load-more-button'));
if (options.threadall)
intersectionObserver.observe(node.querySelector('.load-all-button'));
} else {
console.warn(
'[infinitescroll] ' +
'The intersectionObserver is not ready yet.');
}
}
// Show the "previous posts" links
// Here we're selecting the 'ec-user > div' element (unique child)
if (options.history &&
node.matches('ec-user .main-card .header > .name > span')) {
injectPreviousPostsLinks(node);
}
// Fix the drag&drop issue with the rich text editor
//
// We target both tags because in different contexts different
// elements containing the text editor get added to the DOM structure.
// Sometimes it's a EC-MOVABLE-DIALOG which already contains the
// EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
// directly.
if (options.ccdragndropfix && ('tagName' in node) &&
(node.tagName == 'EC-MOVABLE-DIALOG' ||
node.tagName == 'EC-RICH-TEXT-EDITOR')) {
applyDragAndDropFix(node);
}
// Inject the batch lock button in the thread list
if (options.batchlock && nodeIsReadToggleBtn(node)) {
addBatchLockBtn(node);
}
}
}
function mutationCallback(mutationList, observer) {
mutationList.forEach((mutation) => {
if (mutation.type == 'childList') {
mutation.addedNodes.forEach(function(node) {
handleCandidateNode(node);
});
}
});
}
function intersectionCallback(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.click();
}
});
};
var observerOptions = {
childList: true,
subtree: true,
};
chrome.storage.sync.get(null, function(items) {
options = items;
var startup =
JSON.parse(document.querySelector('html').getAttribute('data-startup'));
authuser = startup[2][1] || '0';
// Before starting the mutation Observer, check whether we missed any
// mutations by manually checking whether some watched nodes already
// exist.
var cssSelectors = watchedNodesSelectors.join(',');
document.querySelectorAll(cssSelectors)
.forEach(node => handleCandidateNode(node));
mutationObserver = new MutationObserver(mutationCallback);
mutationObserver.observe(document.body, observerOptions);
if (options.fixedtoolbar) {
injectStyles(
'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
}
if (options.increasecontrast) {
injectStyles(
'.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
}
if (options.stickysidebarheaders) {
injectStyles(
'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
}
if (options.enhancedannouncementsdot) {
injectStylesheet(
chrome.runtime.getURL('injections/enhanced_announcements_dot.css'));
}
if (options.ccforcehidedrawer) {
var drawer = document.querySelector('material-drawer');
if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
document.querySelector('.material-drawer-button').click();
}
}
if (options.batchlock) {
injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
}
});