moveoperation.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 Operation from './operation.js';
  7. import Position from '../position.js';
  8. import Range from '../range.js';
  9. import CKEditorError from '../../../utils/ckeditorerror.js';
  10. import utils from '../../../utils/utils.js';
  11. /**
  12. * Operation to move list of subsequent nodes from one position in the document to another.
  13. *
  14. * @memberOf core.treeModel.operation
  15. * @extends core.treeModel.operation.Operation
  16. */
  17. export default class MoveOperation extends Operation {
  18. /**
  19. * Creates a move operation.
  20. *
  21. * @param {core.treeModel.Position} sourcePosition Position before the first node to move.
  22. * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
  23. * @param {core.treeModel.Position} targetPosition Position where moved nodes will be inserted.
  24. * @param {Number} baseVersion {@link core.treeModel.Document#version} on which operation can be applied.
  25. */
  26. constructor( sourcePosition, howMany, targetPosition, baseVersion ) {
  27. super( baseVersion );
  28. /**
  29. * Source move position.
  30. *
  31. * @member {core.treeModel.Position} core.treeModel.operation.MoveOperation#sourcePosition
  32. */
  33. this.sourcePosition = Position.createFromPosition( sourcePosition );
  34. /**
  35. * How many nodes to move.
  36. *
  37. * @member {Number} core.treeModel.operation.MoveOperation#howMany
  38. */
  39. this.howMany = howMany;
  40. /**
  41. * Target move position.
  42. *
  43. * @member {core.treeModel.Position} core.treeModel.operation.MoveOperation#targetPosition
  44. */
  45. this.targetPosition = Position.createFromPosition( targetPosition );
  46. }
  47. /**
  48. * @see core.treeModel.operation.Operation#type
  49. */
  50. get type() {
  51. return 'move';
  52. }
  53. /**
  54. * Defines whether `MoveOperation` is sticky. If `MoveOperation` is sticky, during
  55. * {@link core.treeModel.operation.transform operational transformation} if there will be an operation that
  56. * inserts some nodes at the position equal to the boundary of this `MoveOperation`, that operation will
  57. * get their insertion path updated to the position where this `MoveOperation` moves the range.
  58. *
  59. * @protected
  60. * @type {Boolean}
  61. */
  62. get isSticky() {
  63. return true;
  64. }
  65. /**
  66. * @returns {core.treeModel.operation.MoveOperation}
  67. */
  68. clone() {
  69. return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );
  70. }
  71. /**
  72. * @returns {core.treeModel.operation.MoveOperation}
  73. */
  74. getReversed() {
  75. let newSourcePosition = this.targetPosition.getTransformedByDeletion( this.sourcePosition, this.howMany );
  76. let newTargetPosition = this.sourcePosition.getTransformedByInsertion( this.targetPosition, this.howMany );
  77. return new this.constructor( newSourcePosition, this.howMany, newTargetPosition, this.baseVersion + 1 );
  78. }
  79. _execute() {
  80. let sourceElement = this.sourcePosition.parent;
  81. let targetElement = this.targetPosition.parent;
  82. let sourceOffset = this.sourcePosition.offset;
  83. let targetOffset = this.targetPosition.offset;
  84. // Validate whether move operation has correct parameters.
  85. // Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
  86. // We expect that many errors might be connected with one of scenarios described below.
  87. if ( !sourceElement || !targetElement ) {
  88. /**
  89. * Source position or target position is invalid.
  90. *
  91. * @error operation-move-position-invalid
  92. */
  93. throw new CKEditorError(
  94. 'operation-move-position-invalid: Source position or target position is invalid.'
  95. );
  96. } else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
  97. /**
  98. * The nodes which should be moved do not exist.
  99. *
  100. * @error operation-move-nodes-do-not-exist
  101. */
  102. throw new CKEditorError(
  103. 'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.'
  104. );
  105. } else if ( sourceElement === targetElement && sourceOffset <= targetOffset && targetOffset < sourceOffset + this.howMany ) {
  106. /**
  107. * Trying to move a range of nodes into the middle of that range.
  108. *
  109. * @error operation-move-range-into-itself
  110. */
  111. throw new CKEditorError(
  112. 'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.'
  113. );
  114. } else {
  115. if ( utils.compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == 'PREFIX' ) {
  116. let i = this.sourcePosition.path.length - 1;
  117. if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
  118. /**
  119. * Trying to move a range of nodes into one of nodes from that range.
  120. *
  121. * @error operation-move-node-into-itself
  122. */
  123. throw new CKEditorError(
  124. 'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.'
  125. );
  126. }
  127. }
  128. }
  129. // End of validation.
  130. // If we move children in the same element and we remove elements on the position before the target we
  131. // need to update a target offset.
  132. if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
  133. targetOffset -= this.howMany;
  134. }
  135. const removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
  136. targetElement.insertChildren( targetOffset, removedNodes );
  137. return {
  138. sourcePosition: this.sourcePosition,
  139. range: Range.createFromPositionAndShift( this.targetPosition, this.howMany )
  140. };
  141. }
  142. }