/***************************************************************************************************
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>