mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-02 01:46:47 +00:00
bb45d984ca
<a href="Foo%3F"> would dirty-diff to <a href="Foo?"> and also render as such, pointing to the wrong page. We also called decodeURIComponent() on the href twice, which can't have been good. Move URI decoding and underscore normalization into getTargetDataFromHref(), and add rawTitle for callers that need it. Put rawTitle in the origTitle attribute, so that equivalence comparisons (decode(origTitle) === title) work as intended. Bug: T145978 Change-Id: I29331a4ab0f8f7ef059c109f6813fa670a2c7390
277 lines
8.1 KiB
JavaScript
277 lines
8.1 KiB
JavaScript
/*!
|
|
* VisualEditor ContentEditable MWTransclusionNode class.
|
|
*
|
|
* @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* ContentEditable MediaWiki transclusion node.
|
|
*
|
|
* @class
|
|
* @abstract
|
|
* @extends ve.ce.LeafNode
|
|
* @mixins ve.ce.GeneratedContentNode
|
|
* @mixins ve.ce.FocusableNode
|
|
* @mixins ve.ce.TableCellableNode
|
|
*
|
|
* @constructor
|
|
* @param {ve.dm.MWTransclusionNode} model Model to observe
|
|
* @param {Object} [config] Configuration options
|
|
*/
|
|
ve.ce.MWTransclusionNode = function VeCeMWTransclusionNode( model, config ) {
|
|
// Parent constructor
|
|
ve.ce.MWTransclusionNode.super.call( this, model, config );
|
|
|
|
// Mixin constructors
|
|
ve.ce.GeneratedContentNode.call( this );
|
|
ve.ce.FocusableNode.call( this );
|
|
ve.ce.TableCellableNode.call( this );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ce.MWTransclusionNode, ve.ce.LeafNode );
|
|
|
|
OO.mixinClass( ve.ce.MWTransclusionNode, ve.ce.GeneratedContentNode );
|
|
OO.mixinClass( ve.ce.MWTransclusionNode, ve.ce.FocusableNode );
|
|
OO.mixinClass( ve.ce.MWTransclusionNode, ve.ce.TableCellableNode );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ce.MWTransclusionNode.static.name = 'mwTransclusion';
|
|
|
|
ve.ce.MWTransclusionNode.static.renderHtmlAttributes = false;
|
|
|
|
ve.ce.MWTransclusionNode.static.primaryCommandName = 'transclusion';
|
|
|
|
ve.ce.MWTransclusionNode.static.iconWhenInvisible = 'template';
|
|
|
|
/* Static Methods */
|
|
|
|
/**
|
|
* Get a list of descriptions of template parts in a transclusion node
|
|
*
|
|
* @static
|
|
* @param {ve.dm.Node} model Node model
|
|
* @return {string[]} List of template part descriptions
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.getTemplatePartDescriptions = function ( model ) {
|
|
var i, len, part,
|
|
parts = model.getPartsList(),
|
|
words = [];
|
|
|
|
for ( i = 0, len = parts.length; i < len; i++ ) {
|
|
part = parts[ i ];
|
|
if ( part.template ) {
|
|
words.push( part.template );
|
|
}
|
|
}
|
|
|
|
return words;
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.getDescription = function ( model ) {
|
|
return this.getTemplatePartDescriptions( model )
|
|
.map( function ( template ) {
|
|
var title = mw.Title.newFromText( template, mw.config.get( 'wgNamespaceIds' ).template );
|
|
if ( title ) {
|
|
return title.getRelativeText( mw.config.get( 'wgNamespaceIds' ).template );
|
|
} else {
|
|
return template;
|
|
}
|
|
} )
|
|
.join( ve.msg( 'comma-separator' ) );
|
|
};
|
|
|
|
/**
|
|
* Filter rendering to remove auto-generated content and wrappers
|
|
*
|
|
* @static
|
|
* @param {HTMLElement[]} contentNodes Rendered nodes
|
|
* @return {HTMLElement[]} Filtered rendered nodes
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.filterRendering = function ( contentNodes ) {
|
|
// Filter out auto-generated items, e.g. reference lists
|
|
contentNodes = contentNodes.filter( function ( node ) {
|
|
var dataMw = node.nodeType === Node.ELEMENT_NODE &&
|
|
node.hasAttribute( 'data-mw' ) &&
|
|
JSON.parse( node.getAttribute( 'data-mw' ) );
|
|
if ( dataMw && dataMw.autoGenerated ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} );
|
|
// HACK: if $content consists of a single paragraph, unwrap it.
|
|
// We have to do this because the parser wraps everything in <p>s, and inline templates
|
|
// will render strangely when wrapped in <p>s.
|
|
if ( contentNodes.length === 1 && contentNodes[ 0 ].nodeName.toLowerCase() === 'p' ) {
|
|
contentNodes = Array.prototype.slice.apply( contentNodes[ 0 ].childNodes );
|
|
}
|
|
return contentNodes;
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.createInvisibleIcon = function () {
|
|
var icon = new OO.ui.ButtonWidget( {
|
|
classes: [ 've-ce-focusableNode-invisibleIcon' ],
|
|
framed: false,
|
|
icon: this.constructor.static.iconWhenInvisible,
|
|
label: this.constructor.static.getDescription( this.getModel() )
|
|
} );
|
|
return icon.$element;
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.generateContents = function ( config ) {
|
|
var xhr, deferred = $.Deferred();
|
|
xhr = new mw.Api().post( {
|
|
action: 'visualeditor',
|
|
paction: 'parsefragment',
|
|
page: mw.config.get( 'wgRelevantPageName' ),
|
|
wikitext: ( config && config.wikitext ) || this.model.getWikitext(),
|
|
pst: 1
|
|
} )
|
|
.done( this.onParseSuccess.bind( this, deferred ) )
|
|
.fail( this.onParseError.bind( this, deferred ) );
|
|
|
|
return deferred.promise( { abort: xhr.abort } );
|
|
};
|
|
|
|
/**
|
|
* Handle a successful response from the parser for the wikitext fragment.
|
|
*
|
|
* @param {jQuery.Deferred} deferred The Deferred object created by #generateContents
|
|
* @param {Object} response Response data
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.onParseSuccess = function ( deferred, response ) {
|
|
var contentNodes;
|
|
|
|
if ( ve.getProp( response, 'visualeditor', 'result' ) !== 'success' ) {
|
|
return this.onParseError.call( this, deferred );
|
|
}
|
|
|
|
// Work around https://github.com/jquery/jquery/issues/1997
|
|
contentNodes = $.parseHTML( response.visualeditor.content, this.getModelHtmlDocument() ) || [];
|
|
deferred.resolve( this.constructor.static.filterRendering( contentNodes ) );
|
|
};
|
|
|
|
/**
|
|
* Extend the ve.ce.GeneratedContentNode render method to check for hidden templates.
|
|
*
|
|
* Check if the final result of the imported template is empty.
|
|
*
|
|
* @see ve.ce.GeneratedContentNode#render
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.render = function ( generatedContents ) {
|
|
// Call parent mixin
|
|
ve.ce.GeneratedContentNode.prototype.render.call( this, generatedContents );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.onSetup = function () {
|
|
// Parent method
|
|
ve.ce.MWTransclusionNode.super.prototype.onSetup.apply( this, arguments );
|
|
|
|
// Render replaces this.$element with a new node so re-add classes
|
|
this.$element.addClass( 've-ce-mwTransclusionNode' );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.getRenderedDomElements = function ( domElements ) {
|
|
var $elements = $( ve.ce.GeneratedContentNode.prototype.getRenderedDomElements.call( this, domElements ) ),
|
|
transclusionNode = this;
|
|
if ( this.getModelHtmlDocument() ) {
|
|
$elements
|
|
.find( 'a[href][rel="mw:WikiLink"]' ).addBack( 'a[href][rel="mw:WikiLink"]' )
|
|
.each( function () {
|
|
var targetData = ve.dm.MWInternalLinkAnnotation.static.getTargetDataFromHref(
|
|
this.href, transclusionNode.getModelHtmlDocument()
|
|
),
|
|
normalisedHref = ve.decodeURIComponentIntoArticleTitle( targetData.rawTitle, false );
|
|
if ( mw.Title.newFromText( normalisedHref ) ) {
|
|
normalisedHref = mw.Title.newFromText( normalisedHref ).getPrefixedText();
|
|
}
|
|
ve.init.platform.linkCache.styleElement( normalisedHref, $( this ) );
|
|
} );
|
|
}
|
|
return $elements.toArray();
|
|
};
|
|
|
|
/**
|
|
* Handle an unsuccessful response from the parser for the wikitext fragment.
|
|
*
|
|
* @param {jQuery.Deferred} deferred The promise object created by #generateContents
|
|
* @param {Object} response Response data
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.onParseError = function ( deferred ) {
|
|
deferred.reject();
|
|
};
|
|
|
|
/* Concrete subclasses */
|
|
|
|
/**
|
|
* ContentEditable MediaWiki transclusion block node.
|
|
*
|
|
* @class
|
|
* @extends ve.ce.MWTransclusionNode
|
|
* @constructor
|
|
* @param {ve.dm.MWTransclusionBlockNode} model Model to observe
|
|
*/
|
|
ve.ce.MWTransclusionBlockNode = function VeCeMWTransclusionBlockNode( model ) {
|
|
// Parent constructor
|
|
ve.ce.MWTransclusionBlockNode.super.call( this, model );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ce.MWTransclusionBlockNode, ve.ce.MWTransclusionNode );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ce.MWTransclusionBlockNode.static.name = 'mwTransclusionBlock';
|
|
|
|
ve.ce.MWTransclusionBlockNode.static.tagName = 'div';
|
|
|
|
/**
|
|
* ContentEditable MediaWiki transclusion inline node.
|
|
*
|
|
* @class
|
|
* @extends ve.ce.MWTransclusionNode
|
|
* @constructor
|
|
* @param {ve.dm.MWTransclusionInlineNode} model Model to observe
|
|
*/
|
|
ve.ce.MWTransclusionInlineNode = function VeCeMWTransclusionInlineNode( model ) {
|
|
// Parent constructor
|
|
ve.ce.MWTransclusionInlineNode.super.call( this, model );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.ce.MWTransclusionInlineNode, ve.ce.MWTransclusionNode );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ce.MWTransclusionInlineNode.static.name = 'mwTransclusionInline';
|
|
|
|
ve.ce.MWTransclusionInlineNode.static.tagName = 'span';
|
|
|
|
/* Registration */
|
|
|
|
ve.ce.nodeFactory.register( ve.ce.MWTransclusionNode );
|
|
ve.ce.nodeFactory.register( ve.ce.MWTransclusionBlockNode );
|
|
ve.ce.nodeFactory.register( ve.ce.MWTransclusionInlineNode );
|