« MediaWiki:Common.js » : différence entre les versions
Aller à la navigation
Aller à la recherche
Aucun résumé des modifications |
Aucun résumé des modifications Balise : Révoqué |
||
| Ligne 17 : | Ligne 17 : | ||
/* jshint strict:false, browser:true */ | /* jshint strict:false, browser:true */ | ||
/** | |||
* MediaWiki:Common.js | |||
* | |||
* Common JavaScript for all users on this wiki. | |||
*/ | |||
( function ( $, mw ) { | |||
'use strict'; | |||
/* ========================================================================== | |||
1. Helper Utilities & Legacy Fixes | |||
========================================================================== */ | |||
/** | |||
* Deprecation fallback for window.addPortletLink | |||
*/ | |||
window.addPortletLink = function () { | |||
return mw.util.addPortletLink.apply( mw.util, arguments ); | |||
}; | |||
/** | |||
* Dynamically loads a script from the MediaWiki namespace | |||
*/ | |||
function withJS( page ) { | |||
if ( !page || !/^MediaWiki:/.test( page ) ) { | |||
return; | |||
} | } | ||
mw.loader.load( mw.config.get( 'wgScript' ) + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript' ); | |||
} | } | ||
/** | |||
if ( /^MediaWiki: | * Dynamically loads a stylesheet from the MediaWiki namespace | ||
*/ | |||
function withCSS( page ) { | |||
if ( !page || !/^MediaWiki:/.test( page ) ) { | |||
return; | |||
} | } | ||
mw.loader.load( mw.config.get( 'wgScript' ) + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/css', 'text/css' ); | |||
} | } | ||
/** | /** | ||
* | * Open interwiki links in a new window/tab | ||
*/ | */ | ||
function openInterwikiLinksInNewWindow( $content ) { | |||
$content.find( 'a.extiw, a.external' ).each( function () { | |||
var href = $( this ).attr( 'href' ); | |||
if ( href && href.indexOf( mw.config.get( 'wgServer' ) ) === -1 ) { | |||
$( this ).attr( { | |||
target: '_blank', | |||
rel: 'noopener noreferrer' | |||
} ); | |||
} | |||
} ); | |||
} | |||
$ | /** | ||
* Converts legacy collapsible tables into MediaWiki collapsible elements | |||
$ | */ | ||
function setupCollapsibleElements( $content ) { | |||
$content.find( 'table.collapsible' ).each( function () { | |||
var $table = $( this ); | |||
$table.addClass( 'mw-collapsible' ); | |||
if ( $table.hasClass( 'collapsed' ) ) { | |||
$table.addClass( 'mw-collapsed' ); | |||
} | } | ||
} ); | } ); | ||
} | |||
/* ========================================================================== | |||
2. Native MediaWiki Commons Image Resolver | |||
========================================================================== */ | |||
/** | |||
* Fetches scaled thumbnail URLs directly using native MediaWiki Action API. | |||
* Batches file titles into a single API request for performance. | |||
* | |||
* @param {Array<string>} fileTitles Array of file titles or Special:FilePath URIs | |||
* @param {number} width Target thumbnail width in pixels | |||
* @return {jQuery.Promise} Resolves with a mapping object { cleanFileName: thumbUrl } | |||
*/ | |||
function resolveCommonsThumbnailsNative( fileTitles, width ) { | |||
width = width || 160; | |||
var deferred = $.Deferred(); | |||
if ( !fileTitles || !fileTitles.length ) { | |||
return deferred.resolve( {} ).promise(); | |||
} | |||
// Clean titles to standard 'File:Example.jpg' format | |||
var titleMap = {}; | |||
var cleanTitles = []; | |||
$.each( fileTitles, function ( i, raw ) { | |||
var fileName = decodeURIComponent( raw ).replace( /^.*Special:FilePath\//, '' ).replace( /_/g, ' ' ); | |||
var fullTitle = 'File:' + fileName; | |||
titleMap[ fullTitle ] = raw; | |||
if ( cleanTitles.indexOf( fullTitle ) === -1 ) { | |||
cleanTitles.push( fullTitle ); | |||
} | } | ||
} ); | |||
// Batch call Wikimedia Commons MediaWiki API natively via mw.ForeignApi / mw.Api | |||
var api = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' ); | |||
api.get( { | |||
if ( | action: 'query', | ||
$ | titles: cleanTitles.join( '|' ), | ||
prop: 'imageinfo', | |||
iiprop: 'url', | |||
iiurlwidth: width, | |||
formatversion: 2 | |||
} ).done( function ( data ) { | |||
var urlMap = {}; | |||
if ( data && data.query && data.query.pages ) { | |||
$.each( data.query.pages, function ( i, page ) { | |||
if ( page.imageinfo && page.imageinfo[ 0 ] ) { | |||
var info = page.imageinfo[ 0 ]; | |||
var origKey = titleMap[ page.title ]; | |||
urlMap[ origKey ] = info.thumburl || info.url; | |||
} | |||
} ); | |||
} | } | ||
deferred.resolve( urlMap ); | |||
} ).fail( function () { | |||
deferred.resolve( {} ); // Fallback gracefully if API fails | |||
} ); | } ); | ||
/* | return deferred.promise(); | ||
} | |||
/* ========================================================================== | |||
} | 3. Wikidata SPARQL Table Engine | ||
========================================================================== */ | |||
var SPARQL_ENDPOINT = 'https://query.wikidata.org/sparql'; | |||
var SPARQL_MAX_PARALLEL = 2; | |||
var CACHE_FRESHNESS_MS = 2 * 60 * 1000; // 2 Minutes (Fresh) | |||
var CACHE_EXPIRATION_MS = 6 * 60 * 60 * 1000; // 6 Hours (Max TTL) | |||
var activeRequests = 0; | |||
var requestQueue = []; | |||
var inFlightPromises = {}; | |||
/** | /** | ||
* | * Compute a fast FNV-1a hash key for SPARQL string deduplication | ||
*/ | */ | ||
function fnv1aHash( str ) { | |||
var hash = 0x811c9dc5; | |||
function | |||
var | |||
for ( var i = 0; i < str.length; i++ ) { | for ( var i = 0; i < str.length; i++ ) { | ||
hash ^= str.charCodeAt( i ); | |||
hash += ( hash << 1 ) + ( hash << 4 ) + ( hash << 7 ) + ( hash << 8 ) + ( hash << 24 ); | |||
} | } | ||
return | return 'wdsparql:' + ( hash >>> 0 ).toString( 36 ); | ||
} | } | ||
/* | /** | ||
function | * Safe storage write wrapper with automatic cache pruning | ||
*/ | |||
function setCache( key, val ) { | |||
try { | |||
var data = { | |||
var | timestamp: Date.now(), | ||
payload: val | |||
}; | |||
localStorage.setItem( key, JSON.stringify( data ) ); | |||
} catch ( e ) { | |||
// Prune old SPARQL cache keys if quota exceeded | |||
for ( var k in localStorage ) { | |||
if ( k.indexOf( 'wdsparql:' ) === 0 ) { | |||
localStorage.removeItem( k ); | |||
} | |||
} | |||
} | |||
} | } | ||
} | } | ||
} | } | ||
/** | |||
* Retrieve items from cache with timestamp metadata | |||
function | */ | ||
var | function getCache( key ) { | ||
try { | |||
var raw = localStorage.getItem( key ); | |||
if ( !raw ) return null; | |||
var parsed = JSON.parse( raw ); | |||
var age = Date.now() - parsed.timestamp; | |||
return | if ( age > CACHE_EXPIRATION_MS ) { | ||
} catch ( e ) { | localStorage.removeItem( key ); | ||
return null; | |||
} | |||
return { | |||
isFresh: age < CACHE_FRESHNESS_MS, | |||
payload: parsed.payload | |||
}; | |||
} catch ( e ) { | |||
return null; | |||
} | } | ||
} | } | ||
/** | |||
* Process throttled SPARQL execution queue | |||
function | */ | ||
function processQueue() { | |||
if ( | if ( activeRequests >= SPARQL_MAX_PARALLEL || requestQueue.length === 0 ) { | ||
return; | |||
} | } | ||
var task = requestQueue.shift(); | |||
var | activeRequests++; | ||
var hashKey = task.hashKey; | |||
// Deduplicate in-flight network promises | |||
if ( inFlightPromises[ hashKey ] ) { | |||
inFlightPromises[ hashKey ].then( task.resolve ).catch( task.reject ).always( function () { | |||
activeRequests--; | |||
processQueue(); | |||
} ); | |||
return; | |||
} | } | ||
var promise = $.ajax( { | |||
var | url: SPARQL_ENDPOINT, | ||
data: { query: task.sparql }, | |||
dataType: 'json', | |||
headers: { 'Accept': 'application/sparql-results+json' } | |||
} ).then( function ( data ) { | |||
setCache( hashKey, data ); | |||
return data; | |||
} ); | } ); | ||
inFlightPromises[ hashKey ] = promise; | |||
promise.done( task.resolve ) | |||
.fail( function ( xhr, status, error ) { | |||
if ( xhr.status === 429 || xhr.status === 503 ) { | |||
var retryAfter = parseInt( xhr.getResponseHeader( 'Retry-After' ), 10 ) || 5; | |||
setTimeout( function () { | |||
requestQueue.unshift( task ); | |||
}, retryAfter * 1000 ); | |||
} else { | |||
task.reject( error ); | |||
} | |||
} ) | |||
.always( function () { | |||
delete inFlightPromises[ hashKey ]; | |||
activeRequests--; | |||
processQueue(); | |||
} ); | |||
} | } | ||
function | /** | ||
* Queue a SPARQL query request | |||
var | */ | ||
function fetchSparql( sparql ) { | |||
var hashKey = fnv1aHash( sparql ); | |||
var cached = getCache( hashKey ); | |||
return $.Deferred( function ( deferred ) { | |||
if ( cached && cached.isFresh ) { | |||
deferred.resolve( cached.payload ); | |||
return; | |||
} | |||
if ( cached && !cached.isFresh ) { | |||
deferred.resolve( cached.payload ); | |||
} | |||
requestQueue.push( { | |||
sparql: sparql, | |||
hashKey: hashKey, | |||
if ( | resolve: function ( data ) { | ||
if ( !cached ) deferred.resolve( data ); | |||
}, | |||
reject: function ( err ) { | |||
if ( !cached ) deferred.reject( err ); | |||
} | } | ||
} ); | |||
processQueue(); | |||
} ).promise(); | |||
} | } | ||
// | /** | ||
function | * Renders bindings into a standard MediaWiki formatted wikitable using native image resolution | ||
var | */ | ||
function renderSparqlTable( $container, data, title ) { | |||
var vars = data.head.vars; | |||
var bindings = data.results.bindings; | |||
var | // Collect all Commons file references for native API batch resolution | ||
var imageFiles = []; | |||
$.each( bindings, function ( i, row ) { | |||
$.each( vars, function ( j, v ) { | |||
var cell = row[ v ]; | |||
if ( cell && cell.type === 'uri' && cell.value.indexOf( 'Special:FilePath/' ) !== -1 ) { | |||
imageFiles.push( cell.value ); | |||
} | |||
} ); | |||
} ); | |||
// Resolve images natively via MediaWiki API, then render DOM | |||
resolveCommonsThumbnailsNative( imageFiles, 160 ).done( function ( imageMap ) { | |||
var $table = $( '<table>' ).addClass( 'wikitable sortable wikidata-sparql-rendered' ); | |||
if ( title ) { | |||
$table.append( $( '<caption>' ).text( title ) ); | |||
} | |||
// Header | |||
var $thead = $( '<thead>' ); | |||
var $headerRow = $( '<tr>' ); | |||
$.each( vars, function ( i, v ) { | |||
$headerRow.append( $( '<th>' ).text( v ) ); | |||
} ); | |||
$thead.append( $headerRow ); | |||
$table.append( $thead ); | |||
// Body | |||
var $tbody = $( '<tbody>' ); | |||
$.each( bindings, function ( i, row ) { | |||
var $row = $( '<tr>' ); | |||
$.each( vars, function ( j, v ) { | |||
var cell = row[ v ]; | |||
var $td = $( '<td>' ); | |||
if ( !cell ) { | |||
$td.html( '—' ); | |||
} else if ( cell.type === 'uri' && cell.value.indexOf( 'Special:FilePath/' ) !== -1 ) { | |||
var thumbUrl = imageMap[ cell.value ] || cell.value; | |||
var $img = $( '<img>' ).attr( { | |||
src: thumbUrl, | |||
loading: 'lazy', | |||
height: 80 | |||
} ).css( 'max-height', '80px' ); | |||
var $link = $( '<a>' ).attr( { | |||
href: cell.value, | |||
target: '_blank', | |||
rel: 'noopener noreferrer' | |||
} ).append( $img ); | |||
$td.append( $link ); | |||
} else if ( cell.type === 'uri' && cell.value.indexOf( 'http://www.wikidata.org/entity/' ) === 0 ) { | |||
var qid = cell.value.replace( 'http://www.wikidata.org/entity/', '' ); | |||
var $link = $( '<a>' ).attr( { | |||
href: cell.value, | |||
target: '_blank', | |||
rel: 'noopener noreferrer' | |||
} ).text( qid ); | |||
$td.append( $link ); | |||
} else { | |||
$td.text( cell.value ); | |||
} | |||
$row.append( $td ); | |||
} ); | |||
$tbody.append( $row ); | |||
} ); | |||
$table.append( $tbody ); | |||
$container.empty().append( $table ); | |||
mw.loader.using( 'jquery.tablesorter', function () { | |||
$table.tablesorter(); | |||
} ); | } ); | ||
} ); | |||
} | } | ||
/** | |||
var | * Initializes elements using the .wikidata-sparql-table class via IntersectionObserver | ||
*/ | |||
function initSparqlTables( $content ) { | |||
var $targets = $content.find( '.wikidata-sparql-table:not(.wd-sparql-initialized)' ); | |||
if ( !$targets.length ) return; | |||
if ( | var observer = new IntersectionObserver( function ( entries, obs ) { | ||
entries.forEach( function ( entry ) { | |||
if ( entry.isIntersecting ) { | |||
var $elem = $( entry.target ); | |||
$elem.addClass( 'wd-sparql-initialized' ); | |||
obs.unobserve( entry.target ); | |||
var sparql = $elem.attr( 'data-sparql' ); | |||
var title = $elem.attr( 'data-title' ); | |||
if ( !sparql ) return; | |||
$elem.html( '<span class="mw-ajax-loader"></span> Loading Wikidata SPARQL results...' ); | |||
fetchSparql( sparql ) | |||
.done( function ( data ) { | |||
renderSparqlTable( $elem, data, title ); | |||
} ) | |||
.fail( function () { | |||
$elem.html( '<span class="error">Failed to load SPARQL results from Wikidata.</span>' ); | |||
} ); | |||
} | } | ||
} ); | } ); | ||
} | }, { rootMargin: '300px 0px' } ); | ||
$targets.each( function () { | |||
observer.observe( this ); | |||
} ); | |||
} | |||
/* ========================================================================== | |||
4. Document Ready Hook | |||
========================================================================== */ | |||
mw.hook( 'wikipage.content' ).add( function ( $content ) { | |||
openInterwikiLinksInNewWindow( $content ); | |||
setupCollapsibleElements( $content ); | |||
initSparqlTables( $content ); | |||
} ); | } ); | ||
} ); | |||
}( jQuery, mediaWiki ) ); | |||
/* DO NOT ADD CODE BELOW THIS LINE */ | /* DO NOT ADD CODE BELOW THIS LINE */ | ||
Version du 21 septembre 2026 à 14:53
/* Tout JavaScript présent ici sera exécuté par tous les utilisateurs à chaque chargement de page. */
/**
* Keep code in MediaWiki:Common.js to a minimum as it is unconditionally
* loaded for all users on every wiki page. If possible create a gadget that is
* enabled by default instead of adding it here (since gadgets are fully
* optimized ResourceLoader modules with possibility to add dependencies etc.)
*
* Since Common.js isn't a gadget, there is no place to declare its
* dependencies, so we have to lazy load them with mw.loader.using on demand and
* then execute the rest in the callback. In most cases these dependencies will
* be loaded (or loading) already and the callback will not be delayed. In case a
* dependency hasn't arrived yet it'll make sure those are loaded before this.
*/
/* global mw, $ */
/* jshint strict:false, browser:true */
/**
* MediaWiki:Common.js
*
* Common JavaScript for all users on this wiki.
*/
( function ( $, mw ) {
'use strict';
/* ==========================================================================
1. Helper Utilities & Legacy Fixes
========================================================================== */
/**
* Deprecation fallback for window.addPortletLink
*/
window.addPortletLink = function () {
return mw.util.addPortletLink.apply( mw.util, arguments );
};
/**
* Dynamically loads a script from the MediaWiki namespace
*/
function withJS( page ) {
if ( !page || !/^MediaWiki:/.test( page ) ) {
return;
}
mw.loader.load( mw.config.get( 'wgScript' ) + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript' );
}
/**
* Dynamically loads a stylesheet from the MediaWiki namespace
*/
function withCSS( page ) {
if ( !page || !/^MediaWiki:/.test( page ) ) {
return;
}
mw.loader.load( mw.config.get( 'wgScript' ) + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/css', 'text/css' );
}
/**
* Open interwiki links in a new window/tab
*/
function openInterwikiLinksInNewWindow( $content ) {
$content.find( 'a.extiw, a.external' ).each( function () {
var href = $( this ).attr( 'href' );
if ( href && href.indexOf( mw.config.get( 'wgServer' ) ) === -1 ) {
$( this ).attr( {
target: '_blank',
rel: 'noopener noreferrer'
} );
}
} );
}
/**
* Converts legacy collapsible tables into MediaWiki collapsible elements
*/
function setupCollapsibleElements( $content ) {
$content.find( 'table.collapsible' ).each( function () {
var $table = $( this );
$table.addClass( 'mw-collapsible' );
if ( $table.hasClass( 'collapsed' ) ) {
$table.addClass( 'mw-collapsed' );
}
} );
}
/* ==========================================================================
2. Native MediaWiki Commons Image Resolver
========================================================================== */
/**
* Fetches scaled thumbnail URLs directly using native MediaWiki Action API.
* Batches file titles into a single API request for performance.
*
* @param {Array<string>} fileTitles Array of file titles or Special:FilePath URIs
* @param {number} width Target thumbnail width in pixels
* @return {jQuery.Promise} Resolves with a mapping object { cleanFileName: thumbUrl }
*/
function resolveCommonsThumbnailsNative( fileTitles, width ) {
width = width || 160;
var deferred = $.Deferred();
if ( !fileTitles || !fileTitles.length ) {
return deferred.resolve( {} ).promise();
}
// Clean titles to standard 'File:Example.jpg' format
var titleMap = {};
var cleanTitles = [];
$.each( fileTitles, function ( i, raw ) {
var fileName = decodeURIComponent( raw ).replace( /^.*Special:FilePath\//, '' ).replace( /_/g, ' ' );
var fullTitle = 'File:' + fileName;
titleMap[ fullTitle ] = raw;
if ( cleanTitles.indexOf( fullTitle ) === -1 ) {
cleanTitles.push( fullTitle );
}
} );
// Batch call Wikimedia Commons MediaWiki API natively via mw.ForeignApi / mw.Api
var api = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' );
api.get( {
action: 'query',
titles: cleanTitles.join( '|' ),
prop: 'imageinfo',
iiprop: 'url',
iiurlwidth: width,
formatversion: 2
} ).done( function ( data ) {
var urlMap = {};
if ( data && data.query && data.query.pages ) {
$.each( data.query.pages, function ( i, page ) {
if ( page.imageinfo && page.imageinfo[ 0 ] ) {
var info = page.imageinfo[ 0 ];
var origKey = titleMap[ page.title ];
urlMap[ origKey ] = info.thumburl || info.url;
}
} );
}
deferred.resolve( urlMap );
} ).fail( function () {
deferred.resolve( {} ); // Fallback gracefully if API fails
} );
return deferred.promise();
}
/* ==========================================================================
3. Wikidata SPARQL Table Engine
========================================================================== */
var SPARQL_ENDPOINT = 'https://query.wikidata.org/sparql';
var SPARQL_MAX_PARALLEL = 2;
var CACHE_FRESHNESS_MS = 2 * 60 * 1000; // 2 Minutes (Fresh)
var CACHE_EXPIRATION_MS = 6 * 60 * 60 * 1000; // 6 Hours (Max TTL)
var activeRequests = 0;
var requestQueue = [];
var inFlightPromises = {};
/**
* Compute a fast FNV-1a hash key for SPARQL string deduplication
*/
function fnv1aHash( str ) {
var hash = 0x811c9dc5;
for ( var i = 0; i < str.length; i++ ) {
hash ^= str.charCodeAt( i );
hash += ( hash << 1 ) + ( hash << 4 ) + ( hash << 7 ) + ( hash << 8 ) + ( hash << 24 );
}
return 'wdsparql:' + ( hash >>> 0 ).toString( 36 );
}
/**
* Safe storage write wrapper with automatic cache pruning
*/
function setCache( key, val ) {
try {
var data = {
timestamp: Date.now(),
payload: val
};
localStorage.setItem( key, JSON.stringify( data ) );
} catch ( e ) {
// Prune old SPARQL cache keys if quota exceeded
for ( var k in localStorage ) {
if ( k.indexOf( 'wdsparql:' ) === 0 ) {
localStorage.removeItem( k );
}
}
}
}
/**
* Retrieve items from cache with timestamp metadata
*/
function getCache( key ) {
try {
var raw = localStorage.getItem( key );
if ( !raw ) return null;
var parsed = JSON.parse( raw );
var age = Date.now() - parsed.timestamp;
if ( age > CACHE_EXPIRATION_MS ) {
localStorage.removeItem( key );
return null;
}
return {
isFresh: age < CACHE_FRESHNESS_MS,
payload: parsed.payload
};
} catch ( e ) {
return null;
}
}
/**
* Process throttled SPARQL execution queue
*/
function processQueue() {
if ( activeRequests >= SPARQL_MAX_PARALLEL || requestQueue.length === 0 ) {
return;
}
var task = requestQueue.shift();
activeRequests++;
var hashKey = task.hashKey;
// Deduplicate in-flight network promises
if ( inFlightPromises[ hashKey ] ) {
inFlightPromises[ hashKey ].then( task.resolve ).catch( task.reject ).always( function () {
activeRequests--;
processQueue();
} );
return;
}
var promise = $.ajax( {
url: SPARQL_ENDPOINT,
data: { query: task.sparql },
dataType: 'json',
headers: { 'Accept': 'application/sparql-results+json' }
} ).then( function ( data ) {
setCache( hashKey, data );
return data;
} );
inFlightPromises[ hashKey ] = promise;
promise.done( task.resolve )
.fail( function ( xhr, status, error ) {
if ( xhr.status === 429 || xhr.status === 503 ) {
var retryAfter = parseInt( xhr.getResponseHeader( 'Retry-After' ), 10 ) || 5;
setTimeout( function () {
requestQueue.unshift( task );
}, retryAfter * 1000 );
} else {
task.reject( error );
}
} )
.always( function () {
delete inFlightPromises[ hashKey ];
activeRequests--;
processQueue();
} );
}
/**
* Queue a SPARQL query request
*/
function fetchSparql( sparql ) {
var hashKey = fnv1aHash( sparql );
var cached = getCache( hashKey );
return $.Deferred( function ( deferred ) {
if ( cached && cached.isFresh ) {
deferred.resolve( cached.payload );
return;
}
if ( cached && !cached.isFresh ) {
deferred.resolve( cached.payload );
}
requestQueue.push( {
sparql: sparql,
hashKey: hashKey,
resolve: function ( data ) {
if ( !cached ) deferred.resolve( data );
},
reject: function ( err ) {
if ( !cached ) deferred.reject( err );
}
} );
processQueue();
} ).promise();
}
/**
* Renders bindings into a standard MediaWiki formatted wikitable using native image resolution
*/
function renderSparqlTable( $container, data, title ) {
var vars = data.head.vars;
var bindings = data.results.bindings;
// Collect all Commons file references for native API batch resolution
var imageFiles = [];
$.each( bindings, function ( i, row ) {
$.each( vars, function ( j, v ) {
var cell = row[ v ];
if ( cell && cell.type === 'uri' && cell.value.indexOf( 'Special:FilePath/' ) !== -1 ) {
imageFiles.push( cell.value );
}
} );
} );
// Resolve images natively via MediaWiki API, then render DOM
resolveCommonsThumbnailsNative( imageFiles, 160 ).done( function ( imageMap ) {
var $table = $( '<table>' ).addClass( 'wikitable sortable wikidata-sparql-rendered' );
if ( title ) {
$table.append( $( '<caption>' ).text( title ) );
}
// Header
var $thead = $( '<thead>' );
var $headerRow = $( '<tr>' );
$.each( vars, function ( i, v ) {
$headerRow.append( $( '<th>' ).text( v ) );
} );
$thead.append( $headerRow );
$table.append( $thead );
// Body
var $tbody = $( '<tbody>' );
$.each( bindings, function ( i, row ) {
var $row = $( '<tr>' );
$.each( vars, function ( j, v ) {
var cell = row[ v ];
var $td = $( '<td>' );
if ( !cell ) {
$td.html( '—' );
} else if ( cell.type === 'uri' && cell.value.indexOf( 'Special:FilePath/' ) !== -1 ) {
var thumbUrl = imageMap[ cell.value ] || cell.value;
var $img = $( '<img>' ).attr( {
src: thumbUrl,
loading: 'lazy',
height: 80
} ).css( 'max-height', '80px' );
var $link = $( '<a>' ).attr( {
href: cell.value,
target: '_blank',
rel: 'noopener noreferrer'
} ).append( $img );
$td.append( $link );
} else if ( cell.type === 'uri' && cell.value.indexOf( 'http://www.wikidata.org/entity/' ) === 0 ) {
var qid = cell.value.replace( 'http://www.wikidata.org/entity/', '' );
var $link = $( '<a>' ).attr( {
href: cell.value,
target: '_blank',
rel: 'noopener noreferrer'
} ).text( qid );
$td.append( $link );
} else {
$td.text( cell.value );
}
$row.append( $td );
} );
$tbody.append( $row );
} );
$table.append( $tbody );
$container.empty().append( $table );
mw.loader.using( 'jquery.tablesorter', function () {
$table.tablesorter();
} );
} );
}
/**
* Initializes elements using the .wikidata-sparql-table class via IntersectionObserver
*/
function initSparqlTables( $content ) {
var $targets = $content.find( '.wikidata-sparql-table:not(.wd-sparql-initialized)' );
if ( !$targets.length ) return;
var observer = new IntersectionObserver( function ( entries, obs ) {
entries.forEach( function ( entry ) {
if ( entry.isIntersecting ) {
var $elem = $( entry.target );
$elem.addClass( 'wd-sparql-initialized' );
obs.unobserve( entry.target );
var sparql = $elem.attr( 'data-sparql' );
var title = $elem.attr( 'data-title' );
if ( !sparql ) return;
$elem.html( '<span class="mw-ajax-loader"></span> Loading Wikidata SPARQL results...' );
fetchSparql( sparql )
.done( function ( data ) {
renderSparqlTable( $elem, data, title );
} )
.fail( function () {
$elem.html( '<span class="error">Failed to load SPARQL results from Wikidata.</span>' );
} );
}
} );
}, { rootMargin: '300px 0px' } );
$targets.each( function () {
observer.observe( this );
} );
}
/* ==========================================================================
4. Document Ready Hook
========================================================================== */
mw.hook( 'wikipage.content' ).add( function ( $content ) {
openInterwikiLinksInNewWindow( $content );
setupCollapsibleElements( $content );
initSparqlTables( $content );
} );
}( jQuery, mediaWiki ) );
/* DO NOT ADD CODE BELOW THIS LINE */