MediaWiki:Common.js

From SZ
Revision as of 15:58, 25 August 2025 by Test2 (talk | contribs)
Jump to navigation Jump to search

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.
/* Collapsible Sidebar for MediaWiki 1.44 (Vector + Vector-2022)
 * - Arrow at left of each portal header
 * - Remembers state via localStorage
 * - No deprecated libs; RL-safe
 * - Console logs for easy debugging
 */
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() {
      try {
        var raw = localStorage.getItem( STORAGE_KEY );
        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);
      }
    }

    // Normalize an identifier for a portal/menu node
    function portalIdFor( node ) {
      // Prefer element id like "p-navigation" -> "navigation"
      if ( node.id && node.id.indexOf('p-') === 0 ) {
        return node.id.slice(2).toLowerCase();
      }
      // 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 ) {
      return node.classList.contains('vector-collapsible--expanded');
    }

    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');
      if ( heading ) {
        heading.setAttribute( 'role', 'button' );
        heading.setAttribute( 'tabindex', '0' );
        heading.setAttribute( 'aria-expanded', String( !!expanded ) );
      }
    }

    function toggleNode( node, persist ) {
      var nextState = !isExpandedClass( node );
      setExpanded( node, nextState );
      if ( persist ) {
        var id = portalIdFor( node );
        var state = readState();
        state[ id ] = nextState ? 1 : 0;
        writeState( state );
        log('Toggled', id, '=>', nextState ? 'open' : 'closed' );
      }
    }

    function attachToggle( node ) {
      var heading = node.querySelector('h3, .vector-menu-heading');
      if ( !heading ) return;

      // Click
      heading.addEventListener( 'click', function ( e ) {
        e.preventDefault();
        toggleNode( node, true );
      } );

      // Keyboard (Enter/Space)
      heading.addEventListener( 'keydown', function ( e ) {
        if ( e.key === 'Enter' || e.key === ' ' ) {
          e.preventDefault();
          toggleNode( node, true );
        }
      } );
    }

    // --- Locate portals for both skins -------------------------------------
    var isV22 = document.body.classList.contains('skin-vector-2022');

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

    portals.forEach( function ( portal ) {
      var id = portalIdFor( portal );
      var initial;

      if ( saved.hasOwnProperty( id ) ) {
        initial = !!saved[ id ];
      } 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);
});