moveoperation.js 4.8 KB

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