liveposition.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 Position from './position.js';
  7. import Range from './range.js';
  8. import EmitterMixin from '../emittermixin.js';
  9. import utils from '../utils.js';
  10. /**
  11. * LivePosition is a position in the Tree Model that updates itself as the tree changes. It may be used as a bookmark.
  12. * **Note:** Be very careful when dealing with LivePosition. Each LivePosition instance bind events that might
  13. * have to be unbound. Use {@link core.treeModel.LivePosition#detach} whenever you don't need LivePosition anymore.
  14. *
  15. * @memberOf core.treeModel
  16. * @extends core.treeModel.Position
  17. */
  18. export default class LivePosition extends Position {
  19. /**
  20. * Creates a live position.
  21. *
  22. * @see @link core.treeModel.Position
  23. * @param {core.treeModel.RootElement} root
  24. * @param {Array.<Number>} path
  25. * @param {core.treeModel.PositionStickiness} [stickiness] Defaults to `'STICKS_TO_NEXT'`. See
  26. * {@link core.treeModel.LivePosition#stickiness}.
  27. */
  28. constructor( root, path, stickiness ) {
  29. super( root, path );
  30. /**
  31. * Flag representing LivePosition stickiness. LivePosition might be sticking to previous node or next node.
  32. * Whenever some nodes are inserted at the same position as LivePosition, `stickiness` is checked to decide if
  33. * LivePosition should be moved. Similar applies when a range of nodes is moved and one of it's boundary
  34. * position is same as LivePosition.
  35. *
  36. * Examples:
  37. * Insert:
  38. * Position is at | and we insert at the same position, marked as ^:
  39. * - | sticks to previous node: `<p>f|^oo</p>` => `<p>f|baroo</p>`
  40. * - | sticks to next node: `<p>f^|oo</p>` => `<p>fbar|oo</p>`
  41. *
  42. * Move:
  43. * Position is at | and range [ ] is moved to position ^:
  44. * - | sticks to previous node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f|</p><p>booar</p>`
  45. * - | sticks to next node: `<p>f|[oo]</p><p>b^ar</p>` => `<p>f</p><p>b|ooar</p>`
  46. *
  47. * @member {core.treeModel.PositionStickiness} core.treeModel.LivePosition#stickiness
  48. */
  49. this.stickiness = stickiness || 'STICKS_TO_NEXT';
  50. bindWithDocument.call( this );
  51. }
  52. /**
  53. * Unbinds all events previously bound by LivePosition. Use it whenever you don't need LivePosition instance
  54. * anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
  55. * referring to it).
  56. *
  57. * @method core.treeModel.LivePosition#detach
  58. */
  59. detach() {
  60. this.stopListening();
  61. }
  62. /**
  63. * @static
  64. * @method core.treeModel.LivePosition.createAfter
  65. * @see {@link core.treeModel.Position#createAfter}
  66. * @param {core.treeModel.Node} node
  67. * @returns {core.treeModel.LivePosition}
  68. */
  69. /**
  70. * @static
  71. * @method core.treeModel.LivePosition.createBefore
  72. * @see {@link core.treeModel.Position#createBefore}
  73. * @param {core.treeModel.Node} node
  74. * @returns {core.treeModel.LivePosition}
  75. */
  76. /**
  77. * @static
  78. * @method core.treeModel.LivePosition.createFromParentAndOffset
  79. * @see {@link core.treeModel.Position#createFromParentAndOffset}
  80. * @param {core.treeModel.Element} parent
  81. * @param {Number} offset
  82. * @returns {core.treeModel.LivePosition}
  83. */
  84. /**
  85. * @static
  86. * @method core.treeModel.LivePosition.createFromPosition
  87. * @see {@link core.treeModel.Position#createFromPosition}
  88. * @param {core.treeModel.Position} position
  89. * @returns {core.treeModel.LivePosition}
  90. */
  91. }
  92. /**
  93. * Binds this LivePosition to the {@link core.treeModel.Document} that owns this position {@link core.treeModel.RootElement root}.
  94. *
  95. * @ignore
  96. * @private
  97. * @method core.treeModel.LivePosition.bindWithDocument
  98. */
  99. function bindWithDocument() {
  100. /*jshint validthis: true */
  101. this.listenTo(
  102. this.root.document,
  103. 'change',
  104. ( event, type, changes ) => {
  105. transform.call( this, type, changes.range, changes.sourcePosition );
  106. },
  107. this
  108. );
  109. }
  110. /**
  111. * Updates this position accordingly to the updates applied to the Tree Model. Bases on change events.
  112. *
  113. * @ignore
  114. * @private
  115. * @method transform
  116. * @param {String} type Type of changes applied to the Tree Model.
  117. * @param {core.treeModel.Range} range Range containing the result of applied change.
  118. * @param {core.treeModel.Position} [position] Additional position parameter provided by some change events.
  119. */
  120. function transform( type, range, position ) {
  121. /*jshint validthis: true */
  122. let howMany = range.end.offset - range.start.offset;
  123. let transformed;
  124. switch ( type ) {
  125. case 'insert':
  126. let insertBefore = this.stickiness == 'STICKS_TO_NEXT';
  127. transformed = this.getTransformedByInsertion( range.start, howMany, insertBefore );
  128. break;
  129. case 'move':
  130. case 'remove':
  131. case 'reinsert':
  132. let originalRange = Range.createFromPositionAndShift( position, howMany );
  133. let gotMoved = originalRange.containsPosition( this ) ||
  134. ( originalRange.start.isEqual( this ) && this.stickiness == 'STICKS_TO_NEXT' ) ||
  135. ( originalRange.end.isEqual( this ) && this.stickiness == 'STICKS_TO_PREVIOUS' );
  136. // We can't use .getTransformedByMove() because we have a different if-condition.
  137. if ( gotMoved ) {
  138. transformed = this._getCombined( position, range.start );
  139. } else {
  140. let insertBefore = this.stickiness == 'STICKS_TO_NEXT';
  141. transformed = this.getTransformedByMove( position, range.start, howMany, insertBefore );
  142. }
  143. break;
  144. }
  145. this.path = transformed.path;
  146. this.root = transformed.root;
  147. }
  148. utils.mix( LivePosition, EmitterMixin );
  149. /**
  150. * Enum representing how position is "sticking" with their neighbour nodes.
  151. * Possible values: `'STICKS_TO_NEXT'`, `'STICKS_TO_PREVIOUS'`.
  152. *
  153. * @typedef {String} core.treeModel.PositionStickiness
  154. */