moveoperation.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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 '../../ckeditorerror.js';
  10. import utils from '../../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. get type() {
  48. return 'move';
  49. }
  50. /**
  51. * Defines whether `MoveOperation` is sticky. If `MoveOperation` is sticky, during
  52. * {@link core.treeModel.operation.transform operational transformation} if there will be an operation that
  53. * inserts some nodes at the position equal to the boundary of this `MoveOperation`, that operation will
  54. * get their insertion path updated to the position where this `MoveOperation` moves the range.
  55. *
  56. * @protected
  57. * @type {Boolean}
  58. */
  59. get isSticky() {
  60. return true;
  61. }
  62. /**
  63. * @returns {core.treeModel.operation.MoveOperation}
  64. */
  65. clone() {
  66. return new this.constructor( this.sourcePosition, this.howMany, this.targetPosition, this.baseVersion );
  67. }
  68. /**
  69. * @returns {core.treeModel.operation.MoveOperation}
  70. */
  71. getReversed() {
  72. let newSourcePosition = this.targetPosition.getTransformedByDeletion( this.sourcePosition, this.howMany );
  73. let newTargetPosition = this.sourcePosition.getTransformedByInsertion( this.targetPosition, this.howMany );
  74. return new this.constructor( newSourcePosition, this.howMany, newTargetPosition, this.baseVersion + 1 );
  75. }
  76. _execute() {
  77. let sourceElement = this.sourcePosition.parent;
  78. let targetElement = this.targetPosition.parent;
  79. let sourceOffset = this.sourcePosition.offset;
  80. let targetOffset = this.targetPosition.offset;
  81. // Validate whether move operation has correct parameters.
  82. // Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
  83. // We expect that many errors might be connected with one of scenarios described below.
  84. if ( !sourceElement || !targetElement ) {
  85. /**
  86. * Source position or target position is invalid.
  87. *
  88. * @error operation-move-position-invalid
  89. */
  90. throw new CKEditorError(
  91. 'operation-move-position-invalid: Source position or target position is invalid.'
  92. );
  93. } else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
  94. /**
  95. * The nodes which should be moved do not exist.
  96. *
  97. * @error operation-move-nodes-do-not-exist
  98. */
  99. throw new CKEditorError(
  100. 'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.'
  101. );
  102. } else if ( sourceElement === targetElement && sourceOffset <= targetOffset && targetOffset < sourceOffset + this.howMany ) {
  103. /**
  104. * Trying to move a range of nodes into the middle of that range.
  105. *
  106. * @error operation-move-range-into-itself
  107. */
  108. throw new CKEditorError(
  109. 'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.'
  110. );
  111. } else {
  112. if ( utils.compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == 'PREFIX' ) {
  113. let i = this.sourcePosition.path.length - 1;
  114. if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
  115. /**
  116. * Trying to move a range of nodes into one of nodes from that range.
  117. *
  118. * @error operation-move-node-into-itself
  119. */
  120. throw new CKEditorError(
  121. 'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.'
  122. );
  123. }
  124. }
  125. }
  126. // End of validation.
  127. // If we move children in the same element and we remove elements on the position before the target we
  128. // need to update a target offset.
  129. if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
  130. targetOffset -= this.howMany;
  131. }
  132. const removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
  133. targetElement.insertChildren( targetOffset, removedNodes );
  134. return {
  135. sourcePosition: this.sourcePosition,
  136. range: Range.createFromPositionAndShift( this.targetPosition, this.howMany )
  137. };
  138. }
  139. }