8
0

attributedelta.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import Delta from './delta.js';
  7. import { registerDeserializer } from './deltafactory.js';
  8. import { register } from '../batch.js';
  9. import AttributeOperation from '../operation/attributeoperation.js';
  10. import RootAttributeOperation from '../operation/rootattributeoperation.js';
  11. import Position from '../position.js';
  12. import Range from '../range.js';
  13. import RootElement from '../rootelement.js';
  14. import Element from '../element.js';
  15. /**
  16. * To provide specific OT behavior and better collisions solving, change methods ({@link engine.treeModel.Batch#setAttr}
  17. * and {@link engine.treeModel.Batch#removeAttr}) use `AttributeDelta` class which inherits from the `Delta` class and may
  18. * overwrite some methods.
  19. *
  20. * @memberOf engine.treeModel.delta
  21. * @extends engine.treeModel.delta.Delta
  22. */
  23. export default class AttributeDelta extends Delta {
  24. /**
  25. * The attribute key that is changed by the delta or `null` if the delta has no operations.
  26. *
  27. * @type {String|null}
  28. */
  29. get key() {
  30. return this.operations[ 0 ] ? this.operations[ 0 ].key : null;
  31. }
  32. /**
  33. * The attribute value that is set by the delta or `null` if the delta has no operations.
  34. *
  35. * @type {*|null}
  36. */
  37. get value() {
  38. return this.operations[ 0 ] ? this.operations[ 0 ].newValue : null;
  39. }
  40. /**
  41. * The range on which delta operates or `null` if the delta has no operations.
  42. *
  43. * @type {engine.treeModel.Range|null}
  44. */
  45. get range() {
  46. // Check if it is cached.
  47. if ( this._range ) {
  48. return this._range;
  49. }
  50. // If it is not cached we will evaluate it and cache it.
  51. let firstOperation = this.operations[ 0 ];
  52. let lastOperation = this.operations[ this.operations.length - 1 ];
  53. if ( firstOperation ) {
  54. this._range = new Range( firstOperation.range.start, lastOperation.range.end );
  55. return this._range;
  56. }
  57. return null;
  58. }
  59. get _reverseDeltaClass() {
  60. return AttributeDelta;
  61. }
  62. /** @inheritDoc */
  63. static get className() {
  64. return 'engine.treeModel.delta.AttributeDelta';
  65. }
  66. static get _priority() {
  67. return 20;
  68. }
  69. }
  70. /**
  71. * To provide specific OT behavior and better collisions solving, change methods ({@link engine.treeModel.Batch#setAttr}
  72. * and {@link engine.treeModel.Batch#removeAttr}) use `RootAttributeDelta` class which inherits from the `Delta` class and may
  73. * overwrite some methods.
  74. *
  75. * @memberOf engine.treeModel.delta
  76. * @extends engine.treeModel.delta.Delta
  77. */
  78. export class RootAttributeDelta extends Delta {
  79. /** @inheritDoc */
  80. static get className() {
  81. return 'engine.treeModel.delta.RootAttributeDelta';
  82. }
  83. }
  84. /**
  85. * Sets the value of the attribute of the node or on the range.
  86. *
  87. * @chainable
  88. * @method engine.treeModel.Batch#setAttr
  89. * @param {String} key Attribute key.
  90. * @param {*} value Attribute new value.
  91. * @param {engine.treeModel.Node|engine.treeModel.Range} nodeOrRange Node or range on which the attribute will be set.
  92. */
  93. register( 'setAttr', function( key, value, nodeOrRange ) {
  94. attribute( this, key, value, nodeOrRange );
  95. return this;
  96. } );
  97. /**
  98. * Removes an attribute from the range.
  99. *
  100. * @chainable
  101. * @method engine.treeModel.Batch#removeAttr
  102. * @param {String} key Attribute key.
  103. * @param {engine.treeModel.Node|engine.treeModel.Range} nodeOrRange Node or range on which the attribute will be removed.
  104. */
  105. register( 'removeAttr', function( key, nodeOrRange ) {
  106. attribute( this, key, null, nodeOrRange );
  107. return this;
  108. } );
  109. function attribute( batch, key, value, nodeOrRange ) {
  110. if ( nodeOrRange instanceof Range ) {
  111. changeRange( batch, batch.doc, key, value, nodeOrRange );
  112. } else {
  113. changeNode( batch, batch.doc, key, value, nodeOrRange );
  114. }
  115. }
  116. function changeNode( batch, doc, key, value, node ) {
  117. const previousValue = node.getAttribute( key );
  118. let range, operation;
  119. const delta = node instanceof RootElement ? new RootAttributeDelta() : new AttributeDelta();
  120. batch.addDelta( delta );
  121. if ( previousValue != value ) {
  122. if ( node instanceof RootElement ) {
  123. // If we change attributes of root element, we have to use `RootAttributeOperation`.
  124. operation = new RootAttributeOperation( node, key, previousValue, value, doc.version );
  125. } else {
  126. if ( node instanceof Element ) {
  127. // If we change the attribute of the element, we do not want to change attributes of its children, so
  128. // the end on the range can not be put after the closing tag, it should be inside that element with the
  129. // offset 0, so the range will contains only the opening tag...
  130. range = new Range( Position.createBefore( node ), Position.createFromParentAndOffset( node, 0 ) );
  131. } else {
  132. // ...but for characters we can not put the range inside it, so we end the range after that character.
  133. range = new Range( Position.createBefore( node ), Position.createAfter( node ) );
  134. }
  135. operation = new AttributeOperation( range, key, previousValue || null, value || null, doc.version );
  136. }
  137. delta.addOperation( operation );
  138. doc.applyOperation( operation );
  139. }
  140. }
  141. // Because attribute operation needs to have the same attribute value on the whole range, this function split the range
  142. // into smaller parts.
  143. function changeRange( batch, doc, attributeKey, attributeValue, range ) {
  144. const delta = new AttributeDelta();
  145. batch.addDelta( delta );
  146. // Position of the last split, the beginning of the new range.
  147. let lastSplitPosition = range.start;
  148. // Currently position in the scanning range. Because we need value after the position, it is not a current
  149. // position of the iterator but the previous one (we need to iterate one more time to get the value after).
  150. let position;
  151. // Value before the currently position.
  152. let attributeValueBefore;
  153. // Value after the currently position.
  154. let attributeValueAfter;
  155. for ( let value of range ) {
  156. attributeValueAfter = value.item.getAttribute( attributeKey );
  157. // At the first run of the iterator the position in undefined. We also do not have a attributeValueBefore, but
  158. // because attributeValueAfter may be null, attributeValueBefore may be equal attributeValueAfter ( undefined == null ).
  159. if ( position && attributeValueBefore != attributeValueAfter ) {
  160. // if attributeValueBefore == attributeValue there is nothing to change, so we add operation only if these values are different.
  161. if ( attributeValueBefore != attributeValue ) {
  162. addOperation();
  163. }
  164. lastSplitPosition = position;
  165. }
  166. position = value.nextPosition;
  167. attributeValueBefore = attributeValueAfter;
  168. }
  169. // Because position in the loop is not the iterator position (see let position comment), the last position in
  170. // the while loop will be last but one position in the range. We need to check the last position manually.
  171. if ( position instanceof Position && position != lastSplitPosition && attributeValueBefore != attributeValue ) {
  172. addOperation();
  173. }
  174. function addOperation() {
  175. let range = new Range( lastSplitPosition, position );
  176. const operation = new AttributeOperation( range, attributeKey, attributeValueBefore || null, attributeValue || null, doc.version );
  177. delta.addOperation( operation );
  178. doc.applyOperation( operation );
  179. }
  180. }
  181. registerDeserializer( AttributeDelta.className, AttributeDelta );
  182. registerDeserializer( RootAttributeDelta.className, RootAttributeDelta );