User:Alexis Jazz/Restore-a-lot.js

/**
 * Restore-a-lot
 * Forked from Cat-a-lot
 * Undelete multiple files from Special:DeletedContributions, deletion requests and COM:UDR
 * See talk page for documentation
 * @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 Various fixes by Zhuyifei1999 (Restore-a-lot, 2020)



/* 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 formattedNS = mw.config.get( 'wgFormattedNamespaces' ), ns = mw.config.get( 'wgNamespaceNumber' ), userGrp = mw.config.get( 'wgUserGroups' );

var msgs = { // Preferences // new: added 2012-09-19. Please translate. // Use user language for i18n 'restore-a-lot-comment-label': 'Custom undeletion reason', 'restore-a-lot-edit-question': 'Why is this change necessary?',

// Progress // 'restore-a-lot-loading': 'Loading …', 'restore-a-lot-editing': 'Undeleting page', '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 pages have been undeleted.', 'restore-a-lot-done': 'Done!', // mw.msg("Feedback-close") 'restore-a-lot-undelete': 'File undeleted',

// as in 17 files selected 'restore-a-lot-files-selected': 'NaN undefineds selected.',

// Actions 'restore-a-lot-undeletelink': 'Undelete', 'restore-a-lot-instructions': 'Select files, hit undelete.', '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': 'Undeleted using Restore-a-lot', }; 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; }

// There is only one Restore-a-lot on one page var $body, $container, $dataContainer, $markCounter, $instructions, $selections, $selectFiles, $selectPages, $selectNone, $selectInvert, $head, $link, non;

var RAL = 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', 'cat_a_lot' ) .appendTo( $body ); $dataContainer = $( ' ' ) .attr( 'id', 'cat_a_lot_data' ) .appendTo( $container ); $markCounter = $( ' ' ) .attr( 'id', 'cat_a_lot_mark_counter' ) .appendTo( $dataContainer ); $instructions = $( ' ' ) .attr( 'id', 'cat_a_lot_selections' ) .text( msg( 'instructions' ) ) .appendTo( $dataContainer ); $selections = $( ' ' ) .attr( 'id', 'cat_a_lot_selections' ) .text( msg( 'select' ) + ':' ) .appendTo( $dataContainer ); $head = $( ' ' ) .attr( 'id', 'cat_a_lot_head' ) .appendTo( $container ); $link = $( '' ) .attr( 'id', 'cat_a_lot_toggle' ) .text( 'Restore-a-lot' ) .appendTo( $head ); $container.one( 'mouseover', function { // Try load on demand earliest as possible			mw.loader.load( [ 'jquery.ui'] );		} );

// NOTE ZYF: Update this? // NOTE AJ: I'm not sure what this does? It gets a css file (which restore-a-lot shares with cat-a-lot so that's fine) and I guess it registers the gadget? // NOTE ZYF: It means, if the url contains withJS=MediaWiki:Gadget-Restore-a-lot.js but without withCSS it loads the css. if ( ( mw.util.getParamValue( 'withJS' ) === 'MediaWiki:Gadget-Restore-a-lot.js' && !mw.util.getParamValue( 'withCSS' ) ) ||			mw.loader.getState( 'ext.gadget.Restore-a-lot' ) === 'registered' ) { mw.loader.load( mw.config.get( 'wgServer' ) + '/w/index.php?title=MediaWiki:Gadget-Cat-a-lot.css&action=raw&ctype=text/css', 'text/css' ); }

$( '' ) // .attr( 'id', 'cat_a_lot_select_all' ) .text( msg( 'all' ) ) .on( 'click', function {				RAL.toggleAll( true );			} ) .appendTo( $selections.append( ' ' ) ); if ( this.settings.editpages ) { $selectFiles = $( '' ) .on( 'click', function {					RAL.toggleAll( 'files' );				} ); $selectPages = $( '' ) .on( 'click', function {					RAL.toggleAll( 'pages' );				} ); $selections.append( $( ' ' ).hide.append( [ ' / ', $selectFiles, ' / ', $selectPages ] ) ); }		$selectNone = $( '' ) // .attr( 'id', 'cat_a_lot_select_none' ) .text( msg( 'none' ) ) .on( 'click', function {				RAL.toggleAll( false );			} ); $selectInvert = $( '' ) .on( 'click', function {				RAL.toggleAll( null );			} ); $selections.append( [ ' • ', $selectNone, ' • ', $selectInvert,			$( ' ' ).append( [ $( ' ' )					.attr( {						'for': 'cat_a_lot_comment',						style: 'line-height:1.5em;vertical-align:bottom'					} ) .text( msg( 'comment-label' ) ), $( ' ' )					.attr( {						id: 'cat_a_lot_comment',						type: 'checkbox'					} ) ] )		] );		$selections.append( $( '' )			.text( msg( 'undeletelink' ) )			.on( 'click', function { RAL.doSomething; } )			.addClass( 'cat_a_lot_action ui-button' )			.css( { margin: '5px', padding: '2px' } )		);

$link .on( 'click', function {				$( this ).toggleClass( 'cat_a_lot_enabled' );				// Load autocomplete on demand				mw.loader.using( 'jquery.ui' );

if ( !RAL.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 {							RAL.run;						} ); } else { RAL.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 'deletedcontribs': this.labels = this.labels.add( $( 'div.mw-body-content li' ) ); break; case 'delreq': // same thing NOW but this may change in the future this.labels = this.labels.add( $( 'div.mw-body-content li' ) ); break; case 'UDR': // same thing NOW but this may change in the future this.labels = this.labels.add( $( 'div.mw-body-content li' ) ); break; case 'WPFFD': // same thing NOW but this may change in the future this.labels = this.labels.add( $( 'div.mw-body-content h4' ) ); break; }	},

/**	getMarkedLabels: function { this.selectedLabels = this.labels.filter( '.cat_a_lot_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 ), file = label.find( 'a.new' ).eq( 0 );			var title = new mw.Uri( file.attr( 'href' ) ).query.title.replace( /_/g, ' ' );			if ( title.indexOf( formattedNS[ 2 ] + ':' ) ) { return [ [ title, label ] ]; }		} ); },
 * @brief Get title from selected pages
 * @return [array] touple of page title and $object

updateSelectionCounter: function { this.selectedLabels = this.labels.filter( '.cat_a_lot_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 ( RAL.labels.filter( '.cat_a_lot_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.catALotShiftClick( function {			RAL.updateSelectionCounter;		} ) .addClass( 'cat_a_lot_label' ); },

toggleAll: function ( select ) { if ( typeof select === 'string' && this.pageLabels[ 0 ] ) { this.pageLabels.toggleClass( 'cat_a_lot_selected', true ); if ( select === 'files' ) // pages get deselected { this.labels.toggleClass( 'cat_a_lot_selected' ); } } else { // invert / none / all this.labels.toggleClass( 'cat_a_lot_selected', select ); }		this.updateSelectionCounter; },

undeleteFile: function ( file ) { var sumCmt; // summary comment sumCmt = msg( 'summary' ); sumCmt += this.summary ?  + this.summary : ;

var data = { action: 'undelete', reason: sumCmt, title: file[ 0 ], token: this.edittoken };		this.doAPICall( data, function ( r ) {			delete RAL.XHR[ file[ 0 ] ];			return RAL.updateCounter( r );		} ); this.markAsDone( file[ 1 ] ); },

markAsDone: function ( label ) { label.addClass( 'cat_a_lot_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( 'cat_a_lot_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 ( $( '#cat_a_lot_comment' ).prop( 'checked' ) ) { this.summary = ': '+window.prompt( msg( 'edit-question' ), 'restored per request' ); } // TODO custom pre-value if ( this.summary !== null ) { mw.loader.using( [ 'jquery.ui', 'mediawiki.util' ], function {				RAL.showProgress;				if ( !RAL.cancelled ) {					RAL.doAPICall( { meta: 'tokens', }, function ( result ) { if ( !result || !result.query ) { return; } RAL.edittoken = result.query.tokens.csrftoken; pages = Array.from( pages ).map( function ( page ) {							return function {								var defer = $.Deferred;								setTimeout( function timer { RAL.undeleteFile( page ); defer.resolve; }, 800 );								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.title ) { this.connectionError.push( params.title ); 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' && !RAL.cancelled ) { RAL.XHR[ params.title ] = 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' ) + ' ' + RAL.counterCurrent + ' ' + msg( 'of' ) + RAL.counterNeeded ) .dialog( {				width: 450,				height: 180,				minHeight: 90,				modal: true,				resizable: false,				draggable: false,				// closeOnEscape: true,				dialogClass: 'cat_a_lot_feedback',				buttons: [ {					text: mw.msg( 'Cancel' ), // Stops all actions					click: function {						$( this ).dialog( 'close' );					}				} ],				close: function  {					RAL.cancelled = 1;					RAL.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 = $( '#cat_a_lot_current' ); },

minimize: function ( e ) { RAL.top = Math.max( 0, $container.position.top ); RAL.height = $container.height; $dataContainer.hide; $container.animate( {			height: $head.height,			top: $( window ).height - $head.height * 1.4		}, function {			$( e.target ).one( 'click', RAL.maximize );		} ); },

maximize: function ( e ) { $dataContainer.show; $container.animate( {			top: RAL.top,			height: RAL.height		}, function {			$( e.target ).one( 'click', RAL.minimize );		} ); },

run: function { if ( $( '.cat_a_lot_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( 'RESTORE-A-LOT 0.112' ) .css( { float: 'right' } ) );			}			$dataContainer.show;			$container.one( 'mouseover', function { $( this ) .resizable( {						handles: 'n',						alsoResize: '#cat_a_lot_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.catALot' ); this.setHeight = 450; $link.text( 'Restore-a-lot' ) .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 {	non = mw.config.get( 'wgUserName' );	if ( non ) {		if ( mw.config.get( 'wgRelevantUserName' ) === non ) { non = 0; } else {			$.each( [ 'sysop', 'filemover', 'editor', 'rollbacker', 'patroller', 'autopatrolled', 'image-reviewer', 'reviewer', 'extendedconfirmed' ], function ( i, v ) { non = $.inArray( v, userGrp ) === -1; return non; } );		}	} else { non = 1; }

switch ( ns ) { case -1: RAL.searchmode = { 'DeletedContributions': 'deletedcontribs', }[ mw.config.get( 'wgCanonicalSpecialPageName' ) ]; break; //Commons: namespace case 4: RAL.searchmode = { 'Deletion requests': 'delreq', 'Undeletion requests': 'UDR', 'Files for discussion': 'WPFFD', 'Administrators\' noticeboard': 'delreq', 'Deletion review': 'delreq', }[ mw.config.get( 'wgTitle' ).replace( /\/.*/g, '' ) ]; // basepagename break; }

