WikiPartner (talk | contribs) No edit summary |
WikiPartner (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
/* ============================================================ | /* ============================================================ | ||
WIKIPARTNER | WIKIPARTNER | ||
THEME + USER MENU | THEME + LOGGED-IN USER MENU | ||
============================================================ */ | ============================================================ */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
var STORAGE_KEY = 'wikipartner-theme'; | var STORAGE_KEY = 'wikipartner-theme'; | ||
var LIGHT = 'light'; | var LIGHT = 'light'; | ||
var DARK = 'dark'; | var DARK = 'dark'; | ||
| Line 23: | Line 17: | ||
function getSavedTheme() { | function getSavedTheme() { | ||
try { | try { | ||
var saved = window.localStorage.getItem(STORAGE_KEY); | |||
if (saved === LIGHT || saved === DARK) { | |||
if ( | |||
return saved; | return saved; | ||
} | } | ||
} catch (e) { | } catch (e) { | ||
/* | /* localStorage unavailable */ | ||
} | } | ||
return LIGHT; | return LIGHT; | ||
| Line 49: | Line 32: | ||
function saveTheme(theme) { | function saveTheme(theme) { | ||
try { | try { | ||
window.localStorage.setItem(STORAGE_KEY, theme); | |||
window.localStorage.setItem( | |||
} catch (e) { | } catch (e) { | ||
/* | /* localStorage unavailable */ | ||
} | } | ||
} | } | ||
| Line 64: | Line 41: | ||
/* ======================================================== | /* ======================================================== | ||
THEME STATE | |||
======================================================== */ | ======================================================== */ | ||
function getCurrentTheme() { | function getCurrentTheme() { | ||
return document.documentElement.classList.contains( | |||
return document.documentElement | 'wp-theme-dark' | ||
) | |||
? DARK | ? DARK | ||
: LIGHT; | : LIGHT; | ||
| Line 78: | Line 53: | ||
function applyTheme(theme, shouldSave) { | |||
var html = document.documentElement; | |||
var isDark = theme === DARK; | |||
function applyTheme( | |||
var html = | |||
var isDark = | |||
html.classList.toggle( | html.classList.toggle( | ||
| Line 103: | Line 66: | ||
!isDark | !isDark | ||
); | ); | ||
html.classList.remove( | html.classList.remove( | ||
| Line 110: | Line 72: | ||
'skin-theme-clientpref-os' | 'skin-theme-clientpref-os' | ||
); | ); | ||
html.classList.add( | html.classList.add( | ||
| Line 117: | Line 78: | ||
: 'skin-theme-clientpref-day' | : 'skin-theme-clientpref-day' | ||
); | ); | ||
html.setAttribute( | html.setAttribute( | ||
'data-wikipartner-theme', | 'data-wikipartner-theme', | ||
isDark | isDark ? DARK : LIGHT | ||
); | ); | ||
if (shouldSave !== false) { | if (shouldSave !== false) { | ||
saveTheme(theme); | saveTheme(theme); | ||
} | } | ||
updateThemeButton(); | updateThemeButton(); | ||
| Line 137: | Line 93: | ||
/* ======================================================== | /* ======================================================== | ||
ICONS | THEME ICONS | ||
======================================================== */ | ======================================================== */ | ||
function getMoonIcon() { | function getMoonIcon() { | ||
return [ | return [ | ||
'<svg viewBox="0 0 24 24" aria-hidden="true">', | '<svg viewBox="0 0 24 24" aria-hidden="true">', | ||
'<path | '<path d="M20.3 15.2A8.4 8.4 0 0 1 8.8 3.7 8.5 8.5 0 1 0 11.5 11.5Z"></path>', | ||
'<path | '<path class="wp-theme-star" d="M17.5 4v3M16 5.5h3"></path>', | ||
'</svg>' | '</svg>' | ||
| Line 162: | Line 110: | ||
function getSunIcon() { | function getSunIcon() { | ||
return [ | return [ | ||
'<svg viewBox="0 0 24 24" aria-hidden="true">', | '<svg viewBox="0 0 24 24" aria-hidden="true">', | ||
'<circle | '<circle cx="12" cy="12" r="3.8"></circle>', | ||
'<path d="M12 2.3v2.1"></path>', | '<path d="M12 2.3v2.1"></path>', | ||
| Line 194: | Line 137: | ||
function updateThemeButton() { | function updateThemeButton() { | ||
var button = document.getElementById( | |||
var button = | 'wikipartner-theme-toggle' | ||
); | |||
if (!button) { | if (!button) { | ||
return; | return; | ||
} | } | ||
var isDark = | var isDark = | ||
getCurrentTheme() === DARK; | getCurrentTheme() === DARK; | ||
button.innerHTML = | button.innerHTML = | ||
| Line 226: | Line 164: | ||
) + | ) + | ||
'</span>'; | '</span>'; | ||
button.setAttribute( | button.setAttribute( | ||
| Line 234: | Line 171: | ||
: 'Switch to dark mode' | : 'Switch to dark mode' | ||
); | ); | ||
button.setAttribute( | button.setAttribute( | ||
| Line 250: | Line 186: | ||
function createThemeToggle(list) { | function createThemeToggle(list) { | ||
var existing = | |||
document.getElementById( | document.getElementById( | ||
'wikipartner-theme-item' | 'wikipartner-theme-item' | ||
) | ); | ||
) { | |||
return; | if (existing) { | ||
return existing; | |||
} | } | ||
| Line 262: | Line 198: | ||
var item = | var item = | ||
document.createElement('li'); | document.createElement('li'); | ||
item.id = | item.id = | ||
| Line 268: | Line 203: | ||
item.className = | item.className = | ||
'mw-list-item | 'mw-list-item wikipartner-theme-item'; | ||
var button = | var button = | ||
document.createElement( | document.createElement('button'); | ||
button.id = | button.id = | ||
| Line 295: | Line 226: | ||
? LIGHT | ? LIGHT | ||
: DARK; | : DARK; | ||
applyTheme( | applyTheme( | ||
| Line 308: | Line 238: | ||
var loginItem = | var loginItem = | ||
list.querySelector( | list.querySelector( | ||
| Line 320: | Line 245: | ||
if (loginItem) { | if (loginItem) { | ||
/* | |||
* LOGGED OUT: | |||
* | |||
* Create account | Theme | Log in | |||
*/ | |||
list.insertBefore( | list.insertBefore( | ||
| Line 328: | Line 259: | ||
} else { | } else { | ||
list.appendChild( | |||
item | |||
); | |||
list.appendChild(item); | |||
} | } | ||
| Line 338: | Line 267: | ||
updateThemeButton(); | updateThemeButton(); | ||
return item; | |||
} | } | ||
| Line 346: | Line 277: | ||
function getMenuIcon() { | function getMenuIcon() { | ||
return [ | return [ | ||
'<span class="wp-menu-bars" aria-hidden="true">', | '<span class="wp-menu-bars" aria-hidden="true">', | ||
| Line 358: | Line 288: | ||
/* ======================================================== | /* ======================================================== | ||
LOGGED-IN USER MENU | |||
======================================================== */ | ======================================================== */ | ||
function buildLoggedInUserMenu(list) { | function buildLoggedInUserMenu( | ||
list, | |||
themeItem | |||
) { | |||
/* | /* | ||
* #pt-logout only | * #pt-logout exists only when | ||
* authenticated | * MediaWiki sees an authenticated user. | ||
*/ | */ | ||
var logoutItem = | var logoutItem = | ||
list.querySelector( | list.querySelector( | ||
| Line 376: | Line 310: | ||
/* | /* | ||
* | * LOGGED OUT | ||
*/ | */ | ||
list.classList.remove( | list.classList.remove( | ||
'wp-is-logged-in' | 'wp-is-logged-in' | ||
| Line 390: | Line 324: | ||
} | } | ||
/* | |||
* LOGGED IN | |||
*/ | |||
list.classList.remove( | list.classList.remove( | ||
| Line 399: | Line 337: | ||
); | ); | ||
/* | |||
* Prevent duplicate hamburger menus. | |||
*/ | |||
if ( | if ( | ||
| Line 410: | Line 352: | ||
/* | /* | ||
* Username stays | * Username stays OUTSIDE. | ||
*/ | */ | ||
var usernameItem = | var usernameItem = | ||
list.querySelector( | list.querySelector( | ||
| Line 418: | Line 361: | ||
/* | /* ==================================================== | ||
CREATE DROPDOWN WRAPPER | |||
==================================================== */ | |||
var dropdown = | var dropdown = | ||
document.createElement('li'); | document.createElement('li'); | ||
dropdown.className = | dropdown.className = | ||
| Line 429: | Line 372: | ||
/* | /* ==================================================== | ||
CREATE HAMBURGER BUTTON | |||
==================================================== */ | |||
var trigger = | var trigger = | ||
document.createElement( | document.createElement( | ||
| Line 437: | Line 381: | ||
); | ); | ||
trigger.type = | |||
trigger.type = 'button'; | 'button'; | ||
trigger.className = | trigger.className = | ||
| Line 457: | Line 401: | ||
'false' | 'false' | ||
); | ); | ||
trigger.innerHTML = | trigger.innerHTML = | ||
| Line 463: | Line 406: | ||
/* | /* ==================================================== | ||
CREATE DROPDOWN MENU | |||
==================================================== */ | |||
var menu = | var menu = | ||
document.createElement('ul'); | document.createElement('ul'); | ||
menu.className = | menu.className = | ||
'wp-user-dropdown-menu'; | 'wp-user-dropdown-menu'; | ||
menu.hidden = true; | menu.hidden = | ||
true; | |||
/* ==================================================== | |||
MOVE USER LINKS | |||
==================================================== */ | |||
/* | /* | ||
* | * ONLY THESE REMAIN OUTSIDE: | ||
* | * | ||
* Username | |||
* Theme button | |||
* | |||
* Everything else moves inside ☰ | |||
*/ | */ | ||
Array.prototype | |||
.slice | |||
.call(list.children) | |||
.forEach( | |||
function (item) { | |||
if ( | |||
item === usernameItem || | |||
item === themeItem | |||
) { | |||
return; | |||
} | |||
menu.appendChild( | |||
item | |||
); | |||
} | |||
); | |||
dropdown.appendChild( | |||
trigger | |||
); | ); | ||
dropdown.appendChild( | |||
menu | |||
); | |||
/* ==================================================== | |||
FORCE EXACT HEADER ORDER | |||
==================================================== */ | |||
/* | /* | ||
* Username | Theme | ☰ | |||
*/ | |||
if (usernameItem) { | |||
list.appendChild( | |||
usernameItem | |||
); | |||
} | |||
if (themeItem) { | |||
list.appendChild( | list.appendChild( | ||
| Line 544: | Line 488: | ||
} | } | ||
list.appendChild( | list.appendChild( | ||
dropdown | dropdown | ||
); | ); | ||
/* ==================================================== | /* ==================================================== | ||
OPEN / CLOSE FUNCTIONS | |||
==================================================== */ | ==================================================== */ | ||
| Line 613: | Line 504: | ||
); | ); | ||
menu.hidden = true; | menu.hidden = | ||
true; | |||
trigger.setAttribute( | trigger.setAttribute( | ||
| Line 629: | Line 521: | ||
); | ); | ||
menu.hidden = false; | menu.hidden = | ||
false; | |||
trigger.setAttribute( | trigger.setAttribute( | ||
| Line 638: | Line 531: | ||
} | } | ||
/* ==================================================== | |||
HAMBURGER CLICK | |||
==================================================== */ | |||
trigger.addEventListener( | trigger.addEventListener( | ||
'click', | 'click', | ||
function (event) { | function (event) { | ||
event.preventDefault(); | |||
event.stopPropagation(); | event.stopPropagation(); | ||
| Line 664: | Line 563: | ||
/* | /* ==================================================== | ||
CLICK INSIDE MENU | |||
==================================================== */ | |||
menu.addEventListener( | menu.addEventListener( | ||
'click', | 'click', | ||
| Line 678: | Line 577: | ||
/* | /* ==================================================== | ||
CLICK OUTSIDE | |||
==================================================== */ | |||
document.addEventListener( | document.addEventListener( | ||
'click', | 'click', | ||
| Line 690: | Line 590: | ||
) | ) | ||
) { | ) { | ||
closeMenu(); | closeMenu(); | ||
} | } | ||
| Line 697: | Line 599: | ||
/* | /* ==================================================== | ||
ESCAPE KEY | |||
==================================================== */ | |||
document.addEventListener( | document.addEventListener( | ||
'keydown', | 'keydown', | ||
| Line 705: | Line 608: | ||
if ( | if ( | ||
event.key === 'Escape' | event.key === 'Escape' && | ||
dropdown.classList.contains( | |||
'is-open' | |||
) | |||
) { | ) { | ||
| Line 720: | Line 626: | ||
/* ======================================================== | /* ======================================================== | ||
INITIALIZE | INITIALIZE USER AREA | ||
======================================================== */ | ======================================================== */ | ||
| Line 736: | Line 642: | ||
createThemeToggle(list); | /* | ||
* First create the theme button. | |||
*/ | |||
var themeItem = | |||
createThemeToggle( | |||
list | |||
); | |||
/* | |||
* Then build logged-in dropdown. | |||
*/ | |||
buildLoggedInUserMenu( | |||
list, | |||
themeItem | |||
); | |||
updateThemeButton(); | updateThemeButton(); | ||
Revision as of 23:42, 19 August 2026
/* ============================================================
WIKIPARTNER
THEME + LOGGED-IN USER MENU
============================================================ */
(function () {
'use strict';
var STORAGE_KEY = 'wikipartner-theme';
var LIGHT = 'light';
var DARK = 'dark';
/* ========================================================
THEME STORAGE
======================================================== */
function getSavedTheme() {
try {
var saved = window.localStorage.getItem(STORAGE_KEY);
if (saved === LIGHT || saved === DARK) {
return saved;
}
} catch (e) {
/* localStorage unavailable */
}
return LIGHT;
}
function saveTheme(theme) {
try {
window.localStorage.setItem(STORAGE_KEY, theme);
} catch (e) {
/* localStorage unavailable */
}
}
/* ========================================================
THEME STATE
======================================================== */
function getCurrentTheme() {
return document.documentElement.classList.contains(
'wp-theme-dark'
)
? DARK
: LIGHT;
}
function applyTheme(theme, shouldSave) {
var html = document.documentElement;
var isDark = theme === DARK;
html.classList.toggle(
'wp-theme-dark',
isDark
);
html.classList.toggle(
'wp-theme-light',
!isDark
);
html.classList.remove(
'skin-theme-clientpref-day',
'skin-theme-clientpref-night',
'skin-theme-clientpref-os'
);
html.classList.add(
isDark
? 'skin-theme-clientpref-night'
: 'skin-theme-clientpref-day'
);
html.setAttribute(
'data-wikipartner-theme',
isDark ? DARK : LIGHT
);
if (shouldSave !== false) {
saveTheme(theme);
}
updateThemeButton();
}
/* ========================================================
THEME ICONS
======================================================== */
function getMoonIcon() {
return [
'<svg viewBox="0 0 24 24" aria-hidden="true">',
'<path d="M20.3 15.2A8.4 8.4 0 0 1 8.8 3.7 8.5 8.5 0 1 0 11.5 11.5Z"></path>',
'<path class="wp-theme-star" d="M17.5 4v3M16 5.5h3"></path>',
'</svg>'
].join('');
}
function getSunIcon() {
return [
'<svg viewBox="0 0 24 24" aria-hidden="true">',
'<circle cx="12" cy="12" r="3.8"></circle>',
'<path d="M12 2.3v2.1"></path>',
'<path d="M12 19.6v2.1"></path>',
'<path d="M4.93 4.93l1.5 1.5"></path>',
'<path d="M17.57 17.57l1.5 1.5"></path>',
'<path d="M2.3 12h2.1"></path>',
'<path d="M19.6 12h2.1"></path>',
'<path d="M4.93 19.07l1.5-1.5"></path>',
'<path d="M17.57 6.43l1.5-1.5"></path>',
'</svg>'
].join('');
}
/* ========================================================
UPDATE THEME BUTTON
======================================================== */
function updateThemeButton() {
var button = document.getElementById(
'wikipartner-theme-toggle'
);
if (!button) {
return;
}
var isDark =
getCurrentTheme() === DARK;
button.innerHTML =
'<span class="wp-theme-icon">' +
(
isDark
? getSunIcon()
: getMoonIcon()
) +
'</span>' +
'<span class="wp-theme-menu-label">' +
(
isDark
? 'Light mode'
: 'Dark mode'
) +
'</span>';
button.setAttribute(
'aria-label',
isDark
? 'Switch to light mode'
: 'Switch to dark mode'
);
button.setAttribute(
'title',
isDark
? 'Light mode'
: 'Dark mode'
);
}
/* ========================================================
CREATE THEME BUTTON
======================================================== */
function createThemeToggle(list) {
var existing =
document.getElementById(
'wikipartner-theme-item'
);
if (existing) {
return existing;
}
var item =
document.createElement('li');
item.id =
'wikipartner-theme-item';
item.className =
'mw-list-item wikipartner-theme-item';
var button =
document.createElement('button');
button.id =
'wikipartner-theme-toggle';
button.className =
'wikipartner-theme-toggle';
button.type = 'button';
button.addEventListener(
'click',
function () {
var nextTheme =
getCurrentTheme() === DARK
? LIGHT
: DARK;
applyTheme(
nextTheme,
true
);
}
);
item.appendChild(button);
var loginItem =
list.querySelector(
'#pt-login'
);
if (loginItem) {
/*
* LOGGED OUT:
*
* Create account | Theme | Log in
*/
list.insertBefore(
item,
loginItem
);
} else {
list.appendChild(
item
);
}
updateThemeButton();
return item;
}
/* ========================================================
HAMBURGER ICON
======================================================== */
function getMenuIcon() {
return [
'<span class="wp-menu-bars" aria-hidden="true">',
'<span></span>',
'<span></span>',
'<span></span>',
'</span>'
].join('');
}
/* ========================================================
LOGGED-IN USER MENU
======================================================== */
function buildLoggedInUserMenu(
list,
themeItem
) {
/*
* #pt-logout exists only when
* MediaWiki sees an authenticated user.
*/
var logoutItem =
list.querySelector(
'#pt-logout'
);
if (!logoutItem) {
/*
* LOGGED OUT
*/
list.classList.remove(
'wp-is-logged-in'
);
list.classList.add(
'wp-is-logged-out'
);
return;
}
/*
* LOGGED IN
*/
list.classList.remove(
'wp-is-logged-out'
);
list.classList.add(
'wp-is-logged-in'
);
/*
* Prevent duplicate hamburger menus.
*/
if (
list.querySelector(
'.wp-user-dropdown'
)
) {
return;
}
/*
* Username stays OUTSIDE.
*/
var usernameItem =
list.querySelector(
'#pt-userpage'
);
/* ====================================================
CREATE DROPDOWN WRAPPER
==================================================== */
var dropdown =
document.createElement('li');
dropdown.className =
'wp-user-dropdown';
/* ====================================================
CREATE HAMBURGER BUTTON
==================================================== */
var trigger =
document.createElement(
'button'
);
trigger.type =
'button';
trigger.className =
'wp-user-menu-trigger';
trigger.setAttribute(
'aria-label',
'Open user menu'
);
trigger.setAttribute(
'title',
'User menu'
);
trigger.setAttribute(
'aria-expanded',
'false'
);
trigger.innerHTML =
getMenuIcon();
/* ====================================================
CREATE DROPDOWN MENU
==================================================== */
var menu =
document.createElement('ul');
menu.className =
'wp-user-dropdown-menu';
menu.hidden =
true;
/* ====================================================
MOVE USER LINKS
==================================================== */
/*
* ONLY THESE REMAIN OUTSIDE:
*
* Username
* Theme button
*
* Everything else moves inside ☰
*/
Array.prototype
.slice
.call(list.children)
.forEach(
function (item) {
if (
item === usernameItem ||
item === themeItem
) {
return;
}
menu.appendChild(
item
);
}
);
dropdown.appendChild(
trigger
);
dropdown.appendChild(
menu
);
/* ====================================================
FORCE EXACT HEADER ORDER
==================================================== */
/*
* Username | Theme | ☰
*/
if (usernameItem) {
list.appendChild(
usernameItem
);
}
if (themeItem) {
list.appendChild(
themeItem
);
}
list.appendChild(
dropdown
);
/* ====================================================
OPEN / CLOSE FUNCTIONS
==================================================== */
function closeMenu() {
dropdown.classList.remove(
'is-open'
);
menu.hidden =
true;
trigger.setAttribute(
'aria-expanded',
'false'
);
}
function openMenu() {
dropdown.classList.add(
'is-open'
);
menu.hidden =
false;
trigger.setAttribute(
'aria-expanded',
'true'
);
}
/* ====================================================
HAMBURGER CLICK
==================================================== */
trigger.addEventListener(
'click',
function (event) {
event.preventDefault();
event.stopPropagation();
if (
dropdown.classList.contains(
'is-open'
)
) {
closeMenu();
} else {
openMenu();
}
}
);
/* ====================================================
CLICK INSIDE MENU
==================================================== */
menu.addEventListener(
'click',
function (event) {
event.stopPropagation();
}
);
/* ====================================================
CLICK OUTSIDE
==================================================== */
document.addEventListener(
'click',
function (event) {
if (
!dropdown.contains(
event.target
)
) {
closeMenu();
}
}
);
/* ====================================================
ESCAPE KEY
==================================================== */
document.addEventListener(
'keydown',
function (event) {
if (
event.key === 'Escape' &&
dropdown.classList.contains(
'is-open'
)
) {
closeMenu();
trigger.focus();
}
}
);
}
/* ========================================================
INITIALIZE USER AREA
======================================================== */
function initializeUserArea() {
var list =
document.querySelector(
'.wp-user-menu-list'
);
if (!list) {
return;
}
/*
* First create the theme button.
*/
var themeItem =
createThemeToggle(
list
);
/*
* Then build logged-in dropdown.
*/
buildLoggedInUserMenu(
list,
themeItem
);
updateThemeButton();
}
/* ========================================================
APPLY SAVED THEME
======================================================== */
applyTheme(
getSavedTheme(),
false
);
/* ========================================================
START
======================================================== */
function initialize() {
initializeUserArea();
}
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initialize
);
} else {
initialize();
}
})();