mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-25 06:46:26 +00:00
83e1888275
Annotations' attributes might contain DOM elements, which cause infinite recursion in ve.compare(). Annotation classes can protect against this by overriding getHashObject() to summarize DOM nodes, but that doesn't help if that's not respected everywhere. Instead, compare the hash objects, those are safe. This does not appear to be a problem in practice, currently, because the nowiki annotation is experimental, oo.compare() now short-circuits if a === b, and because of optimizations in openAndCloseAnnotations() which lead to the relevant compareToForSerialization() code path being taken very rarely. Bug: 51948 Change-Id: If1bcc3eee4fd14d107db1935d89dcc5516643b53
176 lines
5.6 KiB
JavaScript
176 lines
5.6 KiB
JavaScript
/*!
|
|
* VisualEditor DataModel Annotation class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Generic DataModel annotation.
|
|
*
|
|
* This is an abstract class, annotations should extend this and call this constructor from their
|
|
* constructor. You should not instantiate this class directly.
|
|
*
|
|
* Annotations in the linear model are instances of subclasses of this class. Subclasses should
|
|
* only override static properties and functions.
|
|
*
|
|
* @class
|
|
* @extends {ve.dm.Model}
|
|
* @constructor
|
|
* @param {Object} element Linear model annotation
|
|
*/
|
|
ve.dm.Annotation = function VeDmAnnotation( element ) {
|
|
// Parent constructor
|
|
ve.dm.Model.call( this, element );
|
|
// Properties
|
|
this.name = this.constructor.static.name; // For ease of filtering
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.dm.Annotation, ve.dm.Model );
|
|
|
|
/* Static properties */
|
|
|
|
/**
|
|
* About grouping is not supported for annotations; setting this to true has no effect.
|
|
*
|
|
* @static
|
|
* @property {boolean} static.enableAboutGrouping
|
|
* @inheritable
|
|
*/
|
|
ve.dm.Annotation.static.enableAboutGrouping = false;
|
|
|
|
/**
|
|
* Automatically apply annotation to content inserted after it.
|
|
*
|
|
* @type {boolean}
|
|
*/
|
|
ve.dm.Annotation.static.applyToAppendedContent = true;
|
|
|
|
/**
|
|
* Abandon continuation when a wordbreak is generated
|
|
*
|
|
* @type {boolean}
|
|
*/
|
|
ve.dm.Annotation.static.splitOnWordbreak = false;
|
|
|
|
/**
|
|
* Static function to convert a linear model data element for this annotation type back to
|
|
* a DOM element.
|
|
*
|
|
* As special facilities for annotations, the annotated content that the returned element will
|
|
* wrap around is passed in as childDomElements, and this function may return an empty array to
|
|
* indicate that the annotation should produce no output. In that case, the child DOM elements will
|
|
* not be wrapped in anything and will be inserted directly into this annotation's parent.
|
|
*
|
|
* @static
|
|
* @inheritable
|
|
* @method
|
|
* @param {Object|Array} dataElement Linear model element or array of linear model data
|
|
* @param {HTMLDocument} doc HTML document for creating elements
|
|
* @param {ve.dm.Converter} converter Converter object to optionally call .getDomSubtreeFromData() on
|
|
* @param {HTMLElement[]} childDomElements Children that will be appended to the returned element
|
|
* @returns {HTMLElement[]} Array of DOM elements; only the first element is used; may be empty
|
|
*/
|
|
ve.dm.Annotation.static.toDomElements = function ( /*dataElement, doc, converter, childDomElements*/ ) {
|
|
throw new Error( 've.dm.Annotation subclass must implement toDomElements' );
|
|
};
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Convenience wrapper for .toDomElements() on the current annotation
|
|
* @method
|
|
* @param {HTMLDocument} [doc] HTML document to use to create elements
|
|
* @see ve.dm.Model#toDomElements
|
|
*/
|
|
ve.dm.Annotation.prototype.getDomElements = function ( doc ) {
|
|
return this.constructor.static.toDomElements( this.element, doc || document );
|
|
};
|
|
|
|
/**
|
|
* Get an object containing comparable annotation properties.
|
|
*
|
|
* This is used by the converter to merge adjacent annotations.
|
|
*
|
|
* @returns {Object} An object containing a subset of the annotation's properties
|
|
*/
|
|
ve.dm.Annotation.prototype.getComparableObject = function () {
|
|
var hashObject = this.getHashObject();
|
|
delete hashObject.htmlAttributes;
|
|
return hashObject;
|
|
};
|
|
|
|
/**
|
|
* HACK: This method strips data-parsoid from HTML attributes for comparisons.
|
|
*
|
|
* This should be removed once similar annotation merging is handled correctly
|
|
* by Parsoid.
|
|
*
|
|
* @returns {Object} An object all HTML attributes except data-parsoid
|
|
*/
|
|
ve.dm.Annotation.prototype.getComparableHtmlAttributes = function () {
|
|
var comparableAttributes, attributes = this.getHtmlAttributes();
|
|
|
|
if ( attributes[0] ) {
|
|
comparableAttributes = ve.copyObject( attributes[0].values );
|
|
delete comparableAttributes['data-parsoid'];
|
|
return comparableAttributes;
|
|
} else {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* HACK: This method adds in HTML attributes so comparable objects aren't serialized
|
|
* together if they have different HTML attributes.
|
|
*
|
|
* This method needs to be different from getComparableObject which is
|
|
* still used for editing annotations.
|
|
*
|
|
* @returns {Object} An object containing a subset of the annotation's properties and HTML attributes
|
|
*/
|
|
ve.dm.Annotation.prototype.getComparableObjectForSerialization = function () {
|
|
var object = this.getComparableObject(),
|
|
htmlAttributes = this.getComparableHtmlAttributes();
|
|
|
|
if ( !ve.isEmptyObject( htmlAttributes ) ) {
|
|
object.htmlAttributes = htmlAttributes;
|
|
}
|
|
return object;
|
|
};
|
|
|
|
/**
|
|
* HACK: Check if the annotation was generated by the converter
|
|
*
|
|
* Used by compareToForSerialization to avoid merging generated annotations.
|
|
*
|
|
* @returns {boolean} The annotation was generated
|
|
*/
|
|
ve.dm.Annotation.prototype.isGenerated = function () {
|
|
var attributes = this.getHtmlAttributes();
|
|
return attributes[0] && attributes[0].values && attributes[0].values['data-parsoid'];
|
|
};
|
|
|
|
/**
|
|
* HACK: Compare to another annotation for serialization
|
|
*
|
|
* Compares two annotations using getComparableObjectForSerialization, unless
|
|
* they are both generated annotations, in which case they must be identical.
|
|
*
|
|
* @param {ve.dm.Annotation} annotation Annotation to compare to
|
|
* @returns {boolean} The other annotation is similar to this one
|
|
*/
|
|
ve.dm.Annotation.prototype.compareToForSerialization = function ( annotation ) {
|
|
// If both annotations were generated
|
|
if ( this.isGenerated() && annotation.isGenerated() ) {
|
|
return ve.compare( this.getHashObject(), annotation.getHashObject() );
|
|
}
|
|
|
|
return ve.compare(
|
|
this.getComparableObjectForSerialization(),
|
|
annotation.getComparableObjectForSerialization()
|
|
);
|
|
};
|