/*! * VisualEditor UserInterface MWSaveDialog class. * * @copyright 2011-2019 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 {Object} [config] Config options */ ve.ui.MWSaveDialog = function VeUiMwSaveDialog( config ) { // Parent constructor ve.ui.MWSaveDialog.super.call( this, config ); // Properties this.editSummaryByteLimit = mw.config.get( 'wgCommentByteLimit' ); this.editSummaryCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ); this.restoring = false; this.messages = {}; this.setupDeferred = $.Deferred(); this.checkboxesByName = null; this.changedEditSummary = false; this.canReview = false; this.canPreview = false; this.hasDiff = false; this.diffElement = null; this.diffElementPromise = null; this.getDiffElementPromise = null; // Initialization this.$element.addClass( 've-ui-mwSaveDialog' ); }; /* 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.feedbackUrl = 'https://www.mediawiki.org/wiki/Talk:VisualEditor/Diffs'; ve.ui.MWSaveDialog.static.actions = [ { action: 'save', // May be overridden by config.saveButtonLabel label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review' ), flags: [ 'primary', 'progressive' ], modes: [ 'save', 'review', 'preview' ] }, { label: OO.ui.deferMsg( 'visualeditor-savedialog-label-resume-editing' ), flags: [ 'safe', 'back' ], modes: [ 'save', 'review', 'preview', 'conflict' ] }, { action: 'review', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review' ), modes: [ 'save', 'preview' ] }, { action: 'preview', label: OO.ui.deferMsg( 'showpreview' ), modes: [ 'save', 'review' ] }, { action: 'approve', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-review-good' ), modes: [ 'review', 'preview' ] }, { action: 'resolve', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-resolve-conflict' ), flags: [ 'primary', 'progressive' ], modes: 'conflict' }, { action: 'report', label: OO.ui.deferMsg( 'visualeditor-savedialog-label-visual-diff-report' ), flags: [ 'progressive' ], modes: 'review', framed: false, icon: 'feedback', classes: [ 've-ui-mwSaveDialog-visualDiffFeedback' ], href: ve.ui.MWSaveDialog.static.feedbackUrl } ]; /* 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 preview * Emitted when the user clicks the show preview button */ /** * @event resolve * Emitted when the user clicks the resolve conflict button */ /** * @event retry * Emitted when the user clicks the retry/continue save button after an error. */ /* Methods */ /** * Set review content and show review panel. * * @param {jQuery.Promise} wikitextDiffPromise Wikitext diff HTML promise * @param {jQuery.Promise} visualDiffGeneratorPromise Visual diff promise * @param {HTMLDocument} [baseDoc] Base document against which to normalise links when rendering visualDiff */ ve.ui.MWSaveDialog.prototype.setDiffAndReview = function ( wikitextDiffPromise, visualDiffGeneratorPromise, baseDoc ) { var dialog = this; this.clearDiff(); function createDiffElement( visualDiff ) { var diffElement = new ve.ui.DiffElement( visualDiff ); diffElement.$document.addClass( 'mw-body-content mw-parser-output mw-content-' + visualDiff.newDoc.getDir() ); ve.targetLinksToNewWindow( diffElement.$document[ 0 ] ); // Run styles so links render with their appropriate classes ve.init.platform.linkCache.styleParsoidElements( diffElement.$document, baseDoc ); return diffElement; } // Visual diff this.$reviewVisualDiff.append( new OO.ui.ProgressBarWidget().$element ); // Don't generate the DiffElement until the tab is switched to this.getDiffElementPromise = function () { return visualDiffGeneratorPromise.then( function ( visualDiffGenerator ) { return createDiffElement( visualDiffGenerator() ); } ); }; this.baseDoc = baseDoc; // Wikitext diff this.$reviewWikitextDiff.append( new OO.ui.ProgressBarWidget().$element ); wikitextDiffPromise.then( function ( wikitextDiff ) { if ( wikitextDiff ) { dialog.$reviewWikitextDiff.empty().append( wikitextDiff ); } else { dialog.$reviewWikitextDiff.empty().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.css( 'display', 'none' ) );
// FIXME: Use CSS transitions
// eslint-disable-next-line no-jquery/no-slide
$message.slideDown( {
duration: 250,
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: false } );
this.savePanel = new OO.ui.PanelLayout( {
expanded: false,
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: '',
title: ve.msg( this.editSummaryCodePointLimit ?
'visualeditor-editsummary-characters-remaining' : 'visualeditor-editsummary-bytes-remaining' )
} );
// Save panel
this.$editSummaryLabel = $( ' ' ).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 );
} else {
this.editSummaryInput.$input.byteLimit( this.editSummaryByteLimit );
}
this.editSummaryInput.on( 'change', function () {
var remaining;
if ( dialog.editSummaryCodePointLimit ) {
remaining = dialog.editSummaryCodePointLimit - mwString.codePointLength( dialog.editSummaryInput.getValue() );
} else {
remaining = dialog.editSummaryByteLimit - mwString.byteLength( dialog.editSummaryInput.getValue() );
}
// 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 if it's byteLimit. Facing users with the
// word "byte" is bad? (T42035)
dialog.changedEditSummary = true;
if ( remaining > 99 ) {
dialog.editSummaryCountLabel.setLabel( '' );
} else {
dialog.editSummaryCountLabel.setLabel( mw.language.convertNumber( remaining ) );
}
} );
this.$saveCheckboxes = $( ' ' ).addClass( 've-ui-mwSaveDialog-license' )
.html( ve.init.platform.getParsedMessage( 'copyrightwarning' ) )
);
ve.targetLinksToNewWindow( this.$saveFoot[ 0 ] );
this.savePanel.$element.append(
this.$editSummaryLabel,
this.editSummaryInput.$element,
this.$saveOptions,
this.$saveMessages,
this.$saveFoot
);
// Review panel
this.reviewPanel = new OO.ui.PanelLayout( {
expanded: false,
padded: true
} );
this.$reviewVisualDiff = $( '
' ), this.$reviewEditSummary ),
this.$reviewVisualDiff,
this.$reviewWikitextDiff,
this.$reviewActions
);
// Preview panel
this.previewPanel = new OO.ui.PanelLayout( {
expanded: false,
padded: true
} );
this.$previewViewer = $( '