<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://unfinishedprojects.net/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-categories.js</id>
	<title>MediaWiki:Gadget-categories.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://unfinishedprojects.net/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-categories.js"/>
	<link rel="alternate" type="text/html" href="https://unfinishedprojects.net/index.php?title=MediaWiki:Gadget-categories.js&amp;action=history"/>
	<updated>2026-04-06T02:32:14Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://unfinishedprojects.net/index.php?title=MediaWiki:Gadget-categories.js&amp;diff=631&amp;oldid=prev</id>
		<title>Anthony at 11:19, 7 March 2026</title>
		<link rel="alternate" type="text/html" href="https://unfinishedprojects.net/index.php?title=MediaWiki:Gadget-categories.js&amp;diff=631&amp;oldid=prev"/>
		<updated>2026-03-07T11:19:09Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;/* =============================================================================&lt;br /&gt;
   Gadget-categories.js&lt;br /&gt;
   Runs on all Category:* pages.&lt;br /&gt;
   - Strips path prefixes from labels&lt;br /&gt;
   - Wraps link text in structured spans (name + meta)&lt;br /&gt;
   - Injects search bar&lt;br /&gt;
   - Batch API fetch: contributor count + last edited per page&lt;br /&gt;
   ============================================================================= */&lt;br /&gt;
( function () {&lt;br /&gt;
    if ( mw.config.get( &amp;#039;wgNamespaceNumber&amp;#039; ) !== 14 ) return;&lt;br /&gt;
&lt;br /&gt;
    $( function () {&lt;br /&gt;
        var $pageLinks = $( &amp;#039;#mw-pages .mw-category-group ul li a&amp;#039; );&lt;br /&gt;
        if ( !$pageLinks.length ) return;&lt;br /&gt;
&lt;br /&gt;
        // --- Wrap link content in structured spans ---------------------------&lt;br /&gt;
        $pageLinks.each( function () {&lt;br /&gt;
            var $a    = $( this );&lt;br /&gt;
            var label = $a.text().replace( /^.*\//, &amp;#039;&amp;#039; );  // strip path prefix&lt;br /&gt;
            $a.empty()&lt;br /&gt;
              .append( $( &amp;#039;&amp;lt;span&amp;gt;&amp;#039; ).addClass( &amp;#039;cat-item-name&amp;#039; ).text( label ) )&lt;br /&gt;
              .append( $( &amp;#039;&amp;lt;span&amp;gt;&amp;#039; ).addClass( &amp;#039;cat-item-meta&amp;#039; ).text( &amp;#039;\u00a0&amp;#039; ) );&lt;br /&gt;
              // \u00a0 = non-breaking space — keeps height while loading&lt;br /&gt;
        } );&lt;br /&gt;
&lt;br /&gt;
        // --- Inject search bar above #mw-pages -------------------------------&lt;br /&gt;
        var total    = $pageLinks.length;&lt;br /&gt;
        var $count   = $( &amp;#039;&amp;lt;span&amp;gt;&amp;#039; ).addClass( &amp;#039;cat-search-count&amp;#039; )&lt;br /&gt;
                           .text( total + &amp;#039; page&amp;#039; + ( total === 1 ? &amp;#039;&amp;#039; : &amp;#039;s&amp;#039; ) );&lt;br /&gt;
        var $input   = $( &amp;#039;&amp;lt;input&amp;gt;&amp;#039; ).attr( {&lt;br /&gt;
            type        : &amp;#039;text&amp;#039;,&lt;br /&gt;
            placeholder : &amp;#039;Search this category...&amp;#039;,&lt;br /&gt;
            &amp;#039;class&amp;#039;     : &amp;#039;cat-search-input&amp;#039;&lt;br /&gt;
        } );&lt;br /&gt;
        var $wrap = $( &amp;#039;&amp;lt;div&amp;gt;&amp;#039; ).addClass( &amp;#039;cat-search-wrap&amp;#039; )&lt;br /&gt;
                       .append( $input, $count );&lt;br /&gt;
        $( &amp;#039;#mw-pages h2&amp;#039; ).after( $wrap );&lt;br /&gt;
&lt;br /&gt;
        $input.on( &amp;#039;input&amp;#039;, function () {&lt;br /&gt;
            var query   = $( this ).val().toLowerCase().trim();&lt;br /&gt;
            var visible = 0;&lt;br /&gt;
&lt;br /&gt;
            $pageLinks.each( function () {&lt;br /&gt;
                var $a   = $( this );&lt;br /&gt;
                var name = $a.find( &amp;#039;.cat-item-name&amp;#039; ).text().toLowerCase();&lt;br /&gt;
                var show = !query || name.indexOf( query ) !== -1;&lt;br /&gt;
                $a.closest( &amp;#039;li&amp;#039; ).toggle( show );&lt;br /&gt;
                if ( show ) visible++;&lt;br /&gt;
            } );&lt;br /&gt;
&lt;br /&gt;
            // Hide empty letter groups&lt;br /&gt;
            $( &amp;#039;#mw-pages .mw-category-group&amp;#039; ).each( function () {&lt;br /&gt;
                var hasVisible = $( this ).find( &amp;#039;li:visible&amp;#039; ).length &amp;gt; 0;&lt;br /&gt;
                $( this ).toggle( hasVisible );&lt;br /&gt;
            } );&lt;br /&gt;
&lt;br /&gt;
            $count.text(&lt;br /&gt;
                query&lt;br /&gt;
                    ? visible + &amp;#039; / &amp;#039; + total + &amp;#039; page&amp;#039; + ( total === 1 ? &amp;#039;&amp;#039; : &amp;#039;s&amp;#039; )&lt;br /&gt;
                    : total + &amp;#039; page&amp;#039; + ( total === 1 ? &amp;#039;&amp;#039; : &amp;#039;s&amp;#039; )&lt;br /&gt;
            );&lt;br /&gt;
        } );&lt;br /&gt;
&lt;br /&gt;
        // --- Batch API fetch -------------------------------------------------&lt;br /&gt;
        var pageNames = $pageLinks.map( function () {&lt;br /&gt;
            return $( this ).attr( &amp;#039;href&amp;#039; )&lt;br /&gt;
                ? decodeURIComponent(&lt;br /&gt;
                    $( this ).attr( &amp;#039;href&amp;#039; ).replace( /^.*\/wiki\//, &amp;#039;&amp;#039; )&lt;br /&gt;
                  ).replace( /_/g, &amp;#039; &amp;#039; )&lt;br /&gt;
                : null;&lt;br /&gt;
        } ).get().filter( Boolean );&lt;br /&gt;
&lt;br /&gt;
        if ( !pageNames.length ) return;&lt;br /&gt;
&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
        for ( var i = 0; i &amp;lt; pageNames.length; i += 50 ) {&lt;br /&gt;
            ( function ( batch ) {&lt;br /&gt;
                api.get( {&lt;br /&gt;
                    action  : &amp;#039;query&amp;#039;,&lt;br /&gt;
                    titles  : batch.join( &amp;#039;|&amp;#039; ),&lt;br /&gt;
                    prop    : &amp;#039;revisions|contributors&amp;#039;,&lt;br /&gt;
                    rvprop  : &amp;#039;timestamp&amp;#039;,&lt;br /&gt;
                    rvlimit : 1,&lt;br /&gt;
                    pclimit : 500,&lt;br /&gt;
                    format  : &amp;#039;json&amp;#039;&lt;br /&gt;
                } ).done( function ( data ) {&lt;br /&gt;
                    var pages = data.query &amp;amp;&amp;amp; data.query.pages;&lt;br /&gt;
                    if ( !pages ) return;&lt;br /&gt;
&lt;br /&gt;
                    $.each( pages, function ( _, page ) {&lt;br /&gt;
                        var title    = page.title;&lt;br /&gt;
                        var ts       = page.revisions &amp;amp;&amp;amp; page.revisions[0] &amp;amp;&amp;amp; page.revisions[0].timestamp || &amp;#039;&amp;#039;;&lt;br /&gt;
                        var nContrib = ( page.contributors ? page.contributors.length : 0 )&lt;br /&gt;
                                     + ( page.anoncontributors || 0 );&lt;br /&gt;
&lt;br /&gt;
                        // Match by href&lt;br /&gt;
                        var $a = $pageLinks.filter( function () {&lt;br /&gt;
                            var href = $( this ).attr( &amp;#039;href&amp;#039; ) || &amp;#039;&amp;#039;;&lt;br /&gt;
                            return decodeURIComponent( href.replace( /^.*\/wiki\//, &amp;#039;&amp;#039; ) )&lt;br /&gt;
                                .replace( /_/g, &amp;#039; &amp;#039; ) === title;&lt;br /&gt;
                        } );&lt;br /&gt;
                        if ( !$a.length ) return;&lt;br /&gt;
&lt;br /&gt;
                        var dateStr = ts&lt;br /&gt;
                            ? new Date( ts ).toLocaleDateString( &amp;#039;en-GB&amp;#039;, { year: &amp;#039;numeric&amp;#039;, month: &amp;#039;short&amp;#039; } )&lt;br /&gt;
                            : &amp;#039;—&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
                        var metaText = nContrib + ( nContrib === 1 ? &amp;#039; contributor&amp;#039; : &amp;#039; contributors&amp;#039; )&lt;br /&gt;
                                     + &amp;#039; · &amp;#039; + dateStr;&lt;br /&gt;
&lt;br /&gt;
                        $a.find( &amp;#039;.cat-item-meta&amp;#039; )&lt;br /&gt;
                          .text( metaText )&lt;br /&gt;
                          .addClass( &amp;#039;loaded&amp;#039; );&lt;br /&gt;
                    } );&lt;br /&gt;
                } );&lt;br /&gt;
            }( pageNames.slice( i, i + 50 ) ) );&lt;br /&gt;
        }&lt;br /&gt;
    } );&lt;br /&gt;
}() );&lt;/div&gt;</summary>
		<author><name>Anthony</name></author>
	</entry>
</feed>