/*!
* VisualEditor DataModel Annotation class.
*
* @copyright See AUTHORS.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
* @param {ve.dm.HashValueStore} store Store used by annotation
*/
ve.dm.Annotation = function VeDmAnnotation( element, store ) {
// Parent constructor
ve.dm.Annotation.super.call( this, element );
// Properties
this.name = this.constructor.static.name; // For ease of filtering
this.store = store;
};
/* Inheritance */
OO.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}
* @inheritable
*/
ve.dm.Annotation.static.enableAboutGrouping = false;
/**
* Automatically apply annotation to content inserted after it.
*
* @property {boolean}
* @see ve.ce.TextState#getChangeTransaction
*/
ve.dm.Annotation.static.applyToAppendedContent = true;
/**
* Accept this annotation when the browser spontaneously adds it to view's DOM.
*
* @property {boolean}
*/
ve.dm.Annotation.static.inferFromView = false;
/**
* Annotations which are removed when this one is applied
*
* @property {string[]}
*/
ve.dm.Annotation.static.removes = [];
/**
* Move whitespace at the edge of the transaction outside of it when converting
*
* e.g. "foo<b> bar </b>baz" -> "foo <b>bar</b> baz"
*
* @property {boolean}
*/
ve.dm.Annotation.static.trimWhitespace = true;
/**
* 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.
*
* @abstract
* @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 {Node[]} childDomElements Children that will be appended to the returned element
* @return {HTMLElement[]} Array of DOM elements; only the first element is used; may be empty
*/
ve.dm.Annotation.static.toDomElements = null;
/* Methods */
/**
* Get an object containing comparable annotation properties.
*
* This is used by the converter to merge adjacent annotations.
*
* @return {Object} An object containing a subset of the annotation's properties
*/
ve.dm.Annotation.prototype.getComparableObject = function () {
const hashObject = this.getHashObject();
delete hashObject.originalDomElementsHash;
return hashObject;
};
/**
* FIXME T126037: This method strips data-parsoid & RESTBase IDs from HTML attributes for comparisons.
*
* This should be removed once similar annotation merging is handled correctly
* by Parsoid.
*
* @return {Object} An object all HTML attributes except data-parsoid & RESTBase IDs
*/
ve.dm.Annotation.prototype.getComparableHtmlAttributes = function () {
const domElements = this.store && this.getOriginalDomElements( this.store );
if ( domElements && domElements[ 0 ] ) {
const comparableAttributes = ve.getDomAttributes( domElements[ 0 ] );
delete comparableAttributes[ 'data-parsoid' ];
if ( comparableAttributes.id ) {
const metadataIdRegExp = ve.init.platform.getMetadataIdRegExp();
if ( metadataIdRegExp && metadataIdRegExp.test( comparableAttributes.id ) ) {
delete comparableAttributes.id;
}
}
return comparableAttributes;
}
return {};
};
/**
* FIXME T126038: 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.
*
* @return {Object} An object containing a subset of the annotation's properties and HTML attributes
*/
ve.dm.Annotation.prototype.getComparableObjectForSerialization = function () {
const object = this.getComparableObject(),
htmlAttributes = this.getComparableHtmlAttributes();
if ( !ve.isEmptyObject( htmlAttributes ) ) {
object.htmlAttributes = htmlAttributes;
}
return object;
};
/**
* Check if the annotation was generated by the converter
*
* Used by compareToForSerialization to avoid merging generated annotations.
*
* @return {boolean} The annotation was generated
*/
ve.dm.Annotation.prototype.isGenerated = function () {
// Only annotations and nodes generated by the converter have originalDomElements set.
// If this annotation was not generated by the converter, this.getOriginalDomElements()
// will return an empty array.
return this.getOriginalDomElementsHash() !== undefined;
};
/**
* Compare two annotations using #getComparableObject
*
* @param {ve.dm.Annotation} annotation Other annotation to compare against
* @return {boolean} Annotation is comparable
*/
ve.dm.Annotation.prototype.compareTo = function ( annotation ) {
return ve.compare(
this.getComparableObject(),
annotation.getComparableObject()
);
};
/**
* FIXME T126039: 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
* @return {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()
);
};
/**
* Describe the addition of this annotation to some text
*
* @return {Array} Descriptions, list of strings or Node arrays
*/
ve.dm.Annotation.prototype.describeAdded = function () {
return [];
};
/**
* Describe the removal of this annotation from some text
*
* @return {Array} Descriptions, list of strings or Node arrays
*/
ve.dm.Annotation.prototype.describeRemoved = function () {
return [];
};