| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- 'use strict';
- import Delta from './delta.js';
- import { registerDeserializer } from './deltafactory.js';
- import { register } from '../batch.js';
- import AttributeOperation from '../operation/attributeoperation.js';
- import RootAttributeOperation from '../operation/rootattributeoperation.js';
- import Position from '../position.js';
- import Range from '../range.js';
- import RootElement from '../rootelement.js';
- import Element from '../element.js';
- /**
- * To provide specific OT behavior and better collisions solving, change methods ({@link engine.treeModel.Batch#setAttr}
- * and {@link engine.treeModel.Batch#removeAttr}) use `AttributeDelta` class which inherits from the `Delta` class and may
- * overwrite some methods.
- *
- * @memberOf engine.treeModel.delta
- * @extends engine.treeModel.delta.Delta
- */
- export default class AttributeDelta extends Delta {
- /**
- * The attribute key that is changed by the delta or `null` if the delta has no operations.
- *
- * @type {String|null}
- */
- get key() {
- return this.operations[ 0 ] ? this.operations[ 0 ].key : null;
- }
- /**
- * The attribute value that is set by the delta or `null` if the delta has no operations.
- *
- * @type {*|null}
- */
- get value() {
- return this.operations[ 0 ] ? this.operations[ 0 ].newValue : null;
- }
- /**
- * The range on which delta operates or `null` if the delta has no operations.
- *
- * @type {engine.treeModel.Range|null}
- */
- get range() {
- // Check if it is cached.
- if ( this._range ) {
- return this._range;
- }
- // If it is not cached we will evaluate it and cache it.
- let firstOperation = this.operations[ 0 ];
- let lastOperation = this.operations[ this.operations.length - 1 ];
- if ( firstOperation ) {
- this._range = new Range( firstOperation.range.start, lastOperation.range.end );
- return this._range;
- }
- return null;
- }
- get _reverseDeltaClass() {
- return AttributeDelta;
- }
- /** @inheritDoc */
- static get className() {
- return 'engine.treeModel.delta.AttributeDelta';
- }
- static get _priority() {
- return 20;
- }
- }
- /**
- * To provide specific OT behavior and better collisions solving, change methods ({@link engine.treeModel.Batch#setAttr}
- * and {@link engine.treeModel.Batch#removeAttr}) use `RootAttributeDelta` class which inherits from the `Delta` class and may
- * overwrite some methods.
- *
- * @memberOf engine.treeModel.delta
- * @extends engine.treeModel.delta.Delta
- */
- export class RootAttributeDelta extends Delta {
- /** @inheritDoc */
- static get className() {
- return 'engine.treeModel.delta.RootAttributeDelta';
- }
- }
- /**
- * Sets the value of the attribute of the node or on the range.
- *
- * @chainable
- * @method engine.treeModel.Batch#setAttr
- * @param {String} key Attribute key.
- * @param {*} value Attribute new value.
- * @param {engine.treeModel.Node|engine.treeModel.Range} nodeOrRange Node or range on which the attribute will be set.
- */
- register( 'setAttr', function( key, value, nodeOrRange ) {
- attribute( this, key, value, nodeOrRange );
- return this;
- } );
- /**
- * Removes an attribute from the range.
- *
- * @chainable
- * @method engine.treeModel.Batch#removeAttr
- * @param {String} key Attribute key.
- * @param {engine.treeModel.Node|engine.treeModel.Range} nodeOrRange Node or range on which the attribute will be removed.
- */
- register( 'removeAttr', function( key, nodeOrRange ) {
- attribute( this, key, null, nodeOrRange );
- return this;
- } );
- function attribute( batch, key, value, nodeOrRange ) {
- if ( nodeOrRange instanceof Range ) {
- changeRange( batch, batch.doc, key, value, nodeOrRange );
- } else {
- changeNode( batch, batch.doc, key, value, nodeOrRange );
- }
- }
- function changeNode( batch, doc, key, value, node ) {
- const previousValue = node.getAttribute( key );
- let range, operation;
- const delta = node instanceof RootElement ? new RootAttributeDelta() : new AttributeDelta();
- batch.addDelta( delta );
- if ( previousValue != value ) {
- if ( node instanceof RootElement ) {
- // If we change attributes of root element, we have to use `RootAttributeOperation`.
- operation = new RootAttributeOperation( node, key, previousValue, value, doc.version );
- } else {
- if ( node instanceof Element ) {
- // If we change the attribute of the element, we do not want to change attributes of its children, so
- // the end on the range can not be put after the closing tag, it should be inside that element with the
- // offset 0, so the range will contains only the opening tag...
- range = new Range( Position.createBefore( node ), Position.createFromParentAndOffset( node, 0 ) );
- } else {
- // ...but for characters we can not put the range inside it, so we end the range after that character.
- range = new Range( Position.createBefore( node ), Position.createAfter( node ) );
- }
- operation = new AttributeOperation( range, key, previousValue || null, value || null, doc.version );
- }
- delta.addOperation( operation );
- doc.applyOperation( operation );
- }
- }
- // Because attribute operation needs to have the same attribute value on the whole range, this function split the range
- // into smaller parts.
- function changeRange( batch, doc, attributeKey, attributeValue, range ) {
- const delta = new AttributeDelta();
- batch.addDelta( delta );
- // Position of the last split, the beginning of the new range.
- let lastSplitPosition = range.start;
- // Currently position in the scanning range. Because we need value after the position, it is not a current
- // position of the iterator but the previous one (we need to iterate one more time to get the value after).
- let position;
- // Value before the currently position.
- let attributeValueBefore;
- // Value after the currently position.
- let attributeValueAfter;
- for ( let value of range ) {
- attributeValueAfter = value.item.getAttribute( attributeKey );
- // At the first run of the iterator the position in undefined. We also do not have a attributeValueBefore, but
- // because attributeValueAfter may be null, attributeValueBefore may be equal attributeValueAfter ( undefined == null ).
- if ( position && attributeValueBefore != attributeValueAfter ) {
- // if attributeValueBefore == attributeValue there is nothing to change, so we add operation only if these values are different.
- if ( attributeValueBefore != attributeValue ) {
- addOperation();
- }
- lastSplitPosition = position;
- }
- position = value.nextPosition;
- attributeValueBefore = attributeValueAfter;
- }
- // Because position in the loop is not the iterator position (see let position comment), the last position in
- // the while loop will be last but one position in the range. We need to check the last position manually.
- if ( position instanceof Position && position != lastSplitPosition && attributeValueBefore != attributeValue ) {
- addOperation();
- }
- function addOperation() {
- let range = new Range( lastSplitPosition, position );
- const operation = new AttributeOperation( range, attributeKey, attributeValueBefore || null, attributeValue || null, doc.version );
- delta.addOperation( operation );
- doc.applyOperation( operation );
- }
- }
- registerDeserializer( AttributeDelta.className, AttributeDelta );
- registerDeserializer( RootAttributeDelta.className, RootAttributeDelta );
|