MediaWiki:Common.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
/**
* Night Mode Toggle for Minerva Neue
* Adds a sticky toggle button that persists user preference in localStorage
*/
(function () {
'use strict';
var STORAGE_KEY = 'minerva-night-mode';
// SVG icons (24x24)
var MOON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>';
var SUN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>';
/**
* Get saved theme or default to 'day'
*/
function getSavedTheme() {
try {
return localStorage.getItem(STORAGE_KEY) || 'day';
} catch (e) {
return 'day';
}
}
/**
* Save theme preference
*/
function saveTheme(theme) {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch (e) {
// localStorage unavailable
}
}
/**
* Apply theme via inline style injection (prevents FOUC)
* This runs immediately, before DOM is ready
*/
function applyThemeImmediate(theme) {
// Remove any existing theme style tag
var existingStyle = document.getElementById('night-mode-immediate');
if (existingStyle) {
existingStyle.parentNode.removeChild(existingStyle);
}
// Inject style tag into head immediately
var style = document.createElement('style');
style.id = 'night-mode-immediate';
if (theme === 'night') {
// Apply night mode class to html element
style.textContent = 'html { color-scheme: dark; }';
document.documentElement.classList.remove('skin-theme-clientpref-day');
document.documentElement.classList.add('skin-theme-clientpref-night');
} else {
style.textContent = 'html { color-scheme: light; }';
document.documentElement.classList.remove('skin-theme-clientpref-night');
document.documentElement.classList.add('skin-theme-clientpref-day');
}
// Insert into head as early as possible
var head = document.head || document.getElementsByTagName('head')[0];
if (head) {
head.insertBefore(style, head.firstChild);
}
}
/**
* Update button appearance
*/
function updateButtonState(button, theme) {
var icon = button.querySelector('.minerva-icon');
var label = button.querySelector('.toggle-label');
if (theme === 'night') {
icon.innerHTML = SUN_SVG;
if (label) label.textContent = 'Light mode';
button.setAttribute('title', 'Switch to light mode');
} else {
icon.innerHTML = MOON_SVG;
if (label) label.textContent = 'Dark mode';
button.setAttribute('title', 'Switch to dark mode');
}
}
/**
* Create the toggle button element
*/
function createToggleButton() {
var currentTheme = getSavedTheme();
var li = document.createElement('li');
var button = document.createElement('a');
button.setAttribute('role', 'button');
button.setAttribute('href', '#');
button.id = 'ca-nightmode';
button.className = 'cdx-button cdx-button--size-large cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--icon-only cdx-button--weight-quiet';
button.style.cursor = 'pointer';
var icon = document.createElement('span');
icon.className = 'minerva-icon';
icon.style.display = 'flex';
icon.style.alignItems = 'center';
icon.style.justifyContent = 'center';
icon.style.width = '20px';
icon.style.height = '20px';
var label = document.createElement('span');
label.className = 'toggle-label';
// Visually hidden but accessible
label.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0;';
button.appendChild(icon);
button.appendChild(label);
li.appendChild(button);
// Set initial state
updateButtonState(button, currentTheme);
// Handle click
button.addEventListener('click', function (e) {
e.preventDefault();
var newTheme = getSavedTheme() === 'night' ? 'day' : 'night';
saveTheme(newTheme);
applyThemeImmediate(newTheme);
updateButtonState(button, newTheme);
});
return li;
}
/**
* Insert button into the navigation
*/
function insertButton() {
var notificationsList = document.querySelector('.minerva-notifications ul');
if (notificationsList) {
var toggleButton = createToggleButton();
notificationsList.appendChild(toggleButton);
} else {
// Fallback: try inserting into minerva-user-navigation
var userNav = document.querySelector('.minerva-user-navigation');
if (userNav) {
var container = document.createElement('div');
container.className = 'minerva-notifications';
var ul = document.createElement('ul');
ul.appendChild(createToggleButton());
container.appendChild(ul);
userNav.insertBefore(container, userNav.firstChild);
}
}
}
/**
* Initialize
*/
function init() {
// Apply saved theme IMMEDIATELY (this is the key to preventing FOUC)
applyThemeImmediate(getSavedTheme());
// Insert button when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', insertButton);
} else {
insertButton();
}
}
// Run immediately
init();
})();