MediaWiki:Common.js: Difference between revisions

From Legendary Hardcore Wiki
Jump to navigation Jump to search
mNo edit summary
mNo edit summary
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
( function () {
( function () {
'use strict';
'use strict';
 
var DEFAULTS = {
var DEFAULTS = {
minBytes: 3000,
minBytes: 1500,
dailyStable: true,
dailyStable: false,
blurbChars: 200,
blurbChars: 200,
maxPages: 6
minImageWidth: 80,
maxPages: 6
};
};
 
var FURNITURE_SELECTOR = '.badge';
 
function init() {
function init() {
var panels = document.querySelectorAll( '.lhc-article' );
var panels = document.querySelectorAll( '.lhc-article' );
Line 19: Line 22:
} );
} );
}
}
 
function start( api, panel ) {
function start( api, panel ) {
if ( panel.hasAttribute( 'data-random' ) ||
if ( panel.hasAttribute( 'data-random' ) ||
panel.hasAttribute( 'data-category' ) ) {
panel.hasAttribute( 'data-category' ) ) {
return pickRandom( api, panel ).catch( function () {
return pickRandom( api, panel ).catch( function () {} );
} );
}
}
 
var page = resolvePage( panel );
var page = resolvePage( panel );
if ( page ) {
if ( page ) {
Line 32: Line 34:
}
}
}
}
 
function resolvePage( panel ) {
function resolvePage( panel ) {
var explicit = panel.getAttribute( 'data-page' );
var explicit = panel.getAttribute( 'data-page' );
Line 48: Line 50:
return title;
return title;
}
}
 
function pickRandom( api, panel ) {
function pickRandom( api, panel ) {
var categories = splitList( panel.getAttribute( 'data-category' ) );
var categories = splitList( panel.getAttribute( 'data-category' ) );
var exclude = panel.getAttribute( 'data-exclude-category' );
var exclude = panel.getAttribute( 'data-exclude-category' );
 
var minBytes = parseInt( panel.getAttribute( 'data-min-bytes' ), 10 );
var minBytes = parseInt( panel.getAttribute( 'data-min-bytes' ), 10 );
if ( isNaN( minBytes ) ) {
if ( isNaN( minBytes ) ) {
minBytes = DEFAULTS.minBytes;
minBytes = DEFAULTS.minBytes;
}
}
 
var listing = categories.length ?
var listing = categories.length ?
Promise.all( categories.map( function ( c ) {
Promise.all( categories.map( function ( c ) {
Line 63: Line 65:
} ) ).then( merge ) :
} ) ).then( merge ) :
listAll( api, minBytes );
listAll( api, minBytes );
 
return Promise.all( [ listing, listExcluded( api, exclude ) ] )
return Promise.all( [ listing, listExcluded( api, exclude ) ] )
.then( function ( results ) {
.then( function ( results ) {
var titles = results[ 0 ];
var titles = results[ 0 ];
var skip = results[ 1 ];
var skip = results[ 1 ];
 
var candidates = titles.filter( function ( t ) {
var candidates = titles.filter( function ( t ) {
return !skip[ t ];
return !skip[ t ];
Line 74: Line 76:
return a.localeCompare( b );
return a.localeCompare( b );
} );
} );
 
if ( !candidates.length ) {
if ( !candidates.length ) {
mw.log.warn( '[lhc-article] no candidates found' );
mw.log.warn( '[lhc-article] no candidates found' );
return;
return;
}
}
 
return loadArticle(
return loadArticle(
api, panel, candidates[ pickIndex( candidates.length ) ]
api, panel, candidates[ pickIndex( candidates.length ) ]
Line 85: Line 87:
} );
} );
}
}
 
function splitList( value ) {
function splitList( value ) {
if ( !value ) {
if ( !value ) {
Line 96: Line 98:
} );
} );
}
}
 
function merge( lists ) {
function merge( lists ) {
var seen = {};
var seen = {};
Line 110: Line 112:
return out;
return out;
}
}
 
