mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-04 18:58:37 +00:00
8759a60516
The first click opens a little context menu (…ContextItem in the code). This patch makes a double click trigger the "Edit" button in this context item instead of executing the "open window" command itself. This is needed to trigger the "Loading…" code in the context item. Bug: T297773 Change-Id: Ie1daa2e6b460c5c506a847bd767af2cb48e73a61
246 lines
7.4 KiB
JavaScript
246 lines
7.4 KiB
JavaScript
/*!
|
|
* VisualEditor ContentEditable MWTransclusionNode class.
|
|
*
|
|
* @copyright 2011-2020 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
|
|
*
|
|
* @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 );
|
|
};
|
|
|
|
/* 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 );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.ce.MWTransclusionNode.static.name = 'mwTransclusion';
|
|
|
|
ve.ce.MWTransclusionNode.static.primaryCommandName = 'transclusion';
|
|
|
|
ve.ce.MWTransclusionNode.static.iconWhenInvisible = 'puzzle';
|
|
|
|
/* Static Methods */
|
|
|
|
/**
|
|
* Get a list of descriptions of template parts in a transclusion node
|
|
*
|
|
* @static
|
|
* @param {ve.dm.MWTransclusionNode} model Node model
|
|
* @return {string[]} List of template part descriptions
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.getTemplatePartDescriptions = function ( model ) {
|
|
return model.getPartsList().map( this.getTemplatePartDescription );
|
|
};
|
|
|
|
/**
|
|
* Get a description of a template part in a transclusion node
|
|
*
|
|
* @static
|
|
* @param {Object} part Template part
|
|
* @return {string} Template part description
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.getTemplatePartDescription = function ( part ) {
|
|
// Ignore parts that are just content
|
|
if ( part.templatePage ) {
|
|
return mw.Title.newFromText( part.templatePage )
|
|
.getRelativeText( mw.config.get( 'wgNamespaceIds' ).template );
|
|
} else if ( part.template ) {
|
|
// Not actually a template, but e.g. a parser function
|
|
return part.template;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.getDescription = function ( model ) {
|
|
return this.getTemplatePartDescriptions( model )
|
|
.filter( function ( desc ) {
|
|
return desc;
|
|
} )
|
|
.join( ve.msg( 'comma-separator' ) );
|
|
};
|
|
|
|
/**
|
|
* Filter rendering to remove auto-generated content and wrappers
|
|
*
|
|
* @static
|
|
* @param {Node[]} contentNodes Rendered nodes
|
|
* @return {Node[]} Filtered rendered nodes
|
|
*/
|
|
ve.ce.MWTransclusionNode.static.filterRendering = function ( contentNodes ) {
|
|
if ( !contentNodes.length ) {
|
|
return;
|
|
}
|
|
|
|
var whitespaceRegex = new RegExp( '^[' + ve.dm.Converter.static.whitespaceList + ']+$' );
|
|
|
|
// Filter out auto-generated items, e.g. reference lists
|
|
contentNodes = contentNodes.filter( function ( node ) {
|
|
var dataMw = node &&
|
|
node.nodeType === Node.ELEMENT_NODE &&
|
|
node.hasAttribute( 'data-mw' ) &&
|
|
JSON.parse( node.getAttribute( 'data-mw' ) );
|
|
|
|
return !dataMw || !dataMw.autoGenerated;
|
|
} );
|
|
|
|
contentNodes.forEach( function ( node ) {
|
|
if ( node.nodeType === Node.ELEMENT_NODE ) {
|
|
mw.libs.ve.stripParsoidFallbackIds( node );
|
|
}
|
|
} );
|
|
|
|
function isWhitespaceNode( node ) {
|
|
return node && node.nodeType === Node.TEXT_NODE && !!node.data.match( whitespaceRegex );
|
|
}
|
|
|
|
while ( isWhitespaceNode( contentNodes[ 0 ] ) ) {
|
|
contentNodes.shift();
|
|
}
|
|
while ( isWhitespaceNode( contentNodes[ contentNodes.length - 1 ] ) ) {
|
|
contentNodes.pop();
|
|
}
|
|
// 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.call( contentNodes[ 0 ].childNodes );
|
|
}
|
|
return contentNodes;
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/** @inheritDoc */
|
|
ve.ce.MWTransclusionNode.prototype.executeCommand = function () {
|
|
var contextItems = this.focusableSurface.getSurface().getContext().items;
|
|
if ( contextItems[ 0 ] instanceof ve.ui.MWTransclusionContextItem ) {
|
|
// Utilize the context item when it's there instead of triggering the command manually.
|
|
// Required to make the context item show the "Loading…" message (see T297773).
|
|
contextItems[ 0 ].onEditButtonClick();
|
|
return;
|
|
}
|
|
|
|
// Parent method
|
|
ve.ce.FocusableNode.prototype.executeCommand.apply( this, arguments );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.generateContents = function ( config ) {
|
|
var deferred = ve.createDeferred();
|
|
var xhr = ve.init.target.parseWikitextFragment(
|
|
( config && config.wikitext ) || this.model.getWikitext(),
|
|
true,
|
|
this.getModel().getDocument()
|
|
)
|
|
.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 ) {
|
|
if ( ve.getProp( response, 'visualeditor', 'result' ) !== 'success' ) {
|
|
this.onParseError( deferred );
|
|
return;
|
|
}
|
|
|
|
// Work around https://github.com/jquery/jquery/issues/1997
|
|
var contentNodes = $.parseHTML( response.visualeditor.content, this.model && 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.
|
|
*
|
|
* @inheritdoc ve.ce.GeneratedContentNode
|
|
*/
|
|
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 () {
|
|
// Parent method
|
|
var elements = ve.ce.GeneratedContentNode.prototype.getRenderedDomElements.apply( this, arguments );
|
|
|
|
if ( this.model && this.getModelHtmlDocument() ) {
|
|
ve.init.platform.linkCache.styleParsoidElements(
|
|
$( elements ),
|
|
this.getModelHtmlDocument()
|
|
);
|
|
}
|
|
return elements;
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
ve.ce.MWTransclusionNode.prototype.filterRenderedDomElements = function ( domElements ) {
|
|
// We want to remove all styles and links which aren't from TemplateStyles.
|
|
var selector = 'style:not([data-mw-deduplicate^="TemplateStyles:"]), link:not([rel="mw-deduplicated-inline-style"][href^="mw-data:TemplateStyles:"])';
|
|
return $( domElements ).find( selector ).addBack( selector ).remove().end().end().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();
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ce.nodeFactory.register( ve.ce.MWTransclusionNode );
|