' ).addClass( 've-ui-mwSaveDialog-message' );
if ( options.wrap !== false ) {
$message.append( $( '
' ).append(
// visualeditor-savedialog-label-error
// visualeditor-savedialog-label-warning
$( '' ).text( mw.msg( 'visualeditor-savedialog-label-' + options.wrap ) ),
document.createTextNode( mw.msg( 'colon-separator' ) ),
message
) );
} else {
$message.append( message );
}
this.$saveMessages.append( $message );
$message.slideDown( {
progress: this.updateSize.bind( this )
} );
this.swapPanel( 'save' );
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 ].slideUp( {
progress: this.updateSize.bind( this )
} );
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.setValue( '' );
// Uncheck minoredit
if ( this.checkboxesByName.wpMinoredit ) {
this.checkboxesByName.wpMinoredit.setSelected( false );
}
this.clearDiff();
};
/**
* Initialize MediaWiki page specific checkboxes.
*
* This method is safe to call even when the dialog hasn't been initialized yet.
*
* @param {OO.ui.FieldLayout[]} checkboxFields Checkbox fields
*/
ve.ui.MWSaveDialog.prototype.setupCheckboxes = function ( checkboxFields ) {
var dialog = this;
this.setupDeferred.done( function () {
checkboxFields.forEach( function ( field ) {
dialog.$saveCheckboxes.append( field.$element );
} );
} );
};
/**
* 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 ) {
var dialog = this;
this.setupDeferred.done( function () {
dialog.editSummaryInput.setValue( summary );
} );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.initialize = function () {
var dialog = this,
mwString = require( 'mediawiki.String' );
// Parent method
ve.ui.MWSaveDialog.super.prototype.initialize.call( this );
// Properties
this.panels = new OO.ui.StackLayout( { scrollable: true } );
this.savePanel = new OO.ui.PanelLayout( {
expanded: false,
scrollable: true,
padded: true,
classes: [ 've-ui-mwSaveDialog-savePanel' ]
} );
// Byte counter in edit summary
this.editSummaryCountLabel = new OO.ui.LabelWidget( {
classes: [ 've-ui-mwSaveDialog-editSummary-count' ],
label: String( this.editSummaryCodePointLimit || this.editSummaryByteLimit ),
title: ve.msg( this.editSummaryCodePointLimit ?
'visualeditor-editsummary-characters-remaining' : 'visualeditor-editsummary-bytes-remaining' )
} );
// Save panel
this.$editSummaryLabel = $( '' ).addClass( 've-ui-mwSaveDialog-summaryLabel' )
.html( ve.init.platform.getParsedMessage( 'summary' ) )
.find( 'a' ).attr( 'target', '_blank' ).attr( 'rel', 'noopener' ).end();
this.editSummaryInput = new OO.ui.MultilineTextInputWidget( {
placeholder: ve.msg( 'visualeditor-editsummary' ),
classes: [ 've-ui-mwSaveDialog-summary' ],
inputFilter: function ( value ) {
// Prevent the user from inputting newlines (this kicks in on paste, etc.)
return value.replace( /\r?\n/g, ' ' );
}
} );
// Prevent the user from inputting newlines from keyboard
this.editSummaryInput.$input.on( 'keypress', function ( e ) {
if ( e.which === OO.ui.Keys.ENTER ) {
e.preventDefault();
dialog.showMessage(
'keyboard-shortcut-submit',
$( '
' ).msg(
'visualeditor-savedialog-keyboard-shortcut-submit',
new ve.ui.Trigger( ve.ui.commandHelpRegistry.lookup( 'dialogConfirm' ).shortcuts[ 0 ] ).getMessage()
),
{ wrap: false }
);
}
} );
// Limit length, and display the remaining bytes/characters
if ( this.editSummaryCodePointLimit ) {
this.editSummaryInput.$input.codePointLimit( this.editSummaryCodePointLimit );
this.editSummaryInput.on( 'change', function () {
dialog.changedEditSummary = true;
dialog.editSummaryCountLabel.setLabel(
String( dialog.editSummaryCodePointLimit -
mwString.codePointLength( dialog.editSummaryInput.getValue() ) )
);
} );
} else {
this.editSummaryInput.$input.byteLimit( this.editSummaryByteLimit );
this.editSummaryInput.on( 'change', function () {
// TODO: This looks a bit weird, there is no unit in the UI, just numbers
// Users likely assume characters but then it seems to count down quicker
// than expected. Facing users with the word "byte" is bad? (bug 40035)
dialog.changedEditSummary = true;
dialog.editSummaryCountLabel.setLabel(
String( dialog.editSummaryByteLimit -
mwString.byteLength( dialog.editSummaryInput.getValue() ) )
);
} );
}
this.$saveCheckboxes = $( '
' ).addClass( 've-ui-mwSaveDialog-checkboxes' );
this.$saveOptions = $( '
' ).addClass( 've-ui-mwSaveDialog-options' ).append(
this.$saveCheckboxes,
this.editSummaryCountLabel.$element
);
this.$saveMessages = $( '
' ).addClass( 've-ui-mwSaveDialog-messages' );
this.$saveFoot = $( '
' ).addClass( 've-ui-mwSaveDialog-foot' ).append(
$( '
' ).addClass( 've-ui-mwSaveDialog-license' )
.html( ve.init.platform.getParsedMessage( 'copyrightwarning' ) )
.find( 'a' ).attr( 'target', '_blank' ).attr( 'rel', 'noopener' ).end()
);
this.savePanel.$element.append(
this.$editSummaryLabel,
this.editSummaryInput.$element,
this.$saveOptions,
this.$saveFoot,
this.$saveMessages
);
// Review panel
this.reviewPanel = new OO.ui.PanelLayout( {
expanded: false,
scrollable: true,
padded: true
} );
this.$reviewVisualDiff = $( '
' ).addClass( 've-ui-mwSaveDialog-viewer' );
this.$reviewWikitextDiff = $( '
' ).addClass( 've-ui-mwSaveDialog-viewer' );
this.reviewModeButtonSelect = new OO.ui.ButtonSelectWidget( {
items: [
new OO.ui.ButtonOptionWidget( { data: 'visual', icon: 'eye', label: ve.msg( 'visualeditor-savedialog-review-visual' ) } ),
new OO.ui.ButtonOptionWidget( { data: 'source', icon: 'wikiText', label: ve.msg( 'visualeditor-savedialog-review-wikitext' ) } )
],
classes: [ 've-ui-mwSaveDialog-reviewMode' ]
} );
this.reviewModeButtonSelect.connect( this, { select: 'updateReviewMode' } );
this.$reviewEditSummary = $( '
' ).addClass( 've-ui-mwSaveDialog-summaryPreview' ).addClass( 'comment' );
this.$reviewActions = $( '' ).addClass( 've-ui-mwSaveDialog-actions' );
this.reviewPanel.$element.append(
this.reviewModeButtonSelect.$element,
$( '
' )
.addClass( 'mw-summary-preview' )
.text( ve.msg( 'summary-preview' ) )
.append( $( '
' ), this.$reviewEditSummary ),
this.$reviewVisualDiff,
this.$reviewWikitextDiff,
this.$reviewActions
);
// Preview panel
this.previewPanel = new OO.ui.PanelLayout( {
expanded: false,
scrollable: true,
padded: true
} );
this.$previewViewer = $( '
' ).addClass( 'mw-body-content mw-parser-output' );
this.previewPanel.$element
// Make focusable for keyboard accessible scrolling
.prop( 'tabIndex', 0 )
.append( this.$previewViewer );
// Conflict panel
this.conflictPanel = new OO.ui.PanelLayout( {
expanded: false,
scrollable: true,
padded: true
} );
this.$conflict = $( '
' ).addClass( 've-ui-mwSaveDialog-conflict' )
.html( ve.init.platform.getParsedMessage( 'visualeditor-editconflict' ) )
.find( 'a' ).attr( 'target', '_blank' ).attr( 'rel', 'noopener' ).end();
this.conflictPanel.$element.append( this.$conflict );
// Panel stack
this.panels.addItems( [
this.savePanel,
this.reviewPanel,
this.previewPanel,
this.conflictPanel
] );
// Initialization
this.$body.append( this.panels.$element );
this.setupDeferred.resolve();
};
ve.ui.MWSaveDialog.prototype.updateReviewMode = function () {
var dialog = this,
diffMode = this.reviewModeButtonSelect.findSelectedItem().getData(),
surfaceMode = ve.init.target.getSurface().getMode(),
isVisual = diffMode === 'visual';
if ( !this.hasDiff ) {
return;
}
// Config values used here:
// * visualeditor-diffmode-visual
// * visualeditor-diffmode-source
ve.userConfig( 'visualeditor-diffmode-' + surfaceMode, diffMode );
// Hack: cache report action so it is getable even when hidden (see T174497)
if ( !this.report ) {
this.report = this.getActions().get( { actions: 'report' } )[ 0 ];
}
this.$reviewVisualDiff.toggleClass( 'oo-ui-element-hidden', !isVisual );
this.$reviewWikitextDiff.toggleClass( 'oo-ui-element-hidden', isVisual );
if ( isVisual ) {
this.report.toggle( true );
if ( !this.diffElement ) {
if ( !this.diffElementPromise ) {
this.diffElementPromise = this.getDiffElementPromise().then( function ( diffElement ) {
dialog.diffElement = diffElement;
dialog.$reviewVisualDiff.empty().append( diffElement.$element );
dialog.positionDiffElement();
} );
}
return;
}
this.positionDiffElement();
} else {
this.report.toggle( false );
}
this.updateSize();
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.setDimensions = function () {
// Parent method
ve.ui.MWSaveDialog.parent.prototype.setDimensions.apply( this, arguments );
if ( !this.positioning ) {
this.positionDiffElement();
}
};
/**
* Re-position elements within the diff element
*
* Should be called whenever the diff element's container has changed width.
*/
ve.ui.MWSaveDialog.prototype.positionDiffElement = function () {
var dialog = this;
if ( this.panels.getCurrentItem() === this.reviewPanel ) {
setTimeout( function () {
dialog.withoutSizeTransitions( function () {
// This is delayed, so check the visual diff is still visible
if ( dialog.diffElement && dialog.isVisible() && dialog.reviewModeButtonSelect.findSelectedItem().getData() === 'visual' ) {
dialog.diffElement.positionDescriptions();
dialog.positioning = true;
dialog.updateSize();
dialog.positioning = false;
}
} );
}, OO.ui.theme.getDialogTransitionDuration() );
}
};
/**
* @inheritdoc
* @param {Object} [data]
* @param {boolean} [data.canReview] User can review changes
* @param {boolean} [data.canPreview] User can preview changes
* @param {OO.ui.FieldLayout[]} [data.checkboxFields] Checkbox fields
* @param {Object} [data.checkboxesByName] Checkbox widgets, indexed by name
* @param {string} [data.sectionTitle] Section title, if in new section mode
* @param {string} [data.editSummary] Edit summary
* @param {string} [data.initialPanel='save'] Initial panel to show
* @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} [data.saveButtonLabel] Label for the save button
*/
ve.ui.MWSaveDialog.prototype.getSetupProcess = function ( data ) {
return ve.ui.MWSaveDialog.super.prototype.getSetupProcess.call( this, data )
.next( function () {
var surfaceMode = ve.init.target.getSurface().getMode();
this.canReview = !!data.canReview;
this.canPreview = !!data.canPreview;
this.setupCheckboxes( data.checkboxFields || [] );
this.checkboxesByName = data.checkboxesByName || {};
if ( data.sectionTitle ) {
this.setEditSummary( ve.msg( 'newsectionsummary', data.sectionTitle ) );
this.editSummaryInput.setDisabled( true );
} else {
this.editSummaryInput.setDisabled( false );
if ( !this.changedEditSummary ) {
this.setEditSummary( data.editSummary );
}
}
// Config values used here:
// * visualeditor-diffmode-visual
// * visualeditor-diffmode-source
this.reviewModeButtonSelect.selectItemByData(
ve.userConfig( 'visualeditor-diffmode-' + surfaceMode ) || surfaceMode
);
// Old messages should not persist
this.clearAllMessages();
// Don't focus during setup to prevent scroll jumping (T153010)
this.swapPanel( data.initialPanel || 'save', true );
// Update save button label
if ( data.saveButtonLabel ) {
this.actions.forEach( { actions: 'save' }, function ( action ) {
action.setLabel( data.saveButtonLabel );
} );
}
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.getReadyProcess = function ( data ) {
return ve.ui.MWSaveDialog.super.prototype.getReadyProcess.call( this, data )
.next( function () {
// Support: Firefox
// In Firefox, trying to focus a hidden input will throw an
// exception. This would happen when opening the preview via
// keyboard shortcut.
if ( this.panels.getCurrentItem() === this.savePanel ) {
// This includes a #focus call
this.editSummaryInput.moveCursorToEnd();
}
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.getTeardownProcess = function ( data ) {
return ve.ui.MWSaveDialog.super.prototype.getTeardownProcess.call( this, data )
.next( function () {
this.emit( 'close' );
this.report = null;
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.getActionProcess = function ( action ) {
if ( action === 'save' ) {
return new OO.ui.Process( function () {
var saveDeferred = $.Deferred();
this.clearMessage( 'keyboard-shortcut-submit' );
this.emit( 'save', saveDeferred );
return saveDeferred.promise();
}, this );
}
if ( action === 'review' || action === 'preview' || action === 'resolve' ) {
return new OO.ui.Process( function () {
this.emit( action );
}, this );
}
if ( action === 'approve' ) {
return new OO.ui.Process( function () {
this.swapPanel( 'save' );
}, this );
}
if ( action === 'report' ) {
return new OO.ui.Process( function () {
window.open( this.constructor.static.feedbackUrl, '_new' );
}, this );
}
return ve.ui.MWSaveDialog.super.prototype.getActionProcess.call( this, action );
};
/**
* @inheritdoc
*/
ve.ui.MWSaveDialog.prototype.getBodyHeight = function () {
// Don't vary the height when the foot is made visible or not
return this.panels.getCurrentItem().$element.outerHeight( true );
};
/**
* Handle retry button click events.
*
* Hides errors and then tries again.
*/
ve.ui.MWSaveDialog.prototype.onRetryButtonClick = function () {
this.emit( 'retry' );
ve.ui.MWSaveDialog.super.prototype.onRetryButtonClick.apply( this, arguments );
};
/* Registration */
ve.ui.windowFactory.register( ve.ui.MWSaveDialog );