/*! * VisualEditor UserInterface MWSaveDialog class. * * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * Dialog for saving MediaWiki pages. * * Note that most methods are not safe to call before the dialog has initialized, except where * noted otherwise. * * @class * @extends OO.ui.ProcessDialog * * @constructor * @param {OO.ui.WindowManager} manager Manager of window * @param {Object} [config] Config options */ ve.ui.MWSaveDialog = function VeUiMWSaveDialog( manager, config ) { // Parent constructor ve.ui.MWSaveDialog.super.call( this, manager, config ); // Properties this.sanityCheckVerified = false; this.editSummaryByteLimit = 255; this.restoring = false; this.messages = {}; this.setupDeferred = $.Deferred(); }; /* Inheritance */ OO.inheritClass( ve.ui.MWSaveDialog, OO.ui.ProcessDialog ); /* Static Properties */ ve.ui.MWSaveDialog.static.name = 'mwSave'; ve.ui.MWSaveDialog.static.title = OO.ui.deferMsg( 'visualeditor-savedialog-title-save' ); ve.ui.MWSaveDialog.static.actions = [ { 'action': 'save', 'label': OO.ui.deferMsg( 'visualeditor-dialog-action-apply' ), 'flags': [ 'primary', 'constructive' ], 'modes': 'save' }, { 'label': OO.ui.deferMsg( 'visualeditor-dialog-action-cancel' ), 'flags': 'safe', 'modes': 'save' }, { 'action': 'review', 'label': OO.ui.deferMsg( 'visualeditor-savedialog-label-review' ), 'modes': 'save' }, { 'action': 'approve', 'label': OO.ui.deferMsg( 'visualeditor-savedialog-label-review-good' ), 'flags': 'primary', 'modes': 'review' }, { 'action': 'resolve', 'label': OO.ui.deferMsg( 'visualeditor-savedialog-label-resolve-conflict' ), 'flags': [ 'primary', 'constructive' ], 'modes': 'conflict' } ]; /* Events */ /** * @event save * @param {jQuery.Deferred} saveDeferred Deferred object to resolve/reject when the save * succeeds/fails. * Emitted when the user clicks the save button */ /** * @event review * Emitted when the user clicks the review changes button */ /** * @event resolve * Emitted when the user clicks the resolve conflict button */ /* Methods */ /** * Set review content and show review panel. * * @param {string} content Diff HTML or wikitext */ ve.ui.MWSaveDialog.prototype.setDiffAndReview = function ( content ) { this.$reviewViewer.empty().append( content ); this.actions.setAbilities( { 'approve': true } ); this.popPending(); this.swapPanel( 'review' ); }; /** * Clear the diff displayed in the review panel, if any. */ ve.ui.MWSaveDialog.prototype.clearDiff = function () { this.$reviewViewer.empty(); }; /** * Set sanity check flag * * @param {boolean} verified Status of sanity check */ ve.ui.MWSaveDialog.prototype.setSanityCheck = function ( verified ) { this.sanityCheckVerified = !!verified; }; /** * Swap state in the save dialog. * * @param {string} panel One of 'save', 'review', 'conflict' or 'nochanges' * @returns {jQuery} The now active panel * @throws {Error} Unknown saveDialog panel */ ve.ui.MWSaveDialog.prototype.swapPanel = function ( panel ) { var currentEditSummaryWikitext, dialog = this, panelObj = dialog[panel + 'Panel']; if ( ve.indexOf( panel, [ 'save', 'review', 'conflict', 'nochanges' ] ) === -1 ) { throw new Error( 'Unknown saveDialog panel: ' + panel ); } this.setSize( 'medium' ); // Update the window title // The following messages can be used here: // visualeditor-savedialog-title-save // visualeditor-savedialog-title-reviews // visualeditor-savedialog-title-conflict // visualeditor-savedialog-title-nochanges this.title.setLabel( ve.msg( 'visualeditor-savedialog-title-' + panel ) ); // Reset save button if we disabled it for e.g. unrecoverable spam error this.actions.setAbilities( { 'save': true } ); switch ( panel ) { case 'save': if ( !this.sanityCheckVerified ) { this.showMessage( 'dirtywarning', mw.msg( 'visualeditor-savedialog-warning-dirty' ) ); } this.actions.setMode( 'save' ); // HACK: FF needs *another* defer setTimeout( function () { var $textarea = dialog.editSummaryInput.$input; $textarea[0].focus(); // If message has be pre-filled (e.g. section edit), move cursor to end if ( $textarea.val() !== '' ) { ve.selectEnd( $textarea[0] ); } } ); break; case 'conflict': this.actions .setAbilities( { 'save': false } ) .setMode( 'save' ); break; case 'review': this.setSize( 'large' ); currentEditSummaryWikitext = this.editSummaryInput.getValue(); if ( this.lastEditSummaryWikitext === undefined || this.lastEditSummaryWikitext !== currentEditSummaryWikitext ) { if ( this.editSummaryXhr ) { this.editSummaryXhr.abort(); } this.lastEditSummaryWikitext = currentEditSummaryWikitext; this.$reviewEditSummary.empty() .parent().show().addClass( 'mw-ajax-loader' ); this.editSummaryXhr = new mw.Api().post( { action: 'parse', summary: currentEditSummaryWikitext } ).done( function ( result ) { if ( result.parse.parsedsummary['*'] === '' ) { dialog.$reviewEditSummary.parent().hide(); } else { dialog.$reviewEditSummary.html( ve.msg( 'parentheses', result.parse.parsedsummary['*'] ) ); } } ).fail( function () { dialog.$reviewEditSummary.parent().hide(); } ).always( function () { dialog.$reviewEditSummary.parent().removeClass( 'mw-ajax-loader' ); } ); } /* falls through */ case 'nochanges': this.actions.setMode( 'review' ); break; } // Show the target panel this.panels.setItem( panelObj ); mw.hook( 've.saveDialog.stateChanged' ).fire(); return dialog; }; /** * Show a message in the save dialog. * * @param {string} name Message's unique name * @param {string|jQuery|Array} message Message content (string of HTML, jQuery object or array of * Node objects) * @param {Object} [options] * @param {boolean} [options.wrap="warning"] Whether to wrap the message in a paragraph and if * so, how. One of "warning", "error" or false. */ ve.ui.MWSaveDialog.prototype.showMessage = function ( name, message, options ) { var $message; if ( !this.messages[name] ) { options = options || {}; if ( options.wrap === undefined ) { options.wrap = 'warning'; } $message = this.$( '
' ); if ( options.wrap !== false ) { $message.append( this.$( '').append(
// visualeditor-savedialog-label-error
// visualeditor-savedialog-label-warning
this.$( '' ).text( mw.msg( 'visualeditor-savedialog-label-' + options.wrap ) ),
document.createTextNode( mw.msg( 'colon-separator' ) ),
message
) );
} else {
$message.append( message );
}
this.$saveMessages.append( $message );
this.messages[name] = $message;
}
};
/**
* Remove a message from the save dialog.
* @param {string} name Message's unique name
*/
ve.ui.MWSaveDialog.prototype.clearMessage = function ( name ) {
if ( this.messages[name] ) {
this.messages[name].remove();
delete this.messages[name];
}
};
/**
* Remove all messages from the save dialog.
*/
ve.ui.MWSaveDialog.prototype.clearAllMessages = function () {
this.$saveMessages.empty();
this.messages = {};
};
/**
* Reset the fields of the save dialog.
*
* @method
*/
ve.ui.MWSaveDialog.prototype.reset = function () {
// Reset summary input
this.editSummaryInput.$input.val( '' );
// Uncheck minoredit
this.$saveOptions.find( '.ve-ui-mwSaveDialog-checkboxes' )
.find( '#wpMinoredit' ).prop( 'checked', false );
// Clear the diff
this.$reviewViewer.empty();
};
/**
* Initialize MediaWiki page specific checkboxes.
*
* This method is safe to call even when the dialog hasn't been initialized yet.
*
* @param {jQuery} $checkboxes jQuery collection of checkboxes
*/
ve.ui.MWSaveDialog.prototype.setupCheckboxes = function ( $checkboxes ) {
this.setupDeferred.done( function () {
this.$saveOptions.find( '.ve-ui-mwSaveDialog-checkboxes' )
.html( $checkboxes )
.find( 'a' )
.attr( 'target', '_blank' )
.end()
.find( 'input' )
.prop( 'tabIndex', 0 );
}.bind( this ) );
};
/**
* Change the edit summary prefilled in the save dialog.
*
* This method is safe to call even when the dialog hasn't been initialized yet.
*
* @param {string} summary Edit summary to prefill
*/
ve.ui.MWSaveDialog.prototype.setEditSummary = function ( summary ) {
this.setupDeferred.done( function () {
this.editSummaryInput.setValue( summary );
}.bind( this ) );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.initialize = function () {
var saveAccessKey;
// Parent method
ve.ui.MWSaveDialog.super.prototype.initialize.call( this );
// Properties
this.panels = new OO.ui.StackLayout( { '$': this.$, 'scrollable': true } );
this.savePanel = new OO.ui.PanelLayout( {
'$': this.$,
'scrollable': true,
'classes': ['ve-ui-mwSaveDialog-savePanel']
} );
// Save panel
this.$editSummaryLabel = this.$( ' ' ).addClass( 've-ui-mwSaveDialog-license' )
.html( ve.init.platform.getParsedMessage( 'copyrightwarning' ) )
.find( 'a' ).attr( 'target', '_blank' ).end()
);
this.savePanel.$element.append(
this.$editSummaryLabel,
this.editSummaryInput.$element,
this.$saveOptions,
this.$saveMessages,
this.$saveActions,
this.$saveFoot
);
// Review panel
this.reviewPanel = new OO.ui.PanelLayout( { '$': this.$, 'scrollable': true } );
this.$reviewViewer = this.$( '
' ),
$( '
' ), this.$reviewEditSummary ),
this.$reviewViewer,
this.$reviewActions
);
// Conflict panel
this.conflictPanel = new OO.ui.PanelLayout( { '$': this.$, 'scrollable': true } );
this.$conflict = this.$( '