MediaWiki:Timeless.js: Difference between revisions

From Game Wiki - VortanMU
No edit summary
No edit summary
Line 1: Line 1:
/* ==============================
/*
  VortanMU - Timeless Sidebar V3
  VortanMU - Timeless sidebar behavior (v3)
  - Normaliza sidebar esquerda
  - Hover to open (desktop) + click to toggle (touch/keyboard)
  - Remove resíduos (has-mega/submenu inline)
  - Keeps original Timeless structure (portlets)
  - Accordion moderno + persistência (localStorage)
  - Avoids conflicts with any previously-injected mega-menu nodes
  ============================== */


  Install: paste into MediaWiki:Timeless.js (replace your current sidebar script).
*/
(function () {
(function () {
   'use strict';
   'use strict';
  if (window.VM_TIMELESS_SIDEBAR_V3) return;
  window.VM_TIMELESS_SIDEBAR_V3 = true;


  const STORAGE_KEY = 'vm_sidebar_open_portlets_v3';
   function enhanceSidebar() {
 
     var nav = document.getElementById('mw-site-navigation');
  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;
     if (!nav) return;


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


     const inner = nav.querySelector('.sidebar-inner') || nav;
     // Remove any old injected mega-menu nodes (common source of “applies then reverts”)
    const portlets = qsa(inner, '.mw-portlet[role="navigation"]');
    Array.prototype.forEach.call(nav.querySelectorAll('ul.submenu'), function (el) {
     const saved = new Set(readState());
      el.parentNode && el.parentNode.removeChild(el);
     });


     portlets.forEach((p, idx) => {
     var portlets = Array.prototype.slice.call(
       p.classList.add('vm-portlet');
       nav.querySelectorAll('.mw-portlet[role="navigation"]')
    );


      const header = p.querySelector('h3');
    var hoverCapable = !!(window.matchMedia && window.matchMedia('(hover:hover) and (pointer:fine)').matches);
      const body = p.querySelector('.mw-portlet-body');
      if (!header || !body) return;


       const id = ensurePortletId(p, idx);
    function setExpanded(p, expanded) {
       var h = p.querySelector('h3.vm-portlet__header') || p.querySelector('h3');
      if (h) h.setAttribute('aria-expanded', expanded ? 'true' : 'false');
    }


       sanitizeHeader(header);
    function closePortlet(p) {
       removeInjectedSubmenus(p);
       p.classList.remove('is-open');
       setExpanded(p, false);
    }


       // default open: use saved state, otherwise open only first
    function openPortlet(p) {
       const shouldOpen = saved.size ? saved.has(id) : (idx === 0);
       // close others (keeps sidebar clean), but never close Wiki tools
       setOpen(p, header, shouldOpen);
       portlets.forEach(function (other) {
        if (other !== p && other.id !== 'p-tb') closePortlet(other);
       });
      p.classList.add('is-open');
      setExpanded(p, true);
    }


      if (!header.__vmBound) {
    portlets.forEach(function (p) {
        const toggle = () => {
      var h3 = p.querySelector('h3');
          const willOpen = !p.classList.contains('is-open');
      var body = p.querySelector('.mw-portlet-body');
      if (!h3 || !body) return;


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


          setOpen(p, header, willOpen);
      // If something hid the original <ul> inline, bring it back
      var ul = body.querySelector('ul');
      if (ul && ul.style && ul.style.display === 'none') ul.style.display = '';


          // persist open ones (only one open)
      // Wiki tools: always visible
          const openIds = portlets
      if (p.id === 'p-tb') {
            .filter(x => x.classList.contains('is-open'))
        p.classList.add('is-open');
            .map(x => x.id)
        h3.setAttribute('aria-expanded', 'true');
            .filter(Boolean);
        return;
      }


          writeState(openIds);
      // default closed
        };
      closePortlet(p);


        header.addEventListener('click', toggle);
      // accessibility + click toggle
        header.addEventListener('keydown', (e) => {
      h3.setAttribute('role', 'button');
          if (e.key === 'Enter' || e.key === ' ') {
      h3.setAttribute('tabindex', '0');
            e.preventDefault();
      h3.setAttribute('aria-expanded', 'false');
            toggle();
          }
        });


         header.__vmBound = true;
      function toggle() {
         if (p.classList.contains('is-open')) closePortlet(p);
        else openPortlet(p);
       }
       }
    });


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


  function runSoon() {
      // Hover open (desktop)
    normalizeLeftSidebar();
      if (hoverCapable) {
    setTimeout(normalizeLeftSidebar, 200);
        var t;
    setTimeout(normalizeLeftSidebar, 900);
        p.addEventListener('mouseenter', function () {
          if (t) clearTimeout(t);
          openPortlet(p);
        });
        p.addEventListener('mouseleave', function () {
          if (t) clearTimeout(t);
          t = setTimeout(function () { closePortlet(p); }, 180);
        });
      }
    });
   }
   }


  // Run on normal loads + after purge/navigation
   if (window.mw && mw.hook) {
   if (window.mw && mw.hook) {
     mw.hook('wikipage.content').add(() => runSoon());
     mw.hook('wikipage.content').add(enhanceSidebar);
   } else if (document.readyState === 'loading') {
   } else if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', runSoon);
     document.addEventListener('DOMContentLoaded', enhanceSidebar);
   } else {
   } else {
     runSoon();
     enhanceSidebar();
   }
   }
})();
})();

