MediaWiki:Common.js: Difference between revisions

From SZ
Jump to navigation Jump to search
No edit summary
Tag: Reverted
No edit summary
Tag: Reverted
Line 1: Line 1:
/* Collapsible Sidebar for MediaWiki 1.44 (Vector + Vector-2022)
mw.loader.using(['mediawiki.util']).then(function () {
* - Arrow at left of each portal header
   $(function () {
* - Remembers state via localStorage
     var STORAGE_KEY = 'sidebar-collapse-state:v2';
* - No deprecated libs; RL-safe
     var defaultExpanded = ['navigation', 'book1'];
* - Console logs for easy debugging
     var defaultCollapsed = [];
*/
mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
   $( function () {
 
    // --- Config -------------------------------------------------------------
     var STORAGE_KEY = 'sidebar-collapse-state:v1';
    // Portals to leave open on first load; you can edit this list:
     var defaultExpanded = [ 'navigation', 'book1' ];
    // Force-collapse these even on first load:
     var defaultCollapsed = [ /* e.g. 'tools' */ ];
 
    // --- Helpers ------------------------------------------------------------
    function log() { /* flip to false to silence */
      if ( true && window.console && console.log ) console.log.apply( console, [ '[SidebarCollapse]' ].concat( Array.from( arguments ) ) );
    }


     function readState() {
     function readState() {
       try {
       try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; }
        var raw = localStorage.getItem( STORAGE_KEY );
       catch(e) { return {}; }
        return raw ? JSON.parse( raw ) : {};
       } catch (e) {
        log('localStorage read failed', e);
        return {};
      }
    }
 
    function writeState(state) {
      try {
        localStorage.setItem( STORAGE_KEY, JSON.stringify( state ) );
      } catch (e) {
        log('localStorage write failed', e);
      }
     }
     }
    function writeState(s) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch(e) {} }


    // Normalize an identifier for a portal/menu node
     function portalIdFor(node) {
     function portalIdFor( node ) {
       if (node.id?.startsWith('p-')) return node.id.slice(2).toLowerCase();
      // Prefer element id like "p-navigation" -> "navigation"
       var dn = node.getAttribute('data-name');
       if ( node.id && node.id.indexOf('p-') === 0 ) {
       if (dn) return dn.toLowerCase();
        return node.id.slice(2).toLowerCase();
       var h = node.querySelector('h3, .vector-menu-heading');
      }
       return h ? h.textContent.trim().toLowerCase().replace(/\s+/g,'-') : 'portal-'+Math.random();
      // Vector-2022 often has data-name="navigation"
       var dn = node.getAttribute && node.getAttribute('data-name');
       if ( dn ) return dn.toLowerCase();
 
      // Fallback: use heading text
       var heading = node.querySelector('h3, .vector-menu-heading, .portal h3');
       if ( heading ) {
        return heading.textContent.trim().toLowerCase().replace(/\s+/g,'-');
      }
      // Last resort: index among siblings
      return 'portal-' + ( Array.prototype.indexOf.call( node.parentNode.children, node ) );
     }
     }


    function isExpandedClass( node ) {
     function setExpanded(node, exp) {
      return node.classList.contains('vector-collapsible--expanded');
       node.classList.toggle('vector-collapsible--expanded', exp);
    }
       node.classList.toggle('vector-collapsible--collapsed', !exp);
 
     function setExpanded( node, expanded ) {
       node.classList.toggle( 'vector-collapsible--expanded', !!expanded );
       node.classList.toggle( 'vector-collapsible--collapsed', !expanded );
      // ARIA for accessibility
       var heading = node.querySelector('h3, .vector-menu-heading');
       var heading = node.querySelector('h3, .vector-menu-heading');
       if ( heading ) {
       if (heading) heading.setAttribute('aria-expanded', exp);
        heading.setAttribute( 'role', 'button' );
        heading.setAttribute( 'tabindex', '0' );
        heading.setAttribute( 'aria-expanded', String( !!expanded ) );
      }
     }
     }


     function toggleNode( node, persist ) {
     function toggleNode(node, persist) {
       var nextState = !isExpandedClass( node );
       var ns = !node.classList.contains('vector-collapsible--expanded');
       setExpanded( node, nextState );
       setExpanded(node, ns);
       if ( persist ) {
       if (persist) {
         var id = portalIdFor( node );
         var s = readState(); s[portalIdFor(node)] = ns ? 1 : 0; writeState(s);
        var state = readState();
        state[ id ] = nextState ? 1 : 0;
        writeState( state );
        log('Toggled', id, '=>', nextState ? 'open' : 'closed' );
       }
       }
     }
     }


     function attachToggle( node ) {
     function attachToggle(node) {
       var heading = node.querySelector('h3, .vector-menu-heading');
       var heading = node.querySelector('h3, .vector-menu-heading');
       if ( !heading ) return;
       if (!heading) return;
 
      // inject span.icon if missing
       // Click
       if (!heading.querySelector('.vector-collapse-icon')) {
       heading.addEventListener( 'click', function ( e ) {
        var span = document.createElement('span');
        e.preventDefault();
        span.className = 'vector-collapse-icon';
        toggleNode( node, true );
        span.innerHTML = '▼'; // ▼ symbol
      } );
        heading.style.position = 'relative';
 
        heading.style.paddingLeft = '1.5em';
      // Keyboard (Enter/Space)
        heading.insertBefore(span, heading.firstChild);
       heading.addEventListener( 'keydown', function ( e ) {
      }
        if ( e.key === 'Enter' || e.key === ' ' ) {
       heading.addEventListener('click', function(e){ e.preventDefault(); toggleNode(node,true); });
          e.preventDefault();
       heading.addEventListener('keydown', function(e){ if (e.key==='Enter'||e.key===' '){ e.preventDefault(); toggleNode(node,true);} });
          toggleNode( node, true );
        }
      } );
     }
     }


     // --- Locate portals for both skins -------------------------------------
     var portals = document.body.classList.contains('skin-vector-2022')
    var isV22 = document.body.classList.contains('skin-vector-2022');
      ? Array.from(document.querySelectorAll('nav.vector-menu-portal')).filter(e=>!e.closest('#vector-main-menu'))
 
      : Array.from(document.querySelectorAll('#mw-panel .portal'));
    // Vector legacy portals: div.portal under #mw-panel
    var legacyPortals = Array.from( document.querySelectorAll('#mw-panel .portal') );


    // Vector 2022 portals: nav.vector-menu-portal (exclude the main hamburger)
    var v22Portals = Array.from( document.querySelectorAll('nav.vector-menu-portal') )
      .filter( function (el) { return !el.closest('#vector-main-menu'); } );
    var portals = isV22 ? v22Portals : legacyPortals;
    if ( portals.length === 0 ) {
      log('No portals found — are you using Vector skin?');
      return;
    }
    // --- Initialize state ---------------------------------------------------
     var saved = readState();
     var saved = readState();
 
     portals.forEach(function(p){
     portals.forEach( function ( portal ) {
       var id = portalIdFor(p);
       var id = portalIdFor( portal );
       var initial = saved.hasOwnProperty(id) ? !!saved[id] :
       var initial;
                    defaultExpanded.includes(id) ? true :
 
                    defaultCollapsed.includes(id) ? false : true;
      if ( saved.hasOwnProperty( id ) ) {
       setExpanded(p, initial);
        initial = !!saved[ id ];
       attachToggle(p);
      } else if ( defaultCollapsed.indexOf( id ) !== -1 ) {
        initial = false;
      } else if ( defaultExpanded.indexOf( id ) !== -1 ) {
        initial = true;
      } else {
        // Default behavior: expanded
        initial = true;
      }
 
      portal.classList.add('vector-collapsible'); // cosmetic marker if you want to theme
       setExpanded( portal, initial );
       attachToggle( portal );
 
      log('Init portal:', id, 'expanded =', initial);
     });
     });
 
   });
   } );
} ).catch( function (e) {
  console.error('[SidebarCollapse] loader failed:', e);
});
});

