MediaWiki:Synchronizer.js

/** * Synchronizer is a tool for synchronizing Lua modules, templates and other pages across Wikimedia wikis * Documentation: https://www.mediawiki.org/wiki/Synchronizer * Author: User:Sophivorus * License: CC-BY-SA-3.0 */ var Synchronizer = {

/**	 * tag that will hold the HTML we will generate * @type {Object} jQuery object containing the */	$span: null,

/**	 * Data about the local page we will synchronize * @type {Object} Map from data key to data value */	local: null,

init: function {

// Do basic checks var id = location.hash.replace( ':', '\\:' ).replace( '.', '\\.' ); if ( !id ) { return Synchronizer.error( 'ID not found.'); }		var $span = $( id ); if ( !$span.length ) { return Synchronizer.error( 'Span with ID ' + id + ' not found.'); }		Synchronizer.$span = $span; var page = $span.data( 'page' ).trim; if ( !page ) { return Synchronizer.error( 'Page not set.' ); }

// Set basic local data Synchronizer.local = { wiki: mw.config.get( 'wgWikiID' ), page: page, status: 'Local', api: new mw.Api };

// Get wikidata item Synchronizer.local.api.get( {			formatversion: 2,			action: 'query',			prop: 'pageprops',			ppprop: 'wikibase_item',			titles: page		} ).done( function ( data ) {			var page = data.query.pages[0];			if ( page.missing ) {				return Synchronizer.error( Synchronizer.local.page + ' not found.' );			}			if ( !page.hasOwnProperty( 'pageprops' ) ) {				return Synchronizer.error( 'No Wikidata item associated to ' + Synchronizer.local.page );			}			Synchronizer.local.item = page.pageprops.wikibase_item;			Synchronizer.makeTable;			Synchronizer.getLocalData;		} ); },

makeTable: function { var $table = $( ' ' ); var $row = $( ' ' ); var $th1 = $( ' ' ).text( 'Wiki' ); var $th2 = $( ' ' ).text( 'Link' ); var $th3 = $( ' ' ).text( 'Status' ); var $th4 = $( ' ' ).text( 'Action' ); $row.append( $th1, $th2, $th3, $th4 ); $table.append( $row ); Synchronizer.$span.html( $table ); },

getLocalData: function { Synchronizer.local.api.get( {			formatversion: 2,			action: 'query',			prop: 'revisions',			rvprop: 'timestamp|sha1|content',			rvslots: 'main',			titles: Synchronizer.local.page		} ).done( function ( data ) {			var revision = data.query.pages[0].revisions[0];			Synchronizer.local.time = new Date( revision.timestamp );			Synchronizer.local.sha1 = revision.sha1;			Synchronizer.local.content = revision.slots.main.content;

// We need to do a separate API call to get 500 revisions // because "content" is considered an "expensive" property // so if we request all together we only get 50 revisions Synchronizer.local.api.get( {				formatversion: 2,				action: 'query',				prop: 'revisions',				rvprop: 'sha1',				rvlimit: 'max',				rvslots: 'main',				titles: Synchronizer.local.page			} ).done( function ( data ) {				//console.log( data );				var revisions = data.query.pages[0].revisions;				Synchronizer.local.hashes = revisions.map( function ( revision ) { return revision.sha1; } );				Synchronizer.makeRow( Synchronizer.local );				Synchronizer.getRemoteData;			} ); } );	},

getRemoteData: function { $.get( '//www.wikidata.org/wiki/Special:EntityData/' + Synchronizer.local.item + '.json' ).done( function ( data ) {			var entities = data.entities;			var entity = entities[ Synchronizer.local.item ];			var siteLinks = entity.sitelinks;			var siteLink;			for ( var key in siteLinks ) {				if ( key === Synchronizer.local.wiki ) {					continue;				}				siteLink = siteLinks[ key ];				var remote = {					wiki: siteLink.site,					page: siteLink.title,					status: 'Loading',					url: siteLink.url,					api: siteLink.url.replace( /\/wiki\/.+/, '/w/api.php' ) // Hacky but efficient				};				Synchronizer.makeRow( remote );				Synchronizer.updateStatus( remote );			}		} ); },

updateStatus: function ( remote ) { new mw.ForeignApi( remote.api ).get( {			formatversion: 2,			action: 'query',			prop: 'revisions',			rvprop: 'timestamp|sha1',			rvslots: 'main',			titles: remote.page		} ).done( function ( data ) {			//console.log( data );			var revision = data.query.pages[0].revisions[0];			var sha1 = revision.sha1;			var time = new Date( revision.timestamp );			if ( sha1 === Synchronizer.local.sha1 ) {				remote.status = 'Updated';			} else if ( Synchronizer.local.hashes.includes( sha1 ) ) {				remote.status = 'Outdated';			} else {				remote.status = 'Forked';			}			Synchronizer.makeRow( remote );		} ); },

update: function ( remote ) { remote.status = 'Updating'; Synchronizer.makeRow( remote ); return new mw.ForeignApi( remote.api ).edit( remote.page, function ( revision ) {			var prefix = Synchronizer.getInterwikiPrefix;			var master = prefix + ':' + Synchronizer.local.page;			var summary = 'Update from master using Synchronizer';			return {				text: Synchronizer.local.content,				summary: summary,				assert: 'user'			};		} ).done( function {			remote.status = 'Updated';			remote.content = Synchronizer.local.content;			Synchronizer.makeRow( remote );		} ); },

makeRow: function ( remote ) { var color, $button; switch ( remote.status ) { case 'Local': color = '#aff'; break; case 'Updated': color = '#afa'; break; case 'Outdated': color = '#ffa'; $button = $( ' ' ).text( 'Update' ).click( function  {					Synchronizer.update( remote );				} ); break; case 'Forked': color = '#faa'; $button = $( ' ' ).text( 'Update' ).click( function  {					var confirm = window.confirm( 'Carelessly updating a forked page may break things. Are you sure you want to continue?' );					if ( confirm ) {						Synchronizer.update( remote );					}				} ); break; }		var $link = $( '' ).text( remote.page ).attr( 'href', remote.url ); var $td1 = $( ' ' ).text( remote.wiki ); var $td2 = $( ' ' ).html( $link ); var $td3 = $( ' ' ).text( remote.status ).css( 'background-color', color ); var $td4 = $( ' ' ).html( $button ); var $span = Synchronizer.$span; var $table = $span.find( 'table' ); var $row = $table.find( '.' + remote.wiki ); if ( $row.length ) { $row.empty.append( $td1, $td2, $td3, $td4 ); } else { $row = $( ' ' ).addClass( remote.wiki ).append( $td1, $td2, $td3, $td4 ); $table.append( $row ); }	},

getInterwikiPrefix: function { var prefix = Synchronizer.local.wiki; var server = mw.config.get( 'wgServerName' ); var parts = server.split( '.' ); var language = parts[0]; var project = parts[1]; var projects = [ 'wikipedia', 'wiktionary', 'wikinews', 'wikibooks', 'wikisource', 'wikiversity', 'wikiquote', 'wikivoyage', ];		if ( projects.includes( project ) ) { prefix = project + ':' + language; }		return prefix; },

/**	 * Make error message */	error: function ( message ) { if ( Synchronizer.$span && Synchronizer.$span.length ) { Synchronizer.$span.addClass( 'error' ).text( message ); } else { console.log( message ); }	} };

mw.loader.using( 'mediawiki.ForeignApi', Synchronizer.init );