/**
* @name AutoExpandTOC
* @description Adds an Expand/Collapse all button next to the "Beginning"
* TOC item, auto-expands the TOC when loaded, and displays section counts.
* @author [[User:RoyZuo]]
* @version 1.1
* @updated 2026-06-26
*/
(function () {
'use strict';
let cachedSuffix = null;
/**
* Determines the highest available heading level and its count
*/
function getHeadingSuffix() {
// Return cached result if already calculated
if (cachedSuffix !== null) return cachedSuffix;
// Scope to the article body to avoid counting navigation or sidebar headers
const container = document.querySelector('.mw-parser-output') || document.body;
const levels = ['h2', 'h3', 'h4', 'h5', 'h6'];
for (const level of levels) {
const count = container.querySelectorAll(level).length;
if (count > 0) {
cachedSuffix = ` (${count} ${level} section${count > 1 ? 's' : ''})`;
return cachedSuffix;
}
}
// Fallback if no headers are found
cachedSuffix = ' sections';
return cachedSuffix;
}
/**
* Get all Vector TOC toggle buttons inside a TOC
*/
function getToggles(toc) {
return toc.querySelectorAll('.vector-toc-toggle');
}
/**
* Check if all sections are expanded
*/
function allExpanded(toc) {
const toggles = getToggles(toc);
return toggles.length > 0 && Array.from(toggles).every(btn => btn.getAttribute('aria-expanded') === 'true');
}
/**
* Expand or collapse all TOC sections
*/
function setAll(toc, expand) {
getToggles(toc).forEach(btn => {
const isExpanded = btn.getAttribute('aria-expanded') === 'true';
if (expand !== isExpanded) btn.click();
});
}
/**
* Update the button label with dynamic counts
*/
function updateLabel(button, toc) {
const prefix = allExpanded(toc) ? 'Collapse all' : 'Expand all';
button.textContent = prefix + getHeadingSuffix();
}
/**
* Insert the button next to "Beginning" TOC item
*/
function insertButton(toc) {
const beginningItem = toc.querySelector('#toc-mw-content-text');
if (!beginningItem) return false;
// Prevent duplicates
if (beginningItem.querySelector('.vector-toc-toggle-all')) return true;
const button = document.createElement('button');
button.type = 'button';
button.className = 'cdx-button cdx-button--weight-quiet vector-toc-toggle-all';
button.style.marginLeft = '0.5em';
button.style.fontSize = '0.875em';
updateLabel(button, toc);
button.addEventListener('click', () => {
const expand = !allExpanded(toc);
setAll(toc, expand);
updateLabel(button, toc);
});
const link = beginningItem.querySelector('.vector-toc-link');
if (!link) return false;
link.insertAdjacentElement('afterend', button);
// Auto-expand TOC once toggle buttons exist
setTimeout(() => {
setAll(toc, true);
updateLabel(button, toc);
}, 50);
return true;
}
/**
* Observe TOC for dynamic content
*/
function observeTOC() {
const toc = document.querySelector('.vector-toc');
if (!toc) return;
const observer = new MutationObserver(() => {
if (insertButton(toc)) {
observer.disconnect(); // stop observing once button is added
}
});
observer.observe(toc, { childList: true, subtree: true });
// Try immediately in case TOC already exists
insertButton(toc);
}
// Start after full page load
if (document.readyState === 'complete') {
observeTOC();
} else {
window.addEventListener('load', observeTOC);
}
})();