MediaWiki:Gadget-projects.js: Difference between revisions
MediaWiki interface page
More actions
m Removed protection from "MediaWiki:Gadget-projects.js" |
(No difference)
|
Revision as of 05:06, 14 March 2026
/* =============================================================================
Gadget-projects.js
Handles: tag rendering, filter bar, contributor count + last edited.
============================================================================= */
( function () {
var pageName = mw.config.get( 'wgPageName' ) || '';
if ( !/^Projects[:/][^/]+$/.test( pageName ) ) return;
$( function () {
var $grid = $( '.project-hub-grid' );
var $filterBar = $( '.project-hub-filters' );
if ( !$grid.length || !$filterBar.length ) return;
var $cards = $grid.find( '.project-card-wrap' );
var total = $cards.length;
var hubSlug = pageName.replace( /^Projects[:/]/, '' );
// --- Tag spans -------------------------------------------------------
$( '.project-card-tags[data-tags]' ).each( function () {
var $el = $( this );
$el.attr( 'data-tags' ).trim().split( /[\s,]+/ ).forEach( function ( tag ) {
if ( tag ) $el.append( $( '<span>' ).addClass( 'project-card-tag' ).text( tag ) );
} );
} );
// --- Filter bar (options populated from Cargo) -----------------------
var $search = $( '<input>' ).attr( { type: 'text', placeholder: 'Search projects, tags, creators...', 'class': 'project-hub-search' } );
var $status = $( '<select>' ).addClass( 'project-hub-select' ).append( $( '<option>' ).val( '' ).text( 'All statuses' ) );
var $license = $( '<select>' ).addClass( 'project-hub-select' ).append( $( '<option>' ).val( '' ).text( 'All licenses' ) );
var $sort = $( '<select>' ).addClass( 'project-hub-select' ).append(
$( '<option>' ).val( 'alpha' ).text( 'A β Z' ),
$( '<option>' ).val( 'edited' ).text( 'Recently edited' ),
$( '<option>' ).val( 'contributors' ).text( 'Most contributors' )
);
var $count = $( '<span>' ).addClass( 'project-hub-count' )
.text( total + ' project' + ( total === 1 ? '' : 's' ) );
$filterBar.append( $search, $status, $license, $sort, $count );
// Populate status + license options from Cargo
var apiBase = mw.util.wikiScript( 'api' );
$.getJSON( apiBase, {
action : 'cargoquery',
tables : 'Projects',
fields : 'status',
where : 'categories HOLDS \'' + hubSlug + '\' AND status IS NOT NULL AND status != \'\'',
group_by : 'status',
order_by : 'status',
limit : 20,
format : 'json'
} ).done( function ( data ) {
( data.cargoquery || [] ).forEach( function ( row ) {
var v = row.title && row.title.status;
if ( v ) $status.append( $( '<option>' ).val( v.toLowerCase() ).text( v ) );
} );
} );
$.getJSON( apiBase, {
action : 'cargoquery',
tables : 'Projects',
fields : 'license',
where : 'categories HOLDS \'' + hubSlug + '\' AND license IS NOT NULL AND license != \'\'',
group_by : 'license',
order_by : 'license',
limit : 20,
format : 'json'
} ).done( function ( data ) {
( data.cargoquery || [] ).forEach( function ( row ) {
var v = row.title && row.title.license;
if ( v ) $license.append( $( '<option>' ).val( v.toLowerCase() ).text( v ) );
} );
} );
// --- Filter + sort ---------------------------------------------------
function applyFilters() {
var q = $search.val().toLowerCase().trim();
var s = $status.val();
var l = $license.val();
var visible = 0;
$cards.each( function () {
var $c = $( this );
var hay = [ $c.data('name'), $c.data('tags'), $c.data('creator'), $c.data('extra') ]
.join( ' ' ).toLowerCase();
var show = ( !q || hay.indexOf(q) !== -1 ) &&
( !s || ( $c.data('status') || '' ).toLowerCase() === s ) &&
( !l || ( $c.data('license') || '' ).toLowerCase() === l );
$c.toggle( show );
if ( show ) visible++;
} );
$count.text( ( q||s||l ? visible + ' / ' : '' ) + total + ' project' + ( total===1 ? '' : 's' ) );
$( '.project-hub-empty' ).toggle( visible === 0 );
var sortVal = $sort.val();
$cards.filter( ':visible' ).toArray().sort( function ( a, b ) {
if ( sortVal === 'alpha' ) return ( $( a ).data('name') || '' ).localeCompare( $( b ).data('name') || '' );
if ( sortVal === 'edited' ) return ( $( b ).data('timestamp') || '' ).localeCompare( $( a ).data('timestamp') || '' );
if ( sortVal === 'contributors' ) return ( +$( b ).data('contributor-count') || 0 ) - ( +$( a ).data('contributor-count') || 0 );
return 0;
} ).forEach( function ( el ) { $grid.append( el ); } );
}
$search.on( 'input', applyFilters );
$status.on( 'change', applyFilters );
$license.on( 'change', applyFilters );
$sort.on( 'change', applyFilters );
// --- API calls -------------------------------------------------------
var pageNames = $cards.map( function () {
return decodeURIComponent( $( this ).data('page') || '' ).replace( /_/g, ' ' ) || null;
} ).get().filter( Boolean );
mw.loader.using( 'mediawiki.api' ).then( function () {
var api = new mw.Api();
// Card enrichment: contributor count + last edited
if ( pageNames.length ) {
for ( var i = 0; i < pageNames.length; i += 20 ) {
( function ( batch ) {
api.get( {
action : 'query',
titles : batch.join( '|' ),
prop : 'revisions|contributors',
rvprop : 'timestamp',
rvlimit : 1,
pclimit : 20,
format : 'json'
} ).done( function ( data ) {
if ( !data || !data.query || !data.query.pages ) return;
$.each( data.query.pages, function ( _, page ) {
if ( page.missing !== undefined ) return;
var ts = page.revisions && page.revisions[0] && page.revisions[0].timestamp || '';
var n = ( page.contributors ? page.contributors.length : 0 )
+ ( page.anoncontributors || 0 );
var $c = $cards.filter( function () {
return decodeURIComponent( $( this ).data('page') || '' ).replace( /_/g, ' ' ) === page.title;
} );
if ( !$c.length ) return;
$c.data( 'timestamp', ts ).data( 'contributor-count', n );
var d = ts ? new Date( ts ).toLocaleDateString( 'en-GB', { year: 'numeric', month: 'short' } ) : 'β';
$c.find( '.project-card-contributors' ).text( n + ( n === 1 ? ' contributor' : ' contributors' ) ).addClass( 'loaded' );
$c.find( '.project-card-updated' ).text( 'Edited ' + d ).addClass( 'loaded' );
} );
} );
}( pageNames.slice( i, i + 20 ) ) );
}
}
// Hub header: most recently touched page in this hub's category
api.get( {
action : 'query',
list : 'categorymembers',
cmtitle : 'Category:Projects/' + hubSlug,
cmsort : 'timestamp',
cmdir : 'desc',
cmlimit : 1,
cmtype : 'page',
cmprop : 'ids|title|timestamp',
format : 'json'
} ).done( function ( data ) {
var members = data.query && data.query.categorymembers;
if ( !members || !members.length ) return;
var name = members[0].title.replace( /^.*\//, '' );
var date = members[0].timestamp
? new Date( members[0].timestamp ).toLocaleDateString( 'en-GB', { day: 'numeric', month: 'short', year: 'numeric' } )
: 'β';
var str = name + ' β ' + date;
$( '#hub-meta-last-submission' ).text( str );
$( '#hub-meta-last-edit' ).text( str );
} );
} );
} );
}() );