diff --git a/extension.json b/extension.json
index 4a1b956a99..9bcaee6264 100644
--- a/extension.json
+++ b/extension.json
@@ -194,6 +194,7 @@
"services": [
"RevisionLookup",
"TempUserCreator",
+ "UserFactory",
"UserOptionsLookup",
"WatchlistManager",
"ContentTransformer",
diff --git a/includes/ApiVisualEditor.php b/includes/ApiVisualEditor.php
index ea7a841607..d6a4c5c731 100644
--- a/includes/ApiVisualEditor.php
+++ b/includes/ApiVisualEditor.php
@@ -35,10 +35,12 @@ use MediaWiki\Revision\RevisionLookup;
use MediaWiki\SpecialPage\SpecialPageFactory;
use MediaWiki\Title\Title;
use MediaWiki\User\TempUser\TempUserCreator;
+use MediaWiki\User\UserFactory;
use MediaWiki\User\UserOptionsLookup;
use MediaWiki\Watchlist\WatchlistManager;
use MessageLocalizer;
use RequestContext;
+use User;
use Wikimedia\ParamValidator\ParamValidator;
use WikitextContent;
@@ -48,6 +50,7 @@ class ApiVisualEditor extends ApiBase {
private RevisionLookup $revisionLookup;
private TempUserCreator $tempUserCreator;
+ private UserFactory $userFactory;
private UserOptionsLookup $userOptionsLookup;
private WatchlistManager $watchlistManager;
private ContentTransformer $contentTransformer;
@@ -62,6 +65,7 @@ class ApiVisualEditor extends ApiBase {
string $name,
RevisionLookup $revisionLookup,
TempUserCreator $tempUserCreator,
+ UserFactory $userFactory,
UserOptionsLookup $userOptionsLookup,
WatchlistManager $watchlistManager,
ContentTransformer $contentTransformer,
@@ -77,6 +81,7 @@ class ApiVisualEditor extends ApiBase {
$this->setStats( $statsdDataFactory );
$this->revisionLookup = $revisionLookup;
$this->tempUserCreator = $tempUserCreator;
+ $this->userFactory = $userFactory;
$this->userOptionsLookup = $userOptionsLookup;
$this->watchlistManager = $watchlistManager;
$this->contentTransformer = $contentTransformer;
@@ -96,6 +101,20 @@ class ApiVisualEditor extends ApiBase {
);
}
+ /**
+ * @see ApiParse::getUserForPreview
+ * @return User
+ */
+ private function getUserForPreview() {
+ $user = $this->getUser();
+ if ( $this->tempUserCreator->shouldAutoCreate( $user, 'edit' ) ) {
+ return $this->userFactory->newUnsavedTempUser(
+ $this->tempUserCreator->getStashedName( $this->getRequest()->getSession() )
+ );
+ }
+ return $user;
+ }
+
/**
* Run wikitext through the parser's Pre-Save-Transform
*
@@ -108,7 +127,7 @@ class ApiVisualEditor extends ApiBase {
return $this->contentTransformer->preSaveTransform(
$content,
$title,
- $this->getUser(),
+ $this->getUserForPreview(),
$this->wikiPageFactory->newFromTitle( $title )->makeParserOptions( $this->getContext() )
)
->serialize( 'text/x-wiki' );
diff --git a/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js b/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js
index ed13a1e437..a61dd928dc 100644
--- a/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js
+++ b/modules/ve-mw/ce/nodes/ve.ce.MWSignatureNode.js
@@ -106,45 +106,62 @@ ve.ce.MWSignatureNode.prototype.onTeardown = function () {
* @inheritdoc ve.ce.GeneratedContentNode
*/
ve.ce.MWSignatureNode.prototype.generateContents = function () {
- // Parsoid doesn't support pre-save transforms. PHP parser doesn't support Parsoid's
- // meta attributes (that may or may not be required).
-
- // We could try hacking up one (or even both) of these, but just calling the two parsers
- // in order seems slightly saner.
-
- // We must have only one top-level node, this is the easiest way.
- var wikitext = '~~~~';
var doc = this.getModel().getDocument();
+ var abortable, aborted;
+ var abortedPromise = ve.createDeferred().reject( 'http',
+ { textStatus: 'abort', exception: 'abort' } ).promise();
- var deferred = ve.createDeferred();
- var xhr = ve.init.target.getContentApi( doc ).post( {
- action: 'parse',
- text: wikitext,
- contentmodel: 'wikitext',
- prop: 'text',
- onlypst: true
- } )
- .done( function ( resp ) {
- var wt = ve.getProp( resp, 'parse', 'text' );
- if ( wt ) {
- ve.init.target.parseWikitextFragment( wt, true, doc ).then( function ( response ) {
- if ( ve.getProp( response, 'visualeditor', 'result' ) !== 'success' ) {
- deferred.reject();
- return;
- }
+ function abort() {
+ aborted = true;
+ if ( abortable && abortable.abort ) {
+ abortable.abort();
+ }
+ }
- // Simplified case of template rendering, don't need to worry about filtering etc
- deferred.resolve( $( response.visualeditor.content ).contents().toArray() );
- } );
- } else {
- deferred.reject();
+ // Acquire a temporary user username before previewing, so that signatures
+ // display the temp user instead of IP user. (T331397)
+ return mw.user.acquireTempUserName()
+ .then( function () {
+ if ( aborted ) {
+ return abortedPromise;
}
- } )
- .fail( function () {
- deferred.reject();
- } );
- return deferred.promise( { abort: xhr.abort } );
+ // We must have only one top-level node, this is the easiest way.
+ var wikitext = '~~~~';
+
+ // Parsoid doesn't support pre-save transforms. PHP parser doesn't support Parsoid's
+ // meta attributes (that may or may not be required).
+ // We could try hacking up one (or even both) of these, but just calling the two parsers
+ // in order seems slightly saner.
+ return ( abortable = ve.init.target.getContentApi( doc ).post( {
+ action: 'parse',
+ text: wikitext,
+ contentmodel: 'wikitext',
+ prop: 'text',
+ onlypst: true
+ } ) );
+ } )
+ .then( function ( pstResponse ) {
+ if ( aborted ) {
+ return abortedPromise;
+ }
+ var wikitext = ve.getProp( pstResponse, 'parse', 'text' );
+ if ( !wikitext ) {
+ return ve.createDeferred().reject();
+ }
+ return ( abortable = ve.init.target.parseWikitextFragment( wikitext, true, doc ) );
+ } )
+ .then( function ( parseResponse ) {
+ if ( aborted ) {
+ return abortedPromise;
+ }
+ if ( ve.getProp( parseResponse, 'visualeditor', 'result' ) !== 'success' ) {
+ return ve.createDeferred().reject();
+ }
+ // Simplified case of template rendering, don't need to worry about filtering etc
+ return $( parseResponse.visualeditor.content ).contents().toArray();
+ } )
+ .promise( { abort: abort } );
};
/* Registration */
diff --git a/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js b/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js
index 1e413e452a..c3b59c847e 100644
--- a/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js
+++ b/modules/ve-mw/init/targets/ve.init.mw.ArticleTarget.js
@@ -915,14 +915,18 @@ ve.init.mw.ArticleTarget.prototype.onSaveDialogReview = function () {
if ( !this.saveDialog.hasDiff ) {
this.emit( 'saveReview' );
this.saveDialog.pushPending();
- if ( this.pageExists ) {
- // Has no callback, handled via target.showChangesDiff
- this.showChanges( this.getDocToSave() );
- } else {
- this.serialize( this.getDocToSave() ).then( function ( data ) {
- target.onSaveDialogReviewComplete( data.content );
- } );
- }
+ // Acquire a temporary user username before diffing, so that signatures and
+ // user-related magic words display the temp user instead of IP user in the diff. (T331397)
+ mw.user.acquireTempUserName().then( function () {
+ if ( target.pageExists ) {
+ // Has no callback, handled via target.showChangesDiff
+ target.showChanges( target.getDocToSave() );
+ } else {
+ target.serialize( target.getDocToSave() ).then( function ( data ) {
+ target.onSaveDialogReviewComplete( data.content );
+ } );
+ }
+ } );
} else {
this.saveDialog.swapPanel( 'review' );
}
@@ -952,19 +956,23 @@ ve.init.mw.ArticleTarget.prototype.onSaveDialogPreview = function () {
params.variant = mw.config.get( 'wgUserVariant' );
}
- api.post( ve.extendObject( params, {
- action: 'parse',
- title: this.getPageName(),
- text: this.getDocToSave(),
- pst: true,
- preview: true,
- sectionpreview: this.section !== null,
- disableeditsection: true,
- uselang: mw.config.get( 'wgUserLanguage' ),
- useskin: mw.config.get( 'skin' ),
- mobileformat: OO.ui.isMobile(),
- prop: [ 'text', 'categorieshtml', 'displaytitle', 'subtitle', 'modules', 'jsconfigvars' ]
- } ) ).then( function ( response ) {
+ // Acquire a temporary user username before previewing, so that signatures and
+ // user-related magic words display the temp user instead of IP user in the preview. (T331397)
+ mw.user.acquireTempUserName().then( function () {
+ return api.post( ve.extendObject( params, {
+ action: 'parse',
+ title: target.getPageName(),
+ text: target.getDocToSave(),
+ pst: true,
+ preview: true,
+ sectionpreview: target.section !== null,
+ disableeditsection: true,
+ uselang: mw.config.get( 'wgUserLanguage' ),
+ useskin: mw.config.get( 'skin' ),
+ mobileformat: OO.ui.isMobile(),
+ prop: [ 'text', 'categorieshtml', 'displaytitle', 'subtitle', 'modules', 'jsconfigvars' ]
+ } ) );
+ } ).then( function ( response ) {
target.saveDialog.showPreview( response );
}, function ( errorCode, details ) {
target.saveDialog.showPreview( target.extractErrorMessages( details ) );
@@ -1035,14 +1043,18 @@ ve.init.mw.ArticleTarget.prototype.getVisualDiffGeneratorPromise = function () {
}
if ( mode === 'source' ) {
- var newRevPromise = target.getContentApi().post( {
- action: 'visualeditor',
- paction: 'parse',
- page: target.getPageName(),
- wikitext: target.getSurface().getDom(),
- section: target.section,
- stash: 0,
- pst: true
+ // Acquire a temporary user username before diffing, so that signatures and
+ // user-related magic words display the temp user instead of IP user in the diff. (T331397)
+ var newRevPromise = mw.user.acquireTempUserName().then( function () {
+ return target.getContentApi().post( {
+ action: 'visualeditor',
+ paction: 'parse',
+ page: target.getPageName(),
+ wikitext: target.getSurface().getDom(),
+ section: target.section,
+ stash: 0,
+ pst: true
+ } );
} ).then( function ( response ) {
// Source mode always fetches the whole document, so set section=null to unwrap sections
return mw.libs.ve.diffLoader.getModelFromResponse( response, null );
diff --git a/modules/ve-mw/init/targets/ve.init.mw.Target.js b/modules/ve-mw/init/targets/ve.init.mw.Target.js
index 3749c8a331..9cda401d68 100644
--- a/modules/ve-mw/init/targets/ve.init.mw.Target.js
+++ b/modules/ve-mw/init/targets/ve.init.mw.Target.js
@@ -615,13 +615,41 @@ ve.init.mw.Target.prototype.getWikitextFragment = function ( doc, useRevision )
* @return {jQuery.Promise} Abortable promise
*/
ve.init.mw.Target.prototype.parseWikitextFragment = function ( wikitext, pst, doc ) {
- return this.getContentApi( doc ).post( {
- action: 'visualeditor',
- paction: 'parsefragment',
- page: this.getPageName( doc ),
- wikitext: wikitext,
- pst: pst
- } );
+ var target = this;
+ var abortable, aborted;
+ var abortedPromise = ve.createDeferred().reject( 'http',
+ { textStatus: 'abort', exception: 'abort' } ).promise();
+
+ function abort() {
+ aborted = true;
+ if ( abortable && abortable.abort ) {
+ abortable.abort();
+ }
+ }
+
+ // Acquire a temporary user username before previewing or diffing, so that signatures and
+ // user-related magic words display the temp user instead of IP user in the preview. (T331397)
+ var tempUserNamePromise;
+ if ( pst ) {
+ tempUserNamePromise = mw.user.acquireTempUserName();
+ } else {
+ tempUserNamePromise = ve.createDeferred().resolve( null );
+ }
+
+ return tempUserNamePromise
+ .then( function () {
+ if ( aborted ) {
+ return abortedPromise;
+ }
+ return ( abortable = target.getContentApi( doc ).post( {
+ action: 'visualeditor',
+ paction: 'parsefragment',
+ page: target.getPageName( doc ),
+ wikitext: wikitext,
+ pst: pst
+ } ) );
+ } )
+ .promise( { abort: abort } );
};
/**