User:WolfMan/gadgets/Draftify.js

// /* * Draftify * * This gadget allows you to move a user page to a different location (usually * the Draft namespace), notify the user, and optionally soft-block them. * * To install the script, add the following to your personal .js page: importScript(( 'User:WolfMan/gadgets/Draftify.js' ); // Linkback: User:WolfMan/gadgets/Draftify.js * Author: WolfMan * Licence: MIT * * The MIT License (MIT) * * Copyright (c) 2015 ) WolfMan * * 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.jqueryMsg',	'mediawiki.Title',	'mediawiki.util',	'oojs-ui' ], function {	"use strict";

var config, ApiManager, Dialog;

/**************************************************************************	 *                    MediaWiki config and exit check **************************************************************************/

// A global object that stores all the page config, defaults, and user // preferences. config = {};

config.mw = mw.config.get( [		'wgTitle',		'wgNamespaceNumber',		'wgArticleId',		'wgFormattedNamespaces',		'wgPageContentModel',		'wgPageName',		'wgUserName',		'wgUserGroups',		'wgRelevantUserName',	] );

// If we're not going to work on the page, exit as soon as possible. if (			config.mw.wgNamespaceNumber !== 2 && config.mw.wgNamespaceNumber !== 3 ||			config.mw.wgArticleId === 0 || // Page doesn't exist			!config.mw.wgRelevantUserName || // Userspace of non-existent user			config.mw.wgUserName === config.mw.wgRelevantUserName || // User's own userspace			config.mw.wgPageContentModel !== 'wikitext' || // Exclude user JS/CSS			mw.util.getParamValue( 'redirect', window.location.href ) === 'no' // Current page is a redirect	) { return; }

/**************************************************************************	 *                             Messages *	 * This is the part you need to edit to localise the gadget. **************************************************************************/

