MediaWiki:Common.js
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); });