mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 02:23:58 +00:00
Wikitext mode: Use action=parse for preview
Using Parsoid HTML in the 2017WTE has enabled us to iron out lots of rendering bugs over the past few years. In that time Parsoid has been moved into PHP, and at some point we also become the default parser. Also more extensions have started to use content transform hooks, which are only supported by the action API. As a result it now seems like a good time to migrate back to the content API instead of building the preview from Parsoid HTML. Bug: T154844 Change-Id: I90d775dd71d5f5a61d651b63d946ab60a27e2ca3
This commit is contained in:
parent
b1a12aeaab
commit
3148e28f69
|
@ -953,21 +953,31 @@ ve.init.mw.ArticleTarget.prototype.onSaveDialogPreview = function () {
|
|||
this.emit( 'savePreview' );
|
||||
this.saveDialog.pushPending();
|
||||
|
||||
var wikitext = this.getDocToSave();
|
||||
if ( this.sectionTitle && this.sectionTitle.getValue() ) {
|
||||
wikitext = '== ' + this.sectionTitle.getValue() + ' ==\n\n' + wikitext;
|
||||
var params = {};
|
||||
|
||||
var sectionTitle = this.sectionTitle && this.sectionTitle.getValue();
|
||||
if ( sectionTitle ) {
|
||||
params.section = 'new';
|
||||
params.sectiontitle = sectionTitle;
|
||||
}
|
||||
if ( mw.config.get( 'wgUserVariant' ) ) {
|
||||
params.variant = mw.config.get( 'wgUserVariant' );
|
||||
}
|
||||
|
||||
api.post( {
|
||||
action: 'visualeditor',
|
||||
paction: 'parsedoc',
|
||||
page: this.getPageName(),
|
||||
wikitext: wikitext,
|
||||
pst: true
|
||||
} ).then( function ( response ) {
|
||||
var baseDoc = target.getSurface().getModel().getDocument().getHtmlDocument();
|
||||
var doc = target.constructor.static.parseDocument( response.visualeditor.content, 'visual' );
|
||||
target.saveDialog.showPreview( doc, baseDoc );
|
||||
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 ) {
|
||||
target.saveDialog.showPreview( response );
|
||||
}, function ( errorCode, details ) {
|
||||
target.saveDialog.showPreview( target.extractErrorMessages( details ) );
|
||||
} ).always( function () {
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
#catlinks {
|
||||
.ve-init-mw-desktopArticleTarget-originalContent #catlinks {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -214,31 +214,40 @@ mw.libs.ve.fixFragmentLinks = function ( container, docTitle, prefix ) {
|
|||
var docTitleText = docTitle.getPrefixedText();
|
||||
prefix = prefix || '';
|
||||
Array.prototype.forEach.call( container.querySelectorAll( 'a[href*="#"]' ), function ( el ) {
|
||||
var fragment = new mw.Uri( el.href ).fragment,
|
||||
targetData = mw.libs.ve.getTargetDataFromHref( el.href, el.ownerDocument );
|
||||
var fragment = null;
|
||||
if ( el.getAttribute( 'href' )[ 0 ] === '#' ) {
|
||||
// Leagcy parser
|
||||
fragment = el.getAttribute( 'href' ).slice( 1 );
|
||||
} else {
|
||||
// Parsoid HTML
|
||||
var targetData = mw.libs.ve.getTargetDataFromHref( el.href, el.ownerDocument );
|
||||
|
||||
if ( targetData.isInternal ) {
|
||||
var title = mw.Title.newFromText( targetData.title );
|
||||
if ( title && title.getPrefixedText() === docTitleText ) {
|
||||
if ( !fragment ) {
|
||||
// Special case for empty fragment, even if prefix set
|
||||
el.setAttribute( 'href', '#' );
|
||||
} else {
|
||||
if ( prefix ) {
|
||||
var target = container.querySelector( '#' + $.escapeSelector( fragment ) );
|
||||
// There may be multiple links to a specific target, so check the target
|
||||
// hasn't already been fixed (in which case it would be null)
|
||||
if ( target ) {
|
||||
target.setAttribute( 'id', prefix + fragment );
|
||||
target.setAttribute( 'data-mw-id-fixed', '' );
|
||||
}
|
||||
}
|
||||
el.setAttribute( 'href', '#' + prefix + fragment );
|
||||
if ( targetData.isInternal ) {
|
||||
var title = mw.Title.newFromText( targetData.title );
|
||||
if ( title && title.getPrefixedText() === docTitleText ) {
|
||||
fragment = new mw.Uri( el.href ).fragment;
|
||||
}
|
||||
el.removeAttribute( 'target' );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ( fragment !== null ) {
|
||||
if ( !fragment ) {
|
||||
// Special case for empty fragment, even if prefix set
|
||||
el.setAttribute( 'href', '#' );
|
||||
} else {
|
||||
if ( prefix ) {
|
||||
var target = container.querySelector( '#' + $.escapeSelector( fragment ) );
|
||||
// There may be multiple links to a specific target, so check the target
|
||||
// hasn't already been fixed (in which case it would be null)
|
||||
if ( target ) {
|
||||
target.setAttribute( 'id', prefix + fragment );
|
||||
target.setAttribute( 'data-mw-id-fixed', '' );
|
||||
}
|
||||
}
|
||||
el.setAttribute( 'href', '#' + prefix + fragment );
|
||||
}
|
||||
el.removeAttribute( 'target' );
|
||||
}
|
||||
} );
|
||||
// Remove any section heading anchors which weren't fixed above (T218492)
|
||||
Array.prototype.forEach.call( container.querySelectorAll( 'h1, h2, h3, h4, h5, h6' ), function ( el ) {
|
||||
|
@ -314,44 +323,6 @@ mw.libs.ve.getTargetDataFromHref = function ( href, doc ) {
|
|||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to
|
||||
* an array of module names like [ 'jquery.foo', 'jquery.bar',
|
||||
* 'jquery.ui.baz', 'jquery.ui.quux' ]
|
||||
*
|
||||
* Implementation of ResourceLoaderContext::expandModuleNames
|
||||
* TODO: Consider upstreaming this to MW core.
|
||||
*
|
||||
* @param {string} moduleNames Packed module name list
|
||||
* @return {string[]} Array of module names
|
||||
*/
|
||||
mw.libs.ve.expandModuleNames = function ( moduleNames ) {
|
||||
var modules = [];
|
||||
|
||||
moduleNames.split( '|' ).forEach( function ( group ) {
|
||||
if ( group.indexOf( ',' ) === -1 ) {
|
||||
// This is not a set of modules in foo.bar,baz notation
|
||||
// but a single module
|
||||
modules.push( group );
|
||||
} else {
|
||||
// This is a set of modules in foo.bar,baz notation
|
||||
var matches = group.match( /(.*)\.([^.]*)/ );
|
||||
if ( !matches ) {
|
||||
// Prefixless modules, i.e. without dots
|
||||
modules = modules.concat( group.split( ',' ) );
|
||||
} else {
|
||||
// We have a prefix and a bunch of suffixes
|
||||
var prefix = matches[ 1 ];
|
||||
var suffixes = matches[ 2 ].split( ',' ); // [ 'bar', 'baz' ]
|
||||
suffixes.forEach( function ( suffix ) {
|
||||
modules.push( prefix + '.' + suffix );
|
||||
} );
|
||||
}
|
||||
}
|
||||
} );
|
||||
return modules;
|
||||
};
|
||||
|
||||
/**
|
||||
* Split Parsoid resource name into the href prefix and the page title.
|
||||
*
|
||||
|
|
|
@ -204,107 +204,39 @@ ve.ui.MWSaveDialog.prototype.setDiffAndReview = function ( wikitextDiffPromise,
|
|||
/**
|
||||
* Set preview content and show preview panel.
|
||||
*
|
||||
* @param {HTMLDocument|jQuery} docOrMsg Document to preview, or error message
|
||||
* @param {HTMLDocument} [baseDoc] Base document against which to normalise links, if document provided
|
||||
* @param {Object|jQuery} response action=parse API response, or error message
|
||||
*/
|
||||
ve.ui.MWSaveDialog.prototype.showPreview = function ( docOrMsg, baseDoc ) {
|
||||
var dialog = this;
|
||||
|
||||
if ( docOrMsg instanceof HTMLDocument ) {
|
||||
var modules = [];
|
||||
// Extract required modules for stylesheet tags (avoids re-loading styles)
|
||||
Array.prototype.forEach.call( docOrMsg.head.querySelectorAll( 'link[rel~=stylesheet]' ), function ( link ) {
|
||||
var uri = new mw.Uri( link.href );
|
||||
if ( uri.query.modules ) {
|
||||
modules = modules.concat( mw.libs.ve.expandModuleNames( uri.query.modules ) );
|
||||
}
|
||||
} );
|
||||
// Remove skin-specific modules (T187075 / T185284)
|
||||
modules = modules.filter( function ( module ) {
|
||||
return !/^(skins|mediawiki\.skinning)\./.test( module );
|
||||
} );
|
||||
mw.loader.using( modules );
|
||||
var body = docOrMsg.body;
|
||||
var categories = [];
|
||||
// Take a snapshot of all categories
|
||||
Array.prototype.forEach.call( body.querySelectorAll( 'link[rel~="mw:PageProp/Category"]' ), function ( element ) {
|
||||
categories.push( ve.dm.nodeFactory.createFromElement( ve.dm.MWCategoryMetaItem.static.toDataElement( [ element ] ) ) );
|
||||
} );
|
||||
// Import body to current document, then resolve attributes against original document (parseDocument called #fixBase)
|
||||
document.adoptNode( body );
|
||||
|
||||
// TODO: This code is very similar to ve.ui.PreviewElement+ve.ui.MWPreviewElement
|
||||
ve.resolveAttributes( body, docOrMsg, ve.dm.Converter.static.computedAttributes );
|
||||
|
||||
// Document title will only be set if wikitext contains {{DISPLAYTITLE}}
|
||||
if ( docOrMsg.title ) {
|
||||
// HACK: Parse title as it can contain basic wikitext (T122976)
|
||||
ve.init.target.getContentApi().post( {
|
||||
action: 'parse',
|
||||
title: ve.init.target.getPageName(),
|
||||
prop: 'displaytitle',
|
||||
text: '{{DISPLAYTITLE:' + docOrMsg.title + '}}\n'
|
||||
} ).then( function ( response ) {
|
||||
if ( ve.getProp( response, 'parse', 'displaytitle' ) ) {
|
||||
// eslint-disable-next-line no-jquery/no-html
|
||||
dialog.$previewHeading.html( response.parse.displaytitle );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// Redirect
|
||||
var $redirect = $( [] );
|
||||
var redirectMeta = body.querySelector( 'link[rel="mw:PageProp/redirect"]' );
|
||||
if ( redirectMeta ) {
|
||||
$redirect = ve.init.mw.ArticleTarget.static.buildRedirectMsg(
|
||||
mw.libs.ve.getTargetDataFromHref(
|
||||
redirectMeta.getAttribute( 'href' ),
|
||||
document
|
||||
).title
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: This won't work with formatted titles (T122976)
|
||||
this.$previewHeading.text( docOrMsg.title || mw.Title.newFromText( ve.init.target.getPageName() ).getPrefixedText() );
|
||||
ve.ui.MWSaveDialog.prototype.showPreview = function ( response ) {
|
||||
if ( response instanceof $ ) {
|
||||
this.$previewViewer.empty().append(
|
||||
// eslint-disable-next-line no-jquery/no-append-html
|
||||
$( '<em>' ).append( response )
|
||||
);
|
||||
} else {
|
||||
var data = response.parse;
|
||||
|
||||
mw.config.set( data.jsconfigvars );
|
||||
mw.loader.using( ( data.modules || [] ).concat( data.modulestyles || [] ) );
|
||||
|
||||
// eslint-disable-next-line no-jquery/no-html
|
||||
this.$previewHeading.html( data.displaytitle );
|
||||
// eslint-disable-next-line no-jquery/no-append-html
|
||||
this.$previewViewer.empty().append(
|
||||
$redirect,
|
||||
// The following classes are used here:
|
||||
// * mw-content-ltr
|
||||
// * mw-content-rtl
|
||||
// eslint-disable-next-line no-jquery/no-append-html
|
||||
$( '<div>' ).addClass( 'mw-content-' + mw.config.get( 'wgVisualEditor' ).pageLanguageDir ).append(
|
||||
body.childNodes
|
||||
)
|
||||
// eslint-disable-next-line no-jquery/no-html
|
||||
$( '<div>' ).addClass( 'mw-content-' + mw.config.get( 'wgVisualEditor' ).pageLanguageDir ).html(
|
||||
data.text
|
||||
),
|
||||
data.categorieshtml
|
||||
);
|
||||
|
||||
ve.targetLinksToNewWindow( this.$previewViewer[ 0 ] );
|
||||
// Add styles so links render with their appropriate classes
|
||||
ve.init.platform.linkCache.styleParsoidElements( this.$previewViewer, baseDoc );
|
||||
mw.libs.ve.fixFragmentLinks( this.$previewViewer[ 0 ], mw.Title.newFromText( ve.init.target.getPageName() ), 'mw-save-preview-' );
|
||||
|
||||
var deferred;
|
||||
if ( categories.length ) {
|
||||
// If there are categories, we need to render them. This involves
|
||||
// a delay, since they might be hidden categories.
|
||||
deferred = ve.init.target.renderCategories( categories ).done( function ( $categories ) {
|
||||
dialog.$previewViewer.append( $categories );
|
||||
|
||||
ve.targetLinksToNewWindow( $categories[ 0 ] );
|
||||
// Add styles so links render with their appropriate classes
|
||||
ve.init.platform.linkCache.styleParsoidElements( $categories, baseDoc );
|
||||
} );
|
||||
} else {
|
||||
deferred = ve.createDeferred().resolve();
|
||||
}
|
||||
deferred.done( function () {
|
||||
// Run hooks so other things can alter the document
|
||||
mw.hook( 'wikipage.content' ).fire( dialog.$previewViewer );
|
||||
} );
|
||||
} else if ( docOrMsg instanceof $ ) {
|
||||
this.$previewViewer.empty().append(
|
||||
// eslint-disable-next-line no-jquery/no-append-html
|
||||
$( '<em>' ).append( docOrMsg )
|
||||
);
|
||||
// Run hooks so other things can alter the document
|
||||
mw.hook( 'wikipage.content' ).fire( this.$previewViewer );
|
||||
}
|
||||
|
||||
this.popPending();
|
||||
|
|
Loading…
Reference in a new issue