mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-09-24 10:48:42 +00:00
Allow switching from wikitext to VE
Just by pressing the VE tab for now Requires a relatively new version of restbase Bug: T49779 Change-Id: I2a5294345f5e0f469c1dd1bdd29dbce211571a4e
This commit is contained in:
parent
297d7d2cfc
commit
85b745666f
|
@ -85,7 +85,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
return new $class( $params );
|
||||
}
|
||||
|
||||
private function requestRestbase( $method, $path, $params ) {
|
||||
private function requestRestbase( $method, $path, $params, $reqheaders = array() ) {
|
||||
$request = array(
|
||||
'method' => $method,
|
||||
'url' => '/restbase/local/v1/' . $path
|
||||
|
@ -95,6 +95,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
} else {
|
||||
$request['body'] = $params;
|
||||
}
|
||||
$request['headers'] = $reqheaders;
|
||||
$response = $this->serviceClient->run( $request );
|
||||
if ( $response['code'] === 200 && $response['error'] === "" ) {
|
||||
// If response was served directly from Varnish, use the response
|
||||
|
@ -116,11 +117,11 @@ class ApiVisualEditor extends ApiBase {
|
|||
return $response['body'];
|
||||
}
|
||||
|
||||
protected function storeInSerializationCache( $title, $oldid, $html ) {
|
||||
protected function storeInSerializationCache( $title, $oldid, $html, $etag ) {
|
||||
global $wgMemc;
|
||||
|
||||
// Convert the VE HTML to wikitext
|
||||
$text = $this->postHTML( $title, $html, array( 'oldid' => $oldid ) );
|
||||
$text = $this->postHTML( $title, $html, array( 'oldid' => $oldid ), $etag );
|
||||
if ( $text === false ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
return $wgMemc->get( $key );
|
||||
}
|
||||
|
||||
protected function postHTML( $title, $html, $parserParams ) {
|
||||
protected function postHTML( $title, $html, $parserParams, $etag ) {
|
||||
if ( $parserParams['oldid'] === 0 ) {
|
||||
$parserParams['oldid'] = '';
|
||||
}
|
||||
|
@ -163,7 +164,8 @@ class ApiVisualEditor extends ApiBase {
|
|||
array(
|
||||
'html' => $html,
|
||||
'scrub_wikitext' => 1,
|
||||
)
|
||||
),
|
||||
array( 'If-Match' => $etag )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -568,7 +570,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
if ( $params['html'] === null ) {
|
||||
$this->dieUsageMsg( 'missingparam', 'html' );
|
||||
}
|
||||
$content = $this->postHTML( $title, $html, $parserParams );
|
||||
$content = $this->postHTML( $title, $html, $parserParams, $params['etag'] );
|
||||
if ( $content === false ) {
|
||||
$this->dieUsage( 'Error contacting the document server', 'docserver' );
|
||||
}
|
||||
|
@ -583,7 +585,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
$this->dieUsage( 'No cached serialization found with that key', 'badcachekey' );
|
||||
}
|
||||
} else {
|
||||
$wikitext = $this->postHTML( $title, $html, $parserParams );
|
||||
$wikitext = $this->postHTML( $title, $html, $parserParams, $params['etag'] );
|
||||
if ( $wikitext === false ) {
|
||||
$this->dieUsage( 'Error contacting the document server', 'docserver' );
|
||||
}
|
||||
|
@ -601,7 +603,12 @@ class ApiVisualEditor extends ApiBase {
|
|||
if ( !isset( $parserParams['oldid'] ) ) {
|
||||
$parserParams['oldid'] = Revision::newFromTitle( $title )->getId();
|
||||
}
|
||||
$key = $this->storeInSerializationCache( $title, $parserParams['oldid'], $html );
|
||||
$key = $this->storeInSerializationCache(
|
||||
$title,
|
||||
$parserParams['oldid'],
|
||||
$html,
|
||||
$params['etag']
|
||||
);
|
||||
$result = array( 'result' => 'success', 'cachekey' => $key );
|
||||
break;
|
||||
|
||||
|
@ -669,6 +676,7 @@ class ApiVisualEditor extends ApiBase {
|
|||
'wikitext' => null,
|
||||
'oldid' => null,
|
||||
'html' => null,
|
||||
'etag' => null,
|
||||
'cachekey' => null,
|
||||
'pst' => false,
|
||||
);
|
||||
|
|
|
@ -149,7 +149,7 @@ class ApiVisualEditorEdit extends ApiVisualEditor {
|
|||
$this->dieUsage( 'No cached serialization found with that key', 'badcachekey' );
|
||||
}
|
||||
} else {
|
||||
$wikitext = $this->postHTML( $page, $html, $parserParams );
|
||||
$wikitext = $this->postHTML( $page, $html, $parserParams, $params['etag'] );
|
||||
if ( $wikitext === false ) {
|
||||
$this->dieUsage( 'Error contacting the Parsoid/RESTbase server', 'docserver' );
|
||||
}
|
||||
|
@ -253,6 +253,7 @@ class ApiVisualEditorEdit extends ApiVisualEditor {
|
|||
'minor' => null,
|
||||
'watch' => null,
|
||||
'html' => null,
|
||||
'etag' => null,
|
||||
'summary' => null,
|
||||
'captchaid' => null,
|
||||
'captchaword' => null,
|
||||
|
@ -285,6 +286,7 @@ class ApiVisualEditorEdit extends ApiVisualEditor {
|
|||
'oldid' => 'The revision number to use. Defaults to latest revision. Use 0 for new page.',
|
||||
'minor' => 'Flag for minor edit.',
|
||||
'html' => 'HTML to send to Parsoid in exchange for wikitext',
|
||||
'etag' => 'ETag to send',
|
||||
'summary' => 'Edit summary',
|
||||
'basetimestamp' => 'When saving, set this to the timestamp of the revision that was'
|
||||
. ' edited. Used to detect edit conflicts.',
|
||||
|
|
|
@ -469,6 +469,7 @@ class VisualEditorHooks {
|
|||
'namespacesWithSubpages' => $coreConfig->get( 'NamespacesWithSubpages' ),
|
||||
'specialBooksources' => urldecode( SpecialPage::getTitleFor( 'Booksources' )->getPrefixedURL() ),
|
||||
'restbaseUrl' => $coreConfig->get( 'VisualEditorRestbaseURL' ),
|
||||
'fullRestbaseUrl' => $coreConfig->get( 'VisualEditorFullRestbaseURL' ),
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"visualeditor-enable-experimental": "ext.visualEditor.experimental"
|
||||
},
|
||||
"VisualEditorRestbaseURL": false,
|
||||
"VisualEditorFullRestbaseURL": false,
|
||||
"VisualEditorSerializationCacheTimeout": 3600,
|
||||
"VisualEditorUseChangeTagging": true,
|
||||
"VisualEditorSupportedSkins": [
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"apihelp-visualeditor-param-cachekey": "For serialize or diff, use the result of a previous serializeforcache request with this key. Overrides $1html.",
|
||||
"apihelp-visualeditor-param-format": "",
|
||||
"apihelp-visualeditor-param-html": "HTML to send to Parsoid to convert to wikitext.",
|
||||
"apihelp-visualeditor-param-etag": "ETag to send.",
|
||||
"apihelp-visualeditor-param-oldid": "The revision number to use (defaults to latest revision).",
|
||||
"apihelp-visualeditor-param-paction": "Action to perform.",
|
||||
"apihelp-visualeditor-param-page": "The page to perform actions on.",
|
||||
|
@ -35,6 +36,7 @@
|
|||
"apihelp-visualeditoredit-param-captchaid": "Captcha ID (when saving with a captcha response).",
|
||||
"apihelp-visualeditoredit-param-captchaword": "Answer to the captcha (when saving with a captcha response).",
|
||||
"apihelp-visualeditoredit-param-html": "HTML to send to Parsoid in exchange for wikitext.",
|
||||
"apihelp-visualeditoredit-param-etag": "ETag to send.",
|
||||
"apihelp-visualeditoredit-param-minor": "Flag for minor edit.",
|
||||
"apihelp-visualeditoredit-param-needcheck": "When saving, set this parameter if the revision might have roundtrip problems. This will result in the edit being tagged.",
|
||||
"apihelp-visualeditoredit-param-oldid": "The revision number to use. Defaults to latest revision. Use 0 for a new page.",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"apihelp-visualeditor-param-cachekey": "In computer science, in the context of data storage, serialization is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer, or transmitted across a network connection link) and reconstructed later in the same or another computer environment.\n{{doc-apihelp-param|visualeditor|cachekey}}",
|
||||
"apihelp-visualeditor-param-format": "{{doc-apihelp-param|visualeditor|format}}",
|
||||
"apihelp-visualeditor-param-html": "{{doc-apihelp-param|visualeditor|html}}",
|
||||
"apihelp-visualeditor-param-etag": "{{doc-apihelp-param|visualeditor|etag}}",
|
||||
"apihelp-visualeditor-param-oldid": "{{doc-apihelp-param|visualeditor|oldid}}",
|
||||
"apihelp-visualeditor-param-paction": "{{doc-apihelp-param|visualeditor|paction}}",
|
||||
"apihelp-visualeditor-param-page": "{{doc-apihelp-param|visualeditor|page}}",
|
||||
|
@ -45,6 +46,7 @@
|
|||
"apihelp-visualeditoredit-param-captchaid": "{{doc-apihelp-param|visualeditoredit|captchaid}}",
|
||||
"apihelp-visualeditoredit-param-captchaword": "{{doc-apihelp-param|visualeditoredit|captchaword}}",
|
||||
"apihelp-visualeditoredit-param-html": "{{doc-apihelp-param|visualeditoredit|html}}",
|
||||
"apihelp-visualeditoredit-param-etag": "{{doc-apihelp-param|visualeditoredit|etag}}",
|
||||
"apihelp-visualeditoredit-param-minor": "{{doc-apihelp-param|visualeditoredit|minor}}",
|
||||
"apihelp-visualeditoredit-param-needcheck": "{{doc-apihelp-param|visualeditoredit|needcheck}}",
|
||||
"apihelp-visualeditoredit-param-oldid": "{{doc-apihelp-param|visualeditoredit|oldid}}",
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
.then( function () {
|
||||
return mw.libs.ve.targetLoader.requestPageData(
|
||||
mw.config.get( 'wgRelevantPageName' ),
|
||||
uri.query.oldid,
|
||||
uri.query.oldid || $( 'input[name=parentRevId]' ).val(),
|
||||
'mwTarget' // ve.init.mw.DesktopArticleTarget.static.name
|
||||
);
|
||||
} )
|
||||
|
@ -206,6 +206,9 @@
|
|||
isViewPage = (
|
||||
mw.config.get( 'wgIsArticle' ) &&
|
||||
!( 'diff' in uri.query )
|
||||
) || (
|
||||
mw.config.get( 'wgAction' ) === 'edit' ||
|
||||
mw.config.get( 'wgAction' ) === 'submit'
|
||||
);
|
||||
// On a view page, extend the current URI so parameters like oldid are carried over
|
||||
// On a non-view page, use viewUri
|
||||
|
|
|
@ -60,7 +60,7 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
|
|||
this.initialEditSummary = currentUri.query.summary;
|
||||
this.namespaceName = mw.config.get( 'wgCanonicalNamespace' );
|
||||
this.viewUri = new mw.Uri( mw.util.getUrl( this.pageName ) );
|
||||
this.veEditUri = this.viewUri.clone().extend( { veaction: 'edit' } );
|
||||
this.veEditUri = this.viewUri.clone().extend( { veaction: 'edit', action: null } );
|
||||
this.isViewPage = (
|
||||
mw.config.get( 'wgAction' ) === 'view' &&
|
||||
currentUri.query.diff === undefined
|
||||
|
@ -758,7 +758,7 @@ ve.init.mw.DesktopArticleTarget.prototype.onToolbarMetaButtonClick = function ()
|
|||
* @inheritdoc
|
||||
*/
|
||||
ve.init.mw.DesktopArticleTarget.prototype.editSource = function () {
|
||||
if ( !this.getSurface().getModel().hasBeenModified() ) {
|
||||
if ( !this.getSurface().getModel().hasBeenModified() && !this.fromEditedState ) {
|
||||
this.switchToWikitextEditor( true, false );
|
||||
return;
|
||||
}
|
||||
|
@ -848,7 +848,7 @@ ve.init.mw.DesktopArticleTarget.prototype.setupSkinTabs = function () {
|
|||
if ( target.getSurface() && !target.deactivating ) {
|
||||
target.editSource();
|
||||
|
||||
if ( target.getSurface().getModel().hasBeenModified() ) {
|
||||
if ( target.getSurface().getModel().hasBeenModified() || target.fromEditedState ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@ -1001,6 +1001,7 @@ ve.init.mw.DesktopArticleTarget.prototype.transformPage = function () {
|
|||
// Set the current URL
|
||||
uri = this.currentUri;
|
||||
uri.query.veaction = 'edit';
|
||||
delete uri.query.action;
|
||||
|
||||
history.pushState( this.popState, document.title, uri );
|
||||
}
|
||||
|
@ -1032,6 +1033,9 @@ ve.init.mw.DesktopArticleTarget.prototype.restorePage = function () {
|
|||
if ( 'vesection' in uri.query ) {
|
||||
delete uri.query.vesection;
|
||||
}
|
||||
if ( 'action' in uri.query ) {
|
||||
delete uri.query.action;
|
||||
}
|
||||
|
||||
// If there are any other query parameters left, re-use that uri object.
|
||||
// Otherwise use the canonical style view url (T44553, T102363).
|
||||
|
|
|
@ -293,8 +293,7 @@ ve.init.mw.Target.static.fixBase = function ( doc ) {
|
|||
* @param {string} status Text status message
|
||||
*/
|
||||
ve.init.mw.Target.prototype.loadSuccess = function ( response ) {
|
||||
var i, len, linkData, aboutDoc, docRevIdMatches,
|
||||
docRevId = 0,
|
||||
var i, len, linkData, aboutDoc, docRevId, docRevIdMatches,
|
||||
data = response ? response.visualeditor : null;
|
||||
|
||||
if ( typeof data.content !== 'string' ) {
|
||||
|
@ -302,6 +301,8 @@ ve.init.mw.Target.prototype.loadSuccess = function ( response ) {
|
|||
} else {
|
||||
ve.track( 'trace.parseResponse.enter' );
|
||||
this.originalHtml = data.content;
|
||||
this.etag = data.etag;
|
||||
this.fromEditedState = data.fromEditedState;
|
||||
this.doc = ve.parseXhtml( this.originalHtml );
|
||||
|
||||
// Fix relative or missing base URL if needed
|
||||
|
@ -321,7 +322,7 @@ ve.init.mw.Target.prototype.loadSuccess = function ( response ) {
|
|||
docRevId = parseInt( docRevIdMatches[ 1 ] );
|
||||
}
|
||||
}
|
||||
if ( docRevId !== this.revid ) {
|
||||
if ( docRevId && docRevId !== this.revid ) {
|
||||
if ( this.retriedRevIdConflict ) {
|
||||
// Retried already, just error the second time.
|
||||
this.loadFail(
|
||||
|
@ -388,7 +389,7 @@ ve.init.mw.Target.prototype.onReady = function () {
|
|||
);
|
||||
|
||||
this.loading = false;
|
||||
this.edited = false;
|
||||
this.edited = this.fromEditedState;
|
||||
this.setupSurface( this.doc, function () {
|
||||
// loadSuccess() may have called setAssumeExistence( true );
|
||||
ve.init.platform.linkCache.setAssumeExistence( false );
|
||||
|
@ -1264,7 +1265,8 @@ ve.init.mw.Target.prototype.prepareCacheKey = function ( doc ) {
|
|||
paction: 'serializeforcache',
|
||||
html: deflatedHtml,
|
||||
page: target.pageName,
|
||||
oldid: target.revid
|
||||
oldid: target.revid,
|
||||
etag: target.etag
|
||||
},
|
||||
{ contentType: 'multipart/form-data' }
|
||||
);
|
||||
|
@ -1544,7 +1546,8 @@ ve.init.mw.Target.prototype.save = function ( doc, options ) {
|
|||
oldid: this.revid,
|
||||
basetimestamp: this.baseTimeStamp,
|
||||
starttimestamp: this.startTimeStamp,
|
||||
token: this.editToken
|
||||
token: this.editToken,
|
||||
etag: this.etag
|
||||
} );
|
||||
|
||||
this.saving = this.tryWithPreparedCacheKey( doc, data, 'save' )
|
||||
|
@ -1569,7 +1572,8 @@ ve.init.mw.Target.prototype.showChanges = function ( doc ) {
|
|||
action: 'visualeditor',
|
||||
paction: 'diff',
|
||||
page: this.pageName,
|
||||
oldid: this.revid
|
||||
oldid: this.revid,
|
||||
etag: this.etag
|
||||
}, 'diff' )
|
||||
.done( this.showChangesSuccess.bind( this ) )
|
||||
.fail( this.showChangesFail.bind( this ) );
|
||||
|
@ -1646,7 +1650,8 @@ ve.init.mw.Target.prototype.serialize = function ( doc, callback ) {
|
|||
action: 'visualeditor',
|
||||
paction: 'serialize',
|
||||
page: this.pageName,
|
||||
oldid: this.revid
|
||||
oldid: this.revid,
|
||||
etag: this.etag
|
||||
}, 'serialize' )
|
||||
.done( ve.init.mw.Target.prototype.serializeSuccess.bind( this ) )
|
||||
.fail( ve.init.mw.Target.prototype.serializeFail.bind( this ) );
|
||||
|
@ -1773,7 +1778,7 @@ ve.init.mw.Target.prototype.attachToolbarSaveButton = function () {
|
|||
ve.init.mw.Target.prototype.updateToolbarSaveButtonState = function () {
|
||||
var isDisabled;
|
||||
|
||||
this.edited = this.getSurface().getModel().hasBeenModified();
|
||||
this.edited = this.getSurface().getModel().hasBeenModified() || this.fromEditedState;
|
||||
// Disable the save button if we have no history
|
||||
isDisabled = !this.edited && !this.restoring;
|
||||
this.toolbarSaveButton.setDisabled( isDisabled );
|
||||
|
|
|
@ -93,10 +93,11 @@
|
|||
* @return {jQuery.Promise} Abortable promise resolved with a JSON object
|
||||
*/
|
||||
requestPageData: function ( pageName, oldid, targetName ) {
|
||||
var start, apiXhr, restbaseXhr, apiPromise, restbasePromise, dataPromise,
|
||||
var start, apiXhr, restbaseXhr, apiPromise, restbasePromise, dataPromise, pageHtmlUrl,
|
||||
fromEditedState = false,
|
||||
data = {
|
||||
action: 'visualeditor',
|
||||
paction: conf.restbaseUrl ? 'metadata' : 'parse',
|
||||
paction: ( conf.fullRestbaseUrl || conf.restbaseUrl ) ? 'metadata' : 'parse',
|
||||
page: pageName,
|
||||
uselang: mw.config.get( 'wgUserLanguage' )
|
||||
};
|
||||
|
@ -124,14 +125,34 @@
|
|||
return data;
|
||||
} );
|
||||
|
||||
if ( conf.restbaseUrl ) {
|
||||
if ( conf.fullRestbaseUrl || conf.restbaseUrl ) {
|
||||
ve.track( 'trace.restbaseLoad.enter' );
|
||||
restbaseXhr = $.ajax( {
|
||||
url: conf.restbaseUrl + encodeURIComponent( pageName ) +
|
||||
( oldid === undefined ? '' : '/' + oldid ),
|
||||
type: 'GET',
|
||||
dataType: 'text'
|
||||
} );
|
||||
if ( conf.fullRestbaseUrl && $( '#wpTextbox1' ).length ) {
|
||||
fromEditedState = true;
|
||||
restbaseXhr = $.post(
|
||||
conf.fullRestbaseUrl + 'v1/transform/wikitext/to/html/' +
|
||||
encodeURIComponent( pageName ) +
|
||||
( oldid === undefined ? '' : '/' + oldid ),
|
||||
{
|
||||
title: pageName,
|
||||
oldid: oldid,
|
||||
wikitext: $( '#wpTextbox1' ).val(),
|
||||
stash: 'true'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if ( conf.fullRestbaseUrl ) {
|
||||
pageHtmlUrl = conf.fullRestbaseUrl + 'v1/page/html/';
|
||||
} else {
|
||||
pageHtmlUrl = conf.restbaseUrl;
|
||||
}
|
||||
restbaseXhr = $.ajax( {
|
||||
url: pageHtmlUrl + encodeURIComponent( pageName ) +
|
||||
( oldid === undefined ? '' : '/' + oldid ),
|
||||
type: 'GET',
|
||||
dataType: 'text'
|
||||
} );
|
||||
}
|
||||
restbasePromise = restbaseXhr.then(
|
||||
function ( data, status, jqxhr ) {
|
||||
ve.track( 'trace.restbaseLoad.exit' );
|
||||
|
@ -140,7 +161,7 @@
|
|||
duration: ve.now() - start,
|
||||
targetName: targetName
|
||||
} );
|
||||
return data;
|
||||
return [ data, jqxhr.getResponseHeader( 'etag' ) ];
|
||||
},
|
||||
function ( response ) {
|
||||
if ( response.status === 404 ) {
|
||||
|
@ -155,9 +176,11 @@
|
|||
);
|
||||
|
||||
dataPromise = $.when( apiPromise, restbasePromise )
|
||||
.then( function ( apiData, restbaseHtml ) {
|
||||
.then( function ( apiData, restbaseData ) {
|
||||
if ( apiData.visualeditor ) {
|
||||
apiData.visualeditor.content = restbaseHtml;
|
||||
apiData.visualeditor.content = restbaseData[ 0 ];
|
||||
apiData.visualeditor.etag = restbaseData[ 1 ];
|
||||
apiData.visualeditor.fromEditedState = fromEditedState;
|
||||
}
|
||||
return apiData;
|
||||
} )
|
||||
|
|
Loading…
Reference in a new issue