Merge "[BREAKING CHANGE] Target*: Replace target events with methods"

This commit is contained in:
jenkins-bot 2015-08-04 20:35:35 +00:00 committed by Gerrit Code Review
commit d103896e4a
3 changed files with 309 additions and 312 deletions

View file

@ -8,7 +8,7 @@
/*global confirm, alert */
/**
* Initialization MediaWiki view page target.
* MediaWiki desktop article target.
*
* @class
* @extends ve.init.mw.Target
@ -44,6 +44,8 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
this.toolbarSetupDeferred = null;
this.welcomeDialog = null;
this.welcomeDialogPromise = null;
this.captcha = null;
this.docToSave = null;
// If this is true then #transformPage / #restorePage will not call pushState
// This is to avoid adding a new history entry for the url we just got from onpopstate
@ -66,24 +68,6 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
this.originalDocumentTitle = document.title;
this.tabLayout = mw.config.get( 'wgVisualEditorConfig' ).tabLayout;
// Events
this.connect( this, {
save: 'onSave',
saveErrorEmpty: 'onSaveErrorEmpty',
saveErrorSpamBlacklist: 'onSaveErrorSpamBlacklist',
saveErrorAbuseFilter: 'onSaveErrorAbuseFilter',
saveErrorNewUser: 'onSaveErrorNewUser',
saveErrorCaptcha: 'onSaveErrorCaptcha',
saveErrorUnknown: 'onSaveErrorUnknown',
saveErrorPageDeleted: 'onSaveErrorPageDeleted',
saveErrorTitleBlacklist: 'onSaveErrorTitleBlacklist',
editConflict: 'onEditConflict',
showChanges: 'onShowChanges',
showChangesError: 'onShowChangesError',
noChanges: 'onNoChanges',
serializeError: 'onSerializeError'
} );
// Initialization
this.$element.addClass( 've-init-mw-desktopArticleTarget' );
@ -457,15 +441,11 @@ ve.init.mw.DesktopArticleTarget.prototype.cancel = function ( trackMechanism ) {
};
/**
* Handle failed DOM load event.
*
* @method
* @param {string} errorTypeText
* @param {string} error
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.loadFail = function ( errorText, error ) {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.loadFail.call( this, errorText, error );
ve.init.mw.DesktopArticleTarget.super.prototype.loadFail.apply( this, arguments );
// Don't show an error if the load was manually aborted
// The response.status check here is to catch aborts triggered by navigation away from the page
@ -507,9 +487,7 @@ ve.init.mw.DesktopArticleTarget.prototype.loadFail = function ( errorText, error
};
/**
* Once surface is ready ready, init UI
*
* @method
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onSurfaceReady = function () {
var surfaceReadyTime = ve.now(),
@ -590,20 +568,14 @@ ve.init.mw.DesktopArticleTarget.prototype.onViewTabClick = function ( e ) {
};
/**
* Handle successful DOM save event.
*
* @method
* @param {string} html Rendered page HTML from server
* @param {string} categoriesHtml Rendered categories HTML from server
* @param {number} newid New revision id, undefined if unchanged
* @param {boolean} isRedirect Whether this page is a redirect or not
* @param {string} displayTitle What HTML to show as the page title
* @param {Object} lastModified Object containing user-formatted date
and time strings, or undefined if we made no change.
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onSave = function (
ve.init.mw.DesktopArticleTarget.prototype.saveComplete = function (
html, categoriesHtml, newid, isRedirect, displayTitle, lastModified, contentSub
) {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.saveComplete.apply( this, arguments );
var newUrlParams, watchChecked;
this.saveDeferred.resolve();
if ( !this.pageExists || this.restoring ) {
@ -676,82 +648,7 @@ ve.init.mw.DesktopArticleTarget.prototype.onSave = function (
/**
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.saveFail = function () {
this.pageDeletedWarning = false;
ve.init.mw.DesktopArticleTarget.super.prototype.saveFail.apply( this, arguments );
};
/**
* Update save dialog message on general error
*
* @method
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorEmpty = function () {
this.showSaveError( ve.msg( 'visualeditor-saveerror', 'Empty server response' ), false /* prevents reapply */ );
};
/**
* Update save dialog message on spam blacklist error
*
* @method
* @param {Object} editApi
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorSpamBlacklist = function ( editApi ) {
this.showSaveError(
$( $.parseHTML( editApi.sberrorparsed ) ),
false // prevents reapply
);
};
/**
* Update save dialog message on abuse filter error
*
* @method
* @param {Object} editApi
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorAbuseFilter = function ( editApi ) {
this.showSaveError( $( $.parseHTML( editApi.warning ) ) );
// Don't disable the save button. If the action is not disallowed the user may save the
// edit by pressing Save again. The AbuseFilter API currently has no way to distinguish
// between filter triggers that are and aren't disallowing the action.
};
/**
* Update save dialog message on title blacklist error
*
* @method
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorTitleBlacklist = function () {
this.showSaveError( mw.msg( 'visualeditor-saveerror-titleblacklist' ) );
};
/**
* Update save dialog when token fetch indicates another user is logged in
*
* @method
* @param {string|null} username Name of newly logged-in user, or null if anonymous
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorNewUser = function ( username ) {
var badToken, userMsg;
badToken = document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' );
// mediawiki.jqueryMsg has a bug with [[User:$1|$1]] (bug 51388)
if ( username === null ) {
userMsg = 'visualeditor-savedialog-identify-anon';
} else {
userMsg = 'visualeditor-savedialog-identify-user---' + username;
}
this.showSaveError(
$( badToken ).add( $.parseHTML( mw.message( userMsg ).parse() ) )
);
};
/**
* Update save dialog on captcha error
*
* @method
* @param {Object} editApi
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorCaptcha = function ( editApi ) {
ve.init.mw.DesktopArticleTarget.prototype.saveErrorCaptcha = function ( editApi ) {
var $captchaDiv = $( '<div>' ),
$captchaParagraph = $( '<p>' );
@ -799,38 +696,6 @@ ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorCaptcha = function ( editAp
this.saveDialog.popPending();
};
/**
* Update save dialog message on unknown error
*
* @method
* @param {Object} editApi
* @param {Object|null} data API response data
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorUnknown = function ( editApi, data ) {
this.showSaveError(
$( document.createTextNode(
( editApi && editApi.info ) ||
( data.error && data.error.info ) ||
( editApi && editApi.code ) ||
( data.error && data.error.code ) ||
'Unknown error'
) ),
false // prevents reapply
);
};
/**
* Update save dialog message on page deleted error
*
* @method
*/
ve.init.mw.DesktopArticleTarget.prototype.onSaveErrorPageDeleted = function () {
var continueLabel = mw.msg( 'ooui-dialog-process-continue' );
this.pageDeletedWarning = true;
this.showSaveError( mw.msg( 'visualeditor-recreate', continueLabel ), true, true );
};
/**
* Handle MWSaveDialog retry events
* So we can handle trying to save again after page deletion warnings
@ -843,26 +708,19 @@ ve.init.mw.DesktopArticleTarget.prototype.onSaveRetry = function () {
};
/**
* Update save dialog api-save-error message
*
* @method
* @param {string|jQuery|Node[]} msg Message content (string of HTML, jQuery object or array of
* Node objects)
* @param {boolean} [allowReapply=true] Whether or not to allow the user to reapply.
* Reset when swapping panels. Assumed to be true unless explicitly set to false.
* @param {boolean} [warning=false] Whether or not this is a warning.
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.showSaveError = function ( msg, allowReapply, warning ) {
this.saveDeferred.reject( [ new OO.ui.Error( msg, { recoverable: allowReapply, warning: warning } ) ] );
};
/**
* Handle Show changes event.
*
* @method
* @param {string} diffHtml
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onShowChanges = function ( diffHtml ) {
ve.init.mw.DesktopArticleTarget.prototype.showChangesDiff = function ( diffHtml ) {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.showChangesDiff.apply( this, arguments );
// Invalidate the viewer diff on next change
this.getSurface().getModel().getDocument().once( 'transact',
this.saveDialog.clearDiff.bind( this.saveDialog )
@ -871,25 +729,23 @@ ve.init.mw.DesktopArticleTarget.prototype.onShowChanges = function ( diffHtml )
};
/**
* Handle failed show changes event.
*
* @method
* @param {Object} jqXHR
* @param {string} status Text status message
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onShowChangesError = function ( jqXHR, status ) {
ve.init.mw.DesktopArticleTarget.prototype.showChangesFail = function ( jqXHR, status ) {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.showChangesFail.apply( this, arguments );
alert( ve.msg( 'visualeditor-differror', status ) );
this.saveDialog.popPending();
};
/**
* Called if a call to target.serialize() failed.
*
* @method
* @param {jqXHR|null} jqXHR
* @param {string} status Text status message
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onSerializeError = function ( jqXHR, status ) {
ve.init.mw.DesktopArticleTarget.prototype.serializeFail = function ( jqXHR, status ) {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.serializeFail.apply( this, arguments );
alert( ve.msg( 'visualeditor-serializeerror', status ) );
this.getSurface().getDialogs().closeWindow( 'wikitextswitchconfirm' );
@ -902,21 +758,17 @@ ve.init.mw.DesktopArticleTarget.prototype.onSerializeError = function ( jqXHR, s
};
/**
* Handle edit conflict event.
*
* @method
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onEditConflict = function () {
ve.init.mw.DesktopArticleTarget.prototype.editConflict = function () {
this.saveDialog.popPending();
this.saveDialog.swapPanel( 'conflict' );
};
/**
* Handle failed show changes event.
*
* @method
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.onNoChanges = function () {
ve.init.mw.DesktopArticleTarget.prototype.noChanges = function () {
this.saveDialog.popPending();
this.saveDialog.swapPanel( 'nochanges' );
this.saveDialog.getActions().setAbilities( { approve: true } );
@ -944,7 +796,7 @@ ve.init.mw.DesktopArticleTarget.prototype.onSaveDialogReview = function () {
this.saveDialog.getActions().setAbilities( { approve: false } );
this.saveDialog.pushPending();
if ( this.pageExists ) {
// Has no callback, handled via target.onShowChanges
// Has no callback, handled via target.showChangesDiff
this.showChanges( this.docToSave );
} else {
this.serialize( this.docToSave, this.onSaveDialogReviewComplete.bind( this ) );
@ -969,25 +821,23 @@ ve.init.mw.DesktopArticleTarget.prototype.onSaveDialogReviewComplete = function
};
/**
* Try to save the current document.
* @fires saveInitiated
* @inheritdoc
* @param {jQuery.Deferred} saveDeferred Deferred object to resolve/reject when the save
* succeeds/fails.
*/
ve.init.mw.DesktopArticleTarget.prototype.saveDocument = function ( saveDeferred ) {
ve.init.mw.DesktopArticleTarget.prototype.startSave = function ( saveDeferred ) {
if ( this.deactivating ) {
return false;
}
var saveOptions = this.getSaveOptions();
this.emit( 'saveInitiated' );
// Reset any old captcha data
if ( this.captcha ) {
this.saveDialog.clearMessage( 'captcha' );
delete this.captcha;
}
var saveOptions = this.getSaveOptions();
if (
+mw.user.options.get( 'forceeditsummary' ) &&
saveOptions.summary === '' &&
@ -1001,7 +851,8 @@ ve.init.mw.DesktopArticleTarget.prototype.saveDocument = function ( saveDeferred
);
this.saveDialog.popPending();
} else {
this.save( this.docToSave, saveOptions );
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.startSave.call( this );
this.saveDeferred = saveDeferred;
}
};
@ -1076,9 +927,7 @@ ve.init.mw.DesktopArticleTarget.prototype.submitWithSaveFields = function ( fiel
};
/**
* Get edit API options from the save dialog form.
*
* @returns {Object} Save options for submission to the MediaWiki API
* @inheritdoc
*/
ve.init.mw.DesktopArticleTarget.prototype.getSaveOptions = function () {
var key,
@ -1197,20 +1046,14 @@ ve.init.mw.DesktopArticleTarget.prototype.attachToolbarSaveButton = function ()
/**
* @inheritdoc
* @fires saveWorkflowBegin
*/
ve.init.mw.DesktopArticleTarget.prototype.showSaveDialog = function () {
// Parent method
ve.init.mw.DesktopArticleTarget.super.prototype.showSaveDialog.call( this );
var target = this,
windowAction = ve.ui.actionFactory.create( 'window', this.getSurface() );
this.emit( 'saveWorkflowBegin' );
// Preload the serialization
if ( !this.docToSave ) {
this.docToSave = this.getSurface().getDom();
}
this.prepareCacheKey( this.docToSave );
// Connect events to save dialog
this.getSurface().getDialogs().getWindow( 'mwSave' ).done( function ( win ) {
if ( !target.saveDialog ) {
@ -1218,7 +1061,7 @@ ve.init.mw.DesktopArticleTarget.prototype.showSaveDialog = function () {
// Connect to save dialog
target.saveDialog.connect( target, {
save: 'saveDocument',
save: 'startSave',
review: 'onSaveDialogReview',
resolve: 'onSaveDialogResolveConflict',
retry: 'onSaveRetry',
@ -1430,10 +1273,10 @@ ve.init.mw.DesktopArticleTarget.prototype.onWindowPopState = function ( e ) {
* @method
* @param {string} html Rendered HTML from server
* @param {string} categoriesHtml Rendered categories HTML from server
* @param {string} displayTitle What HTML to show as the page title
* @param {string} displayTitle HTML to show as the page title
* @param {Object} lastModified Object containing user-formatted date
and time strings, or undefined if we made no change.
* @param {string} contentSub What HTML to show as the content subtitle
* and time strings, or undefined if we made no change.
* @param {string} contentSub HTML to show as the content subtitle
*/
ve.init.mw.DesktopArticleTarget.prototype.replacePageContent = function (
html, categoriesHtml, displayTitle, lastModified, contentSub

View file

@ -6,12 +6,12 @@
*/
/**
* MediaWiki mobile article target.
*
* @class
* @extends ve.init.mw.Target
*
* @constructor
* @param {jQuery} $container Container to render target into
* @param {Object} [config] Configuration options
* @cfg {number} [section] Number of the section target should scroll to
* @cfg {boolean} [isIos=false] Whether the platform is an iOS device
@ -37,18 +37,6 @@ ve.init.mw.MobileArticleTarget = function VeInitMwMobileArticleTarget( config )
OO.inheritClass( ve.init.mw.MobileArticleTarget, ve.init.mw.Target );
/* Events */
/**
* @event back
* Leave the editor
*/
/**
* @event editSource
* Switch to edit source mode
*/
/* Static Properties */
ve.init.mw.MobileArticleTarget.static.toolbarGroups = [
@ -84,7 +72,7 @@ ve.init.mw.MobileArticleTarget.static.name = 'mobile';
/* Methods */
/**
* Once surface is ready ready, init UI.
* @inheritdoc
*/
ve.init.mw.MobileArticleTarget.prototype.onSurfaceReady = function () {
// Parent method
@ -94,12 +82,7 @@ ve.init.mw.MobileArticleTarget.prototype.onSurfaceReady = function () {
};
/**
* Create a surface.
*
* @method
* @param {ve.dm.Document} dmDoc Document model
* @param {Object} [config] Configuration options
* @returns {ve.ui.MobileSurface}
* @inheritdoc
*/
ve.init.mw.MobileArticleTarget.prototype.createSurface = function ( dmDoc, config ) {
return new ve.ui.MobileSurface( dmDoc, config );
@ -155,13 +138,6 @@ ve.init.mw.MobileArticleTarget.prototype.attachToolbarSaveButton = function () {
this.toolbar.$actions.append( this.actionsToolbar.$element, this.toolbarSaveButton.$element );
};
/**
* @inheritdoc
*/
ve.init.mw.MobileArticleTarget.prototype.editSource = function () {
this.emit( 'editSource' );
};
/**
* @inheritdoc
*/
@ -186,6 +162,12 @@ ve.init.mw.MobileArticleTarget.prototype.scrollToHeading = function ( headingNod
} );
};
/**
* Close the mobile editor
*/
ve.init.mw.MobileArticleTarget.prototype.close = function () {
};
/**
* Back tool
*/
@ -195,7 +177,7 @@ ve.ui.MWBackTool = function VeUiMwBackTool() {
};
OO.inheritClass( ve.ui.MWBackTool, ve.ui.Tool );
ve.ui.MWBackTool.static.name = 'back';
ve.ui.MWBackTool.static.group = 'history';
ve.ui.MWBackTool.static.group = 'navigation';
ve.ui.MWBackTool.static.icon = 'previous';
ve.ui.MWBackTool.static.title =
OO.ui.deferMsg( 'visualeditor-backbutton-tooltip' );
@ -211,6 +193,6 @@ ve.ui.MWBackCommand = function VeUiMwBackCommmand() {
};
OO.inheritClass( ve.ui.MWBackCommand, ve.ui.Command );
ve.ui.MWBackCommand.prototype.execute = function () {
ve.init.target.emit( 'back' );
ve.init.target.close();
};
ve.ui.commandRegistry.register( new ve.ui.MWBackCommand() );

View file

@ -42,6 +42,7 @@ ve.init.mw.Target = function VeInitMwTarget( pageName, revisionId, config ) {
this.revid = revisionId || mw.config.get( 'wgCurRevisionId' );
this.restoring = !!revisionId;
this.pageDeletedWarning = false;
this.editToken = mw.user.tokens.get( 'editToken' );
this.submitUrl = ( new mw.Uri( mw.util.getUrl( this.pageName ) ) )
.extend( { action: 'submit' } );
@ -72,31 +73,16 @@ OO.inheritClass( ve.init.mw.Target, ve.init.Target );
/**
* @event save
* @param {string} html Rendered page HTML from server
* @param {string} categoriesHtml Rendered categories HTML from server
* @param {number} [newid] New revision id, undefined if unchanged
* @param {boolean} isRedirect Whether this page is now a redirect or not.
*/
/**
* @event showChanges
* @param {string} diff
*/
/**
* @event noChanges
*/
/**
* @event saveAsyncBegin
* Fired when we're waiting for network
*/
/**
* @event saveAsyncComplete
* Fired when we're no longer waiting for network
*/
/**
* @event saveErrorEmpty
* Fired when save API returns no data object
@ -105,13 +91,11 @@ OO.inheritClass( ve.init.mw.Target, ve.init.Target );
/**
* @event saveErrorSpamBlacklist
* Fired when save is considered spam or blacklisted
* @param {Object} editApi
*/
/**
* @event saveErrorAbuseFilter
* Fired when AbuseFilter throws warnings
* @param {Object} editApi
*/
/**
@ -123,20 +107,16 @@ OO.inheritClass( ve.init.mw.Target, ve.init.Target );
/**
* @event saveErrorNewUser
* Fired when user is logged in as a new user
* @param {string|null} username Name of newly logged-in user, or null if anonymous
*/
/**
* @event saveErrorCaptcha
* Fired when saveError indicates captcha field is required
* @param {Object} editApi
*/
/**
* @event saveErrorUnknown
* Fired for any other type of save error
* @param {Object} editApi
* @param {Object|null} data API response data
*/
/**
@ -151,29 +131,14 @@ OO.inheritClass( ve.init.mw.Target, ve.init.Target );
/**
* @event loadError
* @param {string} errorTypeText
* @param {Object|string} error
*/
/**
* @event saveError
* @param {jqXHR|null} jqXHR
* @param {string} status Text status message
* @param {Object|null} data API response data
*/
/**
* @event showChangesError
* @param {jqXHR|null} jqXHR
* @param {string} status Text status message
* @param {Mixed|null} error HTTP status text
*/
/**
* @event serializeError
* @param {jqXHR|null} jqXHR
* @param {string} status Text status message
* @param {Mixed|null} error HTTP status text
*/
/**
@ -312,7 +277,6 @@ ve.init.mw.Target.static.fixBase = function ( doc ) {
* @method
* @param {Object} response API response data
* @param {string} status Text status message
* @fires loadError
*/
ve.init.mw.Target.prototype.loadSuccess = function ( response ) {
var i, len, linkData, aboutDoc, docRevIdMatches,
@ -454,9 +418,9 @@ ve.init.mw.Target.prototype.onSurfaceReady = function () {
* @param {Object} error Object containing xhr, textStatus and exception keys
* @fires loadError
*/
ve.init.mw.Target.prototype.loadFail = function ( errorText, error ) {
ve.init.mw.Target.prototype.loadFail = function () {
this.loading = false;
this.emit( 'loadError', errorText, error );
this.emit( 'loadError' );
};
/**
@ -469,8 +433,6 @@ ve.init.mw.Target.prototype.loadFail = function ( errorText, error ) {
* @param {Object} saveData Options that were used
* @param {Object} response Response data
* @param {string} status Text status message
* @fires editConflict
* @fires save
*/
ve.init.mw.Target.prototype.saveSuccess = function ( doc, saveData, response ) {
this.saving = false;
@ -483,8 +445,7 @@ ve.init.mw.Target.prototype.saveSuccess = function ( doc, saveData, response ) {
} else if ( typeof data.content !== 'string' ) {
this.saveFail( doc, saveData, null, 'Invalid HTML content in response from server', response );
} else {
this.emit(
'save',
this.saveComplete(
data.content,
data.categorieshtml,
data.newrevid,
@ -496,6 +457,24 @@ ve.init.mw.Target.prototype.saveSuccess = function ( doc, saveData, response ) {
}
};
/**
* Handle successful DOM save event.
*
* @method
* @param {string} html Rendered page HTML from server
* @param {string} categoriesHtml Rendered categories HTML from server
* @param {number} newid New revision id, undefined if unchanged
* @param {boolean} isRedirect Whether this page is a redirect or not
* @param {string} displayTitle What HTML to show as the page title
* @param {Object} lastModified Object containing user-formatted date
* and time strings, or undefined if we made no change.
* @param {string} contentSub HTML to show as the content subtitle
* @fires save
*/
ve.init.mw.Target.prototype.saveComplete = function () {
this.emit( 'save' );
};
/**
* Handle an unsuccessful save request.
*
@ -505,22 +484,17 @@ ve.init.mw.Target.prototype.saveSuccess = function ( doc, saveData, response ) {
* @param {Object} jqXHR
* @param {string} status Text status message
* @param {Object|null} data API response data
* @fires saveErrorEmpty
* @fires saveErrorSpamBlacklist
* @fires saveErrorAbuseFilter
* @fires saveErrorBadToken
* @fires saveErrorNewUser
* @fires saveErrorCaptcha
* @fires saveErrorUnknown
*/
ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status, data ) {
var api, editApi,
target = this;
this.saving = false;
this.pageDeletedWarning = false;
// Handle empty response
if ( !data ) {
this.emit( 'saveErrorEmpty' );
this.saveErrorEmpty();
return;
}
@ -528,14 +502,14 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
// Handle spam blacklist error (either from core or from Extension:SpamBlacklist)
if ( editApi && editApi.spamblacklist ) {
this.emit( 'saveErrorSpamBlacklist', editApi );
this.saveErrorSpamBlacklist( editApi );
return;
}
// Handle warnings/errors from Extension:AbuseFilter
// TODO: Move this to a plugin
if ( editApi && editApi.info && editApi.info.indexOf( 'Hit AbuseFilter:' ) === 0 && editApi.warning ) {
this.emit( 'saveErrorAbuseFilter', editApi );
this.saveErrorAbuseFilter( editApi );
return;
}
@ -556,7 +530,7 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
intoken: 'edit'
} )
.always( function () {
target.emit( 'saveErrorBadToken' );
target.saveErrorBadToken();
} )
.done( function ( data ) {
var userMsg,
@ -588,7 +562,7 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
// functions like mw.user.isAnon rely on this.
wgUserName: null
} );
target.emit( 'saveErrorNewUser', null );
target.saveErrorNewUser( null );
} else {
// New session is a different user
mw.config.set( { wgUserId: userInfo.id, wgUserName: userInfo.name } );
@ -598,20 +572,20 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
mw.messages.get( 'visualeditor-savedialog-identify-user' )
.replace( /\$1/g, userInfo.name )
);
target.emit( 'saveErrorNewUser', userInfo.name );
target.saveErrorNewUser( userInfo.name );
}
}
}
} );
return;
} else if ( data.error && data.error.code === 'editconflict' ) {
this.emit( 'editConflict' );
this.editConflict();
return;
} else if ( data.error && data.error.code === 'pagedeleted' ) {
this.emit( 'saveErrorPageDeleted' );
this.saveErrorPageDeleted();
return;
} else if ( data.error && data.error.code === 'titleblacklist-forbidden-edit' ) {
this.emit( 'saveErrorTitleBlacklist' );
this.saveErrorTitleBlacklist();
return;
}
@ -629,12 +603,12 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
editApi.captcha.type === 'math' ||
editApi.captcha.type === 'question'
) ) {
this.emit( 'saveErrorCaptcha', editApi );
this.saveErrorCaptcha( editApi );
return;
}
// Handle (other) unknown and/or unrecoverable errors
this.emit( 'saveErrorUnknown', editApi, data );
this.saveErrorUnknown( editApi, data );
};
/**
@ -643,8 +617,6 @@ ve.init.mw.Target.prototype.saveFail = function ( doc, saveData, jqXHR, status,
* @method
* @param {Object} response API response data
* @param {string} status Text status message
* @fires showChanges
* @fires noChanges
*/
ve.init.mw.Target.prototype.showChangesSuccess = function ( response ) {
var data = response.visualeditor;
@ -656,7 +628,7 @@ ve.init.mw.Target.prototype.showChangesSuccess = function ( response ) {
null, 'Unsuccessful request: ' + response.error.info, null
);
} else if ( data.result === 'nochanges' ) {
this.emit( 'noChanges' );
this.noChanges();
} else if ( data.result !== 'success' ) {
this.showChangesFail( null, 'Failed request: ' + data.result, null );
} else if ( typeof data.diff !== 'string' ) {
@ -664,10 +636,20 @@ ve.init.mw.Target.prototype.showChangesSuccess = function ( response ) {
null, 'Invalid HTML content in response from server', null
);
} else {
this.emit( 'showChanges', data.diff );
this.showChangesDiff( data.diff );
}
};
/**
* Show changes diff HTML
*
* @param {string} diffHtml Diff HTML
* @fires showChanges
*/
ve.init.mw.Target.prototype.showChangesDiff = function () {
this.emit( 'showChanges' );
};
/**
* Handle errors during showChanges action.
*
@ -678,9 +660,171 @@ ve.init.mw.Target.prototype.showChangesSuccess = function ( response ) {
* @param {Mixed} error HTTP status text
* @fires showChangesError
*/
ve.init.mw.Target.prototype.showChangesFail = function ( jqXHR, status, error ) {
ve.init.mw.Target.prototype.showChangesFail = function () {
this.diffing = false;
this.emit( 'showChangesError', jqXHR, status, error );
this.emit( 'showChangesError' );
};
/**
* Show an save process error message
*
* @method
* @param {string|jQuery|Node[]} msg Message content (string of HTML, jQuery object or array of
* Node objects)
* @param {boolean} [allowReapply=true] Whether or not to allow the user to reapply.
* Reset when swapping panels. Assumed to be true unless explicitly set to false.
* @param {boolean} [warning=false] Whether or not this is a warning.
*/
ve.init.mw.Target.prototype.showSaveError = function () {
};
/**
* Handle general save error
*
* @method
* @fires saveErrorEmpty
*/
ve.init.mw.Target.prototype.saveErrorEmpty = function () {
this.showSaveError( ve.msg( 'visualeditor-saveerror', 'Empty server response' ), false /* prevents reapply */ );
this.emit( 'saveErrorEmpty' );
};
/**
* Handle spam blacklist error
*
* @method
* @param {Object} editApi
* @fires saveErrorSpamBlacklist
*/
ve.init.mw.Target.prototype.saveErrorSpamBlacklist = function ( editApi ) {
this.showSaveError(
$( $.parseHTML( editApi.sberrorparsed ) ),
false // prevents reapply
);
this.emit( 'saveErrorSpamBlacklist' );
};
/**
* Handel abuse filter error
*
* @method
* @param {Object} editApi
* @fires saveErrorAbuseFilter
*/
ve.init.mw.Target.prototype.saveErrorAbuseFilter = function ( editApi ) {
this.showSaveError( $( $.parseHTML( editApi.warning ) ) );
// Don't disable the save button. If the action is not disallowed the user may save the
// edit by pressing Save again. The AbuseFilter API currently has no way to distinguish
// between filter triggers that are and aren't disallowing the action.
this.emit( 'saveErrorAbuseFilter' );
};
/**
* Handle title blacklist save error
*
* @method
* @fires saveErrorTitleBlacklist
*/
ve.init.mw.Target.prototype.saveErrorTitleBlacklist = function () {
this.showSaveError( mw.msg( 'visualeditor-saveerror-titleblacklist' ) );
this.emit( 'saveErrorTitleBlacklist' );
};
/**
* Handle token fetch indicating another user is logged in
*
* @method
* @param {string|null} username Name of newly logged-in user, or null if anonymous
* @fires saveErrorNewUser
*/
ve.init.mw.Target.prototype.saveErrorNewUser = function ( username ) {
var badToken, userMsg;
badToken = document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' );
// mediawiki.jqueryMsg has a bug with [[User:$1|$1]] (bug 51388)
if ( username === null ) {
userMsg = 'visualeditor-savedialog-identify-anon';
} else {
userMsg = 'visualeditor-savedialog-identify-user---' + username;
}
this.showSaveError(
$( badToken ).add( $.parseHTML( mw.message( userMsg ).parse() ) )
);
this.emit( 'saveErrorNewUser' );
};
/**
* Handle unknown save error
*
* @method
* @param {Object} editApi
* @param {Object|null} data API response data
* @fires onSaveErrorUnknown
*/
ve.init.mw.Target.prototype.saveErrorUnknown = function ( editApi, data ) {
this.showSaveError(
$( document.createTextNode(
( editApi && editApi.info ) ||
( data.error && data.error.info ) ||
( editApi && editApi.code ) ||
( data.error && data.error.code ) ||
'Unknown error'
) ),
false // prevents reapply
);
this.emit( 'onSaveErrorUnknown' );
};
/**
* Handle a bad token
*
* @method
* @fires saveErrorBadToken
*/
ve.init.mw.Target.prototype.saveErrorBadToken = function () {
this.emit( 'saveErrorBadToken' );
};
/**
* Handle captcha error
*
* @method
* @param {Object} editApi
* @fires saveErrorCaptcha
*/
ve.init.mw.Target.prototype.saveErrorCaptcha = function () {
this.emit( 'saveErrorCaptcha' );
};
/**
* Handle page deleted error
*
* @method
* @fires saveErrorPageDeleted
*/
ve.init.mw.Target.prototype.saveErrorPageDeleted = function () {
this.pageDeletedWarning = true;
this.showSaveError( mw.msg( 'visualeditor-recreate', mw.msg( 'ooui-dialog-process-continue' ) ), true, true );
this.emit( 'saveErrorPageDeleted' );
};
/**
* Handle an edit conflict
*
* @method
* @fires editConflict
*/
ve.init.mw.Target.prototype.editConflict = function () {
this.emit( 'editConflict' );
};
/**
* Handle no changes in diff
*
* @method
* @fires noChanges
*/
ve.init.mw.Target.prototype.noChanges = function () {
this.emit( 'noChanges' );
};
/**
@ -694,19 +838,19 @@ ve.init.mw.Target.prototype.showChangesFail = function ( jqXHR, status, error )
* @param {string} status Text status message
* @fires serializeComplete
*/
ve.init.mw.Target.prototype.onSerialize = function ( response ) {
ve.init.mw.Target.prototype.serializeSuccess = function ( response ) {
this.serializing = false;
var data = response.visualeditor;
if ( !data && !response.error ) {
this.onSerializeError( null, 'Invalid response from server', null );
this.serializeFail( null, 'Invalid response from server', null );
} else if ( response.error ) {
this.onSerializeError(
this.serializeFail(
null, 'Unsuccessful request: ' + response.error.info, null
);
} else if ( data.result === 'error' ) {
this.onSerializeError( null, 'Server error', null );
this.serializeFail( null, 'Server error', null );
} else if ( typeof data.content !== 'string' ) {
this.onSerializeError(
this.serializeFail(
null, 'No Wikitext content in response from server', null
);
} else {
@ -729,9 +873,9 @@ ve.init.mw.Target.prototype.onSerialize = function ( response ) {
* @param {Mixed|null} error HTTP status text
* @fires serializeError
*/
ve.init.mw.Target.prototype.onSerializeError = function ( jqXHR, status, error ) {
ve.init.mw.Target.prototype.serializeFail = function () {
this.serializing = false;
this.emit( 'serializeError', jqXHR, status, error );
this.emit( 'serializeError' );
};
/**
@ -1129,6 +1273,28 @@ ve.init.mw.Target.prototype.tryWithPreparedCacheKey = function ( doc, options, e
return preparedCacheKey.then( ajaxRequest, ajaxRequest );
};
/**
* Prepare to save the article
*
* @fires saveInitiated
*/
ve.init.mw.Target.prototype.startSave = function () {
this.emit( 'saveInitiated' );
if ( !this.docToSave ) {
this.docToSave = this.getSurface().getDom();
}
this.save( this.docToSave, this.getSaveOptions() );
};
/**
* Get edit API options from the save dialog form.
*
* @returns {Object} Save options for submission to the MediaWiki API
*/
ve.init.mw.Target.prototype.getSaveOptions = function () {
return {};
};
/**
* Post DOM data to the Parsoid API.
*
@ -1260,8 +1426,8 @@ ve.init.mw.Target.prototype.serialize = function ( doc, callback ) {
page: this.pageName,
oldid: this.revid
}, 'serialize' )
.done( ve.init.mw.Target.prototype.onSerialize.bind( this ) )
.fail( ve.init.mw.Target.prototype.onSerializeError.bind( this ) );
.done( ve.init.mw.Target.prototype.serializeSuccess.bind( this ) )
.fail( ve.init.mw.Target.prototype.serializeFail.bind( this ) );
return true;
};
@ -1391,20 +1557,26 @@ ve.init.mw.Target.prototype.updateToolbarSaveButtonState = function () {
/**
* Handle clicks on the save button in the toolbar.
*
* @fires saveBegin
*/
ve.init.mw.Target.prototype.onToolbarSaveButtonClick = function () {
if ( this.edited || this.restoring ) {
this.showSaveDialog();
this.emit( 'saveBegin' );
}
};
/**
* Show a save dialog
*
* @fires saveWorkflowBegin
*/
ve.init.mw.Target.prototype.showSaveDialog = function () {
this.emit( 'saveWorkflowBegin' );
// Preload the serialization
if ( !this.docToSave ) {
this.docToSave = this.getSurface().getDom();
}
this.prepareCacheKey( this.docToSave );
};
/**