mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-02 09:57:01 +00:00
f0e9ec7922
ve.dm.MWTransclusionNode * Implement getClonedElement to remove originalDomElements and originalMw. We don't want to put these in the clipboard as there is no guarantee they are the rendering of the current set of parameters (a refresh may be in progress) and they may confuse the converter on the way back in. Remove about attribute to prevent about grouping of duplicated nodes. * Set an extra attribute to flag that the outputted DOM doesn't have any generated content attached to it that can be stored on load. * Check for said attribute in toDataElement and skip the storeGeneratedContents step. This will trigger an async update of the generated contents on paste. ve.ce.Surface * Call cloneElements before writing to pasteTarget so data in external clipboard is stripped of generated contents. ve.dm.Node * Only strip data-parsoid as other attributes may be meaningful. ve.dm.mwExample, ve.dm.Node.test * Update tests Bug: 58241 Change-Id: I3e15cc97e94747647078204a0b398e6ac3ec6382
281 lines
8.5 KiB
JavaScript
281 lines
8.5 KiB
JavaScript
/*!
|
|
* VisualEditor DataModel MWTransclusionNode class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* DataModel MediaWiki transclusion node.
|
|
*
|
|
* @class
|
|
* @abstract
|
|
* @extends ve.dm.LeafNode
|
|
* @mixins ve.dm.GeneratedContentNode
|
|
*
|
|
* @constructor
|
|
* @param {number} [length] Length of content data in document; ignored and overridden to 0
|
|
* @param {Object} [element] Reference to element in linear model
|
|
*/
|
|
ve.dm.MWTransclusionNode = function VeDmMWTransclusionNode( length, element ) {
|
|
// Parent constructor
|
|
ve.dm.LeafNode.call( this, 0, element );
|
|
|
|
// Mixin constructors
|
|
ve.dm.GeneratedContentNode.call( this );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.dm.MWTransclusionNode, ve.dm.LeafNode );
|
|
|
|
OO.mixinClass( ve.dm.MWTransclusionNode, ve.dm.GeneratedContentNode );
|
|
|
|
/* Static members */
|
|
|
|
ve.dm.MWTransclusionNode.static.name = 'mwTransclusion';
|
|
|
|
ve.dm.MWTransclusionNode.static.matchTagNames = null;
|
|
|
|
ve.dm.MWTransclusionNode.static.matchRdfaTypes = [
|
|
'mw:Transclusion',
|
|
// We're interested in all nodes that have mw:Transclusion, even if they also have other mw:
|
|
// types. So we match all mw: types, then use a matchFunction to assert that mw:Transclusion
|
|
// is in there.
|
|
/^mw:/
|
|
];
|
|
|
|
ve.dm.MWTransclusionNode.static.matchFunction = function ( domElement ) {
|
|
return ve.indexOf( 'mw:Transclusion',
|
|
( domElement.getAttribute( 'typeof' ) || '' ).split( ' ' )
|
|
) !== -1;
|
|
};
|
|
|
|
ve.dm.MWTransclusionNode.static.enableAboutGrouping = true;
|
|
|
|
ve.dm.MWTransclusionNode.static.getHashObject = function ( dataElement ) {
|
|
return {
|
|
type: dataElement.type,
|
|
mw: dataElement.attributes.mw
|
|
};
|
|
};
|
|
|
|
ve.dm.MWTransclusionNode.static.toDataElement = function ( domElements, converter ) {
|
|
if ( converter.isDomAllMetaOrWhitespace( domElements, ['mwTransclusion', 'mwTransclusionInline', 'mwTransclusionBlock'] ) ) {
|
|
return ve.dm.MWTransclusionMetaItem.static.toDataElement( domElements, converter );
|
|
}
|
|
|
|
var dataElement, index,
|
|
mwDataJSON = domElements[0].getAttribute( 'data-mw' ),
|
|
mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {},
|
|
isInline = this.isHybridInline( domElements, converter ),
|
|
type = isInline ? 'mwTransclusionInline' : 'mwTransclusionBlock';
|
|
|
|
dataElement = {
|
|
'type': type,
|
|
'attributes': {
|
|
'mw': mwData,
|
|
'originalDomElements': ve.copy( domElements ),
|
|
'originalMw': mwDataJSON
|
|
}
|
|
};
|
|
|
|
if ( !domElements[0].getAttribute( 'data-ve-no-generated-contents' ) ) {
|
|
index = this.storeGeneratedContents( dataElement, domElements, converter.getStore() );
|
|
dataElement.attributes.originalIndex = index;
|
|
}
|
|
|
|
return dataElement;
|
|
};
|
|
|
|
ve.dm.MWTransclusionNode.static.toDomElements = function ( dataElement, doc, converter ) {
|
|
var el,
|
|
index = converter.getStore().indexOfHash( OO.getHash( this.getHashObject( dataElement ) ) ),
|
|
originalMw = dataElement.attributes.originalMw;
|
|
|
|
// If the transclusion is unchanged just send back the
|
|
// original DOM elements so selser can skip over it
|
|
if (
|
|
index === dataElement.attributes.originalIndex ||
|
|
( originalMw && ve.compare( dataElement.attributes.mw, JSON.parse( originalMw ) ) )
|
|
) {
|
|
// The object in the store is also used for CE rendering so return a copy
|
|
return ve.copyDomElements( dataElement.attributes.originalDomElements, doc );
|
|
} else {
|
|
if ( dataElement.attributes.originalDomElements ) {
|
|
el = doc.createElement( dataElement.attributes.originalDomElements[0].nodeName );
|
|
} else {
|
|
el = doc.createElement( 'span' );
|
|
}
|
|
// All we need to send back to Parsoid is the original transclusion marker, with a
|
|
// reconstructed data-mw property.
|
|
el.setAttribute( 'typeof', 'mw:Transclusion' );
|
|
el.setAttribute( 'data-mw', JSON.stringify( dataElement.attributes.mw ) );
|
|
// Mark the element as not having a generated contents with it in case it is
|
|
// inserted into another editor (e.g. via paste).
|
|
el.setAttribute( 'data-ve-no-generated-contents', true );
|
|
// TODO: Include last-known generated contents in the output for rich
|
|
// paste into a non-VE editor
|
|
return [ el ];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Escape a template parameter. Helper function for getWikitext().
|
|
* @param {string} param Parameter value
|
|
* @returns {string} Escaped parameter value
|
|
*/
|
|
ve.dm.MWTransclusionNode.static.escapeParameter = function ( param ) {
|
|
var match, needsNowiki, input = param, output = '',
|
|
inNowiki = false, bracketStack = 0, linkStack = 0;
|
|
while ( input.length > 0 ) {
|
|
match = input.match( /(?:\[\[)|(?:\]\])|(?:\{\{)|(?:\}\})|\|+|<\/?nowiki>|<nowiki\s*\/>/ );
|
|
if ( !match ) {
|
|
output += input;
|
|
break;
|
|
}
|
|
output += input.substr( 0, match.index );
|
|
input = input.substr( match.index + match[0].length );
|
|
if ( inNowiki ) {
|
|
if ( match[0] === '</nowiki>' ) {
|
|
inNowiki = false;
|
|
output += match[0];
|
|
} else {
|
|
output += match[0];
|
|
}
|
|
} else {
|
|
needsNowiki = true;
|
|
if ( match[0] === '<nowiki>' ) {
|
|
inNowiki = true;
|
|
needsNowiki = false;
|
|
} else if ( match[0] === '</nowiki>' || match[0].match( /<nowiki\s*\/>/ ) ) {
|
|
needsNowiki = false;
|
|
} else if ( match[0].match( /(?:\[\[)/ ) ) {
|
|
linkStack++;
|
|
needsNowiki = false;
|
|
} else if ( match[0].match( /(?:\]\])/ ) ) {
|
|
if ( linkStack > 0 ) {
|
|
linkStack--;
|
|
needsNowiki = false;
|
|
}
|
|
} else if ( match[0].match( /(?:\{\{)/ ) ) {
|
|
bracketStack++;
|
|
needsNowiki = false;
|
|
} else if ( match[0].match( /(?:\}\})/ ) ) {
|
|
if ( bracketStack > 0 ) {
|
|
bracketStack--;
|
|
needsNowiki = false;
|
|
}
|
|
} else if ( match[0].match( /\|+/ ) ) {
|
|
if ( bracketStack > 0 || linkStack > 0 ) {
|
|
needsNowiki = false;
|
|
}
|
|
}
|
|
|
|
if ( needsNowiki ) {
|
|
output += '<nowiki>' + match[0] + '</nowiki>';
|
|
} else {
|
|
output += match[0];
|
|
}
|
|
}
|
|
}
|
|
return output;
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Get the wikitext for this transclusion.
|
|
*
|
|
* @method
|
|
* @returns {string} Wikitext like `{{foo|1=bar|baz=quux}}`
|
|
*/
|
|
ve.dm.MWTransclusionNode.prototype.getWikitext = function () {
|
|
var i, len, part, template, param,
|
|
content = this.getAttribute( 'mw' ),
|
|
wikitext = '';
|
|
|
|
// Normalize to multi template format
|
|
if ( content.params ) {
|
|
content = { 'parts': [ { 'template': content } ] };
|
|
}
|
|
// Build wikitext from content
|
|
for ( i = 0, len = content.parts.length; i < len; i++ ) {
|
|
part = content.parts[i];
|
|
if ( part.template ) {
|
|
// Template
|
|
template = part.template;
|
|
wikitext += '{{' + template.target.wt;
|
|
for ( param in template.params ) {
|
|
wikitext += '|' + param + '=' +
|
|
this.constructor.static.escapeParameter( template.params[param].wt );
|
|
}
|
|
wikitext += '}}';
|
|
} else {
|
|
// Plain wikitext
|
|
wikitext += part;
|
|
}
|
|
}
|
|
return wikitext;
|
|
};
|
|
|
|
/** */
|
|
ve.dm.MWTransclusionNode.prototype.getClonedElement = function () {
|
|
var clone = ve.dm.LeafNode.prototype.getClonedElement.call( this );
|
|
delete clone.attributes.originalMw;
|
|
delete clone.attributes.originalDomElements;
|
|
// Remove about attribute to prevent about grouping of duplicated transclusions
|
|
this.constructor.static.removeHtmlAttribute( clone, 'about' );
|
|
return clone;
|
|
};
|
|
|
|
/* Concrete subclasses */
|
|
|
|
/**
|
|
* DataModel MediaWiki transclusion block node.
|
|
*
|
|
* @class
|
|
* @extends ve.dm.MWTransclusionNode
|
|
* @constructor
|
|
* @param {number} [length] Length of content data in document; ignored and overridden to 0
|
|
* @param {Object} [element] Reference to element in linear model
|
|
*/
|
|
ve.dm.MWTransclusionBlockNode = function VeDmMWTransclusionBlockNode( length, element ) {
|
|
// Parent constructor
|
|
ve.dm.MWTransclusionNode.call( this, length, element );
|
|
};
|
|
|
|
OO.inheritClass( ve.dm.MWTransclusionBlockNode, ve.dm.MWTransclusionNode );
|
|
|
|
ve.dm.MWTransclusionBlockNode.static.matchTagNames = [];
|
|
|
|
ve.dm.MWTransclusionBlockNode.static.name = 'mwTransclusionBlock';
|
|
|
|
/**
|
|
* DataModel MediaWiki transclusion inline node.
|
|
*
|
|
* @class
|
|
* @extends ve.dm.MWTransclusionNode
|
|
* @constructor
|
|
* @param {number} [length] Length of content data in document; ignored and overridden to 0
|
|
* @param {Object} [element] Reference to element in linear model
|
|
*/
|
|
ve.dm.MWTransclusionInlineNode = function VeDmMWTransclusionInlineNode( length, element ) {
|
|
// Parent constructor
|
|
ve.dm.MWTransclusionNode.call( this, length, element );
|
|
};
|
|
|
|
OO.inheritClass( ve.dm.MWTransclusionInlineNode, ve.dm.MWTransclusionNode );
|
|
|
|
ve.dm.MWTransclusionInlineNode.static.matchTagNames = [];
|
|
|
|
ve.dm.MWTransclusionInlineNode.static.name = 'mwTransclusionInline';
|
|
|
|
ve.dm.MWTransclusionInlineNode.static.isContent = true;
|
|
|
|
/* Registration */
|
|
|
|
ve.dm.modelRegistry.register( ve.dm.MWTransclusionNode );
|
|
ve.dm.modelRegistry.register( ve.dm.MWTransclusionBlockNode );
|
|
ve.dm.modelRegistry.register( ve.dm.MWTransclusionInlineNode );
|