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:
mw.loader.using('jquery', function () {
/* Collapsible Sidebar for MediaWiki 1.44 (Vector + Vector-2022)
    $(function () {
* - Arrow at left of each portal header
        // Select top-level sidebar items with submenus
* - Remembers state via localStorage
        $('.vector-menu-portal .vector-menu-content-list > li').each(function () {
* - No deprecated libs; RL-safe
            var $item = $(this);
* - Console logs for easy debugging
            var $subMenu = $item.children('ul');
*/
mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
  $( function () {


            if ($subMenu.length) {
    // --- Config -------------------------------------------------------------
                $item.addClass('collapsible-header');
    var STORAGE_KEY = 'sidebar-collapse-state:v1';
                $subMenu.hide(); // Hide by default
    // 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' */ ];


                // Click to toggle submenu
    // --- Helpers ------------------------------------------------------------
                $item.on('click', function (e) {
    function log() { /* flip to false to silence */
                    // Prevent links in submenu from triggering collapse
      if ( true && window.console && console.log ) console.log.apply( console, [ '[SidebarCollapse]' ].concat( Array.from( arguments ) ) );
                    if (e.target.tagName !== 'A') {
    }
                        e.preventDefault();
 
                        $item.toggleClass('open');
    function readState() {
                        $subMenu.slideToggle(200);
      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);
});
});

Revision as of 15:58, 25 August 2025

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