Revision as of 07:35, 16 January 2026

/*
  VortanMU - Timeless sidebar behavior (v3)
  - Hover to open (desktop) + click to toggle (touch/keyboard)
  - Keeps original Timeless structure (portlets)
  - Avoids conflicts with any previously-injected mega-menu nodes

  Install: paste into MediaWiki:Timeless.js (replace your current sidebar script).
*/
(function () {
  'use strict';
  if (window.VM_TIMELESS_SIDEBAR_V3) return;
  window.VM_TIMELESS_SIDEBAR_V3 = true;

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

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

    // Remove any old injected mega-menu nodes (common source of “applies then reverts”)
    Array.prototype.forEach.call(nav.querySelectorAll('ul.submenu'), function (el) {
      el.parentNode && el.parentNode.removeChild(el);
    });

    var portlets = Array.prototype.slice.call(
      nav.querySelectorAll('.mw-portlet[role="navigation"]')
    );

    var hoverCapable = !!(window.matchMedia && window.matchMedia('(hover:hover) and (pointer:fine)').matches);

    function setExpanded(p, expanded) {
      var h = p.querySelector('h3.vm-portlet__header') || p.querySelector('h3');
      if (h) h.setAttribute('aria-expanded', expanded ? 'true' : 'false');
    }

    function closePortlet(p) {
      p.classList.remove('is-open');
      setExpanded(p, false);
    }

    function openPortlet(p) {
      // close others (keeps sidebar clean), but never close Wiki tools
      portlets.forEach(function (other) {
        if (other !== p && other.id !== 'p-tb') closePortlet(other);
      });
      p.classList.add('is-open');
      setExpanded(p, true);
    }

    portlets.forEach(function (p) {
      var h3 = p.querySelector('h3');
      var body = p.querySelector('.mw-portlet-body');
      if (!h3 || !body) return;

      h3.classList.add('vm-portlet__header');
      body.classList.add('vm-portlet__body');

      // If something hid the original <ul> inline, bring it back
      var ul = body.querySelector('ul');
      if (ul && ul.style && ul.style.display === 'none') ul.style.display = '';

      // Wiki tools: always visible
      if (p.id === 'p-tb') {
        p.classList.add('is-open');
        h3.setAttribute('aria-expanded', 'true');
        return;
      }

      // default closed
      closePortlet(p);

      // accessibility + click toggle
      h3.setAttribute('role', 'button');
      h3.setAttribute('tabindex', '0');
      h3.setAttribute('aria-expanded', 'false');

      function toggle() {
        if (p.classList.contains('is-open')) closePortlet(p);
        else openPortlet(p);
      }

      h3.addEventListener('click', function (e) {
        e.preventDefault();
        toggle();
      });

      h3.addEventListener('keydown', function (e) {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          toggle();
        }
      });

      // Hover open (desktop)
      if (hoverCapable) {
        var t;
        p.addEventListener('mouseenter', function () {
          if (t) clearTimeout(t);
          openPortlet(p);
        });
        p.addEventListener('mouseleave', function () {
          if (t) clearTimeout(t);
          t = setTimeout(function () { closePortlet(p); }, 180);
        });
      }
    });
  }

  // Run on normal loads + after purge/navigation
  if (window.mw && mw.hook) {
    mw.hook('wikipage.content').add(enhanceSidebar);
  } else if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', enhanceSidebar);
  } else {
    enhanceSidebar();
  }
})();