Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.
       🚧 True to our name, we’re still a work in progress. 🚧
   
       You’re welcome to explore, but account registration is currently invite-only as we finalize the setup. 
       Join our forum or follow Mastodon for updates. 
       Full Wiki launch coming soon!
   

MediaWiki:Gadget-categories.js: Difference between revisions

MediaWiki interface page
No edit summary
 
(No difference)

Latest revision as of 11:19, 7 March 2026

/* =============================================================================
   Gadget-categories.js
   Runs on all Category:* pages.
   - Strips path prefixes from labels
   - Wraps link text in structured spans (name + meta)
   - Injects search bar
   - Batch API fetch: contributor count + last edited per page
   ============================================================================= */
( function () {
    if ( mw.config.get( 'wgNamespaceNumber' ) !== 14 ) return;

    $( function () {
        var $pageLinks = $( '#mw-pages .mw-category-group ul li a' );
        if ( !$pageLinks.length ) return;

        // --- Wrap link content in structured spans ---------------------------
        $pageLinks.each( function () {
            var $a    = $( this );
            var label = $a.text().replace( /^.*\//, '' );  // strip path prefix
            $a.empty()
              .append( $( '<span>' ).addClass( 'cat-item-name' ).text( label ) )
              .append( $( '<span>' ).addClass( 'cat-item-meta' ).text( '\u00a0' ) );
              // \u00a0 = non-breaking space — keeps height while loading
        } );

        // --- Inject search bar above #mw-pages -------------------------------
        var total    = $pageLinks.length;
        var $count   = $( '<span>' ).addClass( 'cat-search-count' )
                           .text( total + ' page' + ( total === 1 ? '' : 's' ) );
        var $input   = $( '<input>' ).attr( {
            type        : 'text',
            placeholder : 'Search this category...',
            'class'     : 'cat-search-input'
        } );
        var $wrap = $( '<div>' ).addClass( 'cat-search-wrap' )
                       .append( $input, $count );
        $( '#mw-pages h2' ).after( $wrap );

        $input.on( 'input', function () {
            var query   = $( this ).val().toLowerCase().trim();
            var visible = 0;

            $pageLinks.each( function () {
                var $a   = $( this );
                var name = $a.find( '.cat-item-name' ).text().toLowerCase();
                var show = !query || name.indexOf( query ) !== -1;
                $a.closest( 'li' ).toggle( show );
                if ( show ) visible++;
            } );

            // Hide empty letter groups
            $( '#mw-pages .mw-category-group' ).each( function () {
                var hasVisible = $( this ).find( 'li:visible' ).length > 0;
                $( this ).toggle( hasVisible );
            } );

            $count.text(
                query
                    ? visible + ' / ' + total + ' page' + ( total === 1 ? '' : 's' )
                    : total + ' page' + ( total === 1 ? '' : 's' )
            );
        } );

        // --- Batch API fetch -------------------------------------------------
        var pageNames = $pageLinks.map( function () {
            return $( this ).attr( 'href' )
                ? decodeURIComponent(
                    $( this ).attr( 'href' ).replace( /^.*\/wiki\//, '' )
                  ).replace( /_/g, ' ' )
                : null;
        } ).get().filter( Boolean );

        if ( !pageNames.length ) return;

        var api = new mw.Api();

        for ( var i = 0; i < pageNames.length; i += 50 ) {
            ( function ( batch ) {
                api.get( {
                    action  : 'query',
                    titles  : batch.join( '|' ),
                    prop    : 'revisions|contributors',
                    rvprop  : 'timestamp',
                    rvlimit : 1,
                    pclimit : 500,
                    format  : 'json'
                } ).done( function ( data ) {
                    var pages = data.query && data.query.pages;
                    if ( !pages ) return;

                    $.each( pages, function ( _, page ) {
                        var title    = page.title;
                        var ts       = page.revisions && page.revisions[0] && page.revisions[0].timestamp || '';
                        var nContrib = ( page.contributors ? page.contributors.length : 0 )
                                     + ( page.anoncontributors || 0 );

                        // Match by href
                        var $a = $pageLinks.filter( function () {
                            var href = $( this ).attr( 'href' ) || '';
                            return decodeURIComponent( href.replace( /^.*\/wiki\//, '' ) )
                                .replace( /_/g, ' ' ) === title;
                        } );
                        if ( !$a.length ) return;

                        var dateStr = ts
                            ? new Date( ts ).toLocaleDateString( 'en-GB', { year: 'numeric', month: 'short' } )
                            : '—';

                        var metaText = nContrib + ( nContrib === 1 ? ' contributor' : ' contributors' )
                                     + ' · ' + dateStr;

                        $a.find( '.cat-item-meta' )
                          .text( metaText )
                          .addClass( 'loaded' );
                    } );
                } );
            }( pageNames.slice( i, i + 50 ) ) );
        }
    } );
}() );