config.defaultMessages = { // The label on the portlet link 'dfy-portlet-label': 'Draftify',

// The portlet link tooltip text 'dfy-portlet-tooltip': 'Move this draft and notify the user',

// The prefix that target draft pages must start with. 'dfy-draft-prefix': 'Draft:',

// The edit summary to use when moving the page. 'dfy-move-summary': 'the Draft namespace is the preferred location for Articles for Creation submissions',

// The template invocation to use when tagging drafts. // $1 - the username of the user whose userspace the draft was in		'dfy-tag-template': '$1',

// The edit summary to use when tagging drafts with dfy-tag-template. // $1 - the username of the user whose userspace the draft was in		'dfy-tag-summary': 'Tag page as draft belonging to User:$1',

// The template invocation to use when soft-blocking users. 'dfy-softblock-template': '', // The edit summary to use when soft-blocking users. 'dfy-softblock-summary': ' ',

// The template invocation to use when notifying users that their // draft has been moved. // $1 - The new page name after the move // $2 - The current page name 'dfy-notify-template': '$1 ~',

// The template invocation to use when suggesting the user should // change their username. 'dfy-suggestrename-template': 'it gives the impression that your account represents a group, organization or website ~',

// Edit summary to leave when notifying the user that their draft has // been moved. 'dfy-notify-summary-moveonly': 'Your draft page has been moved',

// Edit summary to leave when notifying the user that their draft has // been moved and suggesting that they rename their account. 'dfy-notify-summary-suggestrename': 'Your draft page has been moved, and you may need to change your username',

// Edit summary to leave when notifying the user that their draft has // been moved and that they have been soft-blocked. 'dfy-notify-summary-softblock': 'Your draft page has been moved, and you have been indefinitely blocked from editing because your username gives the impression that the account represents a group, organization or website',

// Boilerplate text to add at the end of the edit summary. 'dfy-summary-suffix': '(DFY)',

// The talk heading to be used for the talkpage notification. // $1 - The current month name, as defined in config.months. // $2 - The current year 'dfy-talk-heading': '$1 $2',

// Label for the text input where the user specifies the new draft name. 'dfy-title-input-label': 'New draft name',

// Label for the redirect checkbox 'dfy-redirect-checkbox-label': 'Leave a redirect behind',

// Label for the notification checkbox // $1 - The user who the draft belongs to		'dfy-notify-checkbox-label': 'Notify the user of the page move',

// Label for the checkbox for adding advice about renames // $1 - The user who the draft belongs to		'dfy-suggestrename-checkbox-label': 'Suggest that the user change their username',

// Label for the redirect checkbox 'dfy-softblock-checkbox-label': 'Soft-block the user and leave a block notice',

// Label for the watch checkbox 'dfy-watch-checkbox-label': 'Watch source page and target page',

// Label for the move progress indicator 'dfy-move-progress-label': 'Moving draft...',

// Label for the tag progress indicator 'dfy-tag-progress-label': 'Tagging draft...',

// Label for the softblock progress indicator // $1 - the user being blocked 'dfy-softblock-progress-label': 'Soft-blocking $1...',

// Label for the notify progress indicator // $1 - the user being notified 'dfy-notify-progress-label': 'Notifying $1...',

// Label for the progress indicator for opening the talk page // $1 - the talk page being opened 'dfy-open-talk-progress-label': 'Opening user talk...',

// Value for completed progress indicators. 'dfy-progress-success': 'Done.',

// Value for failed progress indicators. // $1 - the error message 'dfy-progress-failed': 'Error: $1',

// Value for failed progress indicators with unknown errors. 'dfy-progress-unknown-error': 'An unknown error occurred.',

// Error message for non-existent pages // $1 - the page name 'dfy-nonexistent-page-error': 'The page "$1" does not exist.', };

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

/**************************************************************************	 *                             Config *	 * Get user preferences, set messages, and deal with other per-user config. **************************************************************************/

// Get the raw user preferences. config.rawPrefs = typeof window.Draftify === 'object' ? window.Draftify : {};

// These messages are configurable by the user. config.configurableMessages = { movesummary: 'dfy-move-summary', tagtemplate: 'dfy-tag-template', tagsummary: 'dfy-tag-summary', notifytemplate: 'dfy-notify-template', notifysummarymoveonly: 'dfy-notify-summary-moveonly', notifysummarysuggestrename: 'dfy-notify-summary-suggestrename', notifysummarysoftblock: 'dfy-notify-summary-softblock', softblocktemplate: 'dfy-softblock-template', softblocksummary: 'dfy-softblock-summary', suggestrenametemplate: 'dfy-suggestrename-template' };

// Get the messages to use from the defaults and the user preferences. config.messages = {}; $.each( config.defaultMessages, function ( key, value ) {		config.messages[ key ] = value;	} ); $.each( config.configurableMessages, function ( prefKey, messageKey ) {		if ( typeof config.rawPrefs[ prefKey ] === 'string' ) {			config.messages[ messageKey ] = config.rawPrefs[ prefKey ];		}	} );

// Set the messages for use by the MediaWiki message library. mw.messages.set( config.messages );

// Define the default values for non-messages that can be set as // preferences. config.defaults = { redirect: false, notify: true, softblock: false, suggestrename: false, watch: true, menulocation: 'p-cactions', menuposition: null };

// Find the preferences to use from the defaults and the user preferences. config.prefs = {}; $.each( config.defaults, function ( key, value ) {		if ( config.rawPrefs[ key ] !== undefined ) {			config.prefs[ key ] = config.rawPrefs[ key ];		} else {			config.prefs[ key ] = value;		}	} );

/**************************************************************************	 *                          ApiManager class *	 * This class is the interface to the MediaWiki API and the config. Other * classes should go through the ApiManager rather than access the config * directly. **************************************************************************/

ApiManager = function { var currentUserIsAdmin; this.api = new mw.Api; this.currentSubpage = config.mw.wgTitle.replace( /^.*\//, '' ); this.currentDraftTitle = new mw.Title(			config.mw.wgTitle,			config.mw.wgNamespaceNumber		); this.targetDraftTitle = null; this.userTalkTitle = new mw.Title( this.getTargetUser, 3 ); };

OO.initClass( ApiManager );

// Used to validate the title field ApiManager.static.titleValidationRegex = /^[^|<>{}\[\]#]+$/;

ApiManager.prototype.getPreference = function ( key ) { return config.prefs[ key ]; };

// Fetches the current user's rights from the API and stores the relevant // ones in the ApiManager object. We don't check for things // like page protection status, so having the right to do something doesn't	// mean that doing it will be successful. // Returns a jQuery.promise object. ApiManager.prototype.setRights = function { var apiManager = this; return mw.user.getRights.then(			// Done filter			function ( rights ) {				apiManager.rights = {					suppressredirect: $.inArray( 'suppressredirect', rights ) !== -1,					softblock: $.inArray( 'block', rights ) !== -1				};			},			// Fail filter			function {				apiManager.rights = {};			}		); };

ApiManager.prototype.currentUserCan = function ( action ) { var right = this.rights[ action ]; if ( right === undefined ) { return true; } else { return right; }	};

ApiManager.prototype.getCurrentSubpage = function { return this.currentSubpage; };

ApiManager.prototype.getTargetUser = function { return config.mw.wgRelevantUserName; };

ApiManager.prototype.getTitleValidationRegex = function { return this.constructor.static.titleValidationRegex; };

ApiManager.prototype.getCurrentDraftTitle = function { return this.currentDraftTitle; };

ApiManager.prototype.getTargetDraftTitle = function { return this.targetDraftTitle; };

ApiManager.prototype.setTargetDraftTitle = function ( titleObj ) { this.targetDraftTitle = titleObj; };

ApiManager.prototype.addPortletLink = function { return mw.util.addPortletLink(			this.getPreference( 'menulocation' ),			'#',			mw.message( 'dfy-portlet-label' ).plain,			'ca-draftify',			mw.message( 'dfy-portlet-tooltip' ).plain,			null,			this.getPreference( 'menuposition' )		); };

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 = config.months[ date.getMonth ]; this.notificationDate = mw.message(				'dfy-talk-heading',				month,				date.getFullYear			).text; }		return this.notificationDate; };

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

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

ApiManager.prototype.getPageContent = function ( options ) { options = options || {}; var self = this; return $.Deferred( function ( deferred ) {			self.api.get( { format: 'json', action: 'query', prop: 'revisions', rvprop: 'content', indexpageids: '', titles: options.title, redirects: '' } ).then( function ( obj ) { var newTitle, content, pageId = obj.query.pageids[ 0 ];

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

// Get the page content. if ( pageId === '-1' ) { if ( options.allowNonexistent ) { content = ''; } else { // We got a non-existent page where we weren't // expecting one, so bail. return deferred.reject( 'dfy-nonexistent-page-error', { 'error': {							'id': 'dfy-nonexistent-page-error',							'info': mw.message( 'dfy-nonexistent-page-error', newTitle ).text						} } ); }				} else { content = obj.query.pages[ pageId ].revisions[ 0 ][ '*' ]; }

return deferred.resolve( {					title: newTitle,					content: content				} ); } );		} ).promise; };

ApiManager.prototype.editPage = function ( options ) { options = options || {}; return this.api.postWithToken( 'edit', {			format: 'json',			action: 'edit',			title: options.title,			summary: options.editSummary + ' ' + mw.message( 'dfy-summary-suffix' ).plain,			notminor: '',			text: options.content,			watchlist: this.getPreference( 'watch' ) ? 'watch' : 'unwatch'		} ); };

ApiManager.prototype.moveDraft = function ( options ) { options = options || {}; var self = this, targetTitle = this.getTargetDraftTitle; if ( !targetTitle ) { throw new Error( 'moveDraft was called but no target title object was available' ); }		// Get the page content for tagging before we move the page, otherwise // we risk trying to get the content from the draft page before it is // moved, resulting in page blanking. return self.getPageContent( {			title: self.currentDraftTitle.getPrefixedText,			allowNonexistent: false		} ).then( function ( contentObj ) {			var apiArgs = {				action: 'move',				format: 'json',				from: self.getCurrentDraftTitle.getPrefixedText,				to: targetTitle.getPrefixedText,				reason: mw.message( 'dfy-move-summary' ).plain +					' ' +					mw.message( 'dfy-summary-suffix' ).plain,				watchlist: options.watch ? 'watch' : 'unwatch'			};			if ( !options.redirect ) {				apiArgs.noredirect = 1;			}			return self.api.postWithToken( 'move', apiArgs ).then( function { return contentObj; } );		} );	};

ApiManager.prototype.tagDraft = function ( options, contentObj ) { if ( !contentObj ) { throw new Error( 'tagDraft was called without contentObj' ); }		var content = contentObj.content, tag = mw.message( 'dfy-tag-template', this.getTargetUser ).plain;

// Transform the content if ( /^\s*{{/.test( content ) ) { // The page starts with a template. content = tag + '\n' + content; } else { content = tag + '\n\n' + content; }

return this.editPage( {			title: this.getTargetDraftTitle.getPrefixedText,			editSummary: mw.message( 'dfy-tag-summary', this.getTargetUser ).plain,			content: content		} ); };

ApiManager.prototype.softBlockUser = function ( options ) { return this.api.postWithToken( 'block', {			format: 'json',			action: 'block',			user: this.apiManager.getTargetUser,			expiry: 'infinite',			reason: mw.message( 'dfy-softblock-summary' ).plain,			allowusertalk: 1		} ); };

ApiManager.prototype.notifyUser = function ( options ) { options = options || {}; var summary, self = this;

if ( options.softblock ) { summary = mw.message( 'dfy-notify-summary-softblock' ).plain; } else if ( options.suggestrename ) { summary = mw.message( 'dfy-notify-summary-suggestrename' ).plain; } else { summary = mw.message( 'dfy-notify-summary-moveonly' ).plain; }

return self.getPageContent( {			title: self.getUserTalkTitle.getPrefixedText,			allowNonexistent: true		} ).then( function ( contentObj ) {			// Generate the new content.			// First, add a heading for the current month if it is not already			// the last heading on the page.			var lastHeading, headings,				content = contentObj.content,				notificationDate = self.getNotificationDate;

if ( /\S/.test( content ) ) { // Separate the notice from whatever came before. content += '\n\n'; }

// Add a heading for the current month if there isn't one already. headings = content.match( /^==[^=].*==[ \t]*$/gm ); if ( headings ) { lastHeading = headings[ headings.length - 1 ].slice( 2, -2 ).trim; }			if ( !lastHeading || lastHeading !== notificationDate ) { content += '== ' + notificationDate + ' ==\n\n'; }

// Add the notification templates. content += mw.message(				'dfy-notify-template',				self.getTargetDraftTitle ? self.getTargetDraftTitle.getPrefixedText : '',				self.getCurrentDraftTitle.getPrefixedText			).plain; if ( options.softblock ) { content += '\n\n' + mw.message( 'dfy-softblock-template' ).plain; } else if ( options.suggestrename ) { content += '\n\n' + mw.message( 'dfy-suggestrename-template' ).plain; }			return self.editPage( {				title: contentObj.title,				editSummary: summary,				content: content			} ); } );

};

ApiManager.prototype.submit = function ( options ) { var i, len, self = this, actions = [], promises = {}, whenArgs = [];

// Make handler function for each promise that resolves successfully. function makeDoneHandler( i ) { return function ( obj ) { return self[ actions[ i ].method ]( options, obj ); };		}

try { this.setTargetDraftTitle( new mw.Title( mw.message( 'dfy-draft-prefix' ).text + options.title ) );

// Specify the order in which the actions should be performed. actions.push( { action: 'move', method: 'moveDraft' } ); actions.push( { action: 'tag', method: 'tagDraft' } ); if ( options.softblock ) { actions.push( { action: 'softblock', method: 'softBlockUser' } ); }			if ( options.notify ) { actions.push( { action: 'notify', method: 'notifyUser' } ); }

// Make a chain of promises to perform the actions, and reference the // promises in the promises object. promises[ actions[ 0 ].action ] = self[ actions[ 0 ].method ]( options ); for ( i = 1, len = actions.length; i < len; i++ ) { promises[ actions[ i ].action ] = promises[ actions[ i - 1 ].action ].then(					makeDoneHandler( i )				); }		} catch ( e ) { // Create a failed promise with the error info so that users will // see the error message when they click submit. promises.move = $.Deferred.reject( 'dfy-submit-error', { 'error': {				'id': 'dfy-submit-error',				'info': e.message || mw.message( 'dfy-progress-unknown-error' ).text			} } ).promise; }

// Create an "all" promise that tracks the overall status of the // submission process. $.each( promises, function ( key, promise ) {			whenArgs.push( promise );		} ); promises.all = $.when.apply( this, whenArgs );

return promises; };

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

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

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

Dialog.static.name = 'DraftifyDialog'; Dialog.static.title = 'Draftify';

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

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

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

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

// 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 title text input widget this.titleInput = new OO.ui.TextInputWidget( {			// TODO: Fix the label mess in OOjs-ui so that this can be			// uncommented.			// label: mw.message( 'dfy-draft-prefix' ).plain,			// labelPosition: 'before',			validate: apiManager.getTitleValidationRegex,			value: apiManager.getCurrentSubpage		} );

// Initialize checkbox widgets this.redirectCheckbox = new OO.ui.CheckboxInputWidget; this.redirectCheckbox.setSelected( true ); this.redirectCheckbox.setDisabled( true ); this.notifyCheckbox = new OO.ui.CheckboxInputWidget( {			selected: apiManager.getPreference( 'notify' )		} ); this.suggestrenameCheckbox = new OO.ui.CheckboxInputWidget( {			selected: apiManager.getPreference( 'suggestrename' )		} ); this.softblockCheckbox = new OO.ui.CheckboxInputWidget; this.softblockCheckbox.setDisabled( true ); this.watchCheckbox = new OO.ui.CheckboxInputWidget( {			selected: apiManager.getPreference( 'watch' )		} );

// Add widgets to the edit fieldset this.editFieldset.addItems( [			new OO.ui.FieldLayout( this.titleInput, { label: mw.message( 'dfy-title-input-label' ).plain } ),			new OO.ui.FieldLayout( this.redirectCheckbox, { label: mw.message( 'dfy-redirect-checkbox-label' ).plain, align: 'inline' } ),			new OO.ui.FieldLayout( this.notifyCheckbox, { // TODO: Use with the relevant username. I had a go // at this, but apparently jqueryMsg doesn't like me. label: mw.message( 'dfy-notify-checkbox-label' ).plain, align: 'inline' } ),			new OO.ui.FieldLayout( this.suggestrenameCheckbox, { // TODO: Use label: mw.message( 'dfy-suggestrename-checkbox-label' ).plain, align: 'inline' } ),			new OO.ui.FieldLayout( this.softblockCheckbox, { // TODO: Use label: mw.message( 'dfy-softblock-checkbox-label' ).plain, align: 'inline' } ),			new OO.ui.FieldLayout( this.watchCheckbox, { label: mw.message( 'dfy-watch-checkbox-label' ).plain, align: 'inline' } )		] );

// Initialize submit panel. The progress fields aren't added to the // fieldset yet - they will be added dynamically when the user submits // the form. 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.moveProgressLabel = new OO.ui.LabelWidget; this.moveProgressField = new OO.ui.FieldLayout( this.moveProgressLabel ); this.tagProgressLabel = new OO.ui.LabelWidget; this.tagProgressField = new OO.ui.FieldLayout( this.tagProgressLabel ); this.softblockProgressLabel = new OO.ui.LabelWidget; this.softblockProgressField = new OO.ui.FieldLayout( this.softblockProgressLabel ); this.notifyProgressLabel = new OO.ui.LabelWidget; this.notifyProgressField = new OO.ui.FieldLayout( this.notifyProgressLabel ); this.openTalkProgressField = new OO.ui.FieldLayout( new OO.ui.LabelWidget );

// 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 ); };

Dialog.prototype.getReadyProcess = function ( data ) { data = data || {}; var dialog = this; return Dialog.super.prototype.getReadyProcess.call( this, data ) .next( function {			if ( !dialog.isLoaded ) {				dialog.pushPending;				dialog.actions.setAbilities( { submit: false } );				dialog.getApiManager.setRights				.done( function  { dialog.onLoad; dialog.actions.setAbilities( { submit: true } ); dialog.popPending; } )				.fail( function { dialog.popPending; // TODO: use the pretty OOjs-UI error formatting. alert( 'There was a problem fetching your user rights from the API.' ); /*					return [ new OO.ui.Error(						'There was a problem fetching your user rights from the API',						{ recoverable: false }					) ]; */				} );			}			if ( dialog.hasBeenSubmitted ) {				// The dialog has already been submitted when it was previously				// opened, so disable the Submit button.				dialog.actions.setAbilities( { submit: false } );			} else {				// XXX: Workaround for text bunching issue in the title input.				// Remove once this is fixed in OOjs-UI.				this.titleInput.focus;				// Make the cursor appear after the value instead of before.				var val = this.titleInput.getValue;				this.titleInput.setValue( '' );				this.titleInput.setValue( val );			}		}, this ); };

Dialog.prototype.onLoad = function { var apiManager = this.getApiManager;

// Set checkbox statuses if ( apiManager.currentUserCan( 'suppressredirect' ) ) { this.redirectCheckbox.setSelected(				apiManager.getPreference( 'redirect' )			); this.redirectCheckbox.setDisabled( false ); } else { this.redirectCheckbox.setSelected( true ); this.redirectCheckbox.setDisabled( true ); }		if ( apiManager.currentUserCan( 'softblock' ) ) { this.softblockCheckbox.setSelected(				apiManager.getPreference( 'softblock' )			); this.redirectCheckbox.setDisabled( false ); } else { this.softblockCheckbox.setSelected( false ); this.softblockCheckbox.setDisabled( true ); }

// Run the event handlers once in case anyone has set strange // preferences. this.onSuggestrenameChange; if ( apiManager.currentUserCan( 'softblock' ) ) { this.onSoftblockChange; }		this.onTitleChange;

// Add event handlers this.softblockCheckbox.on( 'change', this.onSoftblockChange, null, this ); this.suggestrenameCheckbox.on( 'change', this.onSuggestrenameChange, null, this ); this.titleInput.on( 'change', this.onTitleChange, null, this );

// Mark as loaded this.isLoaded = true; };

Dialog.prototype.onSuggestrenameChange = function { if ( this.suggestrenameCheckbox.isSelected ) { this.notifyCheckbox.setSelected( true ); this.notifyCheckbox.setDisabled( true ); this.softblockCheckbox.setSelected( false ); this.softblockCheckbox.setDisabled( true ); } else { if ( this.getApiManager.currentUserCan( 'softblock' ) ) { this.softblockCheckbox.setDisabled( false ); }			this.notifyCheckbox.setDisabled( false ); }	};

Dialog.prototype.onSoftblockChange = function { if ( this.softblockCheckbox.isSelected ) { this.notifyCheckbox.setSelected( true ); this.notifyCheckbox.setDisabled( true ); this.suggestrenameCheckbox.setSelected( false ); this.suggestrenameCheckbox.setDisabled( true ); } else { this.suggestrenameCheckbox.setDisabled( false ); this.notifyCheckbox.setDisabled( false ); }	};

Dialog.prototype.onTitleChange = function { var self = this, promise = this.titleInput.getValidity; promise.done( function {			self.actions.setAbilities( { submit: true } );		} ); promise.fail( function {			self.actions.setAbilities( { submit: false } );		} ); };

Dialog.prototype.onSubmit = function { var options, promises, messages, self = this, apiManager = this.getApiManager, targetUser = apiManager.getTargetUser;

// 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 } );

options = { title: this.titleInput.getValue, redirect: this.redirectCheckbox.isSelected, notify: this.notifyCheckbox.isSelected, suggestrename: this.suggestrenameCheckbox.isSelected, softblock: this.softblockCheckbox.isSelected, watch: this.watchCheckbox.isSelected };

promises = apiManager.submit( options );

// Increase the pending level for each promise we have. $.each( promises, function ( key, value ) {			if ( value ) {				self.pushPending;			}		} );

// Set the progress labels, hide them from view, and add them to the // fieldset. function addField( field, label, promise ) { if ( !promise ) { return; }			field .setLabel( label ) .setData( promise ) .toggle; self.submitFieldset.addItems( [ field ] ); }		addField(			self.moveProgressField,			mw.message( 'dfy-move-progress-label' ).text,			promises.move		); addField(			self.tagProgressField,			mw.message( 'dfy-tag-progress-label' ).text,			promises.tag		); addField(			self.softblockProgressField,			mw.message( 'dfy-softblock-progress-label', targetUser ).text,			promises.softblock		); addField(			self.notifyProgressField,			mw.message( 'dfy-notify-progress-label', targetUser ).text,			promises.notify		); self.openTalkProgressField .setLabel( mw.message( 'dfy-open-talk-progress-label' ).text ) .toggle; self.submitFieldset.addItems( [ self.openTalkProgressField ] );

// Update progress // This involves making the process fields visible, adding the progress // labels on success or failure, and reducing the pending level. self.moveProgressField.toggle; $.each( self.submitFieldset.getItems, function ( i, field ) {			var				promise = field.getData,				label = field.getField;

if ( !promise ) { return; }

promise .done( function {					var nextField;

label.setLabel( $( ' ' )						.addClass( 'success' )						.text( mw.message( 'dfy-progress-success' ).text )					);

// Only display the next field on success, as on error they // would all have the same error messages. nextField = self.submitFieldset.getItems[ i + 1 ]; if ( nextField ) { nextField.toggle; }				} )				.fail( function ( id, obj ) { if ( obj && obj.error && obj.error.info ) { label.setLabel( $( ' ' )							.addClass( 'error' )							.text( mw.message(								'dfy-progress-failed',								obj.error.info							).text )						); } else { label.setLabel(							mw.message( 'dfy-progress-unknown-error' ).text						); }				} )				.always( function { self.popPending; } );		} );

// Set final actions promises.all.done( function {			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;		} );

// Switch to the panel view so that users can see what's going on. self.stackLayout.setItem( self.submitPanel ); };

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, windowManager, draftifyDialog = new Dialog( { size: 'medium' } ), apiManager = draftifyDialog.getApiManager;

// Set up window manager windowManager = new OO.ui.WindowManager; $( 'body' ).append( windowManager.$element ); windowManager.addWindows( [ draftifyDialog ] );

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

main; } );

//