Main Page:mediawiki:common.js

From Ekatra Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

/* MediaWiki:Common.js

  Lightweight, mobile-friendly table sortable + responsive + collapsible
  Works on Vector (desktop) and Minerva (mobile) without depending on core tablesorter
  • /

( function () {

 /* --- Wrap tables for responsive scrolling --- */
 function wrapTables( $root ) {
   var $scope = $root.find( '.mw-parser-output' ).addBack( '.mw-parser-output' );
   if ( !$scope.length ) $scope = $root;
   $scope.find( 'table' ).each( function () {
     var $table = $( this );
     // Skip if already wrapped, or marked no-responsive
     if ( $table.closest( '.table-responsive' ).length ) return;
     if ( $table.hasClass( 'no-responsive' ) ) return;
     // Skip obvious layout/meta tables
     if ( $table.is( '.infobox, .navbox, .toc, .metadata' ) ) return;
     // Accessible label from caption
     var label = 'Scrollable table';
     var $cap = $table.children( 'caption' ).first();
     if ( $cap.length ) {
       var capText = ( $cap.text() ||  ).trim();
       if ( capText ) label = 'Scrollable table: ' + capText;
     }

var $wrapper = $( '

', {
       'class': 'table-responsive',
       'role': 'region',
       'aria-label': label,
       'tabindex': 0
     } );
     $table.before( $wrapper );
     $wrapper.append( $table );
   } );
 }
 /* --- Init collapsible --- */
 function initCollapsible( $root ) {
   // Try to use core collapsible module if available
   if ( mw.loader.getState( 'jquery.makeCollapsible' ) === 'ready' ) {
     $root.find( '.mw-collapsible' ).each( function () {
       var $c = $( this );
       if ( !$c.data( 'collapsible-initialized' ) ) {
         try { $c.makeCollapsible(); } catch (e) { /* ignore */ }
         $c.data( 'collapsible-initialized', true );
       }
     } );
   } else {
     // lazy-load if needed
     mw.loader.using( 'jquery.makeCollapsible' ).then( function () {
       $root.find( '.mw-collapsible' ).each( function () {
         var $c = $( this );
         if ( !$c.data( 'collapsible-initialized' ) ) {
           try { $c.makeCollapsible(); } catch (e) { /* ignore */ }
           $c.data( 'collapsible-initialized', true );
         }
       } );
     } );
   }
 }
 /* --- Lightweight, dependency-free sortable implementation --- */
 function initSortable( $root ) {
   $root.find( 'table.sortable' ).each( function () {
     var $table = $( this );
     if ( $table.data( 'sortable-init' ) ) return;
     $table.data( 'sortable-init', true );
     // find headers - prefer THEAD
     var $headers = $table.find( 'thead th' );
     if ( !$headers.length ) {
       // fallback to first row's cells
       $headers = $table.find( 'tr' ).first().find( 'th,td' );
     }
     $headers.each( function ( colIndex ) {
       var $th = $( this );
       if ( $th.hasClass( 'unsortable' ) ) return;
       // make touch/click-friendly
       $th.attr( 'role', 'button' ).css( 'cursor', 'pointer' );
       // prevent double-firing (touch + click)
       var lastTap = 0;
       $th.on( 'click touchstart', function ( e ) {
         var now = Date.now();
         if ( e.type === 'touchstart' && now - lastTap < 400 ) return; // ignore
         lastTap = now;
         e.preventDefault();
         e.stopPropagation();
         sortTableByColumn( $table, colIndex, $th );
       } );
     } );
   } );
 }
 /* Sort a single table's tbody rows by column (simple numeric/date/string heuristics) */
 function sortTableByColumn( $table, colIndex, $th ) {
   // toggle order
   var current = $th.data( 'sort-order' ) || 'none';
   var order = ( current === 'asc' ) ? 'desc' : 'asc';
   $th.closest( 'tr' ).find( 'th' ).removeClass( 'headerSortUp headerSortDown' );
   $th.addClass( order === 'asc' ? 'headerSortUp' : 'headerSortDown' );
   $th.data( 'sort-order', order );
   // process each TBODY independently (keeps logical groups)
   $table.find( 'tbody' ).each( function () {
     var tbody = this;
     var $tbody = $( tbody );
     var rows = $tbody.children( 'tr' ).toArray();
     // build keys (stable sort)
     var items = rows.map( function ( row, idx ) {
       return {
         row: row,
         idx: idx,
         key: extractCellValue( row, colIndex )
       };
     } );
     // detect numeric-like keys
     var numeric = items.every( function ( it ) {
       return it.key === null ? true : ( typeof it.key === 'number' );
     } );
     items.sort( function ( a, b ) {
       var va = a.key, vb = b.key;
       // treat nulls as last
       if ( va === null && vb === null ) return a.idx - b.idx;
       if ( va === null ) return 1;
       if ( vb === null ) return -1;
       if ( numeric ) {
         if ( va === vb ) return a.idx - b.idx;
         return order === 'asc' ? ( va - vb ) : ( vb - va );
       } else {
         va = String( va ).toLowerCase();
         vb = String( vb ).toLowerCase();
         if ( va === vb ) return a.idx - b.idx;
         return order === 'asc' ? va.localeCompare( vb ) : vb.localeCompare( va );
       }
     } );
     // append in new order
     var frag = document.createDocumentFragment();
     items.forEach( function ( it ) { frag.appendChild( it.row ); } );
     tbody.appendChild( frag );
   } );
 }
 /* Extract value for a given column index from a row (handles colspans) */
 function extractCellValue( row, colIndex ) {
   var $cells = $( row ).children( 'th,td' );
   var col = 0;
   for ( var i = 0; i < $cells.length; i++ ) {
     var cell = $cells[ i ];
     var colspan = parseInt( cell.getAttribute( 'colspan' ) || 1, 10 );
     if ( col + colspan - 1 >= colIndex ) {
       var txt = $( cell ).text().trim();
       if ( txt ===  ) return null;
       // numeric? strip currency symbols and commas
       var numTxt = txt.replace( /[,\s₹$€£¥]/g,  );
       var num = parseFloat( numTxt );
       if ( !isNaN( num ) && numTxt !==  ) return num;
       // ISO date (YYYY-MM-DD)
       if ( /^\d{4}-\d{2}-\d{2}$/.test( txt ) ) {
         var t = Date.parse( txt );
         if ( !isNaN( t ) ) return t;
       }
       return txt;
     }
     col += colspan;
   }
   return null;
 }
 /* --- Enhance function runs everything --- */
 function enhance( $content ) {
   wrapTables( $content );
   initCollapsible( $content );
   initSortable( $content );
 }
 /* Initial run and re-run on dynamic updates */
 $( function () { enhance( $( document ) ); } );
 mw.hook( 'wikipage.content' ).add( function ( $content ) { enhance( $content ); } );

} )();