8
0

attributedelta.js 7.7 KB

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