if ( RAL.searchmode ) { var maybeLaunch = function { function init { $( function {					RAL.init;				} ); }			mw.loader.using( [ 'user' ], init, init ); };		maybeLaunch; } }, 800 );

/** * When clicking a restore-a-lot label with Shift pressed, select all labels between the current and last-clicked one. */ $.fn.catALotShiftClick = function ( cb ) { var prevCheckbox = null, $box = this; // When our boxes are clicked.. $box.on( 'click.catALot', function ( e ) {	// Prevent following the link and text selection		if ( !e.ctrlKey ) { e.preventDefault; }		// Highlight last selected		$( '#cat_a_lot_last_selected' )			.removeAttr( 'id' );		var $thisControl = $( e.target ),			method;		if ( !$thisControl.hasClass( 'cat_a_lot_label' ) ) { $thisControl = $thisControl.parents( '.cat_a_lot_label' ); }

$thisControl.attr( 'id', 'cat_a_lot_last_selected' ) .toggleClass( 'cat_a_lot_selected' ); // And one has been clicked before… if ( prevCheckbox !== null && e.shiftKey ) { method = $thisControl.hasClass( 'cat_a_lot_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 ]( 'cat_a_lot_selected' ); }		// Either way, update the prevCheckbox variable to the one clicked now prevCheckbox = $thisControl; if ( $.isFunction( cb ) ) { cb; } } );	return $box; };

}( jQuery, mediaWiki ) ); //