liverange.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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 Range from './range.js';
  7. import EmitterMixin from '../../utils/emittermixin.js';
  8. import mix from '../../utils/mix.js';
  9. /**
  10. * LiveRange is a Range in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
  11. *
  12. * **Note:** Be very careful when dealing with `LiveRange`. Each `LiveRange` instance bind events that might
  13. * have to be unbound. Use {@link engine.model.LiveRange#detach detach} whenever you don't need `LiveRange` anymore.
  14. *
  15. * @memberOf engine.model
  16. */
  17. export default class LiveRange extends Range {
  18. /**
  19. * Creates a live range.
  20. *
  21. * @see engine.model.Range
  22. */
  23. constructor( start, end ) {
  24. super( start, end );
  25. bindWithDocument.call( this );
  26. }
  27. /**
  28. * Unbinds all events previously bound by LiveRange. Use it whenever you don't need LiveRange instance
  29. * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
  30. * referring to it).
  31. */
  32. detach() {
  33. this.stopListening();
  34. }
  35. /**
  36. * @see engine.model.Range.createFromElement
  37. * @static
  38. * @method engine.model.LiveRange.createFromElement
  39. * @param {engine.model.Element} element
  40. * @returns {engine.model.LiveRange}
  41. */
  42. /**
  43. * @see engine.model.Range.createFromPositionAndShift
  44. * @static
  45. * @method engine.model.LiveRange.createFromPositionAndShift
  46. * @param {engine.model.Position} position
  47. * @param {Number} shift
  48. * @returns {engine.model.LiveRange}
  49. */
  50. /**
  51. * @see engine.model.Range.createFromParentsAndOffsets
  52. * @static
  53. * @method engine.model.LiveRange.createFromParentsAndOffsets
  54. * @param {engine.model.Element} startElement
  55. * @param {Number} startOffset
  56. * @param {engine.model.Element} endElement
  57. * @param {Number} endOffset
  58. * @returns {engine.model.LiveRange}
  59. */
  60. /**
  61. * @see engine.model.Range.createFromRange
  62. * @static
  63. * @method engine.model.LiveRange.createFromRange
  64. * @param {engine.model.Range} range
  65. * @returns {engine.model.LiveRange}
  66. */
  67. }
  68. /**
  69. * Binds this LiveRange to the {@link engine.model.Document} that owns this range.
  70. *
  71. * @ignore
  72. * @private
  73. * @method engine.model.LiveRange#bindWithDocument
  74. */
  75. function bindWithDocument() {
  76. /*jshint validthis: true */
  77. this.listenTo(
  78. this.root.document,
  79. 'change',
  80. ( event, type, changes ) => {
  81. fixBoundaries.call( this, type, changes.range, changes.sourcePosition );
  82. },
  83. this
  84. );
  85. }
  86. /**
  87. * LiveRange boundaries are instances of {@link engine.model.LivePosition}, so it is updated thanks to them. This method
  88. * additionally fixes the results of updating live positions taking into account that those live positions
  89. * are boundaries of a range. An example case for fixing live positions is end boundary is moved before start boundary.
  90. *
  91. * @ignore
  92. * @private
  93. * @method fixBoundaries
  94. * @param {String} type Type of changes applied to the Tree Model.
  95. * @param {engine.model.Range} range Range containing the result of applied change.
  96. * @param {engine.model.Position} [position] Additional position parameter provided by some change events.
  97. */
  98. function fixBoundaries( type, range, position ) {
  99. /* jshint validthis: true */
  100. let updated;
  101. const howMany = range.end.offset - range.start.offset;
  102. switch ( type ) {
  103. case 'insert':
  104. updated = this.getTransformedByInsertion( range.start, howMany, false, true )[ 0 ];
  105. break;
  106. case 'move':
  107. case 'remove':
  108. case 'reinsert':
  109. const sourcePosition = position;
  110. // Range.getTransformedByMove is expecting `targetPosition` to be "before" move
  111. // (before transformation). `range.start` is already after the move happened.
  112. // We have to revert `range.start` to the state before the move.
  113. const targetPosition = range.start.getTransformedByInsertion( sourcePosition, howMany );
  114. const result = this.getTransformedByMove( sourcePosition, targetPosition, howMany, false, true );
  115. // First item in the array is the "difference" part, so a part of the range
  116. // that did not get moved. We use it as reference range and expand if possible.
  117. updated = result[ 0 ];
  118. // We will check if there is other range and if it is touching the reference range.
  119. // If it does, we will expand the reference range (at the beginning or at the end).
  120. // Keep in mind that without settings `spread` flag, `getTransformedByMove` may
  121. // return maximum two ranges.
  122. if ( result.length > 1 ) {
  123. let otherRange = result[ 1 ];
  124. if ( updated.start.isTouching( otherRange.end ) ) {
  125. updated.start = otherRange.start;
  126. } else if ( updated.end.isTouching( otherRange.start ) ) {
  127. updated.end = otherRange.end;
  128. }
  129. }
  130. break;
  131. }
  132. if ( updated ) {
  133. this.start = updated.start;
  134. this.end = updated.end;
  135. }
  136. }
  137. mix( LiveRange, EmitterMixin );