Jump to content

User:Evad37/TextDiff.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/***************************************************************************************************
 TextDiff --- by Evad37
 > Shows a simpler, text-only diff
 > Alpha version
***************************************************************************************************/
// <nowiki>
$( function($) {
	
/* ========== Load dependencies ================================================================= */
// Load resoucre loader modules
mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.libExtraUtil'], function () {

// Do not operate if not viewing a diff, or if there is no difference
if ( !mw.util.getParamValue('diff') || $('.mw-diff-empty').length ) {
	return;
}

var config = {
	'script': {
		'version': '0.3.0-alpha'
	},
	'params': {
		'diff': mw.util.getParamValue('diff'),
		'oldid': mw.util.getParamValue('oldid')
	},
	'mw': mw.config.get(['wgPageName', 'wgCurRevisionId'])
};

var api = new mw.Api( {
    ajax: {
        headers: { 
			'Api-User-Agent': 'TextDiff/' + config.script.version + 
				' ( https://en.wikipedia.org/wiki/User:Evad37/TextDiff )'
		}
    }
} );

/* ========= Processing functions =============================================================== */

/**
 * normaliseIds
 *
 * Get normalised revision ids (lookup the current / previous / next id if present)
 * @param
 */
var normaliseIds = function(diffParam, oldidParam, title) {
	var relativeDiffWords = ['cur', 'prev', 'next'];
	
	if ( !diffParam ) {
		return $.Deferred().reject('No diff specified');
	}
	if ( !$.isNumeric(diffParam) && relativeDiffWords.indexOf(diffParam) === -1 ) {
		return $.Deferred().reject("Bad diff specified: Must be numeric or one of 'cur', 'prev', 'next'");
	}

	// Both params have numeric ids
	if ( $.isNumeric(diffParam) && $.isNumeric(oldidParam) ) {
		return $.Deferred().resolve({
			'from': oldidParam,
			'to': diffParam
		});
	}
	
	// Neither param has a numeric ids 
	if ( !$.isNumeric(diffParam) && !$.isNumeric(oldidParam) ) {
		return lookupIdsFromRelation(
			'prev',
			config.mw.wgCurRevisionId,
			title
		);
	}
	
	// Only diff param has a numeric id
	if ( $.isNumeric(diffParam) ) {
		return lookupIdsFromRelation(oldidParam, diffParam, title);
	}
	
	// Only oldid param has a numeric id
	return lookupIdsFromRelation(diffParam, oldidParam, title);
};

/**
 * lookupIdsFromRelation
 *
 * @param
 */
var lookupIdsFromRelation = function(relation, otherId, title) {
	return api.get({
		action: 'compare',
		format: 'json',
		fromrev: otherId, //|| false,
		fromtitle: ( $.isNumeric(otherId) ) ? '' : title,
		torelative: relation,
		prop: 'ids'
	})
	.then(function(result) {
		return {
			'from': result.compare.fromrevid,
			'to': result.compare.torevid || config.mw.wgCurRevisionId
		};
	});
};

/**
 * makeText
 *
 * Shorthand for #grabWikitext then #toPlaintext
 */
var makeText = function(id) { return grabWikitext(id).then(toPlaintext); };

/**
 * grabWikitext
 *
 * Gets the wikitext and page title of a specific revision of a page
 *
 * @param {string} page
 *   Page title
 * @param {int} revid
 *   Old revision id
 * @return {Deferred} Promise that is resolved with: {object} results, with keys
 *     wikitext: {string},
 *     pageTitle: {string}
 */
var grabWikitext = function(revid) {
	
	return api.get({
		"action": "query",
		"format": "json",
		"prop": "revisions",
		"revids": revid,
		"rvprop": "content"
	})
	.then(function(response) {
		var pageid = Object.keys(response.query.pages)[0];
		var wikitext = response.query.pages[pageid].revisions[0]['*'];
		var title = response.query.pages[pageid].title;
		return { 'wikitext':wikitext, 'pageTitle':title };
	});

};

/**
 * toPlaintext
 *
 * Transforms a wikitext string into a plaintext string. Images are replaced with alt text.
 *
 * @param {object} info
 *	 @param {string} info.wikitext
 *     Wikitext to be expanded
 *   @param {string} info.pageTitle
 *     Page title, to give context to variables like {{PAGENAME}} 
 * @return {Deferred} Promise that is resolved with: {string} transformed wikitext
 */
var toPlaintext = function(info) {
	return api.post({
		"action": "parse",
		"format": "json",
		"title": info.pageTitle,
		"text": info.wikitext,
		"prop": "text",
		"disablelimitreport": 1,
		"disableeditsection": 1,
		"contentmodel": "wikitext",
		"mobileformat": 1,
		"noimages": 1
	})
	.then( function(response) { return response.parse.text['*']; } )
	.then( function(parsetext) { return $(parsetext).text(); } );
};

/*
Strip lines where that line, the two previous lines, and the two next lines, are identical.
Parameters are arrays of text, with one line of text per array element
*/
var stripIdenticalLines = function(lines, otherLines) {
	return lines.map(function(line, index) {
		if (
			lines[index-2] === otherLines[index-2] &&
			lines[index-1] === otherLines[index-1] &&
			lines[index] === otherLines[index] &&
			lines[index+1] === otherLines[index+1] &&
			lines[index+2] === otherLines[index+2]
		) {
			return '';
		}
		return line;
	});
};

var stripIdenticalText = function(fromText, toText) {
	var fromLines = fromText.split('\n');
	var toLines = toText.split('\n');
	return $.Deferred().resolve(
		stripIdenticalLines(fromLines, toLines).join('\n'),
		stripIdenticalLines(toLines, fromLines).join('\n')
	);
};

/**
 * makeDiff
 *
 * @param
 */
var makeDiff = function(fromText, toText) {
	return api.post({
		"action": "compare",
		"format": "json",
		"fromtext": fromText,
		"fromcontentmodel": "text",
		"totext": toText,
		"tocontentmodel": "text",
		"prop": "diff"
	})
	.then( function(response) { return response.compare['*']; });
};

/**
 * showDiff
 *
 * @param
 */
var showDiff = function(diffRow) {
	$(diffRow).filter('tr').addClass('textdiff-row').hide().insertAfter('#textDiff-buttonRow').show('fast');
	$('tr.textdiff-row').last().nextAll().addClass('origdiff-row').hide('fast');
	$('#textDiff-button').prop('disabled', false).text('Toggle textual diff');
};

var onError = function(code, jqxhr) {
	$('#textDiff-button').text('Error loading textual diff').after(
		$('<div>').addClass('error').append( extraJs.makeErrorMsg(code, jqxhr) )
	);
};


/* ========= Set up =============================================================== */
var doTextDiff = function(diffParam, oldIdParam, pageName) {
	normaliseIds(diffParam, oldIdParam, pageName)
	.then(function(ids) {
		return $.when(makeText(ids.from), makeText(ids.to));
	})
	.then(stripIdenticalText)
	.then(makeDiff)
	.then(showDiff, onError);
};

var $buttonRow = $('<tr>').attr('id', 'textDiff-buttonRow').append(
	$('<td>').attr('id', 'textDiff-buttonCell').append(
		$('<div>').css('text-align', 'center').append(
			$('<button>')
			.attr('title', 'Show textual diff view')
			.text('Textual diff')
			.click(function() {
				$(this).hide().next().show();
				doTextDiff(config.params.diff, config.params.oldid, config.mw.wgPageName);
			}),
			
			$('<button>')
			.attr({'id':'textDiff-button', 'title':'Toggle textual diff view'})
			.text('Textual diff loading...')
			.prop('disabled', 'true')
			.hide()
			.click(function() {
				$('tr.textdiff-row, tr.origdiff-row').toggle();
			})
		)
	)
);

$('table.diff').find('tr').first().after($buttonRow);
$('#textDiff-buttonCell').attr('colspan', '4');

/* ========== Export code for testing by /test.js =============================================== */
window.testTextDiff = {
	'config': config,
	'api': api,
	'normaliseIds': normaliseIds,
	'lookupIdsFromRelation': lookupIdsFromRelation,
	'makeText': makeText,	
	'grabWikitext': grabWikitext,
	'toPlaintext': toPlaintext,
	'makeDiff': makeDiff,
	'showDiff': showDiff,
	'doTextDiff': doTextDiff
};

});
});
// </nowiki>