WikiPartner (talk | contribs) No edit summary |
WikiPartner (talk | contribs) No edit summary |
||
| Line 823: | Line 823: | ||
initialize(); | initialize(); | ||
} | |||
})(); | |||
/* ============================================================ | |||
WIKIPARTNER | |||
ARTICLE CONTENTS MENU | |||
============================================================ */ | |||
(function () { | |||
'use strict'; | |||
function initializeArticleContents() { | |||
var toc = | |||
document.getElementById( | |||
'wp-toc' | |||
); | |||
var article = | |||
document.querySelector( | |||
'.wp-article-body' | |||
); | |||
if (!toc || !article) { | |||
return; | |||
} | |||
var linksContainer = | |||
toc.querySelector( | |||
'.wp-toc-links' | |||
); | |||
if (!linksContainer) { | |||
return; | |||
} | |||
/* ==================================================== | |||
FIND REAL ARTICLE HEADINGS | |||
==================================================== */ | |||
var headings = | |||
Array.prototype.slice.call( | |||
article.querySelectorAll( | |||
'h2, h3, h4, h5, h6' | |||
) | |||
); | |||
/* | |||
* Remove empty headings. | |||
*/ | |||
headings = | |||
headings.filter( | |||
function (heading) { | |||
return ( | |||
heading.textContent | |||
.trim() | |||
.length > 0 | |||
); | |||
} | |||
); | |||
/* ==================================================== | |||
NO HEADINGS = NO HAMBURGER | |||
==================================================== */ | |||
if (!headings.length) { | |||
toc.hidden = true; | |||
return; | |||
} | |||
/* ==================================================== | |||
BUILD CONTENTS | |||
==================================================== */ | |||
linksContainer.innerHTML = ''; | |||
headings.forEach( | |||
function (heading, index) { | |||
/* | |||
* MediaWiki normally gives headings IDs. | |||
* Create one only when an ID is missing. | |||
*/ | |||
if (!heading.id) { | |||
heading.id = | |||
'wikipartner-section-' + | |||
(index + 1); | |||
} | |||
var level = | |||
parseInt( | |||
heading.tagName.substring(1), | |||
10 | |||
); | |||
var link = | |||
document.createElement( | |||
'a' | |||
); | |||
link.href = | |||
'#' + heading.id; | |||
link.className = | |||
'wp-toc-level-' + | |||
level; | |||
link.textContent = | |||
heading.textContent.trim(); | |||
/* | |||
* Close dropdown after selecting | |||
* a section. | |||
*/ | |||
link.addEventListener( | |||
'click', | |||
function () { | |||
toc.open = false; | |||
} | |||
); | |||
linksContainer.appendChild( | |||
link | |||
); | |||
} | |||
); | |||
/* ==================================================== | |||
HEADINGS EXIST = SHOW HAMBURGER | |||
==================================================== */ | |||
toc.hidden = false; | |||
} | |||
if ( | |||
document.readyState === | |||
'loading' | |||
) { | |||
document.addEventListener( | |||
'DOMContentLoaded', | |||
initializeArticleContents | |||
); | |||
} else { | |||
initializeArticleContents(); | |||
} | } | ||
})(); | })(); | ||
Revision as of 00:36, 20 August 2026
/* ============================================================
WIKIPARTNER
THEME + 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 */
}
}
/* ========================================================
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
);
/*
* Keep MediaWiki-compatible theme classes too.
*/
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>';
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
);
updateThemeButton();
return item;
}
/* ========================================================
HAMBURGER ICON
======================================================== */
function getMenuIcon() {
return [
'<span class="wp-menu-bars" aria-hidden="true">',
'<span></span>',
'<span></span>',
'<span></span>',
'</span>'
].join('');
}
/* ========================================================
FIND / CLEAN USERNAME
======================================================== */
function cleanUsernameDuplicates(
primary,
list
) {
var username =
mw.config.get(
'wgUserName'
);
if (!username) {
return;
}
/*
* MediaWiki's data-user-page already puts
* the username in .wp-user-primary.
*
* Some personal-menu configurations can
* also provide another user-page link.
* Remove that duplicate from the menu.
*/
var items =
Array.prototype.slice.call(
list.children
);
items.forEach(
function (item) {
var link =
item.querySelector('a');
var linkText =
link
? link.textContent.trim()
: '';
var isUserPageId =
item.id === 'pt-userpage' ||
item.id === 'pt-userpage-2';
var isUsername =
linkText === username;
if (
isUserPageId ||
isUsername
) {
/*
* If primary somehow has no username,
* move this one there instead.
*/
if (
primary &&
!primary.querySelector('li')
) {
primary.appendChild(
item
);
} else {
item.remove();
}
}
}
);
}
/* ========================================================
LOGGED OUT LAYOUT
======================================================== */
function buildLoggedOutLayout(
list,
themeItem
) {
list.classList.remove(
'wp-is-logged-in'
);
list.classList.add(
'wp-is-logged-out'
);
var loginItem =
list.querySelector(
'#pt-login'
);
/*
* Desired layout:
*
* Create account | Theme | Log in
*/
if (loginItem) {
list.insertBefore(
themeItem,
loginItem
);
} else {
list.appendChild(
themeItem
);
}
}
/* ========================================================
LOGGED IN LAYOUT
======================================================== */
function buildLoggedInLayout(
primary,
list,
themeItem
) {
list.classList.remove(
'wp-is-logged-out'
);
list.classList.add(
'wp-is-logged-in'
);
cleanUsernameDuplicates(
primary,
list
);
/*
* Remove an old dropdown if ResourceLoader
* initialization somehow runs again.
*/
var oldDropdown =
list.querySelector(
'.wp-user-dropdown'
);
if (oldDropdown) {
return;
}
/* ====================================================
CREATE HAMBURGER 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 CONTENT
==================================================== */
var menu =
document.createElement('ul');
menu.className =
'wp-user-dropdown-menu';
menu.hidden =
true;
/*
* Move every existing menu item
* into ☰.
*
* Username is NOT in this list anymore.
* Theme is not inserted yet.
*/
Array.prototype
.slice.call(
list.children
)
.forEach(
function (item) {
if (
item !== themeItem &&
item !== dropdown
) {
menu.appendChild(
item
);
}
}
);
dropdown.appendChild(
trigger
);
dropdown.appendChild(
menu
);
/*
* Exact layout:
*
* Username | Theme | ☰
*
* Username lives in .wp-user-primary,
* while theme and hamburger live here.
*/
list.appendChild(
themeItem
);
list.appendChild(
dropdown
);
/* ====================================================
OPEN / CLOSE
==================================================== */
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'
);
}
/* ====================================================
CLICK HAMBURGER
==================================================== */
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
==================================================== */
document.addEventListener(
'keydown',
function (event) {
if (
event.key === 'Escape' &&
dropdown.classList.contains(
'is-open'
)
) {
closeMenu();
trigger.focus();
}
}
);
}
/* ========================================================
INITIALIZE ACCOUNT AREA
======================================================== */
function initializeUserArea() {
var primary =
document.querySelector(
'.wp-user-primary'
);
var list =
document.querySelector(
'.wp-user-menu-list'
);
if (!list) {
return;
}
/*
* Empty username container should not
* take up space for anonymous users.
*/
if (
primary &&
!primary.querySelector('li')
) {
primary.classList.add(
'is-empty'
);
}
var themeItem =
createThemeToggle(
list
);
var username =
mw.config.get(
'wgUserName'
);
if (username) {
buildLoggedInLayout(
primary,
list,
themeItem
);
} else {
buildLoggedOutLayout(
list,
themeItem
);
}
updateThemeButton();
}
/* ========================================================
APPLY SAVED THEME
======================================================== */
applyTheme(
getSavedTheme(),
false
);
/* ========================================================
START
======================================================== */
function initialize() {
initializeUserArea();
}
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initialize
);
} else {
initialize();
}
})();
/* ============================================================
WIKIPARTNER
ARTICLE CONTENTS MENU
============================================================ */
(function () {
'use strict';
function initializeArticleContents() {
var toc =
document.getElementById(
'wp-toc'
);
var article =
document.querySelector(
'.wp-article-body'
);
if (!toc || !article) {
return;
}
var linksContainer =
toc.querySelector(
'.wp-toc-links'
);
if (!linksContainer) {
return;
}
/* ====================================================
FIND REAL ARTICLE HEADINGS
==================================================== */
var headings =
Array.prototype.slice.call(
article.querySelectorAll(
'h2, h3, h4, h5, h6'
)
);
/*
* Remove empty headings.
*/
headings =
headings.filter(
function (heading) {
return (
heading.textContent
.trim()
.length > 0
);
}
);
/* ====================================================
NO HEADINGS = NO HAMBURGER
==================================================== */
if (!headings.length) {
toc.hidden = true;
return;
}
/* ====================================================
BUILD CONTENTS
==================================================== */
linksContainer.innerHTML = '';
headings.forEach(
function (heading, index) {
/*
* MediaWiki normally gives headings IDs.
* Create one only when an ID is missing.
*/
if (!heading.id) {
heading.id =
'wikipartner-section-' +
(index + 1);
}
var level =
parseInt(
heading.tagName.substring(1),
10
);
var link =
document.createElement(
'a'
);
link.href =
'#' + heading.id;
link.className =
'wp-toc-level-' +
level;
link.textContent =
heading.textContent.trim();
/*
* Close dropdown after selecting
* a section.
*/
link.addEventListener(
'click',
function () {
toc.open = false;
}
);
linksContainer.appendChild(
link
);
}
);
/* ====================================================
HEADINGS EXIST = SHOW HAMBURGER
==================================================== */
toc.hidden = false;
}
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initializeArticleContents
);
} else {
initializeArticleContents();
}
})();