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 = {

$span: null,

api: null,

init: function { var id = location.hash.replace( ':', '\\:' ); // jQuery interprets colons as pseudoselectors var $span = $( id ); if ( !$span.length ) { }		Synchronizer.$span = $span; Synchronizer.api = new mw.Api; var local = { wiki: mw.config.get( 'wgWikiID' ), status: 'Local' };		var page = $span.data( 'page' ); if ( page ) { new mw.Api.get( {				formatversion: 2,				action: 'query',				prop: 'pageprops',				ppprop: 'wikibase_item',				titles: page			} ).done( function ( data ) {				var page = data.query.pages[0];				local.page = page.title;				local.item = 'pageprops' in page ? page.pageprops.wikibase_item : null;				Synchronizer.makeTable( local );			} ); } else { local.page = mw.config.get( 'wgPageName' ); local.item = mw.config.get( 'wgWikibaseItemId' ); Synchronizer.makeTable( local ); }	},

makeTable: function ( local ) { var $span = Synchronizer.$span; if ( local.item ) { var $table = $( ' ' ); var $row = $( ' ' ); var $headerWiki = $( ' ' ).text( 'Wiki' ); var $headerLink = $( ' ' ).text( 'Link' ); var $headerStatus = $( ' ' ).text( 'Status' ); var $headerAction = $( ' ' ).text( 'Action' ); $row.append( $headerWiki, $headerLink, $headerStatus, $headerAction ); $table.append( $row ); $span.html( $table ); Synchronizer.getLocalData( local ); } else { $span.addClass( 'error' ).text( 'No Wikidata item associated to ' + local.page ); }	},

getLocalData: function ( local ) { new mw.Api.get( {			formatversion: 2,			action: 'query',			prop: 'revisions',			rvprop: 'timestamp|sha1|content',			rvslots: 'main',			titles: local.page		} ).done( function ( data ) {			var revision = data.query.pages[0].revisions[0];			local.time = new Date( revision.timestamp );			local.sha1 = revision.sha1;			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 new mw.Api.get( {				formatversion: 2,				action: 'query',				prop: 'revisions',				rvprop: 'sha1',				rvlimit: 'max',				rvslots: 'main',				titles: local.page			} ).done( function ( data ) {				//console.log( data );				var revisions = data.query.pages[0].revisions;				local.hashes = revisions.map( function ( revision ) { return revision.sha1; } );				Synchronizer.makeRow( local );				Synchronizer.getRemoteData( local );			} ); } );	},

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

updateStatus: function ( remote, local ) { 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 === local.sha1 ) {				remote.status = 'Updated';			} else if ( local.hashes.includes( sha1 ) ) {				remote.status = 'Outdated';			} else {				remote.status = 'Forked';			}			Synchronizer.makeRow( remote, local );		} ); },

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

makeRow: function ( remote, local ) { 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, local );				} ); 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, local );					}				} ); 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 ); }	} };

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