WikiPartner (talk | contribs) No edit summary |
WikiPartner (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
/* ============================================================ | /* ============================================================ | ||
WIKIPARTNER | WIKIPARTNER | ||
THEME + USER MENU | |||
============================================================ */ | ============================================================ */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
/* ======================================================== | |||
THEME CONSTANTS | |||
======================================================== */ | |||
var STORAGE_KEY = 'wikipartner-theme'; | var STORAGE_KEY = 'wikipartner-theme'; | ||
var LIGHT = 'light'; | var LIGHT = 'light'; | ||
var DARK = 'dark'; | var DARK = 'dark'; | ||
| Line 13: | Line 19: | ||
/* ======================================================== | /* ======================================================== | ||
STORAGE | THEME STORAGE | ||
======================================================== */ | ======================================================== */ | ||
function getSavedTheme() { | function getSavedTheme() { | ||
try { | try { | ||
if (saved === | var saved = | ||
window.localStorage.getItem( | |||
STORAGE_KEY | |||
); | |||
if ( | |||
saved === LIGHT || | |||
saved === DARK | |||
) { | |||
return saved; | return saved; | ||
} | } | ||
} catch (e) { | } catch (e) { | ||
/* | /* Storage unavailable */ | ||
} | } | ||
return LIGHT; | return LIGHT; | ||
| Line 32: | Line 49: | ||
function saveTheme(theme) { | function saveTheme(theme) { | ||
try { | try { | ||
window.localStorage.setItem(STORAGE_KEY, theme); | |||
window.localStorage.setItem( | |||
STORAGE_KEY, | |||
theme | |||
); | |||
} catch (e) { | } catch (e) { | ||
/* | /* Storage unavailable */ | ||
} | } | ||
} | |||
/* ======================================================== | |||
CURRENT THEME | |||
======================================================== */ | |||
function getCurrentTheme() { | |||
return document.documentElement | |||
.classList.contains( | |||
'wp-theme-dark' | |||
) | |||
? DARK | |||
: LIGHT; | |||
} | } | ||
| Line 44: | Line 82: | ||
======================================================== */ | ======================================================== */ | ||
function applyTheme(theme, shouldSave) { | function applyTheme( | ||
var html = document.documentElement; | theme, | ||
shouldSave | |||
) { | |||
var html = | |||
document.documentElement; | |||
var isDark = theme === DARK; | var isDark = | ||
theme === DARK; | |||
html.classList.toggle( | |||
'wp-theme-dark', | |||
isDark | |||
); | |||
html.classList.toggle( | |||
'wp-theme-light', | |||
!isDark | |||
); | |||
html.classList.remove( | html.classList.remove( | ||
'skin-theme-clientpref-day', | 'skin-theme-clientpref-day', | ||
| Line 62: | Line 110: | ||
'skin-theme-clientpref-os' | 'skin-theme-clientpref-os' | ||
); | ); | ||
html.classList.add( | html.classList.add( | ||
| Line 68: | Line 117: | ||
: 'skin-theme-clientpref-day' | : 'skin-theme-clientpref-day' | ||
); | ); | ||
html.setAttribute( | html.setAttribute( | ||
'data-wikipartner-theme', | 'data-wikipartner-theme', | ||
isDark ? DARK : LIGHT | isDark | ||
? DARK | |||
: LIGHT | |||
); | ); | ||
if (shouldSave !== false) { | if (shouldSave !== false) { | ||
saveTheme(theme); | saveTheme(theme); | ||
} | } | ||
updateThemeButton(); | updateThemeButton(); | ||
} | } | ||
| Line 98: | Line 141: | ||
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 | '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>', | ||
| Line 110: | Line 156: | ||
'></path>', | '></path>', | ||
'</svg>' | '</svg>' | ||
].join(''); | ].join(''); | ||
| Line 120: | Line 162: | ||
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 cx="12" cy="12" r="3.8"></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 135: | Line 183: | ||
'<path d="M4.93 19.07l1.5-1.5"></path>', | '<path d="M4.93 19.07l1.5-1.5"></path>', | ||
'<path d="M17.57 6.43l1.5-1.5"></path>', | '<path d="M17.57 6.43l1.5-1.5"></path>', | ||
'</svg>' | '</svg>' | ||
].join(''); | ].join(''); | ||
| Line 141: | Line 190: | ||
/* ======================================================== | /* ======================================================== | ||
UPDATE BUTTON | UPDATE THEME BUTTON | ||
======================================================== */ | ======================================================== */ | ||
function updateThemeButton() { | function updateThemeButton() { | ||
var button = document.getElementById( | |||
var button = | |||
document.getElementById( | |||
'wikipartner-theme-toggle' | |||
); | |||
if (!button) { | if (!button) { | ||
| Line 153: | Line 205: | ||
} | } | ||
var isDark = getCurrentTheme() === DARK; | |||
var isDark = | |||
getCurrentTheme() === DARK; | |||
button.innerHTML = | button.innerHTML = | ||
'<span class="wp-theme-icon">' + | '<span class="wp-theme-icon">' + | ||
(isDark ? getSunIcon() : getMoonIcon()) + | ( | ||
isDark | |||
? getSunIcon() | |||
: getMoonIcon() | |||
) + | |||
'</span>' + | |||
'<span class="wp-theme-menu-label">' + | |||
( | |||
isDark | |||
? 'Light mode' | |||
: 'Dark mode' | |||
) + | |||
'</span>'; | '</span>'; | ||
button.setAttribute( | button.setAttribute( | ||
| Line 166: | Line 234: | ||
: 'Switch to dark mode' | : 'Switch to dark mode' | ||
); | ); | ||
button.setAttribute( | button.setAttribute( | ||
| Line 177: | Line 246: | ||
/* ======================================================== | /* ======================================================== | ||
CREATE BUTTON | CREATE THEME BUTTON | ||
======================================================== */ | ======================================================== */ | ||
function createThemeToggle() { | function createThemeToggle(list) { | ||
if ( | if ( | ||
document.getElementById( | document.getElementById( | ||
'wikipartner-theme- | 'wikipartner-theme-item' | ||
) | ) | ||
) { | ) { | ||
| Line 189: | Line 259: | ||
} | } | ||
var item = | |||
document.createElement('li'); | |||
item.id = | |||
'wikipartner-theme-item'; | |||
item.className = | item.className = | ||
'mw-list-item wikipartner-theme-item'; | 'mw-list-item ' + | ||
'wikipartner-theme-item'; | |||
var button = document.createElement('button'); | var button = | ||
document.createElement( | |||
'button' | |||
); | |||
button.id = | |||
'wikipartner-theme-toggle'; | |||
button.className = | button.className = | ||
'wikipartner-theme-toggle'; | 'wikipartner-theme-toggle'; | ||
| Line 219: | Line 290: | ||
'click', | 'click', | ||
function () { | function () { | ||
var nextTheme = | var nextTheme = | ||
getCurrentTheme() === DARK | getCurrentTheme() === DARK | ||
| Line 224: | Line 296: | ||
: DARK; | : DARK; | ||
applyTheme(nextTheme, true); | |||
applyTheme( | |||
nextTheme, | |||
true | |||
); | |||
} | } | ||
); | ); | ||
| Line 235: | Line 311: | ||
* LOGGED OUT: | * LOGGED OUT: | ||
* | * | ||
* Create account | | * Create account | theme | Log in | ||
*/ | */ | ||
var loginItem = list.querySelector('#pt-login'); | var loginItem = | ||
list.querySelector( | |||
'#pt-login' | |||
); | |||
if (loginItem) { | if (loginItem) { | ||
list.insertBefore( | list.insertBefore( | ||
item, | item, | ||
loginItem | loginItem | ||
); | ); | ||
} else { | } else { | ||
/* | /* | ||
* | * Logged-in layout will move | ||
* | * this into the hamburger menu. | ||
*/ | */ | ||
list.insertBefore( | list.appendChild(item); | ||
list. | } | ||
updateThemeButton(); | |||
} | |||
/* ======================================================== | |||
HAMBURGER ICON | |||
======================================================== */ | |||
function getMenuIcon() { | |||
return [ | |||
'<span class="wp-menu-bars" aria-hidden="true">', | |||
'<span></span>', | |||
'<span></span>', | |||
'<span></span>', | |||
'</span>' | |||
].join(''); | |||
} | |||
/* ======================================================== | |||
BUILD LOGGED-IN USER DROPDOWN | |||
======================================================== */ | |||
function buildLoggedInUserMenu(list) { | |||
/* | |||
* #pt-logout only exists for an | |||
* authenticated MediaWiki user. | |||
*/ | |||
var logoutItem = | |||
list.querySelector( | |||
'#pt-logout' | |||
); | |||
if (!logoutItem) { | |||
/* | |||
* Anonymous visitor. | |||
* Keep standard account layout. | |||
*/ | |||
list.classList.remove( | |||
'wp-is-logged-in' | |||
); | |||
list.classList.add( | |||
'wp-is-logged-out' | |||
); | |||
return; | |||
} | |||
list.classList.remove( | |||
'wp-is-logged-out' | |||
); | |||
list.classList.add( | |||
'wp-is-logged-in' | |||
); | |||
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'; | |||
/* | |||
* 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(); | |||
/* | |||
* Dropdown menu. | |||
*/ | |||
var menu = | |||
document.createElement('ul'); | |||
menu.className = | |||
'wp-user-dropdown-menu'; | |||
menu.hidden = true; | |||
/* | |||
* Move every account control except | |||
* the username into the dropdown. | |||
*/ | |||
var existingItems = | |||
Array.prototype.slice.call( | |||
list.children | |||
); | |||
existingItems.forEach( | |||
function (item) { | |||
if ( | |||
item === usernameItem || | |||
item === dropdown | |||
) { | |||
return; | |||
} | |||
menu.appendChild(item); | |||
} | |||
); | |||
dropdown.appendChild(trigger); | |||
dropdown.appendChild(menu); | |||
/* | |||
* Username first, hamburger second. | |||
*/ | |||
if (usernameItem) { | |||
if ( | |||
usernameItem.nextSibling | |||
) { | |||
list.insertBefore( | |||
dropdown, | |||
usernameItem.nextSibling | |||
); | |||
} else { | |||
list.appendChild( | |||
dropdown | |||
); | |||
} | |||
} else { | |||
list.appendChild( | |||
dropdown | |||
); | |||
} | |||
/* ==================================================== | |||
TOGGLE | |||
==================================================== */ | |||
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' | |||
); | |||
} | |||
trigger.addEventListener( | |||
'click', | |||
function (event) { | |||
event.stopPropagation(); | |||
if ( | |||
dropdown.classList.contains( | |||
'is-open' | |||
) | |||
) { | |||
closeMenu(); | |||
} else { | |||
openMenu(); | |||
} | |||
} | |||
); | |||
/* | |||
* Don't close while interacting | |||
* inside the dropdown. | |||
*/ | |||
menu.addEventListener( | |||
'click', | |||
function (event) { | |||
event.stopPropagation(); | |||
} | |||
); | |||
/* | |||
* Click outside closes it. | |||
*/ | |||
document.addEventListener( | |||
'click', | |||
function (event) { | |||
if ( | |||
!dropdown.contains( | |||
event.target | |||
) | |||
) { | |||
closeMenu(); | |||
} | |||
} | |||
); | |||
/* | |||
* Escape closes it. | |||
*/ | |||
document.addEventListener( | |||
'keydown', | |||
function (event) { | |||
if ( | |||
event.key === 'Escape' | |||
) { | |||
closeMenu(); | |||
trigger.focus(); | |||
} | |||
} | |||
); | |||
} | |||
/* ======================================================== | |||
INITIALIZE ACCOUNT AREA | |||
======================================================== */ | |||
function initializeUserArea() { | |||
var list = | |||
document.querySelector( | |||
'.wp-user-menu-list' | |||
); | ); | ||
if (!list) { | |||
return; | |||
} | } | ||
createThemeToggle(list); | |||
buildLoggedInUserMenu(list); | |||
updateThemeButton(); | updateThemeButton(); | ||
| Line 267: | Line 679: | ||
/* ======================================================== | /* ======================================================== | ||
APPLY SAVED THEME | |||
======================================================== */ | ======================================================== */ | ||
applyTheme( | applyTheme( | ||
getSavedTheme(), | getSavedTheme(), | ||
| Line 280: | Line 689: | ||
/* ======================================================== | /* ======================================================== | ||
START | |||
======================================================== */ | ======================================================== */ | ||
function initialize() { | function initialize() { | ||
initializeUserArea(); | |||
} | } | ||
if (document.readyState === 'loading') { | if ( | ||
document.readyState === | |||
'loading' | |||
) { | |||
document.addEventListener( | document.addEventListener( | ||
Revision as of 23:30, 19 August 2026
/* ============================================================
WIKIPARTNER
THEME + USER MENU
============================================================ */
(function () {
'use strict';
/* ========================================================
THEME CONSTANTS
======================================================== */
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) {
/* Storage unavailable */
}
return LIGHT;
}
function saveTheme(theme) {
try {
window.localStorage.setItem(
STORAGE_KEY,
theme
);
} catch (e) {
/* Storage unavailable */
}
}
/* ========================================================
CURRENT THEME
======================================================== */
function getCurrentTheme() {
return document.documentElement
.classList.contains(
'wp-theme-dark'
)
? DARK
: LIGHT;
}
/* ========================================================
APPLY THEME
======================================================== */
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();
}
/* ========================================================
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) {
if (
document.getElementById(
'wikipartner-theme-item'
)
) {
return;
}
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);
/*
* LOGGED OUT:
*
* Create account | theme | Log in
*/
var loginItem =
list.querySelector(
'#pt-login'
);
if (loginItem) {
list.insertBefore(
item,
loginItem
);
} else {
/*
* Logged-in layout will move
* this into the hamburger menu.
*/
list.appendChild(item);
}
updateThemeButton();
}
/* ========================================================
HAMBURGER ICON
======================================================== */
function getMenuIcon() {
return [
'<span class="wp-menu-bars" aria-hidden="true">',
'<span></span>',
'<span></span>',
'<span></span>',
'</span>'
].join('');
}
/* ========================================================
BUILD LOGGED-IN USER DROPDOWN
======================================================== */
function buildLoggedInUserMenu(list) {
/*
* #pt-logout only exists for an
* authenticated MediaWiki user.
*/
var logoutItem =
list.querySelector(
'#pt-logout'
);
if (!logoutItem) {
/*
* Anonymous visitor.
* Keep standard account layout.
*/
list.classList.remove(
'wp-is-logged-in'
);
list.classList.add(
'wp-is-logged-out'
);
return;
}
list.classList.remove(
'wp-is-logged-out'
);
list.classList.add(
'wp-is-logged-in'
);
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';
/*
* 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();
/*
* Dropdown menu.
*/
var menu =
document.createElement('ul');
menu.className =
'wp-user-dropdown-menu';
menu.hidden = true;
/*
* Move every account control except
* the username into the dropdown.
*/
var existingItems =
Array.prototype.slice.call(
list.children
);
existingItems.forEach(
function (item) {
if (
item === usernameItem ||
item === dropdown
) {
return;
}
menu.appendChild(item);
}
);
dropdown.appendChild(trigger);
dropdown.appendChild(menu);
/*
* Username first, hamburger second.
*/
if (usernameItem) {
if (
usernameItem.nextSibling
) {
list.insertBefore(
dropdown,
usernameItem.nextSibling
);
} else {
list.appendChild(
dropdown
);
}
} else {
list.appendChild(
dropdown
);
}
/* ====================================================
TOGGLE
==================================================== */
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'
);
}
trigger.addEventListener(
'click',
function (event) {
event.stopPropagation();
if (
dropdown.classList.contains(
'is-open'
)
) {
closeMenu();
} else {
openMenu();
}
}
);
/*
* Don't close while interacting
* inside the dropdown.
*/
menu.addEventListener(
'click',
function (event) {
event.stopPropagation();
}
);
/*
* Click outside closes it.
*/
document.addEventListener(
'click',
function (event) {
if (
!dropdown.contains(
event.target
)
) {
closeMenu();
}
}
);
/*
* Escape closes it.
*/
document.addEventListener(
'keydown',
function (event) {
if (
event.key === 'Escape'
) {
closeMenu();
trigger.focus();
}
}
);
}
/* ========================================================
INITIALIZE ACCOUNT AREA
======================================================== */
function initializeUserArea() {
var list =
document.querySelector(
'.wp-user-menu-list'
);
if (!list) {
return;
}
createThemeToggle(list);
buildLoggedInUserMenu(list);
updateThemeButton();
}
/* ========================================================
APPLY SAVED THEME
======================================================== */
applyTheme(
getSavedTheme(),
false
);
/* ========================================================
START
======================================================== */
function initialize() {
initializeUserArea();
}
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initialize
);
} else {
initialize();
}
})();