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

When you bind to your own events you're probably using the wrong
design pattern.

The events are kept (without arguments) for the purpose of tracking.

Change-Id: I6983319f9e0ca179e609afb00c821e3eab2161c9
This commit is contained in:
Ed Sanders 2015-08-04 14:37:13 +01:00 committed by James D. Forrester
parent 529248f831
commit 2c24efae29
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 );
};
/**