User:Alexis Jazz/Kill-It-With-Fire.js

/*
 * 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)
 * @author Alexis Jazz is screwing around again (Kill-It-With-Fire, 2022)

/* 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 NaN undefineds 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': 'NaN undefineds 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 REVID by USER [Undone using 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 = $( ' ' ) .attr( 'id', 'killitwithfire' ) .appendTo( $body ); $dataContainer = $( ' ' ) .attr( 'id', 'killitwithfire_data' ) .appendTo( $container ); $markCounter = $( ' ' ) .attr( 'id', 'killitwithfire_mark_counter' ) .appendTo( $dataContainer ); $instructions = $( ' ' ) .attr( 'id', 'killitwithfire_selections' ) .text( msg( 'instructions' ) ) .appendTo( $dataContainer ); $selections = $( ' ' ) .attr( 'id', 'killitwithfire_selections' ) .text( msg( 'select' ) + ':' ) .appendTo( $dataContainer ); $head = $( ' ' ) .attr( 'id', 'killitwithfire_head' ) .appendTo( $container ); $link = $( '' ) .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'] );		} );

$( '' ) // .attr( 'id', 'killitwithfire_select_all' ) .text( msg( 'all' ) ) .on( 'click', function {				KIWF.toggleAll( true );			} ) .appendTo( $selections.append( ' ' ) ); if ( this.settings.editpages ) { $selectFiles = $( '' ) .on( 'click', function {					KIWF.toggleAll( 'files' );				} ); $selectPages = $( '' ) .on( 'click', function {					KIWF.toggleAll( 'pages' );				} ); $selections.append( $( ' ' ).hide.append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) ); }		$selectNone = $( '' ) // .attr( 'id', 'killitwithfire_select_none' ) .text( msg( 'none' ) ) .on( 'click', function {				KIWF.toggleAll( false );			} ); $selectInvert = $( '' ) .on( 'click', function {				KIWF.toggleAll( null );			} ); $selections.append( [ ' • ', $selectNone, ' • ', $selectInvert] );

$('#killitwithfire_data').append(			$( ' ' ).append( [ $( ' ' )					.attr( {						id: 'killitwithfire_comment',						tabindex: -1,						type: 'text',						placeholder: msg( 'comment-label' )					} ) ] )		);		$selections.append( $( '' )			.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; }	},

/**	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 ] ];		} ); },
 * @brief Get rev from selected pages
 * @return [array] touple of page rev and $object

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( ' ' + 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( ' ' + msg( 'done' ) + ' ' ) .append( msg( 'all-done' ) + ' ' );

if ( this.connectionError.length ) { rep.append( ' ' + msg( 'skipped-server', this.connectionError.length ) + ' ' ) .append( this.connectionError.join( ' ' ) ); }

},

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 = $( ' ' ) .html( ' ' + msg( 'editing' ) + ' ' + KIWF.counterCurrent + ' ' + 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 ) { $( ' ' )				.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( $( '' )				//	.text( '–' )				//	.css( { fontWeight: 'bold', marginLeft: '.7em' } )				//	.one( 'click', this.minimize ),				$link.after( $( '' ) .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( $( ' ' )				.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 ) ); //