/**
* Script to provide a button on page histories
* which when clicked shows a sanitized version of the history
* with reverted edits (and their reversions) removed
*
*/
var hre = {
init: function() {
if (mw.config.get('wgAction') !== 'history') return;
// bool: are the reverts currently hidden or not?
var hidingReverts;
if (/&hidereverted=y/.test(window.location.href)) {
hre.hideRevertedEdits();
hre.fixLinks(true);
hidingReverts = true;
} else {
hidingReverts = false;
}
$('') .addClass('reverted-edits-showhide') .addClass('mw-ui-button') .text(hidingReverts ? 'Show reverted edits' : 'Hide reverted edits') .css('margin-left', '3px') .on('click', function(e) { e.preventDefault(); if (!hidingReverts) { hre.hideRevertedEdits(); $('.reverted-edits-showhide').text('Show reverted edits'); } else { $('#pagehistory li').show(); $('.reverted-edits-showhide').text('Hide reverted edits'); } hidingReverts = !hidingReverts; hre.fixLinks(hidingReverts); }).insertAfter('.mw-history-compareselectedversions input'); }, hideRevertedEdits: function() { $('#pagehistory li').each(function(idx) { if (this.style.display === 'none') { return true; // continue if already hidden. This handles reverts of reverts. } var $this = $(this); var editcomment = $this.find('.comment').text(); var rgx, reverted_user, last_good_revision; // Plain MediaWiki undo with untampered edit summary if (rgx = /^Undid revision (\d+) by/.exec(editcomment)) { var reverted_rev_id = rgx[1]; var $reverted_rev = $('[data-mw-revid=' + reverted_rev_id +']'); // just to confirm that the edit isn't a partial revert, find the byte count changes for the // two edits: if they add up to 0, then this is a full revert (in all likelihood) var diffbytes1 = parseInt($this.find('[class^=mw-plusminus]').text().replace(/−/, '-')); var diffbytes2 = parseInt($reverted_rev.find('[class^=mw-plusminus]').text().replace(/−/, '-')); if (diffbytes1 + diffbytes2 === 0) { $this.hide(); $reverted_rev.hide(); if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'undo'); } // 'Restore this version' reverts using Twinkle (old) or popups or pending changes reverts // TW(old): Reverted to revision 3234343 by ... // popups: Revert to revision 34234234 by ... // PC tool: Revereted 3 pending edits by Foo and Bar to revision 3243432 by ... } else if (rgx = /^Revert(?:ed)? (?:\d+ pending edits? by .*?)?to revision (\d+)/.exec(editcomment)) { last_good_revision = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'restore'); // 'Restore this version' reverts using Twinkle (new) } else if (rgx = /^Restored revision (\d+) by/.exec(editcomment)) { last_good_revision = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'tw(new) restore'); // Reverts tagged as "Rollback" } else if ($this.find('.mw-tag-marker-mw-rollback').length) { reverted_user = $this.next().find('.mw-userlink bdi').text(); } // Twinkle rollbacks else if (rgx = /^Reverted (?:good faith|\d+) edits? by (.*?) \(talk\)/.exec(editcomment)) { reverted_user = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'Twinkle rollback'); // Old Twinke vandalism rollback } else if (rgx = /^Reverted \d+ edits? by (.*?) identified as vandalism/.exec(editcomment)) { reverted_user = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'Twinkle (old) rollback'); // STiki vandalism rollbacks, and all reverts using MediaWiki rollback, Huggle, Cluebot have the "Rollback" tag added // and hence would have been handled above. The regex checks here are to account for old reverts done before the // "Rollback" tag was introduced // STiki AGF/normal/vandalism revert } else if (rgx = /^Reverted \d+ (?:good faith )?edits? by (.*?) (?:identified as test\/vandalism )?using STiki/.exec(editcomment)) { reverted_user = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'STiki rollback'); // normal MediaWiki rollback and Huggle rollback, and redwarn rollback // MW/Huggle: Reverted edits by User (talk) // RedWarn: Reverting edit(s) by User (talk) } else if (rgx = /^Revert(?:ed|ing) edit\(?s\)? by (.*?) \(talk\)/.exec(editcomment)) { reverted_user = rgx[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'mw/huggle/redwarn rollback'); // ClueBot } else if (['ClueBot NG', 'ClueBot'].includes($this.find('.mw-userlink bdi').text())) { reverted_user = /^Reverting possible vandalism by (.*?) to version by/.exec(editcomment)?.[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'cluebot rollback'); // XLinkBot } else if ($this.find('.mw-userlink bdi').text() === 'XLinkBot') { reverted_user = /^BOT--Reverting link addition\(s\) by (.*?) to/.exec(editcomment)?.[1]; if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'xlinkbot rollback'); } if (reverted_user) { // page history shows compressed IPv6 address (with multiple 0's replaced by ::) // though rollback edit summaries use the uncompressed form (though with leading 0's removed) if (mw.util.isIPv6Address(reverted_user)) { reverted_user = reverted_user.replace(/\b(?:0+:){2,}/, ':').toLowerCase(); } $this.hide(); var $rev = $this.next(); while ($rev.find('.mw-userlink bdi').text() === reverted_user) { $rev.hide(); $rev = $rev.next(); if ($rev.length === 0) break; // end of page history (in current view) } } if (last_good_revision) { $this.hide(); var $rev = $this.next(); if (parseInt(last_good_revision) > parseInt($rev.attr('data-mw-revid')) || parseInt(last_good_revision) < 100) { // sanity checks return true; // revision id given has to be wrong } while ($rev.attr('data-mw-revid') !== last_good_revision) { $rev.hide(); $rev = $rev.next(); if ($rev.length === 0) break; // end of page history in current view } } }); }, /** * Add or remove "&hidereverted=y" from the targets of links: * (newest | oldest) (newer n | older n) (20 | 50 | 100 | 250 | 500) */ fixLinks: function(hidingReverts) { if (hidingReverts) { if (!/&hidereverted=y/.test($('.mw-numlink').attr('href'))) { $('.mw-firstlink, .mw-lastlink, .mw-prevlink, .mw-nextlink, .mw-numlink').each(function() { this.href += '&hidereverted=y'; }); } } else { $('.mw-firstlink, .mw-lastlink, .mw-prevlink, .mw-nextlink, .mw-numlink').each(function() { this.href = this.href.replace(/&hidereverted=y/, ''); }); } } } $(hre.init);
.addClass('reverted-edits-showhide')
.addClass('mw-ui-button')
.text(hidingReverts ? 'Show reverted edits' : 'Hide reverted edits')
.css('margin-left', '3px')
.on('click', function(e) {
e.preventDefault();
if (!hidingReverts) {
$('.reverted-edits-showhide').text('Show reverted edits');
$('#pagehistory li').show();
$('.reverted-edits-showhide').text('Hide reverted edits');
hidingReverts = !hidingReverts;
hre.fixLinks(hidingReverts);
}).insertAfter('.mw-history-compareselectedversions input');
},
hideRevertedEdits: function() {
$('#pagehistory li').each(function(idx) {
if (this.style.display === 'none') {
return true; // continue if already hidden. This handles reverts of reverts.
var $this = $(this);
var editcomment = $this.find('.comment').text();
var rgx, reverted_user, last_good_revision;
// Plain MediaWiki undo with untampered edit summary
if (rgx = /^Undid revision (\d+) by/.exec(editcomment)) {
var reverted_rev_id = rgx[1];
var $reverted_rev = $('[data-mw-revid=' + reverted_rev_id +']');
// just to confirm that the edit isn't a partial revert, find the byte count changes for the
// two edits: if they add up to 0, then this is a full revert (in all likelihood)
var diffbytes1 = parseInt($this.find('[class^=mw-plusminus]').text().replace(/−/, '-'));
var diffbytes2 = parseInt($reverted_rev.find('[class^=mw-plusminus]').text().replace(/−/, '-'));
if (diffbytes1 + diffbytes2 === 0) {
$this.hide();
$reverted_rev.hide();
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'undo');
// 'Restore this version' reverts using Twinkle (old) or popups or pending changes reverts
// TW(old): Reverted to revision 3234343 by ...
// popups: Revert to revision 34234234 by ...
// PC tool: Revereted 3 pending edits by Foo and Bar to revision 3243432 by ...
} else if (rgx = /^Revert(?:ed)? (?:\d+ pending edits? by .*?)?to revision (\d+)/.exec(editcomment)) {
last_good_revision = rgx[1];
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'restore');
// 'Restore this version' reverts using Twinkle (new)
} else if (rgx = /^Restored revision (\d+) by/.exec(editcomment)) {
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'tw(new) restore');
// Reverts tagged as "Rollback"
} else if ($this.find('.mw-tag-marker-mw-rollback').length) {
reverted_user = $this.next().find('.mw-userlink bdi').text();
// Twinkle rollbacks
else if (rgx = /^Reverted (?:good faith|\d+) edits? by (.*?) \(talk\)/.exec(editcomment)) {
reverted_user = rgx[1];
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'Twinkle rollback');
// Old Twinke vandalism rollback
} else if (rgx = /^Reverted \d+ edits? by (.*?) identified as vandalism/.exec(editcomment)) {
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'Twinkle (old) rollback');
// STiki vandalism rollbacks, and all reverts using MediaWiki rollback, Huggle, Cluebot have the "Rollback" tag added
// and hence would have been handled above. The regex checks here are to account for old reverts done before the
// "Rollback" tag was introduced
// STiki AGF/normal/vandalism revert
} else if (rgx = /^Reverted \d+ (?:good faith )?edits? by (.*?) (?:identified as test\/vandalism )?using STiki/.exec(editcomment)) {
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'STiki rollback');
// normal MediaWiki rollback and Huggle rollback, and redwarn rollback
// MW/Huggle: Reverted edits by User (talk)
// RedWarn: Reverting edit(s) by User (talk)
} else if (rgx = /^Revert(?:ed|ing) edit\(?s\)? by (.*?) \(talk\)/.exec(editcomment)) {
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'mw/huggle/redwarn rollback');
// ClueBot
} else if (['ClueBot NG', 'ClueBot'].includes($this.find('.mw-userlink bdi').text())) {
reverted_user = /^Reverting possible vandalism by (.*?) to version by/.exec(editcomment)?.[1];
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'cluebot rollback');
// XLinkBot
} else if ($this.find('.mw-userlink bdi').text() === 'XLinkBot') {
reverted_user = /^BOT--Reverting link addition\(s\) by (.*?) to/.exec(editcomment)?.[1];
if (window.hre_debug) console.log(idx, $this.find('.mw-changeslist-date').text(), 'xlinkbot rollback');
if (reverted_user) {
// page history shows compressed IPv6 address (with multiple 0's replaced by ::)
// though rollback edit summaries use the uncompressed form (though with leading 0's removed)
if (mw.util.isIPv6Address(reverted_user)) {
reverted_user = reverted_user.replace(/\b(?:0+:){2,}/, ':').toLowerCase();
var $rev = $this.next();
while ($rev.find('.mw-userlink bdi').text() === reverted_user) {
$rev.hide();
$rev = $rev.next();
if ($rev.length === 0) break; // end of page history (in current view)
if (last_good_revision) {
if (parseInt(last_good_revision) > parseInt($rev.attr('data-mw-revid')) ||
parseInt(last_good_revision) < 100) { // sanity checks
return true; // revision id given has to be wrong
while ($rev.attr('data-mw-revid') !== last_good_revision) {
if ($rev.length === 0) break; // end of page history in current view
});
* Add or remove "&hidereverted=y" from the targets of links:
* (newest | oldest) (newer n | older n) (20 | 50 | 100 | 250 | 500)
fixLinks: function(hidingReverts) {
if (hidingReverts) {
if (!/&hidereverted=y/.test($('.mw-numlink').attr('href'))) {
$('.mw-firstlink, .mw-lastlink, .mw-prevlink, .mw-nextlink, .mw-numlink').each(function() {
this.href += '&hidereverted=y';
this.href = this.href.replace(/&hidereverted=y/, '');
$(hre.init);