User:Alexis Jazz/Kill-It-With-Fire.js
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.
/*
* Kill-It-With-Fire
* Forked from Restore-a-lot which was in turn forked from Cat-a-lot
* Undo multiple edits from Special:Contributions
* See ? for documentation (that inspires confidence)
*
* @rev 00:13, 10 February 2018 (UTC)
* @author Originally by Magnus Manske (2007)
* @author RegExes by Ilmari Karonen (2010)
* @author Completely rewritten by DieBuche (2010-2012)
* @author Rillke (2012-2014)
* @author Perhelion (2017)
* @author Alexis Jazz is a forking idiot (Restore-a-lot, 2020)
* @author Various fixes by Zhuyifei1999 (Restore-a-lot, 2020)
* @author Alexis Jazz is screwing around again (Kill-It-With-Fire, 2022)
* <nowiki>
*/
/* global jQuery, mediaWiki */
/* eslint one-var:0, vars-on-top:0, no-underscore-dangle:0, valid-jsdoc:0,
curly:0, camelcase:0, no-useless-escape:0, no-alert:0 */ // extends: wikimedia
/* jshint unused:true, forin:false, smarttabs:true, loopfunc:true, browser:true */
( function ( $, mw ) {
'use strict';
var ns = mw.config.get( 'wgNamespaceNumber' );
var msgs = {
// Preferences
// new: added 2012-09-19. Please translate.
// Use user language for i18n
'restore-a-lot-comment-label': 'Custom undo reason',
'restore-a-lot-edit-question': 'Why is this undo necessary?',
// Progress
// 'restore-a-lot-loading': 'Loading …',
'restore-a-lot-editing': 'Undoing edit',
'restore-a-lot-of': 'of ',
'restore-a-lot-skipped-server': 'The following {{PLURAL:$1|1=page|$1 pages}} couldn’t be changed, since there were problems connecting to the server:',
'restore-a-lot-all-done': 'Selected revisions have been undone.',
'restore-a-lot-done': 'Done!', // mw.msg("Feedback-close")
'restore-a-lot-undelete': 'Revision undone',
// as in 17 files selected
'restore-a-lot-files-selected': '{{PLURAL:$1|1=One revision|$1 revisions}} selected.',
// Actions
'restore-a-lot-undeletelink': 'Undo',
'restore-a-lot-instructions': 'Select revisions, hit undo.',
'restore-a-lot-select': 'Select',
'restore-a-lot-all': 'all',
'restore-a-lot-none': 'none',
// 'restore-a-lot-none-selected': 'No files selected!', 'Ooui-selectfile-placeholder'
// Summaries (project language):
'restore-a-lot-summary': 'Undo revision [[Special:Diff/REVID|REVID]] by [[User:USER|USER]] [Undone using [[w:en:WP:Kill-It-With-Fire|Kill-It-With-Fire]]]',
};
mw.messages.set( msgs );
function msg( /* params */ ) {
var args = Array.prototype.slice.call( arguments, 0 );
args[ 0 ] = 'restore-a-lot-' + args[ 0 ];
return ( args.length === 1 ) ?
mw.message( args[ 0 ] ).plain() :
mw.message.apply( mw.message, args ).parse();
}
mw.util.addCSS(
'#killitwithfire {bottom: 0;display: block;position: fixed;left: 0;z-index: 100;padding: 5px;box-shadow: 0 2px 4px rgba(0,0,0,0.5); background-color: #FEF6E7;}'+
'#killitwithfire.ui-resizable {min-width:15em;}'+
'#killitwithfire_data, #killitwithfire_mark_counter {display: none;}'+
'#killitwithfire_data ul {list-style-image: none;list-style-type: none;margin: 0 0 0 5px;}'+
//'#killitwithfire_selections, #killitwithfire_mark_counter, #killitwithfire_settings {background: url(/w/skins/Vector/images/portal-break.png) no-repeat;padding: 5px;}'+
'#killitwithfire_remove {font-weight: bold;display: block;}'+
'a {cursor:pointer;}'+
'.killitwithfire_move, .killitwithfire_action {font-weight: bold;}'+
'.killitwithfire_feedback {border: 1px #A9DE16 solid !important;background: #EAF2CB /*url(//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif)*/ no-repeat 8px 14px !important;padding-left: 2.85em !important;padding-top: 10px !important;font-size: 1.1em !important;}'+
'.killitwithfire_done {background-image: url(//upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Dialog-apply.svg/50px-Dialog-apply.svg.png) !important;background-position: 8px 50% !important;padding-top: 0 !important;}'+
'#killitwithfire_searchcatname,#killitwithfire_comment {font-size: 112%;margin: -5px 0 5px -5px;width: 100%;}'+
'.skin-vector #killitwithfire {font-size: .75em;}'+
'.killitwithfire_markAsDone {background-color: #BBB !important;}'+
'.killitwithfire_selected {background-color: #DF6 !important;}'+
'#killitwithfire_no_found, #killitwithfire_last_selected, #killitwithfire_settings {font-weight:bold;}'+
'#killitwithfire_last_selected {outline:1px dotted #999;}'+
'#killitwithfire_category_list table {border-collapse: collapse;}'+
'#killitwithfire_category_list tr:hover {background-color: #fc3;}'+
'#killitwithfire_category_list {overflow: auto;}');
// There is only one Restore-a-lot on one page
var $body, $container, $dataContainer, $markCounter, $instructions, $selections,
$selectFiles, $selectPages, $selectNone, $selectInvert, $head, $link;
var KIWF = mw.libs.restoreALot = {
apiUrl: mw.util.wikiScript( 'api' ),
origin: '',
searchmode: false,
version: '4.77',
setHeight: 450,
settings: '',
init: function () {
$body = $( document.body );
$container = $( '<div>' )
.attr( 'id', 'killitwithfire' )
.appendTo( $body );
$dataContainer = $( '<div>' )
.attr( 'id', 'killitwithfire_data' )
.appendTo( $container );
$markCounter = $( '<div>' )
.attr( 'id', 'killitwithfire_mark_counter' )
.appendTo( $dataContainer );
$instructions = $( '<div>' )
.attr( 'id', 'killitwithfire_selections' )
.text( msg( 'instructions' ) )
.appendTo( $dataContainer );
$selections = $( '<div>' )
.attr( 'id', 'killitwithfire_selections' )
.text( msg( 'select' ) + ':' )
.appendTo( $dataContainer );
$head = $( '<div>' )
.attr( 'id', 'killitwithfire_head' )
.appendTo( $container );
$link = $( '<a>' )
.attr( 'id', 'killitwithfire_toggle' )
.text( 'Kill-It-With-Fire' )
.appendTo( $head );
$container.one( 'mouseover', function () { // Try load on demand earliest as possible
mw.loader.load( [ 'jquery.ui'] );
} );
$( '<a>' )
// .attr( 'id', 'killitwithfire_select_all' )
.text( msg( 'all' ) )
.on( 'click', function () {
KIWF.toggleAll( true );
} )
.appendTo( $selections.append( ' ' ) );
if ( this.settings.editpages ) {
$selectFiles = $( '<a>' )
.on( 'click', function () {
KIWF.toggleAll( 'files' );
} );
$selectPages = $( '<a>' )
.on( 'click', function () {
KIWF.toggleAll( 'pages' );
} );
$selections.append( $( '<span>' ).hide().append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) );
}
$selectNone = $( '<a>' )
// .attr( 'id', 'killitwithfire_select_none' )
.text( msg( 'none' ) )
.on( 'click', function () {
KIWF.toggleAll( false );
} );
$selectInvert = $( '<a>' )
.on( 'click', function () {
KIWF.toggleAll( null );
} );
$selections.append( [ ' • ', $selectNone, ' • ', $selectInvert] );
$('#killitwithfire_data').append(
$( '<div>' ).append( [
$( '<input>' )
.attr( {
id: 'killitwithfire_comment',
tabindex: -1,
type: 'text',
placeholder: msg( 'comment-label' )
} )
] )
);
$selections.append( $( '<a>' )
.text( msg( 'undeletelink' ) )
.on( 'click', function () {
KIWF.doSomething();
} )
.addClass( 'killitwithfire_action ui-button' )
.css( { margin: '5px', padding: '2px' } )
);
$link
.on( 'click', function () {
$( this ).toggleClass( 'killitwithfire_enabled' );
// Load autocomplete on demand
mw.loader.using( 'jquery.ui' );
if ( !KIWF.executed ) {
$.when( mw.loader.using( [
'jquery.ui',
'jquery.ui',
'jquery.ui',
'mediawiki.api',
'mediawiki.jqueryMsg'
] ), $.ready )
.then( function () {
return new mw.Api().loadMessagesIfMissing( [
'Cancel',
'Mobile-frontend-return-to-page',
'Ooui-selectfile-placeholder',
// 'Visualeditor-clipboard-copy',
'Prefs-files',
'Categories',
'Checkbox-invert',
//'Apifeatureusage-warnings'
] );
} ).then( function () {
KIWF.run();
} );
} else { KIWF.run(); }
} );
mw.loader.using( 'mediawiki.cookie', function () { // Let catAlot stay open
var val = mw.cookie.get( 'catAlotO' );
if ( val && Number( val ) === ns ) { $link.click(); }
}
);
},
findAllLabels: function ( searchmode ) {
// It's possible to allow any kind of pages as well but what happens if you click on "select all" and don't expect it
switch ( searchmode ) {
case 'contribs':
this.labels = this.labels.add( $( 'div.mw-body-content li' ) );
break;
}
},
/**
* @brief Get rev from selected pages
* @return [array] touple of page rev and $object
*/
getMarkedLabels: function () {
this.selectedLabels = this.labels.filter( '.killitwithfire_selected:visible' );
return this.selectedLabels.map( function () {
// this might select too much in cases currently unknown, does it even matter?
// it was 'a.new[class$="title"]' previously but this doesn't work on DRs
var label = $( this ), revHref = label[0].querySelectorAll('.mw-changeslist-diff')[0].attributes.href;
return [ [ revHref, label ] ];
} );
},
updateSelectionCounter: function () {
this.selectedLabels = this.labels.filter( '.killitwithfire_selected:visible' );
var first = $markCounter.is( ':hidden' );
$markCounter
.html( msg( 'files-selected', this.selectedLabels.length ) )
.show();
if ( first && !$dataContainer.is( ':hidden' ) ) { // Workaround to fix position glitch
first = $markCounter.innerHeight();
$container
.offset( { top: $container.offset().top - first } )
.height( $container.height() + first );
$( window ).on( 'beforeunload', function () {
if ( KIWF.labels.filter( '.killitwithfire_selected:visible' )[ 0 ] ) { return 'You have pages selected!'; } // There is a default message in the browser
} );
}
},
makeClickable: function () {
this.labels = $();
this.pageLabels = $(); // only for distinct all selections
this.findAllLabels( this.searchmode );
this.labels.KIWFShiftClick( function () {
KIWF.updateSelectionCounter();
} )
.addClass( 'killitwithfire_label' );
},
toggleAll: function ( select ) {
if ( typeof select === 'string' && this.pageLabels[ 0 ] ) {
this.pageLabels.toggleClass( 'killitwithfire_selected', true );
if ( select === 'files' ) // pages get deselected
{ this.labels.toggleClass( 'killitwithfire_selected' ); }
} else {
// invert / none / all
this.labels.toggleClass( 'killitwithfire_selected', select );
}
this.updateSelectionCounter();
},
undeleteFile: function ( rev ) {
var revIDToUndo;
revIDToUndo = new mw.Uri(window.location.protocol+'//'+window.location.host+rev[0].nodeValue).query.oldid;
var sumCmt; // summary comment
sumCmt = msg( 'summary' ).replace(/USER/g,mw.config.get('wgRelevantUserName')).replace(/REVID/g,revIDToUndo);
sumCmt += this.summary ? ' ' + this.summary : '';
var data = {
action: 'edit',
summary: sumCmt,
title: new mw.Uri(window.location.protocol+'//'+window.location.host+rev[0].nodeValue).query.title.replace(/_/g,' '),
undo: revIDToUndo,
token: this.edittoken
};
this.doAPICall( data, function ( r ) {
delete KIWF.XHR[ rev[ 0 ] ];
return KIWF.updateCounter( r );
} );
this.markAsDone( rev[ 1 ] );
},
markAsDone: function ( label ) {
label.addClass( 'killitwithfire_markAsDone' ).append( '<br>' + msg( 'undelete' ) );
},
updateCounter: function () {
this.counterCurrent++;
if ( this.counterCurrent > this.counterNeeded ) { this.displayResult(); } else { this.domCounter.text( this.counterCurrent ); }
},
displayResult: function () {
document.body.style.cursor = 'auto';
this.progressDialog.parent()
.addClass( 'killitwithfire_done' )
.find( '.ui-dialog-buttonpane button span' ).eq( 0 )
.text( mw.msg( 'Mobile-frontend-return-to-page' ) );
var rep = this.domCounter.parent()
.height( 'auto' )
.html( '<h3>' + msg( 'done' ) + '</h3>' )
.append( msg( 'all-done' ) + '<br>' );
if ( this.connectionError.length ) {
rep.append( '<h5>' + msg( 'skipped-server', this.connectionError.length ) + '</h5>' )
.append( this.connectionError.join( '<br>' ) );
}
},
doSomething: function () {
var pages = this.getMarkedLabels();
if ( !pages.length ) { return alert( mw.msg( 'Ooui-selectfile-placeholder' ) ); }
this.connectionError = [];
this.counterCurrent = 1;
this.counterNeeded = pages.length;
this.XHR = {};
this.cancelled = 0;
this.summary = '';
if ( $( '#killitwithfire_comment' )[0].value != '' ) { this.summary = $( '#killitwithfire_comment' )[0].value; } // TODO custom pre-value
if ( this.summary !== null ) {
mw.loader.using( [ 'jquery.ui', 'mediawiki.util' ], function () {
KIWF.showProgress();
if ( !KIWF.cancelled ) {
KIWF.doAPICall( {
meta: 'tokens',
}, function ( result ) {
if ( !result || !result.query ) { return; }
KIWF.edittoken = result.query.tokens.csrftoken;
pages = Array.from( pages ).map( function ( page ) {
return function () {
var defer = $.Deferred();
setTimeout( function timer() {
KIWF.undeleteFile( page );
defer.resolve();
}, 1500 );
return defer;
};
} );
var defer = pages.shift()();
pages.map( function ( pagefunc ) {
defer = defer.then( pagefunc );
} );
} );
}
} );
}
},
doAPICall: function ( params, callback ) {
params = $.extend( {
action: 'query',
format: 'json'
}, params );
var i = 0,
apiUrl = this.apiUrl,
doCall,
handleError = function ( jqXHR, textStatus, errorThrown ) {
mw.log( 'Error: ', jqXHR, textStatus, errorThrown );
if ( i < 4 ) {
window.setTimeout( doCall, 300 );
i++;
} else if ( params.undo ) {
this.connectionError.push( params.undo );
this.updateCounter();
return;
}
};
doCall = function () {
var xhr = $.ajax( {
url: apiUrl,
cache: false,
dataType: 'json',
data: params,
type: 'POST',
success: callback,
error: handleError
} );
if ( params.action === 'undelete' && !KIWF.cancelled ) { KIWF.XHR[ params.undo ] = xhr; }
};
doCall();
},
doAbort: function () {
for ( var t in this.XHR ) { this.XHR[ t ].abort(); }
if ( this.cancelled ) { // still not for undo
this.progressDialog.remove();
this.toggleAll( false );
$head.last().show();
}
this.cancelled = 1;
},
showProgress: function () {
document.body.style.cursor = 'wait';
this.progressDialog = $( '<div>' )
.html( ' ' + msg( 'editing' ) + ' <span id="killitwithfire_current">' + KIWF.counterCurrent + '</span> ' + msg( 'of' ) + KIWF.counterNeeded )
.dialog( {
width: 450,
height: 180,
minHeight: 90,
modal: true,
resizable: false,
draggable: false,
// closeOnEscape: true,
dialogClass: 'killitwithfire_feedback',
buttons: [ {
text: mw.msg( 'Cancel' ), // Stops all actions
click: function () {
$( this ).dialog( 'close' );
}
} ],
close: function () {
KIWF.cancelled = 1;
KIWF.doAbort();
$( this ).remove();
},
open: function ( event, ui ) { // Workaround modify
ui = $( this ).parent();
ui.find( '.ui-dialog-titlebar' ).hide();
ui.find( '.ui-dialog-buttonpane.ui-widget-content' )
.removeClass( 'ui-widget-content' );
/* .find( 'span' ).css( { fontSize: '90%' } )*/
}
} );
if ( $head.children().length < 3 ) {
$( '<span>' )
.css( {
'float': 'right',
fontSize: '75%'
} );
}
this.domCounter = $( '#killitwithfire_current' );
},
minimize: function ( e ) {
KIWF.top = Math.max( 0, $container.position().top );
KIWF.height = $container.height();
$dataContainer.hide();
$container.animate( {
height: $head.height(),
top: $( window ).height() - $head.height() * 1.4
}, function () {
$( e.target ).one( 'click', KIWF.maximize );
} );
},
maximize: function ( e ) {
$dataContainer.show();
$container.animate( {
top: KIWF.top,
height: KIWF.height
}, function () {
$( e.target ).one( 'click', KIWF.minimize );
} );
},
run: function () {
if ( $( '.killitwithfire_enabled' )[ 0 ] ) {
this.makeClickable();
if ( !this.executed ) { // only once
$selectInvert.text( mw.msg( 'Checkbox-invert' ) );
if ( this.settings.editpages && this.pageLabels[ 0 ] ) {
$selectFiles.text( mw.msg( 'Prefs-files' ) );
$selectPages.text( mw.msg( 'Categories' ) ).parent().show();
}
//$link.after( $( '<a>' )
// .text( '–' )
// .css( { fontWeight: 'bold', marginLeft: '.7em' } )
// .one( 'click', this.minimize ),
$link.after( $( '<a href="https://commons.wikimedia.org/wiki/Special:MyLanguage/Help:Gadget-Restore-a-lot">' )
.text( 'Kill-It-With-Fire 0.1' )
.css( { float: 'right' } )
);
}
$dataContainer.show();
$container.one( 'mouseover', function () {
$( this )
.resizable( {
handles: 'n',
alsoResize: '#killitwithfire_category_list',
start: function ( e, ui ) { // Otherwise box get static if sametime resize with draggable
ui.helper.css( {
top: ui.helper.offset().top - $( window ).scrollTop(),
position: 'fixed'
} );
},
} )
.draggable( {
cursor: 'move',
start: function ( e, ui ) {
ui.helper.on( 'click.prevent',
function ( e ) { e.preventDefault(); }
);
ui.helper.css( 'height', ui.helper.height() );
},
stop: function ( e, ui ) {
setTimeout(
function () {
ui.helper.off( 'click.prevent' );
}, 300
);
}
} )
.one( 'mousedown', function () {
$container.height( $container.height() ); // Workaround to calculate
} );
} );
$link.html( $( '<span>' )
.text( '×' )
.css( { font: 'bold 2em monospace', lineHeight: '.75em' } )
);
$link.next().show();
if ( this.cancelled ) { $head.last().show(); }
mw.cookie.set( 'catAlotO', ns ); // Let stay open on new window
} else { // Reset
$dataContainer.hide();
$container
.draggable( 'destroy' )
.resizable( 'destroy' )
.removeAttr( 'style' );
// Unbind click handlers
this.labels.off( 'click.KIWF' );
this.setHeight = 450;
$link.text( 'Kill-It-With-Fire' )
.nextAll().hide();
this.executed = 1;
mw.cookie.set( 'catAlotO', null );
}
},
};
// The gadget is not immediately needed, so let the page load normally
window.setTimeout( function () {
switch ( ns ) {
case -1:
KIWF.searchmode = {
'Contributions': 'contribs',
}[ mw.config.get( 'wgCanonicalSpecialPageName' ) ];
break;
}
if ( KIWF.searchmode ) {
var maybeLaunch = function () {
function init() {
$( function () {
KIWF.init();
} );
}
mw.loader.using( [ 'user' ], init, init );
};
maybeLaunch();
}
}, 400 );
/**
* When clicking a restore-a-lot label with Shift pressed, select all labels between the current and last-clicked one.
*/
$.fn.KIWFShiftClick = function ( cb ) {
var prevCheckbox = null,
$box = this;
// When our boxes are clicked..
$box.on( 'click.KIWF', function ( e ) {
// Prevent following the link and text selection
if ( !e.ctrlKey ) { e.preventDefault(); }
// Highlight last selected
$( '#killitwithfire_last_selected' )
.removeAttr( 'id' );
var $thisControl = $( e.target ),
method;
if ( !$thisControl.hasClass( 'killitwithfire_label' ) ) { $thisControl = $thisControl.parents( '.killitwithfire_label' ); }
$thisControl.attr( 'id', 'killitwithfire_last_selected' )
.toggleClass( 'killitwithfire_selected' );
// And one has been clicked before…
if ( prevCheckbox !== null && e.shiftKey ) {
method = $thisControl.hasClass( 'killitwithfire_selected' ) ? 'addClass' : 'removeClass';
// Check or uncheck this one and all in-between checkboxes
$box.slice(
Math.min( $box.index( prevCheckbox ), $box.index( $thisControl ) ),
Math.max( $box.index( prevCheckbox ), $box.index( $thisControl ) ) + 1
)[ method ]( 'killitwithfire_selected' );
}
// Either way, update the prevCheckbox variable to the one clicked now
prevCheckbox = $thisControl;
if ( $.isFunction( cb ) ) { cb(); }
} );
return $box;
};
}( jQuery, mediaWiki ) );
// </nowiki>