/*!
* VisualEditor DataModel MWTransclusionNode class.
*
* @copyright See AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki transclusion node.
*
* @class
* @abstract
* @extends ve.dm.LeafNode
* @mixes ve.dm.GeneratedContentNode
* @mixes ve.dm.FocusableNode
*
* @constructor
* @param {Object} [element] Reference to element in linear model
*/
ve.dm.MWTransclusionNode = function VeDmMWTransclusionNode() {
// Parent constructor
ve.dm.MWTransclusionNode.super.apply( this, arguments );
// Mixin constructors
ve.dm.GeneratedContentNode.call( this );
ve.dm.FocusableNode.call( this );
// Properties
this.partsList = null;
// Events
this.connect( this, { attributeChange: 'onAttributeChange' } );
};
/* Inheritance */
OO.inheritClass( ve.dm.MWTransclusionNode, ve.dm.LeafNode );
OO.mixinClass( ve.dm.MWTransclusionNode, ve.dm.GeneratedContentNode );
OO.mixinClass( ve.dm.MWTransclusionNode, ve.dm.FocusableNode );
/* Static members */
ve.dm.MWTransclusionNode.static.name = 'mwTransclusion';
ve.dm.MWTransclusionNode.static.matchTagNames = null;
ve.dm.MWTransclusionNode.static.matchRdfaTypes = [ 'mw:Transclusion' ];
// Transclusion nodes can contain other types, e.g. mw:PageProp/Category.
// Allow all other types (null) so they match to this node.
ve.dm.MWTransclusionNode.static.allowedRdfaTypes = null;
// HACK: This prevents any rules with higher specificity from matching,
// e.g. LanguageAnnotation which uses a match function
ve.dm.MWTransclusionNode.static.matchFunction = function () {
return true;
};
ve.dm.MWTransclusionNode.static.enableAboutGrouping = true;
// We handle rendering ourselves, no need to render attributes from originalDomElements (T207325),
// except for data-parsoid/RESTBase ID (T207325)
ve.dm.MWTransclusionNode.static.preserveHtmlAttributes = function ( attribute ) {
return [ 'data-parsoid', 'id' ].indexOf( attribute ) !== -1;
};
ve.dm.MWTransclusionNode.static.getHashObject = function ( dataElement ) {
return {
type: dataElement.type,
mw: dataElement.attributes.mw
};
};
ve.dm.MWTransclusionNode.static.isDiffComparable = function ( element, other ) {
function getTemplateNames( parts ) {
return parts.map( ( part ) => part.template ? part.template.target.wt : '' ).join( '|' );
}
return ve.dm.MWTransclusionNode.super.static.isDiffComparable.call( this, element, other ) &&
getTemplateNames( element.attributes.mw.parts ) === getTemplateNames( other.attributes.mw.parts );
};
/**
* Node type to use when the transclusion is inline
*
* @static
* @property {string}
* @inheritable
*/
ve.dm.MWTransclusionNode.static.inlineType = 'mwTransclusionInline';
/**
* Node type to use when the transclusion is a block
*
* @static
* @property {string}
* @inheritable
*/
ve.dm.MWTransclusionNode.static.blockType = 'mwTransclusionBlock';
/**
* Node type to use when the transclusion is cellable
*
* @static
* @property {string}
* @inheritable
*/
ve.dm.MWTransclusionNode.static.cellType = 'mwTransclusionTableCell';
ve.dm.MWTransclusionNode.static.toDataElement = function ( domElements, converter ) {
const mwDataJSON = domElements[ 0 ].getAttribute( 'data-mw' ),
mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {},
isInline = this.isHybridInline( domElements, converter ),
type = isInline ? this.inlineType : this.blockType;
const dataElement = {
type: type,
attributes: {
mw: mwData,
originalMw: mwDataJSON
}
};
if ( ve.dm.TableCellableNode.static.areNodesCellable( domElements ) ) {
dataElement.type = this.cellType;
ve.dm.TableCellableNode.static.setAttributes( dataElement.attributes, domElements, true );
}
if ( !domElements[ 0 ].getAttribute( 'data-ve-no-generated-contents' ) ) {
this.storeGeneratedContents( dataElement, domElements, converter.getStore() );
}
return dataElement;
};
ve.dm.MWTransclusionNode.static.toDomElements = function ( dataElement, doc, converter ) {
const store = converter.getStore(),
originalMw = dataElement.attributes.originalMw,
originalDomElements = store.value( dataElement.originalDomElementsHash );
function wrapTextNode( node ) {
if ( node.nodeType === Node.TEXT_NODE ) {
const wrapper = doc.createElement( 'span' );
wrapper.appendChild( node );
return wrapper;
}
return node;
}
let els;
// If the transclusion is unchanged just send back the
// original DOM elements so selser can skip over it
if (
originalDomElements &&
originalMw && ve.compare( dataElement.attributes.mw, JSON.parse( originalMw ) )
) {
// originalDomElements is also used for CE rendering so return a copy
els = ve.copyDomElements( originalDomElements, doc );
} else {
let value;
if (
converter.doesModeNeedRendering() &&
// Use getHashObjectForRendering to get the rendering from the store
( value = store.value( store.hashOfValue( null, OO.getHash( [ this.getHashObjectForRendering( dataElement ), undefined ] ) ) ) )
) {
// For the clipboard use the current DOM contents so the user has something
// meaningful to paste into external applications
els = ve.copyDomElements( value, doc );
els[ 0 ] = wrapTextNode( els[ 0 ] );
} else if ( originalDomElements ) {
els = [ doc.createElement( originalDomElements[ 0 ].nodeName ) ];
} else if ( dataElement.type === this.cellType ) {
els = [ doc.createElement( dataElement.attributes.style === 'header' ? 'th' : 'td' ) ];
} else {
els = [ doc.createElement( 'span' ) ];
}
// All we need to send back to Parsoid is the original transclusion marker, with a
// reconstructed data-mw property.
els[ 0 ].setAttribute( 'typeof', 'mw:Transclusion' );
els[ 0 ].setAttribute( 'data-mw', JSON.stringify( dataElement.attributes.mw ) );
}
if ( converter.isForClipboard() ) {
// If the first element is a , or