User:Mr. Stradivarius/gadgets/Draftify.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Mr. Stradivarius/gadgets/Draftify and an accompanying .css page at User:Mr. Stradivarius/gadgets/Draftify.css. |
// <nowiki>
/*
* 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:Mr. Stradivarius/gadgets/Draftify.js' ); // Linkback: [[User:Mr. Stradivarius/gadgets/Draftify.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.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 [[WP:DRAFTS|Draft namespace]] is the preferred location for [[WP:AFC|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': '{{subst:AFC draft|1=$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': '{{subst:uw-softerblock|sig=yes}}',
// The edit summary to use when soft-blocking users.
'dfy-softblock-summary': '{{uw-softerblock}} <!-- Promotional username, soft block -->',
// 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': '{{subst:uw-draftmoved|1=$1|from=$2}} ~~~~',
// The template invocation to use when suggesting the user should
// change their username.
'dfy-suggestrename-template': '{{subst:uw-username|1=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 [[WP:U|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 [[WP:U|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': '([[WP:DFY|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 user talk checkbox
'dfy-watch-user-talk-checkbox-label': 'Watch user talk page',
// Label for the watch draft checkbox
'dfy-watch-draft-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,
watchusertalk: true,
watchdraft: true,
menulocation: 'p-cactions',
menuposition: null
};
// Define aliases for config keys.
config.aliases = {
watchdraft: [ "watch" ] // For backwards compatibility with old options
}
// Find the preferences to use from the defaults, aliases, and user preferences.
config.prefs = {};
$.each( config.defaults, function ( key, value ) {
if ( config.rawPrefs[ key ] !== undefined ) {
config.prefs[ key ] = config.rawPrefs[ key ];
} else if ( config.aliases[ key ] !== undefined ) {
$.each( config.aliases[ key ], function ( index, alias ) {
if ( config.rawPrefs[ alias ] !== undefined ) {
config.prefs[ key ] = config.rawPrefs[ alias ];
return false; // Break the loop if we find an alias
}
} );
}
if ( config.prefs[ key ] === undefined ) {
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.postWithEditToken( {
format: 'json',
action: 'edit',
title: options.title,
summary: options.editSummary + ' ' + mw.message( 'dfy-summary-suffix' ).plain(),
notminor: '',
text: options.content,
watchlist: options.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.watchdraft ? 'watch' : 'unwatch'
};
if ( !options.redirect ) {
apiArgs.noredirect = 1;
}
return self.api.postWithEditToken( 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,
watch: options.watchdraft
} );
};
ApiManager.prototype.softBlockUser = function ( options ) {
return this.api.postWithEditToken( {
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,
watch: options.watchusertalk
} );
} );
};
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.watchUserTalkCheckbox = new OO.ui.CheckboxInputWidget( {
selected: apiManager.getPreference( 'watchusertalk' )
} );
this.watchDraftCheckbox = new OO.ui.CheckboxInputWidget( {
selected: apiManager.getPreference( 'watchdraft' )
} );
// 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 {{GENDER}} 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 {{GENDER}}
label: mw.message( 'dfy-suggestrename-checkbox-label' ).plain(),
align: 'inline'
} ),
new OO.ui.FieldLayout( this.softblockCheckbox, {
// TODO: Use {{GENDER}}
label: mw.message( 'dfy-softblock-checkbox-label' ).plain(),
align: 'inline'
} ),
new OO.ui.FieldLayout( this.watchUserTalkCheckbox, {
label: mw.message( 'dfy-watch-user-talk-checkbox-label' ).plain(),
align: 'inline'
} ),
new OO.ui.FieldLayout( this.watchDraftCheckbox, {
label: mw.message( 'dfy-watch-draft-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.onNotifyChange();
this.onTitleChange();
// Add event handlers
this.softblockCheckbox.on( 'change', this.onSoftblockChange, null, this );
this.suggestrenameCheckbox.on( 'change', this.onSuggestrenameChange, null, this );
this.notifyCheckbox.on( 'change', this.onNotifyChange, 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 );
}
this.onNotifyChange();
};
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 );
}
this.onNotifyChange();
};
Dialog.prototype.onNotifyChange = function () {
if ( this.notifyCheckbox.isSelected() ) {
this.watchUserTalkCheckbox.setDisabled( false );
} else {
this.watchUserTalkCheckbox.setSelected( false );
this.watchUserTalkCheckbox.setDisabled( true );
}
};
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(),
watchusertalk: this.watchUserTalkCheckbox.isSelected(),
watchdraft: this.watchDraftCheckbox.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( $( '<span>' )
.addClass( 'draftify-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( $( '<span>' )
.addClass( 'draftify-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() {
// Load CSS
importStylesheet( "User:Mr. Stradivarius/gadgets/Draftify.css" );
// Set up objects
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();
} );
// </nowiki>