User:TonySt/av-dark.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | This user script seems to have a documentation page at User:TonySt/av-dark. |
class AntiVandalDarkMode {
constructor() {
this.options_color = mw.storage.store.getItem("AntiVandalDarkModeColor");
if (!this.options_color) {
this.options_color = 'auto'; //default auto
}
this.options_cascadia_font = mw.storage.store.getItem("AntiVandalDarkModeFont");
if (!this.options_cascadia_font) {
this.options_cascadia_font = 'on'; // Default to 'on'
}
}
addFontLinks() {
const head = document.head;
const link1 = document.createElement('link');
link1.rel = 'preconnect';
link1.href = 'https://fonts.googleapis.com';
head.appendChild(link1);
const link2 = document.createElement('link');
link2.rel = 'preconnect';
link2.href = 'https://fonts.gstatic.com';
link2.crossOrigin = 'anonymous';
head.appendChild(link2);
const link3 = document.createElement('link');
link3.rel = 'stylesheet';
link3.href = 'https://fonts.googleapis.com/css2?family=Cascadia+Mono:ital,wght@0,200..700;1,200..700&display=swap';
head.appendChild(link3);
}
appendStyles() {
document.querySelectorAll('style').forEach(styleElement => {
styleElement.textContent += `
/* custom avdark */
.av-dark,
.av-dark .diffActionContainer,
.av-dark .queueControls,
.av-dark .diffToolbar,
.av-dark .diffContainer,
.av-dark .diffToolbarItem,
.av-dark .diffActionBox,
.av-dark .settingsContainer,
.av-dark .changelogContainer,
.av-dark .diffChangeContainer tbody {
background-color:#303841;
color:#d8dee9;
}
.av-black,
.av-black .diffActionContainer,
.av-black .queueControls,
.av-black .diffToolbar,
.av-black .diffContainer,
.av-black .diffToolbarItem,
.av-black .diffActionBox,
.av-black .settingsContainer,
.av-black .changelogContainer,
.av-black .diffChangeContainer tbody {
background-color:#000000;
color:#ffffff;
}
.av-dark .av-dark-mono .diffChangeContainer table,
.av-dark .av-dark-mono .diffChangeContainer tbody,
.av-black .av-dark-mono .diffChangeContainer table,
.av-black .av-dark-mono .diffChangeContainer tbody
{
font-family:"Cascadia Mono", sans-serif;
font-size:14px;
}
.av-dark .queueItemChange, .av-black .queueItemChange {
background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
}
.av-dark .queueItemTag, .av-black .queueItemTag {
background-color:#000;
}
.av-dark .currentQueueItem, .av-dark .diffProgressBar, .av-dark .settingsSectionSelected, .av-dark .setControls, .av-dark .ores,
.av-black .currentQueueItem, .av-black .diffProgressBar, .av-black .settingsSectionSelected, .av-black .setControls, .av-black .ores {
background-color: #303030;
color:#d8dee9;
}
.av-dark div ::selection, .av-black div ::selection {
background-color:#d8dee9;
color:#303841;
}
.av-dark a, .av-black a {
color:#fff;
}
.darkModeSwitch {
display: flex;
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
width: fit-content;
}
.darkModeSwitch input[type="radio"] {
display: none;
}
.darkModeSwitch label {
flex: 1;
padding: 10px 5px;
text-align: center;
cursor: pointer;
background-color: #f0f0f0;
color: #333;
transition: background-color 0.2s, color 0.2s;
border-right: 1px solid #ccc;
}
/* Remove border from the last label */
.darkModeSwitch label:last-of-type {
border-right: none;
}
/* Style for when a radio button is checked */
.darkModeSwitch input[type="radio"]:checked + label {
background-color: #007bff; /* Highlight color for selected option */
color: white;
font-weight: bold;
}
/* Hover effect for labels */
.darkModeSwitch label:hover {
background-color: #e0e0e0;
color: #111;
}
.darkModeSwitch input[type="radio"]:checked + label:hover {
background-color: #0056b3; /* Darker hover for selected */
}
.toggleOptionContainer {
display: grid;
margin: 25px 0;
grid-template-columns: auto auto;
align-items: center;
border: 1px solid gray;
border-radius: 5px;
padding: 5px;
justify-content: space-between;
}
/* For WebKit browsers (Chrome, Safari, newer Edge, Opera) */
::-webkit-scrollbar {
display: none;
width: 0; /* Also set width to 0 to remove space */
height: 0; /* For horizontal scrollbars, if applicable */
}
/* For Firefox */
* {
scrollbar-width: none; /* "none" hides the scrollbar */
}
/* For Internet Explorer and older Edge */
* {
-ms-overflow-style: none; /* "none" hides the scrollbar */
}
`;
});
}
appendInitialStyles() {
document.querySelectorAll('style').forEach(styleElement => {
styleElement.textContent += `
.av-dark, .av-dark button.start {
background-color:#303841;
color:#d8dee9;
}
.av-black, .av-black button.start { /* Apply black mode to initial splash elements if selected */
background-color:#000000;
color:#ffffff;
}
.av-dark button.start, .av-black button.start {
border: 1px solid #34c6eb;
border-radius: 5%;
}
.av-dark div ::selection, .av-black div ::selection {
background-color:#d8dee9;
color:#303841;
}
.av-dark a, .av-black a {
color:#fff;
}
`;
});
}
/**
* Creates a group of radio buttons for a toggle switch.
* @param {HTMLElement} parentDiv - The div to prepend the toggle group to.
* @param {Array<Object>} optionsConfig - An array of objects, each defining an option {id, value, text}.
* @param {string} storageKey - The key to use for storing the selected value in mw.storage.
* @param {Function} applyFunction - The function to call when an option is selected, passing the new value.
* @param {string} currentSelectedValue - The currently selected value for this toggle group.
* @returns {HTMLElement} The created toggle container element.
*/
createToggleGroup(labelString, parentDiv, optionsConfig, storageKey, applyFunction, currentSelectedValue) {
const toggleOptionContainer = document.createElement('div');
const toggleBox = document.createElement('div');
const toggleLabelSpan = document.createElement('span');
toggleOptionContainer.className = 'toggleOptionContainer';
toggleBox.className = 'darkModeSwitch'; // Reusing the same class for styling
toggleLabelSpan.innerHTML = labelString;
optionsConfig.forEach(option => {
const input = document.createElement('input');
input.type = 'radio';
input.id = option.id;
input.name = storageKey; // Use storageKey as name for radio group
input.value = option.value;
const label = document.createElement('label');
label.htmlFor = option.id;
label.textContent = option.text;
if (option.value === currentSelectedValue) {
input.checked = true;
}
toggleBox.appendChild(input);
toggleBox.appendChild(label);
input.addEventListener('change', (event) => {
console.log(`${storageKey} setting changed to: ${event.target.value}`);
mw.storage.store.setItem(storageKey, event.target.value, 600); // 604800);
applyFunction(event.target.value);
});
});
// Prepend each toggle group to the interfaceSettingsDiv
toggleOptionContainer.append(toggleLabelSpan);
toggleOptionContainer.append(toggleBox);
parentDiv.append(toggleOptionContainer)
return toggleOptionContainer;
}
/**
* Adds the dark mode options and Cascadia font options to the interface settings.
*/
addOptions() {
const interfaceSettingsDiv = document.querySelector('.interfaceSettings');
if (interfaceSettingsDiv) {
// Color Mode Toggle
const colorOptions = [
{ id: 'autoMode', value: 'auto', text: 'Auto' },
{ id: 'lightMode', value: 'light', text: 'Light' },
{ id: 'darkMode', value: 'dark', text: 'Dark' },
{ id: 'blackMode', value: 'black', text: 'Black' }
];
this.createToggleGroup('Mode',interfaceSettingsDiv, colorOptions, 'AntiVandalDarkModeColor', this.applyMode.bind(this), this.options_color);
// Cascadia Font Mode Toggle
const cascadiaOptions = [
{ id: 'cascadiaOn', value: 'on', text: 'on' },
{ id: 'cascadiaOff', value: 'off', text: 'off' }
];
this.createToggleGroup('Use Cascadia Font in Dark Modes',interfaceSettingsDiv, cascadiaOptions, 'AntiVandalDarkModeFont', this.applyCascadiaFontMode.bind(this), this.options_cascadia_font);
this.applyMode(this.options_color);
} else {
console.warn('Element with class "interfaceSettings" not found.');
}
}
/**
* Applies or removes the 'av-dark' or 'av-black' class to the body based on the selected mode.
* @param {string} new_mode - The mode to apply ('auto', 'light', 'dark', 'black').
*/
applyMode(new_mode) {
let body_classes = document.body.classList;
// Ensure both classes are removed before applying the new one
body_classes.remove('av-dark', 'av-black');
if (new_mode == 'auto') {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
body_classes.add('av-dark');
}
} else if (new_mode == 'dark') {
body_classes.add('av-dark');
} else if (new_mode == 'black') {
body_classes.add('av-black');
}
// 'light' mode needs no class added
}
/**
* Applies or removes the 'av-dark-mono' class to the target elements
* based on the selected Cascadia font mode.
* @param {string} mode - The Cascadia font mode ('on' or 'off').
*/
applyCascadiaFontMode(mode) {
const mainContainer = document.querySelector('.mainContainer');
// Apply or remove 'av-dark-mono' class from mainContainer, which then applies font via CSS
if (mainContainer) {
if (mode === 'on') {
mainContainer.classList.add('av-dark-mono');
} else {
mainContainer.classList.remove('av-dark-mono');
}
} else {
console.warn('mainContainer not found for Cascadia font application.');
}
}
addToSplash() {
document.querySelector('.container').innerHTML += "<p style='padding-top:15px;font-size:75%;'>Plugin enabled: <a href='https://en.wikipedia.org/wiki/User:TonySt/av-dark' style='color:#34c6eb; text-decoration:none;' target='_BLANK'>av-dark</a></p>";
}
runMain() {
avdark.addFontLinks();
avdark.appendStyles();
avdark.addOptions();
// Initialize observer for diffChangeContainer elements to apply Cascadia font
const diffContainerObserver = new MutationObserver(mutations => {
const targetElements = document.querySelectorAll('.diffChangeContainer table, .diffChangeContainer tbody');
if (targetElements.length > 0) {
avdark.applyCascadiaFontMode(avdark.options_cascadia_font);
diffContainerObserver.disconnect();
}
});
// Observe the document body for changes that might include .diffChangeContainer
diffContainerObserver.observe(document.body, { childList: true, subtree: true });
}
}
avdark = new AntiVandalDarkMode();
const avdarkInitialObserver = new MutationObserver(mutations => {
// Check if any mutation involves adding/changing an element with 'start'
if (mutations.some(m => (m.type === 'childList' && Array.from(m.addedNodes).some(n => n.nodeType === Node.ELEMENT_NODE && (n.classList.contains('start') || n.querySelector('.start')))) || (m.type === 'attributes' && m.target.nodeType === Node.ELEMENT_NODE && m.target.classList.contains('start')))) {
// Only apply modes and styles relevant to the initial splash page
avdark.applyMode(avdark.options_color);
avdark.appendInitialStyles();
avdark.addToSplash();
avdarkInitialObserver.disconnect();
}
});
// Observe the entire document body for child additions, attribute changes, and within subtrees
avdarkInitialObserver.observe(document.body, { childList: true, attributes: true, subtree: true });
const avdarkMainObserver = new MutationObserver(mutations => {
// Check if any mutation involves adding/changing an element with 'mainContainer'
if (mutations.some(m => (m.type === 'childList' && Array.from(m.addedNodes).some(n => n.nodeType === Node.ELEMENT_NODE && (n.classList.contains('mainContainer') || n.querySelector('.mainContainer')))) || (m.type === 'attributes' && m.target.nodeType === Node.ELEMENT_NODE && m.target.classList.contains('mainContainer')))) {
avdark.runMain();
avdarkMainObserver.disconnect();
}
});
// Observe the entire document body for child additions, attribute changes, and within subtrees
avdarkMainObserver.observe(document.body, { childList: true, attributes: true, subtree: true });