User:Evad37/TextDiff.js

/***************************************************************************************************

TextDiff --- by Evad37

> Shows a simpler, text-only diff

> Alpha version

  • /

//

$( 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(

$('

').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 = $('').attr('id', 'textDiff-buttonRow').append(

$('').attr('id', 'textDiff-buttonCell').append(

$('

').css('text-align', 'center').append(

$('