Section editing in NWE

Bug: T144654
Change-Id: Ida6e721e0d980b47e3fda6a1f0744cbce1b2235a
This commit is contained in:
Ed Sanders 2016-09-06 12:16:55 -07:00
parent 9694ef6e63
commit 4040442ad8
10 changed files with 122 additions and 55 deletions

View file

@ -190,13 +190,15 @@ class ApiVisualEditor extends ApiBase {
);
}
protected function diffWikitext( $title, $wikitext ) {
protected function diffWikitext( $title, $wikitext, $section = null ) {
$apiParams = [
'action' => 'query',
'prop' => 'revisions',
'titles' => $title->getPrefixedDBkey(),
'rvdifftotext' => $this->pstWikitext( $title, $wikitext )
'rvdifftotext' => $this->pstWikitext( $title, $wikitext ),
'rvsection' => $section
];
$api = new ApiMain(
new DerivativeRequest(
$this->getRequest(),
@ -345,6 +347,11 @@ class ApiVisualEditor extends ApiBase {
'prop' => 'revisions',
'rvprop' => 'content'
];
if ( isset( $params['section'] ) ) {
$apiParams['rvsection'] = $params['section'];
}
$api = new ApiMain(
new DerivativeRequest(
$this->getRequest(),
@ -623,7 +630,8 @@ class ApiVisualEditor extends ApiBase {
}
}
$diff = $this->diffWikitext( $title, $wikitext );
$section = isset( $params['section'] ) ? $params['section'] : null;
$diff = $this->diffWikitext( $title, $wikitext, $section );
if ( $diff['result'] === 'fail' ) {
$this->dieUsage( 'Diff failed', 'difffailed' );
}
@ -759,6 +767,7 @@ class ApiVisualEditor extends ApiBase {
],
],
'wikitext' => null,
'section' => null,
'oldid' => null,
'html' => null,
'etag' => null,

View file

@ -252,6 +252,7 @@ class ApiVisualEditorEdit extends ApiVisualEditor {
ApiBase::PARAM_REQUIRED => true,
],
'wikitext' => null,
'section' => null,
'basetimestamp' => null,
'starttimestamp' => null,
'oldid' => null,

View file

@ -28,6 +28,7 @@
"apihelp-visualeditor-param-paction": "Action to perform.",
"apihelp-visualeditor-param-page": "The page to perform actions on.",
"apihelp-visualeditor-param-pst": "Pre-save transform wikitext before sending it to Parsoid (paction=parsefragment).",
"apihelp-visualeditor-param-section": "The section on which to act.",
"apihelp-visualeditor-param-starttimestamp": "When saving, set this to the timestamp of when the page was loaded. Used to detect edit conflicts.",
"apihelp-visualeditor-param-wikitext": "Wikitext to send to Parsoid to convert to HTML (paction=parsefragment).",
"apihelp-visualeditoredit-description": "Save an HTML5 page to MediaWiki (converted to wikitext via the Parsoid service).",
@ -41,6 +42,7 @@
"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.",
"apihelp-visualeditoredit-param-page": "The page to perform actions on.",
"apihelp-visualeditoredit-param-section": "The section on which to act.",
"apihelp-visualeditoredit-param-starttimestamp": "When saving, set this to the timestamp of when the page was loaded. Used to detect edit conflicts.",
"apihelp-visualeditoredit-param-summary": "Edit summary.",
"apihelp-visualeditoredit-param-watch": "",

View file

@ -41,6 +41,7 @@
"apihelp-visualeditor-param-paction": "{{doc-apihelp-param|visualeditor|paction}}",
"apihelp-visualeditor-param-page": "{{doc-apihelp-param|visualeditor|page}}",
"apihelp-visualeditor-param-pst": "{{doc-apihelp-param|visualeditor|pst}}",
"apihelp-visualeditor-param-section": "{{doc-apihelp-param|visualeditor|section}}",
"apihelp-visualeditor-param-starttimestamp": "{{doc-apihelp-param|visualeditor|starttimestamp}}",
"apihelp-visualeditor-param-wikitext": "{{doc-apihelp-param|visualeditor|wikitext}}",
"apihelp-visualeditoredit-description": "{{doc-apihelp-description|visualeditoredit}}",
@ -54,6 +55,7 @@
"apihelp-visualeditoredit-param-needcheck": "{{doc-apihelp-param|visualeditoredit|needcheck}}",
"apihelp-visualeditoredit-param-oldid": "{{doc-apihelp-param|visualeditoredit|oldid}}",
"apihelp-visualeditoredit-param-page": "{{doc-apihelp-param|visualeditoredit|page}}",
"apihelp-visualeditoredit-param-section": "{{doc-apihelp-param|visualeditoredit|section}}",
"apihelp-visualeditoredit-param-starttimestamp": "{{doc-apihelp-param|visualeditoredit|starttimestamp}}",
"apihelp-visualeditoredit-param-summary": "{{doc-apihelp-param|visualeditoredit|summary}}\n{{Identical|Edit summary}}",
"apihelp-visualeditoredit-param-watch": "{{doc-apihelp-param|visualeditoredit|watch}}",

View file

@ -197,7 +197,7 @@
uri = veEditUri;
}
activateTarget( mode, null, modified );
activateTarget( mode, null, undefined, modified );
}
}
@ -210,10 +210,11 @@
*
* @private
* @param {string} mode Target mode: 'visual' or 'source'
* @param {number} [section] Section to edit (currently just source mode)
* @param {jQuery.Promise} [targetPromise] Promise that will be resolved with a ve.init.mw.DesktopArticleTarget
* @param {boolean} [modified] The page was been modified before loading (e.g. in source mode)
*/
function activateTarget( mode, targetPromise, modified ) {
function activateTarget( mode, section, targetPromise, modified ) {
var dataPromise;
// Only call requestPageData early if the target object isn't there yet.
// If the target object is there, this is a second or subsequent load, and the
@ -226,6 +227,7 @@
return mw.libs.ve.targetLoader.requestPageData(
mode,
mw.config.get( 'wgRelevantPageName' ),
section,
oldid,
'mwTarget', // ve.init.mw.DesktopArticleTarget.static.name
modified
@ -459,10 +461,10 @@
$caVeEdit.remove();
} else if ( pageCanLoadVE ) {
// Allow instant switching to edit mode, without refresh
$caVeEdit.on( 'click', init.onEditTabClick );
$caVeEdit.on( 'click', init.onEditTabClick.bind( init, 'visual' ) );
}
if ( conf.enableWikitext && mw.user.options.get( 'visualeditor-newwikitext' ) ) {
$caEdit.on( 'click', init.onEditSourceTabClick );
$caEdit.on( 'click', init.onEditTabClick.bind( init, 'source' ) );
}
// Alter the edit tab (#ca-edit)
@ -526,6 +528,7 @@
} );
} )
.addClass( 'mw-editsection-visualeditor' );
if ( conf.tabPosition === 'before' ) {
$editSourceLink.before( $editLink, $divider );
} else {
@ -543,8 +546,13 @@
// and would preserve the wrong DOM with a diff on top.
$editsections
.find( '.mw-editsection-visualeditor' )
.on( 'click', init.onEditSectionLinkClick )
;
.on( 'click', init.onEditSectionLinkClick.bind( init, 'visual' ) );
if ( conf.enableWikitext ) {
$editsections
// TOOD: Make this less fragile
.find( 'a:not( .mw-editsection-visualeditor )' )
.on( 'click', init.onEditSectionLinkClick.bind( init, 'source' ) );
}
}
},
@ -562,7 +570,7 @@
return e && e.which && e.which === 1 && !( e.shiftKey || e.altKey || e.ctrlKey || e.metaKey );
},
onEditTabClick: function ( e ) {
onEditTabClick: function ( mode, e ) {
if ( !init.isUnmodifiedLeftClick( e ) ) {
return;
}
@ -577,21 +585,10 @@
}
} );
} else {
init.activateVe( 'visual' );
init.activateVe( mode );
}
},
onEditSourceTabClick: function ( e ) {
if ( !init.isUnmodifiedLeftClick( e ) ) {
return;
}
e.preventDefault();
if ( isLoading ) {
return;
}
init.activateVe( 'source' );
},
activateVe: function ( mode ) {
var wikitext = $( '#wpTextbox1' ).textSelection( 'getContents' );
@ -639,8 +636,8 @@
}
},
onEditSectionLinkClick: function ( e ) {
var targetPromise;
onEditSectionLinkClick: function ( mode, e ) {
var section, targetPromise;
if ( !init.isUnmodifiedLeftClick( e ) ) {
return;
}
@ -661,11 +658,20 @@
history.pushState( { tag: 'visualeditor' }, document.title, this.href );
}
targetPromise = getTarget( 'visual' ).then( function ( target ) {
target.saveEditSection( $( e.target ).closest( 'h1, h2, h3, h4, h5, h6' ).get( 0 ) );
return target;
} );
activateTarget( 'visual', targetPromise );
targetPromise = getTarget( mode );
if ( mode === 'visual' ) {
targetPromise = targetPromise.then( function ( target ) {
target.saveEditSection( $( e.target ).closest( 'h1, h2, h3, h4, h5, h6' ).get( 0 ) );
return target;
} );
} else {
section = +( new mw.Uri( e.target.href ).query.section );
targetPromise = targetPromise.then( function ( target ) {
target.section = section;
return target;
} );
}
activateTarget( mode, section, targetPromise );
}
};
@ -765,6 +771,7 @@
$( function () {
var showWikitextWelcome = true,
section = uri.query.vesection !== undefined ? uri.query.vesection : null,
isLoggedIn = !mw.user.isAnon(),
prefSaysShowWelcome = isLoggedIn && !mw.user.options.get( 'visualeditor-hidebetawelcome' ),
urlSaysHideWelcome = 'hidewelcomedialog' in new mw.Uri( location.href ).query,
@ -838,7 +845,7 @@
) {
showWikitextWelcome = false;
trackActivateStart( {
type: uri.query.vesection === undefined ? 'page' : 'section',
type: section === null ? 'page' : 'section',
mechanism: 'url'
} );
if ( isViewPage && uri.query.veaction in editModes ) {
@ -857,7 +864,7 @@
) {
action = 'editsource';
}
activateTarget( editModes[ action ] );
activateTarget( editModes[ action ], section );
}
} else if (
init.isVisualAvailable &&

View file

@ -56,7 +56,7 @@ ve.init.mw.DesktopArticleTarget = function VeInitMwDesktopArticleTarget( config
};
this.scrollTop = null;
this.currentUri = currentUri;
this.section = currentUri.query.vesection;
this.section = currentUri.query.vesection !== undefined ? +currentUri.query.vesection : null;
if ( $( '#wpSummary' ).length ) {
this.initialEditSummary = $( '#wpSummary' ).val();
} else {

View file

@ -66,19 +66,40 @@ ve.init.mw.DesktopWikitextArticleTarget.prototype.switchToWikitextEditor = funct
* Switch to the visual editor.
*/
ve.init.mw.DesktopWikitextArticleTarget.prototype.switchToVisualEditor = function () {
var dataPromise;
var dataPromise, windowManager, switchWindow,
target = this;
dataPromise = mw.libs.ve.targetLoader.requestParsoidData(
this.pageName,
this.revid,
this.constructor.name,
this.edited,
this.getDocToSave()
);
if ( this.section !== null ) {
// WT -> VE switching is not yet supported in sections, so
// show a discard-only confirm dialog, then reload the whole page.
windowManager = new OO.ui.WindowManager();
switchWindow = new mw.libs.ve.SwitchConfirmDialog();
$( 'body' ).append( windowManager.$element );
windowManager.addWindows( [ switchWindow ] );
windowManager.openWindow( switchWindow, { mode: 'simple' } )
.then( function ( opened ) {
return opened;
} )
.then( function ( closing ) { return closing; } )
.then( function ( data ) {
if ( data && data.action === 'discard' ) {
target.setMode( 'visual' );
target.reloadSurface();
}
windowManager.destroy();
} );
} else {
dataPromise = mw.libs.ve.targetLoader.requestParsoidData(
this.pageName,
this.revid,
this.constructor.name,
this.edited,
this.getDocToSave()
);
this.setMode( 'visual' );
this.reloadSurface( dataPromise );
this.setMode( 'visual' );
this.reloadSurface( dataPromise );
}
};
/**
@ -205,6 +226,16 @@ ve.init.mw.DesktopWikitextArticleTarget.prototype.createSurface = function ( dmD
}
};
/**
* @inheritdoc
*/
ve.init.mw.DesktopWikitextArticleTarget.prototype.restoreEditSection = function () {
if ( this.mode !== 'source' ) {
// Parent method
return ve.init.mw.DesktopWikitextArticleTarget.super.prototype.restoreEditSection.apply( this, arguments );
}
};
/**
* Get a wikitext fragment from a document
*
@ -309,11 +340,17 @@ ve.init.mw.DesktopWikitextArticleTarget.prototype.createDocToSave = function ()
* @inheritdoc
*/
ve.init.mw.DesktopWikitextArticleTarget.prototype.tryWithPreparedCacheKey = function ( doc, options ) {
var data;
if ( this.mode === 'source' ) {
return new mw.Api().post( ve.extendObject( {}, options, {
wikitext: doc,
format: 'json'
} ),
data = {
wikitext: doc,
format: 'json'
};
if ( this.section !== null ) {
data.section = this.section;
}
return new mw.Api().post(
ve.extendObject( {}, options, data ),
{ contentType: 'multipart/form-data' }
);
} else {

View file

@ -64,10 +64,12 @@ mw.libs.ve.SwitchConfirmDialog.static.actions = [
/**
* @inheritdoc
*/
mw.libs.ve.SwitchConfirmDialog.prototype.getSetupProcess = function () {
mw.libs.ve.SwitchConfirmDialog.prototype.getSetupProcess = function ( data ) {
return mw.libs.ve.SwitchConfirmDialog.super.prototype.getSetupProcess.apply( this, arguments )
.next( function () {
if (
if ( data && data.mode ) {
this.actions.setMode( data.mode );
} else if (
mw.config.get( 'wgVisualEditorConfig' ).fullRestbaseUrl &&
!$( 'input[name=wpSection]' ).val()
) {

View file

@ -40,6 +40,7 @@ ve.init.mw.ArticleTarget = function VeInitMwArticleTarget( pageName, revisionId,
this.pageExists = mw.config.get( 'wgRelevantArticleId', 0 ) !== 0;
this.toolbarScrollOffset = mw.config.get( 'wgVisualEditorToolbarScrollOffset', 0 );
this.mode = config.mode || 'visual';
this.section = null;
// Sometimes we actually don't want to send a useful oldid
// if we do, PostEdit will give us a 'page restored' message
@ -1022,6 +1023,7 @@ ve.init.mw.ArticleTarget.prototype.load = function ( dataPromise ) {
this.loading = dataPromise || mw.libs.ve.targetLoader.requestPageData(
this.mode,
this.pageName,
this.section,
this.requestedRevId,
this.constructor.name
);
@ -1046,6 +1048,7 @@ ve.init.mw.ArticleTarget.prototype.clearState = function () {
this.startTimeStamp = null;
this.doc = null;
this.originalHtml = null;
this.section = null;
this.editNotices = [];
this.remoteNotices = [];
this.localNoticeMessages = [];
@ -1704,7 +1707,7 @@ ve.init.mw.ArticleTarget.prototype.getSaveDialogOpeningData = function () {
ve.init.mw.ArticleTarget.prototype.restoreEditSection = function () {
var surfaceView, $documentNode, $section, headingNode;
if ( this.section !== undefined && this.section > 0 ) {
if ( this.section !== null && this.section > 0 ) {
surfaceView = this.getSurface().getView();
$documentNode = surfaceView.getDocument().getDocumentNode().$element;
$section = $documentNode.find( 'h1, h2, h3, h4, h5, h6' ).eq( this.section - 1 );
@ -1718,8 +1721,6 @@ ve.init.mw.ArticleTarget.prototype.restoreEditSection = function () {
if ( headingNode ) {
this.goToHeading( headingNode );
}
this.section = undefined;
}
};

View file

@ -107,14 +107,15 @@
*
* @param {string} mode Target mode: 'visual' or 'source'
* @param {string} pageName Page name to request
* @param {number} [section] Section to edit (currently just source mode)
* @param {string} [oldid] Old revision ID, current if omitted
* @param {string} [targetName] Optional target name for tracking
* @param {boolean} [modified] The page was been modified before loading (e.g. in source mode)
* @return {jQuery.Promise} Abortable promise resolved with a JSON object
*/
requestPageData: function ( mode, pageName, oldid, targetName, modified ) {
requestPageData: function ( mode, pageName, section, oldid, targetName, modified ) {
if ( mode === 'source' ) {
return this.requestWikitext( pageName, oldid, targetName, modified );
return this.requestWikitext( pageName, section, oldid, targetName, modified );
} else {
return this.requestParsoidData( pageName, oldid, targetName, modified );
}
@ -245,7 +246,7 @@
return dataPromise;
},
requestWikitext: function ( pageName, oldid /*, targetName */ ) {
requestWikitext: function ( pageName, section, oldid /*, targetName */ ) {
var data = {
action: 'visualeditor',
paction: 'wikitext',
@ -253,6 +254,11 @@
uselang: mw.config.get( 'wgUserLanguage' )
};
// section should never really be undefined, but check just in case
if ( section !== null && section !== undefined ) {
data.section = section;
}
if ( oldid !== undefined ) {
data.oldid = oldid;
}