Jump to content

User:RoyZuo/AutoExpandTOC.js

From Wikimedia Commons, the free media repository
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
// ==UserScript==
// @name         Vector TOC Expand/Collapse All
// @description  Adds an Expand/Collapse all button next to the "Beginning" TOC item and auto-expands the TOC when fully loaded
// @version      1.0
// @author       RoyZuo
// ==/UserScript==

(function () {
  'use strict';

  /**
   * 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
   */
  function updateLabel(button, toc) {
    button.textContent = allExpanded(toc) ? 'Collapse all sections' : 'Expand all sections';
  }

  /**
   * 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);
  }
})();