User:Mr. Stradivarius/gadgets/SpamUserPage.js

// /* * SpamUserPage * * This gadget deletes a user page, blocks the user, and notifies them on their * talk page. See User:Mr. Stradivarius/gadgets/SpamUserPage for * documentation. * * To install the script, add the following to your personal .js page: importScript( 'User:Mr. Stradivarius/gadgets/SpamUserPage.js' ); // Linkback: User:Mr. Stradivarius/gadgets/SpamUserPage.js * Author: Mr. Stradivarius * Licence: MIT * * The MIT License (MIT) * * Copyright (c) 2015 Mr. Stradivarius * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */

mw.loader.using( [	'mediawiki.api',	'mediawiki.Title',	'mediawiki.util',	'oojs-ui' ], function {	"use strict";

var config = mw.config.get( [		'wgTitle',		'wgNamespaceNumber',		'wgUserName'	] );

// Exit if we are not in user or user talk space. if ( config.wgNamespaceNumber !== 2 && config.wgNamespaceNumber !== 3 ) { return; }

/**************************************************************************	 *                         ApiManager class **************************************************************************/

var ApiManager = function { var i, len, preset, key, customPresetIds, defaultPreset, hasUserPreference;

this.api = new mw.Api; this.currentTitle = new mw.Title( config.wgTitle, config.wgNamespaceNumber ); this.userName = this.currentTitle.getMainText.replace( /\/.*$/, '' ); this.userTalkTitle = new mw.Title( this.userName, 3 ); this.userPreferences = typeof window.SpamUserPage === 'object' ? window.SpamUserPage : {};

// Set up caches this.menuItemCache = {};

// Get presets this.presets = {}; this.presetIds = []; customPresetIds = []; // Track custom preset IDs separately so that we can put them first.

function setPreset( obj, thisArg ) { var ret = thisArg.presets[ obj.id ] || {}; ret.id = obj.id; for ( key in ApiManager.static.presetDefaults ) { if ( ApiManager.static.presetDefaults.hasOwnProperty( key ) ) { if ( obj[ key ] !== undefined ) { ret[ key ] = obj[ key ]; }					if ( ret[ key ] === undefined ) { ret[ key ] = ApiManager.static.presetDefaults[ key ]; }				}			}			thisArg.presets[ ret.id ] = ret; }

for ( i = 0, len = ApiManager.static.presets.length; i < len; i++ ) { preset = ApiManager.static.presets[ i ]; this.presetIds.push( preset.id ); setPreset( preset, this ); }		if ( $.isArray( this.userPreferences.presets ) ) { for ( i = 0, len = this.userPreferences.presets.length; i < len; i++ ) { preset = this.userPreferences.presets[ i ]; if ( typeof preset === 'object' ) { if ( typeof preset.id === 'string' ) { if ( !this.presets[ preset.id ] ) { customPresetIds.push( preset.id ); }						setPreset( preset, this ); } else { throw new Error( "missing or invalid 'id' field in custom preset #" + i ); }				}			}		}		this.presetIds = customPresetIds.concat( this.presetIds );

// Get defaults this.defaults = {}; hasUserPreference = false; if ( typeof this.userPreferences.preset === 'string' ) { defaultPreset = this.userPreferences.preset; if ( !this.presets[ defaultPreset ] ) { throw new Error( "'" + defaultPreset + "' is not a valid preset ID" ); }		} else { defaultPreset = ApiManager.static.otherDefaults.preset; }

function setDefaults( obj, thisArg ) { for ( key in obj ) { if ( obj.hasOwnProperty( key ) ) { if ( thisArg.userPreferences[ key ] !== undefined ) { hasUserPreference = true; thisArg.defaults[ key ] = thisArg.userPreferences[ key ]; } else if ( thisArg.presets[ defaultPreset ][ key ] !== undefined ) { thisArg.defaults[ key ] = thisArg.presets[ defaultPreset ][ key ]; } else { thisArg.defaults[ key ] = ApiManager.static.otherDefaults[ key ]; }				}			}		}

setDefaults( ApiManager.static.presetDefaults, this ); setDefaults( ApiManager.static.otherDefaults, this ); if ( hasUserPreference ) { this.defaults.preset = null; } else { this.defaults.preset = defaultPreset; }	};

OO.initClass( ApiManager );

ApiManager.static.presets = [ {			id: 'spamublock', label: 'Spam-only account, username violation', deletesummary: 'G11: Unambiguous advertising or promotion', blocksummary: '', editsummary: 'You have been indefinitely blocked from editing because your account is being ' + 'used only for spam or advertising and your username is a violation of the ' + 'username policy.', template: '' },		{			id: 'soablock', label: 'Spam-only account', deletesummary: 'G11: Unambiguous advertising or promotion', blocksummary: 'Spam / advertising-only account', editsummary: 'You have been indefinitely blocked from editing because your account is being ' + 'used only for spam, advertising, or promotion', template: '' },		{			id: 'sblock', label: 'Spamming', deletesummary: 'G11: Unambiguous advertising or promotion', expiry: '31 hours', blocksummary: 'Using Wikipedia for spam purposes', editsummary: 'You have been blocked from editing for using Wikipedia for spam purposes', template: '' },		{			id: 'vaublock', label: 'Vandalism-only account, username violation', deletesummary: 'G3: Vandalism', blocksummary: '', editsummary: 'You have been indefinitely blocked from editing because your account is being ' + 'used only for vandalism and your username is a blatant violation of the ' + 'username policy', template: '' },		{			id: 'voablock', label: 'Vandalism-only account', deletesummary: 'G3: Vandalism', blocksummary: 'Vandalism-only account', editsummary: 'You have been indefinitely blocked from editing because your account is being ' + 'used only for vandalism', template: '' },		{			id: 'vblock', label: 'Vandalism', deletesummary: 'G3: Vandalism', expiry: '31 hours', blocksummary: 'Vandalism', editsummary: 'You have been blocked from editing for persistent vandalism', template: '' },		{			id: 'softerblock', label: 'Promotional username, soft block', deletesummary: 'G11: Unambiguous advertising or promotion', blocksummary: '', editsummary: 'You have been indefinitely blocked from editing because your username ' + 'gives the impression that the account represents a group, organization or website.', template: '', nocreate: false, autoblock: false },		{			id: 'causeblock', label: 'Username of a non-profit, soft block', deletesummary: 'G11: Unambiguous advertising or promotion', blocksummary: '', editsummary: 'You have been indefinitely blocked from editing because your username ' + 'gives the impression that the account represents a group, organization or website.', template: '', nocreate: false, autoblock: false },		{			id: 'copyrightblock', label: 'Copyright violations', deletesummary: 'G12: Unambiguous copyright infringement', expiry: '24 hours', blocksummary: 'Copyright violations', editsummary: 'You have been blocked from editing for continued copyright infringement', template: '' },		{			id: 'myblock', label: 'Using Wikipedia as a blog or web host', deletesummary: 'U5: Misuse of Wikipedia as a web host', expiry: '24 hours', blocksummary: 'Using Wikipedia as a blog, web host, social networking site or forum', editsummary: 'You have been blocked from editing for using user and/or article pages ' + 'as a blog, web host, social networking site or forum', template: '' },		{			id: 'npblock', label: 'Creating nonsense pages', deletesummary: 'G1: Patent nonsense, meaningless, or incomprehensible', expiry: '24 hours', blocksummary: 'Creating patent nonsense or other inappropriate pages', editsummary: 'You have been blocked from editing for creating nonsense pages', template: '' }	];

ApiManager.static.menuDefaults = { expiry: [ 'indefinite', '3 hours', '12 hours', '24 hours', '31 hours', '36 hours', '48 hours', '60 hours', '72 hours', '1 week', '2 weeks', '1 month', '3 months', '6 months', '1 year', '2 years', '3 years' ],		deletesummary: [ // If you change the order of these, please also update the number of the // default deletion summary in ApiManager.static.presetDefaults. 'G1: Patent nonsense, meaningless, or incomprehensible', 'G2: Test page', 'G3: Vandalism', 'G3: Blatant hoax', 'G4: Recreation of a page that was deleted per a deletion discussion', 'G5: Creation by a blocked or banned user in violation of block or ban', 'G6: Housekeeping and routine (non-controversial) cleanup', 'G7: One author who has requested deletion or blanked the page', 'G8: Page dependent on a deleted or nonexistent page', 'G10: Attack page or negative unsourced BLP', 'G11: Unambiguous advertising or promotion', 'G12: Unambiguous copyright infringement', 'G13: Abandoned Article for creation? to retrieve it, see WP:REFUND/G13', 'U1: User request to delete page in own userspace', 'U2: Userpage or subpage of a nonexistent user', 'U3: Non-free gallery', 'U5: Misuse of Wikipedia as a web host' ],		blocksummary: [ '',			'Spam / advertising-only account', 'Using Wikipedia for spam or advertising purposes', '',			'Vandalism-only account', 'Vandalism', 'Copyright violations', ,			,			'Creating attack pages', 'Violations of the Biographies of living persons policy', 'Creating patent nonsense or other inappropriate pages', 'Disruptive editing', 'Personal attacks or harassment', 'Block evasion', 'Abusing multiple accounts', 'Long-term abuse', ],		editsummary: [ 'You have been blocked from editing because your account ' + 'is being used only for spam or advertising and your ' + 'username is a violation of the username policy.', 'You have been blocked from editing for advertising or self-promotion', 'You have been blocked from editing for using Wikipedia for spam purposes', 'You have been blocked from editing because your account is being ' + 'used only for spam, advertising, or promotion', 'You have been indefinitely blocked from editing because your account ' + 'is being used only for vandalism and your username is ' + 'a blatant violation of the username policy', 'You have been blocked from editing for persistent vandalism', 'You have been blocked from editing because your account is being ' + 'used only for vandalism', 'You have been indefinitely blocked from editing because your username ' + 'gives the impression that the account represents a group, organization or website.', 'You have been blocked from editing for continued copyright infringement', 'You have been blocked from editing for using user and/or article ' + 'pages as a blog, web host, social networking site or forum', ],		template: [ ,			,			,			,			,			,			,			,			,			,			,					]	};

ApiManager.static.presetDefaults = { label: ' ', deletesummary: ApiManager.static.menuDefaults.deletesummary[ 10 ], expiry: ApiManager.static.menuDefaults.expiry[ 0 ], blocksummary: ApiManager.static.menuDefaults.blocksummary[ 0 ], nocreate: true, autoblock: true, noemail: false, nousertalk: false, template: ApiManager.static.menuDefaults.template[ 0 ], editsummary: ApiManager.static.menuDefaults.editsummary[ 0 ] };

ApiManager.static.otherDefaults = { preset :ApiManager.static.presets[ 0 ].id, watchlist: 'preferences', menulocation: 'p-cactions', menuposition: null, oneclick: false };

ApiManager.static.months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];

ApiManager.static.summarySuffix = '(SUPG)';

ApiManager.prototype.getCurrentTitle = function { return this.currentTitle; };

ApiManager.prototype.getUserName = function { return this.userName; };

ApiManager.prototype.getUserTalkTitle = function { return this.userTalkTitle; };

ApiManager.prototype._getNotificationDate = function { // We cache the result of this method so that we will always get the // same date string as we used for the talk notification, even if a // user happened to have the page open over a month boundary. var date, month; if ( !this.notificationDate ) { date = new Date; month = this.constructor.static.months[ date.getMonth ]; this.notificationDate = month + ' ' + date.getFullYear; }		return this.notificationDate; };

ApiManager.prototype.getDefault = function ( key ) { return this.defaults[ key ]; };

ApiManager.prototype.getPresetIds = function { return this.presetIds; };

ApiManager.prototype.getPresetValue = function ( presetId, key ) { return this.presets[ presetId ][ key ]; };

ApiManager.prototype.getMenuItems = function ( key ) { var menuItems, pref, keyItems, isDupe, i, len, val; if ( typeof key !== 'string' ) { throw new TypeError( "argument #1 to 'getMenuItems' was not a string" ); }		function copyArray ( arr ) { var ret = []; for ( var i = 0, len = arr.length; i < len; i++ ) { ret[ i ] = arr[ i ]; }			return ret; }

if ( this.menuItemCache[ key ] === undefined ) { menuItems = this.constructor.static.menuDefaults[ key ]; if ( menuItems ) { this.menuItemCache[ key ] = menuItems; pref = this.userPreferences[ key ]; if ( pref ) { isDupe = false; for ( i = 0, len = menuItems.length; i < len; i++ ) { val = menuItems[ i ]; if ( val === pref ) { isDupe = true; break; }					}					if ( !isDupe ) { // Create a copy of the static array so we can manipulate it. menuItems = copyArray( menuItems ); menuItems.unshift( pref ); this.menuItemCache[ key ] = menuItems; }				}			} else { this.menuItemCache[ key ] = null; }		}		return this.menuItemCache[ key ]; };

ApiManager.prototype.addPortletLink = function { return mw.util.addPortletLink(			this.getDefault( 'menulocation' ),			'#',			'Delete and block',			'ca-spamuserpage',			'Delete this page and block this user',			null,			this.getDefault( 'menuposition' )		); };

ApiManager.prototype.openTalkPage = function { window.open(			this.getUserTalkTitle.getUrl +				'#' +				mw.util.wikiUrlencode( this._getNotificationDate ),			'_top'		); };

ApiManager.prototype._deleteCurrentPage = function ( options ) { options = options || {}; var reason = options.deletesummary !== undefined ? options.deletesummary : this.getDefault( 'deletesummary' ); reason += ' ' + this.constructor.static.summarySuffix; return this.api.postWithToken( 'delete', {			format: 'json',			action: 'delete',			title: this.currentTitle.getPrefixedText,			reason: reason,			watchlist: options.watchlist || this.getDefault( 'watchlist' )		} ); };

ApiManager.prototype._blockUser = function ( options, deletePromise ) { var deleteFinishedPromise, reason; var apiManager = this; options = options || {};

// Make a new promise that is resolved when the delete promise is // either resolved or rejected. deleteFinishedPromise = $.Deferred( function ( deferred ) {			deletePromise.always( function { deferred.resolve; } );		} ).promise;

reason = options.blocksummary !== undefined ? options.blocksummary : apiManager.getDefault( 'blocksummary' ); reason += ' ';

return deleteFinishedPromise.then( function {			return apiManager.api.postWithToken( 'block', { format: 'json', action: 'block', user: apiManager.getUserName, expiry: options.expiry !== undefined ? options.expiry : apiManager.getDefault( 'expiry' ), reason: reason, nocreate: options.nocreate ? '' : undefined, autoblock: options.autoblock ? '' : undefined, noemail: options.noemail ? '' : undefined, allowusertalk: !options.nousertalk ? '' : undefined } );		} );	};

ApiManager.prototype._postTalkNotification = function ( options, blockPromise ) { var self = this; options = options || {}; return $.Deferred( function ( deferred ) {			// Reject the deferred if the block failed.			blockPromise.fail( function { return deferred.reject( 'usernotblocked', { error: {					id: 'usernotblocked',					info: 'There was an error while trying to block the user, ' +						'so notification was aborted'				} } ); } );

blockPromise.done( function {				// Get the talk page content. We will use this to check if there is				// already a section heading for the current month.				return self.api.get( { format: 'json', action: 'query', prop: 'revisions', rvprop: 'content', indexpageids: '', titles: self.getUserTalkTitle.getPrefixedText, redirects: '' } ).then( function ( obj ) { var title, content, headings, lastHeading, notificationDate, editPromise, summary; var template = options.template !== undefined ? options.template : self.getDefault( 'template' ); var pageid = obj.query.pageids[ 0 ];

// If we got a redirect from the GET request, follow it. if ( obj.query.redirects ) { title = obj.query.redirects[ 0 ].to; } else { title = self.getUserTalkTitle.getPrefixedText; }

if ( pageid === '-1' ) { // The page doesn't exist. content = ''; } else { // The page exists; get the content. content = obj.query.pages[ pageid ].revisions[ 0 ][ '*' ];

// Separate our notification from whatever the previous content was. if ( content.match( /\S/ ) ) { content += '\n\n'; }					}

// Add a heading for the current month if it is not already the // last heading on the page. headings = content.match( /^==[^=].*==[ \t]*$/gm ); if ( headings ) { lastHeading = headings[ headings.length - 1 ].slice( 2, -2 ).trim; }					notificationDate = self._getNotificationDate; if ( !lastHeading || lastHeading !== notificationDate ) { content += '== ' + notificationDate + ' ==\n\n'; }

// Add the block template. content += template;

// Generate the edit summary. summary = options.editsummary !== undefined ? options.editsummary : self.getDefault( 'editsummary' ); summary += ' ' + self.constructor.static.summarySuffix;

// Overwrite the page with our new content. editPromise = self.api.postWithToken( 'edit', {						format: 'json',						action: 'edit',						title: title,						summary: summary,						notminor: '',						text: content,						watchlist: options.watchlist || self.getDefault( 'watchlist' )					} );

editPromise.done( function {						return deferred.resolve;					} );

editPromise.fail( function ( id, obj ) {						return deferred.reject( id, obj );					} ); } );			} );		} ).promise;	};

ApiManager.prototype.submit = function ( options ) { var promises = {}; options = options || {}; promises[ 'delete' ] = this._deleteCurrentPage( options ); promises.block = this._blockUser( options, promises[ 'delete' ] ); promises.notify = this._postTalkNotification( options, promises.block ); promises.all = $.when(			promises[ 'delete' ],			promises.block,			promises.notify		); return promises; };

/**************************************************************************	 *                          Dialog class **************************************************************************/

var Dialog = function ( options ) { options = options || {}; this.apiManager = new ApiManager; this.hasBeenSubmitted = false; Dialog.super.call( this, options ); };

OO.inheritClass( Dialog, OO.ui.ProcessDialog );

Dialog.static.name = 'SpamUserPageDialog'; Dialog.static.title = 'Delete, block and notify';

Dialog.static.actions = [ { action: 'submit', label: 'Submit', flags: [ 'primary', 'destructive' ] }, { label: 'Cancel', flags: 'safe' } ];

Dialog.prototype.getApiManager = function { return this.apiManager; };

Dialog.prototype.getBodyHeight = function { return 440; };

Dialog.prototype.initialize = function { Dialog.super.prototype.initialize.apply( this, arguments ); var apiManager = this.getApiManager;

// Converts an array of data from ApiManager into a format usable with // OOjs-ui menus. function makeMenu( arr, labelCallback ) { var i, len; var items = []; for ( i = 0, len = arr.length; i < len; i++ ) { items[ i ] = new OO.ui.MenuOptionWidget( {					data: arr[ i ],					label: labelCallback ? labelCallback( arr[ i ] ) : arr[ i ]				} ); }			return { items: items }; }

// Initialize edit panel this.editPanel = new OO.ui.PanelLayout( {			expanded: false		} ); this.editFieldset = new OO.ui.FieldsetLayout( {			classes: [ 'container' ]		} ); this.editPanel.$element.append( this.editFieldset.$element );

// Initialize preset widget this.presetDropdown = new OO.ui.DropdownWidget( {			data: { label: 'Select a preset' },			menu: makeMenu( apiManager.getPresetIds, function ( id ) { return apiManager.getPresetValue( id, 'label' ); } )		} );		if ( apiManager.getDefault( 'preset' ) ) { this.presetDropdown.getMenu.selectItemByData(				apiManager.getDefault( 'preset' )			); } else { this.presetDropdown.setLabel( this.presetDropdown.getData.label ); }		this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.presetDropdown, { label: 'Preset', } )		] );

// Initialize deletion widgets this.deleteSummaryInput = new OO.ui.ComboBoxInputWidget( {			value: apiManager.getDefault( 'deletesummary' ),			menu: makeMenu( apiManager.getMenuItems( 'deletesummary' ) )		} ); this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.deleteSummaryInput, { label: 'Deletion summary' } )		] );

// Initialize notification widgets this.editSummaryInput = new OO.ui.ComboBoxInputWidget( {			value: apiManager.getDefault( 'editsummary' ),			menu: makeMenu( apiManager.getMenuItems( 'editsummary' ) )		} ); this.templateInput = new OO.ui.ComboBoxInputWidget( {			value: apiManager.getDefault( 'template' ),			menu: makeMenu( apiManager.getMenuItems( 'template' ) )		} ); this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.templateInput, { label: 'Notification template' } ),			new OO.ui.FieldLayout( this.editSummaryInput, { label: 'Edit summary' } )		] );

// Initialize block widgets this.blockSummaryInput = new OO.ui.ComboBoxInputWidget( {			value: apiManager.getDefault( 'blocksummary' ),			menu: makeMenu( apiManager.getMenuItems( 'blocksummary' ) )		} ); this.expiryInput = new OO.ui.ComboBoxInputWidget( {			value: apiManager.getDefault( 'expiry' ),			menu: makeMenu( apiManager.getMenuItems( 'expiry' ) )		} ); this.nocreateToggleButton = new OO.ui.ToggleButtonWidget( {		 label: 'Block account creation',		  value: apiManager.getDefault( 'nocreate' )		} ); this.noemailToggleButton = new OO.ui.ToggleButtonWidget( {		 label: 'Block email',		  value: apiManager.getDefault( 'noemail' )		} ); this.nousertalkToggleButton = new OO.ui.ToggleButtonWidget( {		 label: 'Block talk',		  value: apiManager.getDefault( 'nousertalk' )		} ); this.autoblockToggleButton = new OO.ui.ToggleButtonWidget( {		 label: 'Autoblock',		  value: apiManager.getDefault( 'autoblock' )		} ); this.blockButtonGroup = new OO.ui.ButtonGroupWidget( {			items: [				this.nocreateToggleButton,				this.noemailToggleButton,				this.nousertalkToggleButton,				this.autoblockToggleButton			]		} ); this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.blockSummaryInput, { label: 'Block summary' } ),			new OO.ui.FieldLayout( this.expiryInput, { label: 'Block expiry' } ),			new OO.ui.FieldLayout( this.blockButtonGroup, { label: 'Block options', align: 'top' } ),		] );

// Initialize watchlist widgets this.watchlistOptions = {}; this.watchlistOptions.watch = new OO.ui.ButtonOptionWidget( {			data: 'watch',			label: 'Watch',			title: 'Watch option'		} ); this.watchlistOptions.unwatch = new OO.ui.ButtonOptionWidget( {			data: 'unwatch',			label: 'Unwatch',			title: 'Unwatch option'		} ); this.watchlistOptions.preferences = new OO.ui.ButtonOptionWidget( {			data: 'preferences',			label: 'Follow preferences',			title: 'Follow preferences option'		} ); this.watchlistOptions.nochange = new OO.ui.ButtonOptionWidget( {			data: 'nochange',			label: 'No change',			title: 'No change option'		} ); this.watchlistButtonSelect = new OO.ui.ButtonSelectWidget( {			items: [				this.watchlistOptions.watch,				this.watchlistOptions.unwatch,				this.watchlistOptions.preferences,				this.watchlistOptions.nochange			]		} ); this.watchlistButtonSelect.selectItemByData(			apiManager.getDefault( 'watchlist' )		); this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.watchlistButtonSelect, { label: 'Watchlist options', align: 'top' } )		] );

// Initialize submit panel this.submitPanel = new OO.ui.PanelLayout( {			$: this.$,			expanded: false		} ); this.submitFieldset = new OO.ui.FieldsetLayout( {			classes: [ 'container' ]		} ); this.submitPanel.$element.append( this.submitFieldset.$element ); this.deleteProgressLabel = new OO.ui.LabelWidget; this.deleteProgressField = new OO.ui.FieldLayout( this.deleteProgressLabel ); this.blockProgressLabel = new OO.ui.LabelWidget; this.blockProgressField = new OO.ui.FieldLayout( this.blockProgressLabel ); this.notifyProgressLabel = new OO.ui.LabelWidget; this.notifyProgressField = new OO.ui.FieldLayout( this.notifyProgressLabel ); this.openTalkProgressField = new OO.ui.FieldLayout( new OO.ui.LabelWidget ); this.submitFieldset.addItems( [			this.deleteProgressField,			this.blockProgressField,			this.notifyProgressField,			this.openTalkProgressField		] );

// Initialize stack widget this.stackLayout= new OO.ui.StackLayout( {			items: [ this.editPanel, this.submitPanel ],			padded: true		} );

// Add widgets to the DOM this.$body.append( this.stackLayout.$element );

// Add event handlers if ( apiManager.getDefault( 'preset' ) ) { this.changeInputEventHandlers( 'on', this.onFirstChangeAfterPreset, null, this ); }		this.presetDropdown.getMenu.on( 'select', this.onPresetSelect, null, this ); };

Dialog.prototype.onPresetSelect = function { var presetId = this.presetDropdown.getMenu.findSelectedItem.getData; var apiManager = this.getApiManager;

// Detach any input event handlers. If they are still attached, they will // clear the preset selection, which we don't want. this.changeInputEventHandlers( 'off', this.onFirstChangeAfterPreset, this );

// Set values this.deleteSummaryInput.getInput.setValue(			apiManager.getPresetValue( presetId, 'deletesummary' )		); this.deleteSummaryInput.getMenu.toggle( false ); this.expiryInput.getInput.setValue(			apiManager.getPresetValue( presetId, 'expiry' )		); this.expiryInput.getMenu.toggle( false ); this.templateInput.getInput.setValue(			apiManager.getPresetValue( presetId, 'template' )		); this.templateInput.getMenu.toggle( false ); this.editSummaryInput.getInput.setValue(			apiManager.getPresetValue( presetId, 'editsummary' )		); this.editSummaryInput.getMenu.toggle( false ); this.blockSummaryInput.getInput.setValue(			apiManager.getPresetValue( presetId, 'blocksummary' )		); this.blockSummaryInput.getMenu.toggle( false ); this.nocreateToggleButton.setValue(			apiManager.getPresetValue( presetId, 'nocreate' )		); this.autoblockToggleButton.setValue(			apiManager.getPresetValue( presetId, 'autoblock' )		); this.noemailToggleButton.setValue(			apiManager.getPresetValue( presetId, 'noemail' )		); this.nousertalkToggleButton.setValue(			apiManager.getPresetValue( presetId, 'nousertalk' )		);

// Attach one-time event handler this.changeInputEventHandlers( 'on', this.onFirstChangeAfterPreset, null, this ); };

Dialog.prototype.changeInputEventHandlers = function ( method, func, arg3, arg4 ) { this.deleteSummaryInput.getInput[ method ]( 'change', func, arg3, arg4 ); this.deleteSummaryInput.getMenu[ method ]( 'select', func, arg3, arg4 ); this.expiryInput.getInput[ method ]( 'change', func, arg3, arg4 ); this.expiryInput.getMenu[ method ]( 'select', func, arg3, arg4 ); this.templateInput.getInput[ method ]( 'change', func, arg3, arg4 ); this.templateInput.getMenu[ method ]( 'select', func, arg3, arg4 ); this.editSummaryInput.getInput[ method ]( 'change', func, arg3, arg4 ); this.editSummaryInput.getMenu[ method ]( 'select', func, arg3, arg4 ); this.blockSummaryInput.getInput[ method ]( 'change', func, arg3, arg4 ); this.blockSummaryInput.getMenu[ method ]( 'select', func, arg3, arg4 ); this.nocreateToggleButton[ method ]( 'change', func, arg3, arg4 ); this.autoblockToggleButton[ method ]( 'change', func, arg3, arg4 ); this.noemailToggleButton[ method ]( 'change', func, arg3, arg4 ); this.nousertalkToggleButton[ method ]( 'change', func, arg3, arg4 ); };

Dialog.prototype.onFirstChangeAfterPreset = function { // Detach all input event handlers this.changeInputEventHandlers( 'off', this.onFirstChangeAfterPreset, this );

// Clear the selected preset. Before doing this we detach its select event // listener, as that will cause problems if it's still attached. this.presetDropdown.getMenu.off( 'select', this.onPresetSelect, this ); this.presetDropdown.getMenu.selectItem; this.presetDropdown.setLabel( this.presetDropdown.getData.label ); this.presetDropdown.getMenu.on( 'select', this.onPresetSelect, null, this ); };

Dialog.prototype.onSubmit = function { var self = this; var options, promises; var apiManager = this.getApiManager; var title = apiManager.getCurrentTitle.getPrefixedText; var user = apiManager.getUserName;

// Record that the dialog has been submitted. This is necessary to		// prevent the "Submit" button from being reactivated after the window // is closed and reopened. self.hasBeenSubmitted = true;

// Disable input self.actions.setAbilities( { submit: false } );

// Increase the pending level by 4: one each for the delete, block, // notify, and all promises. self.pushPending; self.pushPending; self.pushPending; self.pushPending;

// Set progress labels self.deleteProgressField.setLabel( 'Deleting "' + title + '"...' ); self.blockProgressField.setLabel( 'Blocking ' + user + '...' ); self.notifyProgressField.setLabel( 'Notifying ' + user + '...' ); self.stackLayout.setItem( self.submitPanel );

// Get options and submit options = { watchlist: self.watchlistButtonSelect.findSelectedItem.getData, deletesummary: self.deleteSummaryInput.getInput.getValue, expiry: self.expiryInput.getInput.getValue, template: self.templateInput.getInput.getValue, editsummary: self.editSummaryInput.getInput.getValue, blocksummary: self.blockSummaryInput.getInput.getValue, nocreate: self.nocreateToggleButton.getValue, autoblock: self.autoblockToggleButton.getValue, noemail: self.noemailToggleButton.getValue, nousertalk: self.nousertalkToggleButton.getValue };		promises = self.getApiManager.submit( options );

// Update progress // This involves editing the progress labels and reducing the pending // level. function updateProgress( promise, label ) { promise.done( function {				label.setLabel( $( ' ' ) .addClass( 'spamuserpage-success' ) .text( 'Done.' ) );			} );			promise.fail( function ( id, obj ) {				if ( obj && obj.error && obj.error.info ) {					label.setLabel( $( ' ' ) .addClass( 'spamuserpage-error' ) .text( 'Error: ' + obj.error.info + '.' ) );				} else {					label.setLabel( 'An unknown error occurred.' );				}			} ); promise.always( function {				self.popPending;			} ); }		updateProgress( promises[ 'delete' ], self.deleteProgressLabel ); updateProgress( promises.block, self.blockProgressLabel ); updateProgress( promises.notify, self.notifyProgressLabel );

// Clean up when everything is done promises.all.done( function {			self.openTalkProgressField.setLabel( 'Opening "' +					apiManager.getUserTalkTitle.getPrefixedText +					'"...' );			apiManager.openTalkPage;		} ); promises.all.fail( function {			// Pop pending only on failure, so that it still looks like we're			// loading something while the talk page is being opened.			self.popPending;		} ); };

Dialog.prototype.getReadyProcess = function ( data ) { // Parent getReadyProcess method return Dialog.super.prototype.getReadyProcess.call( this, data ) .next( function {			if ( this.hasBeenSubmitted ) {				// The dialog has already been submitted when it was previously				// opened, so disable the Submit button.				this.actions.setAbilities( { submit: false } );			} else if ( this.getApiManager.getDefault( 'oneclick' ) ) {				this.executeAction( 'submit' );			}		}, this ); };

Dialog.prototype.getActionProcess = function ( action ) { return Dialog.super.prototype.getActionProcess.call( this, action ) .next( function {			if ( action === 'submit' ) {				return this.onSubmit;			} else {				return Dialog.super.prototype.getActionProcess.call( this, action );			}		}, this ); };

/**************************************************************************	 *                               Main **************************************************************************/

function main { var portletLink; var supDialog = new Dialog( { size: 'large' } ); var apiManager = supDialog.getApiManager; var userName = apiManager.getUserName; if (				// Don't run in the current user's userspace or on any user talk pages.				userName !== config.wgUserName &&				!( config.wgNamespaceNumber === 3 && config.wgTitle === userName )		) {			// Load CSS importStylesheet( "User:Mr. Stradivarius/gadgets/SpamUserPage.css" ); // Set up window manager var windowManager = new OO.ui.WindowManager; $( 'body' ).append( windowManager.$element ); windowManager.addWindows( [ supDialog ] );

// Add portlet link portletLink = apiManager.addPortletLink; $( portletLink ).click( function ( event ) {				event.preventDefault;				windowManager.openWindow( supDialog );			} ); }	}

main; } );

//