moveoperation.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. CKEDITOR.define( [
  7. 'treemodel/operation/operation',
  8. 'treemodel/range',
  9. 'ckeditorerror',
  10. 'utils'
  11. ], ( Operation, Range, CKEditorError, utils ) => {
  12. /**
  13. * Operation to move list of subsequent nodes from one position in the document to another.
  14. *
  15. * @class treeModel.operation.MoveOperation
  16. */
  17. class MoveOperation extends Operation {
  18. /**
  19. * Creates a move operation.
  20. *
  21. * @param {treeModel.Position} sourcePosition Position before the first element to move.
  22. * @param {Number} howMany How many consecutive nodes to move, starting from `sourcePosition`.
  23. * @param {treeModel.Position} targetPosition Position where moved elements will be inserted.
  24. * @param {Number} baseVersion {@link treeModel.Document#version} on which operation can be applied.
  25. * @constructor
  26. */
  27. constructor( sourcePosition, howMany, targetPosition, baseVersion ) {
  28. super( baseVersion );
  29. /**
  30. * Source move position.
  31. *
  32. * @type {treeModel.Position}
  33. */
  34. this.sourcePosition = sourcePosition;
  35. /**
  36. * How many nodes to move.
  37. *
  38. * @type {Number}
  39. */
  40. this.howMany = howMany;
  41. /**
  42. * Target move position.
  43. *
  44. * @type {treeModel.Position}
  45. */
  46. this.targetPosition = targetPosition;
  47. }
  48. get type() {
  49. return 'move';
  50. }
  51. clone() {
  52. return new MoveOperation( this.sourcePosition.clone(), this.howMany, this.targetPosition.clone(), this.baseVersion );
  53. }
  54. getReversed() {
  55. return new MoveOperation( this.targetPosition.clone(), this.howMany, this.sourcePosition.clone(), this.baseVersion + 1 );
  56. }
  57. _execute() {
  58. let sourceElement = this.sourcePosition.parent;
  59. let targetElement = this.targetPosition.parent;
  60. let sourceOffset = this.sourcePosition.offset;
  61. let targetOffset = this.targetPosition.offset;
  62. // Validate whether move operation has correct parameters.
  63. // Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
  64. // We expect that many errors might be connected with one of scenarios described below.
  65. if ( !sourceElement || !targetElement ) {
  66. /**
  67. * Source position or target position is invalid.
  68. *
  69. * @error operation-move-position-invalid
  70. */
  71. throw new CKEditorError(
  72. 'operation-move-position-invalid: Source position or target position is invalid.'
  73. );
  74. } else if ( sourceOffset + this.howMany > sourceElement.getChildCount() ) {
  75. /**
  76. * The nodes which should be moved do not exist.
  77. *
  78. * @error operation-move-nodes-do-not-exist
  79. */
  80. throw new CKEditorError(
  81. 'operation-move-nodes-do-not-exist: The nodes which should be moved do not exist.'
  82. );
  83. } else if ( sourceElement === targetElement && sourceOffset <= targetOffset && targetOffset < sourceOffset + this.howMany ) {
  84. /**
  85. * Trying to move a range of nodes into the middle of that range.
  86. *
  87. * @error operation-move-range-into-itself
  88. */
  89. throw new CKEditorError(
  90. 'operation-move-range-into-itself: Trying to move a range of nodes to the inside of that range.'
  91. );
  92. } else {
  93. if ( utils.compareArrays( this.sourcePosition.getParentPath(), this.targetPosition.getParentPath() ) == utils.compareArrays.PREFIX ) {
  94. let i = this.sourcePosition.path.length - 1;
  95. if ( this.targetPosition.path[ i ] >= sourceOffset && this.targetPosition.path[ i ] < sourceOffset + this.howMany ) {
  96. /**
  97. * Trying to move a range of nodes into one of nodes from that range.
  98. *
  99. * @error operation-move-node-into-itself
  100. */
  101. throw new CKEditorError(
  102. 'operation-move-node-into-itself: Trying to move a range of nodes into one of nodes from that range.'
  103. );
  104. }
  105. }
  106. }
  107. // End of validation.
  108. // If we move children in the same element and we remove elements on the position before the target we
  109. // need to update a target offset.
  110. if ( sourceElement === targetElement && sourceOffset < targetOffset ) {
  111. targetOffset -= this.howMany;
  112. }
  113. const removedNodes = sourceElement.removeChildren( sourceOffset, this.howMany );
  114. targetElement.insertChildren( targetOffset, removedNodes );
  115. return {
  116. sourcePosition: this.sourcePosition,
  117. range: Range.createFromPositionAndShift( this.targetPosition, this.howMany )
  118. };
  119. }
  120. }
  121. return MoveOperation;
  122. } );