2013-08-03 14:17:16 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor DataModel MWExtensionNode class.
|
|
|
|
*
|
2020-01-08 17:13:04 +00:00
|
|
|
* @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt
|
2013-08-03 14:17:16 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2014-05-20 11:56:37 +00:00
|
|
|
* DataModel MediaWiki extension node.
|
2013-08-03 14:17:16 +00:00
|
|
|
*
|
|
|
|
* @class
|
2014-05-20 11:56:37 +00:00
|
|
|
* @abstract
|
2015-03-23 15:49:38 +00:00
|
|
|
* @extends ve.dm.LeafNode
|
2014-09-17 00:33:39 +00:00
|
|
|
* @mixins ve.dm.FocusableNode
|
2013-08-03 14:17:16 +00:00
|
|
|
* @mixins ve.dm.GeneratedContentNode
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
*/
|
2014-06-07 03:17:26 +00:00
|
|
|
ve.dm.MWExtensionNode = function VeDmMWExtensionNode() {
|
2015-03-23 15:49:38 +00:00
|
|
|
// Parent constructor
|
|
|
|
ve.dm.MWExtensionNode.super.apply( this, arguments );
|
|
|
|
|
2013-08-03 14:17:16 +00:00
|
|
|
// Mixin constructors
|
|
|
|
ve.dm.GeneratedContentNode.call( this );
|
2014-09-17 00:33:39 +00:00
|
|
|
ve.dm.FocusableNode.call( this );
|
2013-08-03 14:17:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2015-03-23 15:49:38 +00:00
|
|
|
OO.inheritClass( ve.dm.MWExtensionNode, ve.dm.LeafNode );
|
2014-09-17 00:33:39 +00:00
|
|
|
OO.mixinClass( ve.dm.MWExtensionNode, ve.dm.FocusableNode );
|
2013-10-11 21:44:09 +00:00
|
|
|
OO.mixinClass( ve.dm.MWExtensionNode, ve.dm.GeneratedContentNode );
|
2013-08-03 14:17:16 +00:00
|
|
|
|
|
|
|
/* Static members */
|
|
|
|
|
|
|
|
ve.dm.MWExtensionNode.static.enableAboutGrouping = true;
|
|
|
|
|
|
|
|
ve.dm.MWExtensionNode.static.matchTagNames = null;
|
|
|
|
|
2014-06-10 01:01:42 +00:00
|
|
|
ve.dm.MWExtensionNode.static.childNodeTypes = [];
|
|
|
|
|
2014-01-27 21:45:46 +00:00
|
|
|
/**
|
|
|
|
* HTML tag name.
|
|
|
|
* @static
|
|
|
|
* @property {string}
|
|
|
|
* @inheritable
|
|
|
|
*/
|
2013-08-03 14:17:16 +00:00
|
|
|
ve.dm.MWExtensionNode.static.tagName = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Name of the extension and the parser tag name.
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {string}
|
2013-08-03 14:17:16 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.MWExtensionNode.static.extensionName = null;
|
|
|
|
|
|
|
|
ve.dm.MWExtensionNode.static.getMatchRdfaTypes = function () {
|
|
|
|
return [ 'mw:Extension/' + this.extensionName ];
|
|
|
|
};
|
|
|
|
|
2017-05-19 14:04:01 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
* @param {Node[]} domElements
|
|
|
|
* @param {ve.dm.Converter} converter
|
|
|
|
* @param {string} [type] Type to give dataElement, defaults to static.name
|
|
|
|
*/
|
|
|
|
ve.dm.MWExtensionNode.static.toDataElement = function ( domElements, converter, type ) {
|
2015-10-02 10:39:40 +00:00
|
|
|
var dataElement,
|
2015-08-19 17:33:02 +00:00
|
|
|
mwDataJSON = domElements[ 0 ].getAttribute( 'data-mw' ),
|
2013-08-03 14:17:16 +00:00
|
|
|
mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {};
|
|
|
|
|
|
|
|
dataElement = {
|
2017-05-19 14:04:01 +00:00
|
|
|
type: type || this.name,
|
2014-08-22 20:50:48 +00:00
|
|
|
attributes: {
|
|
|
|
mw: mwData,
|
|
|
|
originalMw: mwDataJSON
|
2013-08-03 14:17:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-10-02 10:39:40 +00:00
|
|
|
this.storeGeneratedContents( dataElement, domElements, converter.getStore() );
|
2017-05-19 14:04:01 +00:00
|
|
|
// Sub-classes should not modify dataElement beyond this point as it will invalidate the cache
|
2013-08-03 14:17:16 +00:00
|
|
|
|
|
|
|
return dataElement;
|
|
|
|
};
|
|
|
|
|
2019-04-16 15:17:29 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc ve.dm.Node
|
|
|
|
*/
|
2016-02-07 18:28:04 +00:00
|
|
|
ve.dm.MWExtensionNode.static.cloneElement = function () {
|
2016-05-04 13:43:27 +00:00
|
|
|
// Parent method
|
|
|
|
var clone = ve.dm.MWExtensionNode.super.static.cloneElement.apply( this, arguments );
|
2016-02-07 18:28:04 +00:00
|
|
|
delete clone.attributes.originalMw;
|
|
|
|
return clone;
|
|
|
|
};
|
|
|
|
|
2013-08-03 14:17:16 +00:00
|
|
|
ve.dm.MWExtensionNode.static.toDomElements = function ( dataElement, doc, converter ) {
|
2016-08-17 19:50:55 +00:00
|
|
|
var el, els, value,
|
2015-10-02 10:39:40 +00:00
|
|
|
store = converter.getStore(),
|
2013-08-03 14:17:16 +00:00
|
|
|
originalMw = dataElement.attributes.originalMw;
|
|
|
|
|
|
|
|
// If the transclusion is unchanged just send back the
|
|
|
|
// original DOM elements so selser can skip over it
|
|
|
|
if (
|
2018-03-06 12:44:37 +00:00
|
|
|
dataElement.originalDomElementsHash &&
|
2016-05-04 13:43:27 +00:00
|
|
|
originalMw && ve.compare( dataElement.attributes.mw, JSON.parse( originalMw ) )
|
2013-08-03 14:17:16 +00:00
|
|
|
) {
|
2015-10-02 10:39:40 +00:00
|
|
|
// originalDomElements is also used for CE rendering so return a copy
|
2018-03-06 12:44:37 +00:00
|
|
|
els = ve.copyDomElements( converter.getStore().value( dataElement.originalDomElementsHash ), doc );
|
2013-08-03 14:17:16 +00:00
|
|
|
} else {
|
2015-10-02 10:39:40 +00:00
|
|
|
if (
|
2018-04-28 13:43:18 +00:00
|
|
|
converter.doesModeNeedRendering() &&
|
2015-10-02 10:39:40 +00:00
|
|
|
// Use getHashObjectForRendering to get the rendering from the store
|
2018-03-06 12:44:37 +00:00
|
|
|
( value = store.value( store.hashOfValue( null, OO.getHash( [ this.getHashObjectForRendering( dataElement ), undefined ] ) ) ) )
|
2015-10-02 10:39:40 +00:00
|
|
|
) {
|
2015-09-09 09:41:39 +00:00
|
|
|
// For the clipboard use the current DOM contents so the user has something
|
|
|
|
// meaningful to paste into external applications
|
2016-08-17 19:50:55 +00:00
|
|
|
els = ve.copyDomElements( value, doc );
|
2015-09-09 09:41:39 +00:00
|
|
|
} else {
|
|
|
|
el = doc.createElement( this.tagName );
|
|
|
|
el.setAttribute( 'typeof', 'mw:Extension/' + this.getExtensionName( dataElement ) );
|
|
|
|
el.setAttribute( 'data-mw', JSON.stringify( dataElement.attributes.mw ) );
|
|
|
|
els = [ el ];
|
|
|
|
}
|
2013-08-03 14:17:16 +00:00
|
|
|
}
|
2015-09-09 10:02:44 +00:00
|
|
|
return els;
|
2013-08-03 14:17:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ve.dm.MWExtensionNode.static.getHashObject = function ( dataElement ) {
|
|
|
|
return {
|
|
|
|
type: dataElement.type,
|
2015-10-01 14:57:49 +00:00
|
|
|
mw: ve.copy( dataElement.attributes.mw )
|
2013-08-03 14:17:16 +00:00
|
|
|
};
|
|
|
|
};
|
2013-08-15 09:40:58 +00:00
|
|
|
|
2013-08-30 23:23:18 +00:00
|
|
|
/**
|
|
|
|
* Get the extension's name
|
|
|
|
*
|
|
|
|
* Static version for toDomElements
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {Object} dataElement Data element
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {string} Extension name
|
2013-08-30 23:23:18 +00:00
|
|
|
*/
|
|
|
|
ve.dm.MWExtensionNode.static.getExtensionName = function () {
|
|
|
|
return this.extensionName;
|
|
|
|
};
|
|
|
|
|
2018-04-04 17:33:47 +00:00
|
|
|
ve.dm.MWExtensionNode.static.describeChanges = function ( attributeChanges, attributes, element ) {
|
|
|
|
var tools, change,
|
2018-11-23 12:18:13 +00:00
|
|
|
descriptions = [],
|
|
|
|
fromBody = attributeChanges.mw.from.body,
|
|
|
|
toBody = attributeChanges.mw.to.body;
|
2018-04-04 17:33:47 +00:00
|
|
|
|
|
|
|
if ( attributeChanges.mw ) {
|
|
|
|
// HACK: Try to generate an '<Extension> has changed' message using the associated tool's title
|
|
|
|
tools = ve.ui.toolFactory.getRelatedItems( [ ve.dm.nodeFactory.createFromElement( element ) ] );
|
|
|
|
if ( tools.length ) {
|
|
|
|
descriptions.push( ve.msg( 'visualeditor-changedesc-unknown',
|
|
|
|
OO.ui.resolveMsg( ve.ui.toolFactory.lookup( tools[ 0 ].name ).static.title )
|
|
|
|
) );
|
|
|
|
}
|
|
|
|
// Compare body - default behaviour in #describeChange does nothing
|
2018-11-23 12:18:13 +00:00
|
|
|
if ( !ve.compare( fromBody, toBody ) ) {
|
2018-04-04 17:33:47 +00:00
|
|
|
change = this.describeChange( 'body', {
|
2018-11-23 12:18:13 +00:00
|
|
|
from: fromBody && fromBody.extsrc,
|
|
|
|
to: toBody && toBody.extsrc
|
2018-04-04 17:33:47 +00:00
|
|
|
} );
|
|
|
|
if ( change ) {
|
|
|
|
descriptions.push( change );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Append attribute changes
|
|
|
|
// Parent method
|
|
|
|
Array.prototype.push.apply( descriptions, ve.dm.MWExtensionNode.super.static.describeChanges.call(
|
|
|
|
this,
|
|
|
|
ve.ui.DiffElement.static.compareAttributes( attributeChanges.mw.from.attrs || {}, attributeChanges.mw.to.attrs || {} ),
|
|
|
|
attributes
|
|
|
|
) );
|
|
|
|
return descriptions;
|
2017-03-16 15:32:59 +00:00
|
|
|
}
|
2018-04-04 17:33:47 +00:00
|
|
|
// 'mw' should be the only attribute that changes...
|
|
|
|
return [];
|
2017-04-10 20:22:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ve.dm.MWExtensionNode.static.describeChange = function ( key ) {
|
2018-04-04 17:33:47 +00:00
|
|
|
if ( key === 'body' ) {
|
|
|
|
// TODO: Produce a diff of the body, suitable to display in the sidebar.
|
2017-04-10 20:22:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
// Parent method
|
2017-04-03 20:57:49 +00:00
|
|
|
return ve.dm.MWExtensionNode.super.static.describeChange.apply( this, arguments );
|
2017-03-16 15:32:59 +00:00
|
|
|
};
|
|
|
|
|
2013-08-15 09:40:58 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the extension's name
|
2015-08-19 18:21:01 +00:00
|
|
|
*
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {string} Extension name
|
2013-08-15 09:40:58 +00:00
|
|
|
*/
|
|
|
|
ve.dm.MWExtensionNode.prototype.getExtensionName = function () {
|
2013-08-30 23:23:18 +00:00
|
|
|
return this.constructor.static.getExtensionName( this.element );
|
|
|
|
};
|