MediaWiki:Timeless.js

From Game Wiki - VortanMU
Revision as of 06:59, 16 January 2026 by Admin (talk | contribs)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ==============================
   VortanMU - Timeless Sidebar V3
   - Normaliza sidebar esquerda
   - Remove resíduos (has-mega/submenu inline)
   - Accordion moderno + persistência (localStorage)
   ============================== */

(function () {
  'use strict';

  const STORAGE_KEY = 'vm_sidebar_open_portlets_v3';

  function qsa(root, sel) {
    return Array.prototype.slice.call((root || document).querySelectorAll(sel));
  }

  function readState() {
    try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
    catch (e) { return []; }
  }

  function writeState(ids) {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(ids)); }
    catch (e) {}
  }

  function removeInjectedSubmenus(portlet) {
    // remove any injected submenu lists
    qsa(portlet, 'ul.submenu, ul[class*="submenu"]').forEach(ul => ul.remove());

    // ensure original UL is visible (remove inline display none)
    const body = portlet.querySelector('.mw-portlet-body');
    if (!body) return;

    const ul = body.querySelector('ul');
    if (!ul) return;

    ul.style.display = '';
    ul.hidden = false;
  }

  function sanitizeHeader(header) {
    header.classList.remove('has-mega', 'active', 'open', 'is-open');
    header.classList.add('vm-portlet__header');
    header.setAttribute('tabindex', '0');
    header.setAttribute('role', 'button');
  }

  function ensurePortletId(portlet, idx) {
    if (!portlet.id) portlet.id = 'vm-portlet-' + idx;
    return portlet.id;
  }

  function setOpen(portlet, header, open) {
    portlet.classList.toggle('is-open', open);
    header.setAttribute('aria-expanded', open ? 'true' : 'false');
  }

  function normalizeLeftSidebar() {
    const nav = document.getElementById('mw-site-navigation');
    if (!nav) return;

    nav.classList.add('vm-nav');

    const inner = nav.querySelector('.sidebar-inner') || nav;
    const portlets = qsa(inner, '.mw-portlet[role="navigation"]');
    const saved = new Set(readState());

    portlets.forEach((p, idx) => {
      p.classList.add('vm-portlet');

      const header = p.querySelector('h3');
      const body = p.querySelector('.mw-portlet-body');
      if (!header || !body) return;

      const id = ensurePortletId(p, idx);

      sanitizeHeader(header);
      removeInjectedSubmenus(p);

      // default open: use saved state, otherwise open only first
      const shouldOpen = saved.size ? saved.has(id) : (idx === 0);
      setOpen(p, header, shouldOpen);

      if (!header.__vmBound) {
        const toggle = () => {
          const willOpen = !p.classList.contains('is-open');

          // accordion behavior (open one at a time)
          portlets.forEach(other => {
            if (other !== p) {
              const h = other.querySelector('h3.vm-portlet__header');
              if (h) setOpen(other, h, false);
            }
          });

          setOpen(p, header, willOpen);

          // persist open ones (only one open)
          const openIds = portlets
            .filter(x => x.classList.contains('is-open'))
            .map(x => x.id)
            .filter(Boolean);

          writeState(openIds);
        };

        header.addEventListener('click', toggle);
        header.addEventListener('keydown', (e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            toggle();
          }
        });

        header.__vmBound = true;
      }
    });

    // guard: if some script tries to inject submenu again, delete it
    if (!inner.__vmObserver) {
      const obs = new MutationObserver(() => {
        const injected = inner.querySelector('ul.submenu, ul[class*="submenu"]');
        if (injected) {
          portlets.forEach(p => removeInjectedSubmenus(p));
        }
      });
      obs.observe(inner, { childList: true, subtree: true });
      inner.__vmObserver = obs;
    }
  }

  function runSoon() {
    normalizeLeftSidebar();
    setTimeout(normalizeLeftSidebar, 200);
    setTimeout(normalizeLeftSidebar, 900);
  }

  if (window.mw && mw.hook) {
    mw.hook('wikipage.content').add(() => runSoon());
  } else if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', runSoon);
  } else {
    runSoon();
  }
})();