Revision as of 16:01, 25 August 2025

mw.loader.using(['mediawiki.util']).then(function () {
  $(function () {
    var STORAGE_KEY = 'sidebar-collapse-state:v2';
    var defaultExpanded = ['navigation', 'book1'];
    var defaultCollapsed = [];

    function readState() {
      try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; }
      catch(e) { return {}; }
    }
    function writeState(s) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch(e) {} }

    function portalIdFor(node) {
      if (node.id?.startsWith('p-')) return node.id.slice(2).toLowerCase();
      var dn = node.getAttribute('data-name');
      if (dn) return dn.toLowerCase();
      var h = node.querySelector('h3, .vector-menu-heading');
      return h ? h.textContent.trim().toLowerCase().replace(/\s+/g,'-') : 'portal-'+Math.random();
    }

    function setExpanded(node, exp) {
      node.classList.toggle('vector-collapsible--expanded', exp);
      node.classList.toggle('vector-collapsible--collapsed', !exp);
      var heading = node.querySelector('h3, .vector-menu-heading');
      if (heading) heading.setAttribute('aria-expanded', exp);
    }

    function toggleNode(node, persist) {
      var ns = !node.classList.contains('vector-collapsible--expanded');
      setExpanded(node, ns);
      if (persist) {
        var s = readState(); s[portalIdFor(node)] = ns ? 1 : 0; writeState(s);
      }
    }

    function attachToggle(node) {
      var heading = node.querySelector('h3, .vector-menu-heading');
      if (!heading) return;
      // inject span.icon if missing
      if (!heading.querySelector('.vector-collapse-icon')) {
        var span = document.createElement('span');
        span.className = 'vector-collapse-icon';
        span.innerHTML = '▼'; // ▼ symbol
        heading.style.position = 'relative';
        heading.style.paddingLeft = '1.5em';
        heading.insertBefore(span, heading.firstChild);
      }
      heading.addEventListener('click', function(e){ e.preventDefault(); toggleNode(node,true); });
      heading.addEventListener('keydown', function(e){ if (e.key==='Enter'||e.key===' '){ e.preventDefault(); toggleNode(node,true);} });
    }

    var portals = document.body.classList.contains('skin-vector-2022')
      ? Array.from(document.querySelectorAll('nav.vector-menu-portal')).filter(e=>!e.closest('#vector-main-menu'))
      : Array.from(document.querySelectorAll('#mw-panel .portal'));

    var saved = readState();
    portals.forEach(function(p){
      var id = portalIdFor(p);
      var initial = saved.hasOwnProperty(id) ? !!saved[id] :
                    defaultExpanded.includes(id) ? true :
                    defaultCollapsed.includes(id) ? false : true;
      setExpanded(p, initial);
      attachToggle(p);
    });
  });
});