function listCategory( api, category, minBytes ) {
function listCategory( api, category, minBytes ) {
return api.get( {
return api.get( {
Line 136: Line 138:
} );
} );
}
}
 
function listAll( api, minBytes ) {
function listAll( api, minBytes ) {
var titles = [];
var titles = [];
 
function fetch( cont, round ) {
function fetch( cont, round ) {
var params = {
var params = {
Line 155: Line 157:
} );
} );
}
}
 
return api.get( params ).then( function ( data ) {
return api.get( params ).then( function ( data ) {
( ( data.query && data.query.pages ) || [] )
( ( data.query && data.query.pages ) || [] )
Line 161: Line 163:
titles.push( p.title );
titles.push( p.title );
} );
} );
 
if ( data.continue && round < DEFAULTS.maxPages ) {
if ( data.continue && round < DEFAULTS.maxPages ) {
return fetch( data.continue, round + 1 );
return fetch( data.continue, round + 1 );
Line 168: Line 170:
} );
} );
}
}
 
return fetch( null, 1 );
return fetch( null, 1 );
}
}
 
function listExcluded( api, category ) {
function listExcluded( api, category ) {
if ( !category ) {
if ( !category ) {
Line 194: Line 196:
} );
} );
}
}
 
function pickIndex( n ) {
function pickIndex( n ) {
if ( !DEFAULTS.dailyStable ) {
if ( !DEFAULTS.dailyStable ) {
Line 208: Line 210:
return h % n;
return h % n;
}
}
 
function loadArticle( api, panel, title ) {
function loadArticle( api, panel, title ) {
return api.get( {
return api.get( {
Line 223: Line 225:
}
}
var doc = new DOMParser().parseFromString( html, 'text/html' );
var doc = new DOMParser().parseFromString( html, 'text/html' );
stripFurniture( doc );
render( panel, {
render( panel, {
// Use the resolved title, so redirects show their target.
title: data.parse.title || title,
title: data.parse.title || title,
image: extractImage( doc ),
image: extractImage( doc ),
Line 231: Line 235:
} );
} );
}
}
 
function stripFurniture( doc ) {
var junk = doc.querySelectorAll( FURNITURE_SELECTOR );
Array.prototype.forEach.call( junk, function ( el ) {
if ( el.parentNode ) {
el.parentNode.removeChild( el );
}
} );
}
 
function extractImage( doc ) {
function extractImage( doc ) {
var img = doc.querySelector(
var all = doc.querySelectorAll( 'img' );
'.infobox img, .infobox-image img, table.infobox img'
var img = null;
) || doc.querySelector( 'img' );
 
for ( var i = 0; i < all.length; i++ ) {
if ( imageWidth( all[ i ] ) >= DEFAULTS.minImageWidth ) {
img = all[ i ];
break;
}
}
 
if ( !img ) {
if ( !img ) {
return null;
return null;
Line 252: Line 271:
return src;
return src;
}
}
 
function imageWidth( img ) {
return parseInt( img.getAttribute( 'width' ), 10 ) || 0;
}
 
function extractBlurb( doc ) {
function extractBlurb( doc ) {
var junk = doc.querySelectorAll(
var tables = doc.querySelectorAll( 'table, .infobox, .thumb' );
'table, .infobox, .navbox, .hatnote, .thumb, ' +
Array.prototype.forEach.call( tables, function ( el ) {
'sup.reference, .mw-editsection, .noexcerpt, style'
if ( el.parentNode ) {
);
el.parentNode.removeChild( el );
Array.prototype.forEach.call( junk, function ( el ) {
}
el.parentNode.removeChild( el );
} );
} );
 
var paras = doc.querySelectorAll( 'p' );
var paras = doc.querySelectorAll( 'p' );
for ( var i = 0; i < paras.length; i++ ) {
for ( var i = 0; i < paras.length; i++ ) {
Line 271: Line 293:
return null;
return null;
}
}
 
function truncate( text, max ) {
function truncate( text, max ) {
if ( text.length <= max ) {
if ( text.length <= max ) {
Line 280: Line 302:
return ( space > 0 ? cut.slice( 0, space ) : cut ) + '…';
return ( space > 0 ? cut.slice( 0, space ) : cut ) + '…';
}
}
 
function render( panel, article ) {
function render( panel, article ) {
var href = mw.util.getUrl( article.title );
var href = mw.util.getUrl( article.title );
 
var wrap = document.createElement( 'div' );
var wrap = document.createElement( 'div' );
wrap.className = 'lhc-ra';
wrap.className = 'lhc-ra';
 
if ( article.image ) {
if ( article.image ) {
var link = document.createElement( 'a' );
var link = document.createElement( 'a' );
link.href = href;
link.href = href;
link.className = 'lhc-ra-img';
var img = document.createElement( 'img' );
var img = document.createElement( 'img' );
img.src = article.image;
img.src = article.image;
Line 296: Line 319:
wrap.appendChild( link );
wrap.appendChild( link );
}
}
 
var heading = document.createElement( 'div' );
var heading = document.createElement( 'div' );
heading.className = 'lhc-ra-title';
heading.className = 'lhc-ra-title';
Line 304: Line 327:
heading.appendChild( titleLink );
heading.appendChild( titleLink );
wrap.appendChild( heading );
wrap.appendChild( heading );
 
if ( article.blurb ) {
if ( article.blurb ) {
var p = document.createElement( 'p' );
var p = document.createElement( 'p' );
Line 311: Line 334:
wrap.appendChild( p );
wrap.appendChild( p );
}
}
 
var more = document.createElement( 'a' );
var more = document.createElement( 'a' );
more.href = href;
more.href = href;
Line 317: Line 340:
more.textContent = 'Read the full article →';
more.textContent = 'Read the full article →';
wrap.appendChild( more );
wrap.appendChild( more );
 
panel.textContent = '';
panel.textContent = '';
panel.appendChild( wrap );
panel.appendChild( wrap );
}
}
 
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
if ( document.readyState !== 'loading' ) {
if ( document.readyState !== 'loading' ) {
Line 329: Line 352:
}
}
} );
} );
 
}() );
}() );

Latest revision as of 06:51, 15 August 2026

( function () {
	'use strict';

	var DEFAULTS = {
		minBytes: 1500,
		dailyStable: false,
		blurbChars: 200,
		minImageWidth: 80,
		maxPages: 6
	};

	var FURNITURE_SELECTOR = '.badge';

	function init() {
		var panels = document.querySelectorAll( '.lhc-article' );
		if ( !panels.length ) {
			return;
		}
		var api = new mw.Api();
		Array.prototype.forEach.call( panels, function ( panel ) {
			start( api, panel );
		} );
	}

	function start( api, panel ) {
		if ( panel.hasAttribute( 'data-random' ) ||
			panel.hasAttribute( 'data-category' ) ) {
			return pickRandom( api, panel ).catch( function () {} );
		}

		var page = resolvePage( panel );
		if ( page ) {
			return loadArticle( api, panel, page ).catch( function () {} );
		}
	}

	function resolvePage( panel ) {
		var explicit = panel.getAttribute( 'data-page' );
		if ( explicit ) {
			return explicit;
		}
		var link = panel.querySelector( 'a[title]' );
		if ( !link ) {
			return null;
		}
		var title = link.getAttribute( 'title' );
		if ( /\(page does not exist\)$/.test( title ) ) {
			return null;
		}
		return title;
	}

	function pickRandom( api, panel ) {
		var categories = splitList( panel.getAttribute( 'data-category' ) );
		var exclude = panel.getAttribute( 'data-exclude-category' );

		var minBytes = parseInt( panel.getAttribute( 'data-min-bytes' ), 10 );
		if ( isNaN( minBytes ) ) {
			minBytes = DEFAULTS.minBytes;
		}

		var listing = categories.length ?
			Promise.all( categories.map( function ( c ) {
				return listCategory( api, c, minBytes );
			} ) ).then( merge ) :
			listAll( api, minBytes );

		return Promise.all( [ listing, listExcluded( api, exclude ) ] )
			.then( function ( results ) {
				var titles = results[ 0 ];
				var skip = results[ 1 ];

				var candidates = titles.filter( function ( t ) {
					return !skip[ t ];
				} ).sort( function ( a, b ) {
					return a.localeCompare( b );
				} );

				if ( !candidates.length ) {
					mw.log.warn( '[lhc-article] no candidates found' );
					return;
				}

				return loadArticle(
					api, panel, candidates[ pickIndex( candidates.length ) ]
				);
			} );
	}

	function splitList( value ) {
		if ( !value ) {
			return [];
		}
		return value.split( '|' ).map( function ( s ) {
			return s.trim();
		} ).filter( function ( s ) {
			return s.length > 0;
		} );
	}

	function merge( lists ) {
		var seen = {};
		var out = [];
		lists.forEach( function ( list ) {
			list.forEach( function ( title ) {
				if ( !seen[ title ] ) {
					seen[ title ] = true;
					out.push( title );
				}
			} );
		} );
		return out;
	}

	function listCategory( api, category, minBytes ) {
		return api.get( {
			action: 'query',
			generator: 'categorymembers',
			gcmtitle: 'Category:' + category,
			gcmnamespace: 0,
			gcmtype: 'page',
			gcmlimit: 'max',
			prop: 'info',
			formatversion: 2
		} ).then( function ( data ) {
			var pages = ( data.query && data.query.pages ) || [];
			if ( !pages.length ) {
				mw.log.warn( '[lhc-article] empty category: ' + category );
			}
			return pages.filter( function ( p ) {
				return p.length >= minBytes;
			} ).map( function ( p ) {
				return p.title;
			} );
		} ).catch( function () {
			mw.log.warn( '[lhc-article] lookup failed: ' + category );
			return [];
		} );
	}

	function listAll( api, minBytes ) {
		var titles = [];

		function fetch( cont, round ) {
			var params = {
				action: 'query',
				generator: 'allpages',
				gapnamespace: 0,
				gapfilterredir: 'nonredirects',
				gapminsize: minBytes,
				gaplimit: 'max',
				formatversion: 2
			};
			if ( cont ) {
				Object.keys( cont ).forEach( function ( k ) {
					params[ k ] = cont[ k ];
				} );
			}

			return api.get( params ).then( function ( data ) {
				( ( data.query && data.query.pages ) || [] )
					.forEach( function ( p ) {
						titles.push( p.title );
					} );

				if ( data.continue && round < DEFAULTS.maxPages ) {
					return fetch( data.continue, round + 1 );
				}
				return titles;
			} );
		}

		return fetch( null, 1 );
	}

	function listExcluded( api, category ) {
		if ( !category ) {
			return Promise.resolve( {} );
		}
		return api.get( {
			action: 'query',
			list: 'categorymembers',
			cmtitle: 'Category:' + category,
			cmnamespace: 0,
			cmlimit: 'max',
			formatversion: 2
		} ).then( function ( data ) {
			var skip = {};
			( ( data.query && data.query.categorymembers ) || [] )
				.forEach( function ( p ) {
					skip[ p.title ] = true;
				} );
			return skip;
		} ).catch( function () {
			return {};
		} );
	}

	function pickIndex( n ) {
		if ( !DEFAULTS.dailyStable ) {
			return Math.floor( Math.random() * n );
		}
		var d = new Date();
		var key = d.getUTCFullYear() + '-' +
			( d.getUTCMonth() + 1 ) + '-' + d.getUTCDate();
		var h = 2166136261;
		for ( var i = 0; i < key.length; i++ ) {
			h = ( h ^ key.charCodeAt( i ) ) * 16777619 >>> 0;
		}
		return h % n;
	}

	function loadArticle( api, panel, title ) {
		return api.get( {
			action: 'parse',
			page: title,
			prop: 'text',
			section: 0,
			redirects: 1,
			formatversion: 2
		} ).then( function ( data ) {
			var html = data.parse && data.parse.text;
			if ( !html ) {
				return;
			}
			var doc = new DOMParser().parseFromString( html, 'text/html' );

			stripFurniture( doc );

			render( panel, {
				title: data.parse.title || title,
				image: extractImage( doc ),
				blurb: extractBlurb( doc )
			} );
		} );
	}

	function stripFurniture( doc ) {
		var junk = doc.querySelectorAll( FURNITURE_SELECTOR );
		Array.prototype.forEach.call( junk, function ( el ) {
			if ( el.parentNode ) {
				el.parentNode.removeChild( el );
			}
		} );
	}

	function extractImage( doc ) {
		var all = doc.querySelectorAll( 'img' );
		var img = null;

		for ( var i = 0; i < all.length; i++ ) {
			if ( imageWidth( all[ i ] ) >= DEFAULTS.minImageWidth ) {
				img = all[ i ];
				break;
			}
		}

		if ( !img ) {
			return null;
		}
		var src = img.getAttribute( 'src' );
		if ( !src ) {
			return null;
		}
		if ( src.indexOf( '//' ) === 0 ) {
			return location.protocol + src;
		}
		if ( src.indexOf( '/' ) === 0 ) {
			return location.origin + src;
		}
		return src;
	}

	function imageWidth( img ) {
		return parseInt( img.getAttribute( 'width' ), 10 ) || 0;
	}

	function extractBlurb( doc ) {
		var tables = doc.querySelectorAll( 'table, .infobox, .thumb' );
		Array.prototype.forEach.call( tables, function ( el ) {
			if ( el.parentNode ) {
				el.parentNode.removeChild( el );
			}
		} );

		var paras = doc.querySelectorAll( 'p' );
		for ( var i = 0; i < paras.length; i++ ) {
			var text = paras[ i ].textContent.replace( /\s+/g, ' ' ).trim();
			if ( text.length > 40 ) {
				return truncate( text, DEFAULTS.blurbChars );
			}
		}
		return null;
	}

	function truncate( text, max ) {
		if ( text.length <= max ) {
			return text;
		}
		var cut = text.slice( 0, max );
		var space = cut.lastIndexOf( ' ' );
		return ( space > 0 ? cut.slice( 0, space ) : cut ) + '…';
	}

	function render( panel, article ) {
		var href = mw.util.getUrl( article.title );

		var wrap = document.createElement( 'div' );
		wrap.className = 'lhc-ra';

		if ( article.image ) {
			var link = document.createElement( 'a' );
			link.href = href;
			link.className = 'lhc-ra-img';
			var img = document.createElement( 'img' );
			img.src = article.image;
			img.alt = article.title;
			link.appendChild( img );
			wrap.appendChild( link );
		}

		var heading = document.createElement( 'div' );
		heading.className = 'lhc-ra-title';
		var titleLink = document.createElement( 'a' );
		titleLink.href = href;
		titleLink.textContent = article.title;
		heading.appendChild( titleLink );
		wrap.appendChild( heading );

		if ( article.blurb ) {
			var p = document.createElement( 'p' );
			p.className = 'lhc-ra-blurb';
			p.textContent = article.blurb;
			wrap.appendChild( p );
		}

		var more = document.createElement( 'a' );
		more.href = href;
		more.className = 'lhc-ra-more';
		more.textContent = 'Read the full article →';
		wrap.appendChild( more );

		panel.textContent = '';
		panel.appendChild( wrap );
	}

	mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
		if ( document.readyState !== 'loading' ) {
			init();
		} else {
			document.addEventListener( 'DOMContentLoaded', init );
		}
	} );

}() );