|
|
@@ -66,70 +66,47 @@ export default class Position {
|
|
|
|
|
|
// Normalize the root and path (if element was passed).
|
|
|
path = root.getPath().concat( path );
|
|
|
-
|
|
|
- // Make path immutable
|
|
|
- Object.freeze( path );
|
|
|
+ root = root.root;
|
|
|
|
|
|
/**
|
|
|
* Root of the position path.
|
|
|
*
|
|
|
- * @protected
|
|
|
+ * @readonly
|
|
|
* @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
|
|
|
- * module:engine/model/position~Position#_root
|
|
|
+ * module:engine/model/position~Position#root
|
|
|
*/
|
|
|
- this._root = root.root;
|
|
|
+ this.root = root;
|
|
|
|
|
|
/**
|
|
|
- * Position of the node in the tree.
|
|
|
+ * Position of the node in the tree. **Path contains offsets, not indexes.**
|
|
|
+ *
|
|
|
+ * Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
|
|
|
+ * {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
|
|
|
+ * {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
|
|
|
+ * down to the position offset in it's parent.
|
|
|
+ *
|
|
|
+ * ROOT
|
|
|
+ * |- P before: [ 0 ] after: [ 1 ]
|
|
|
+ * |- UL before: [ 1 ] after: [ 2 ]
|
|
|
+ * |- LI before: [ 1, 0 ] after: [ 1, 1 ]
|
|
|
+ * | |- foo before: [ 1, 0, 0 ] after: [ 1, 0, 3 ]
|
|
|
+ * |- LI before: [ 1, 1 ] after: [ 1, 2 ]
|
|
|
+ * |- bar before: [ 1, 1, 0 ] after: [ 1, 1, 3 ]
|
|
|
*
|
|
|
- * @protected
|
|
|
- * @member {Array.<Number>} module:engine/model/position~Position#_path
|
|
|
+ * `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
|
|
|
+ * greater than `1` you can place position offset between their start and end:
|
|
|
+ *
|
|
|
+ * ROOT
|
|
|
+ * |- P
|
|
|
+ * |- UL
|
|
|
+ * |- LI
|
|
|
+ * | |- f^o|o ^ has path: [ 1, 0, 1 ] | has path: [ 1, 0, 2 ]
|
|
|
+ * |- LI
|
|
|
+ * |- b^a|r ^ has path: [ 1, 1, 1 ] | has path: [ 1, 1, 2 ]
|
|
|
+ *
|
|
|
+ * @member {Array.<Number>} module:engine/model/position~Position#path
|
|
|
*/
|
|
|
- this._path = path;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Position of the node in the tree. **Path contains offsets, not indexes.**
|
|
|
- *
|
|
|
- * Position can be placed before, after or in a {@link module:engine/model/node~Node node} if that node has
|
|
|
- * {@link module:engine/model/node~Node#offsetSize} greater than `1`. Items in position path are
|
|
|
- * {@link module:engine/model/node~Node#startOffset starting offsets} of position ancestors, starting from direct root children,
|
|
|
- * down to the position offset in it's parent.
|
|
|
- *
|
|
|
- * ROOT
|
|
|
- * |- P before: [ 0 ] after: [ 1 ]
|
|
|
- * |- UL before: [ 1 ] after: [ 2 ]
|
|
|
- * |- LI before: [ 1, 0 ] after: [ 1, 1 ]
|
|
|
- * | |- foo before: [ 1, 0, 0 ] after: [ 1, 0, 3 ]
|
|
|
- * |- LI before: [ 1, 1 ] after: [ 1, 2 ]
|
|
|
- * |- bar before: [ 1, 1, 0 ] after: [ 1, 1, 3 ]
|
|
|
- *
|
|
|
- * `foo` and `bar` are representing {@link module:engine/model/text~Text text nodes}. Since text nodes has offset size
|
|
|
- * greater than `1` you can place position offset between their start and end:
|
|
|
- *
|
|
|
- * ROOT
|
|
|
- * |- P
|
|
|
- * |- UL
|
|
|
- * |- LI
|
|
|
- * | |- f^o|o ^ has path: [ 1, 0, 1 ] | has path: [ 1, 0, 2 ]
|
|
|
- * |- LI
|
|
|
- * |- b^a|r ^ has path: [ 1, 1, 1 ] | has path: [ 1, 1, 2 ]
|
|
|
- *
|
|
|
- * @member {Array.<Number>} module:engine/model/position~Position#path
|
|
|
- */
|
|
|
- get path() {
|
|
|
- return this._path;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Root of the position path.
|
|
|
- *
|
|
|
- * @readonly
|
|
|
- * @member {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
|
|
|
- * module:engine/model/position~Position#root
|
|
|
- */
|
|
|
- get root() {
|
|
|
- return this._root;
|
|
|
+ this.path = path;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -143,6 +120,13 @@ export default class Position {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * @param {Number} newOffset
|
|
|
+ */
|
|
|
+ set offset( newOffset ) {
|
|
|
+ this.path[ this.path.length - 1 ] = newOffset;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* Parent element of this position.
|
|
|
*
|
|
|
* Keep in mind that `parent` value is calculated when the property is accessed.
|
|
|
@@ -358,29 +342,18 @@ export default class Position {
|
|
|
|
|
|
/**
|
|
|
* Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
|
|
|
- * is set to `offset` value.
|
|
|
- *
|
|
|
- * @param {Number} offset Position offset. See {@link module:engine/model/position~Position#offset}.
|
|
|
- * @returns {module:engine/model/position~Position} Moved position.
|
|
|
- */
|
|
|
- getShiftedTo( offset ) {
|
|
|
- const path = this.path.slice();
|
|
|
- path[ path.length - 1 ] = offset;
|
|
|
-
|
|
|
- return new Position( this.root, path );
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns a new instance of `Position`, that has same {@link #parent parent} but it's offset
|
|
|
* is shifted by `shift` value (can be a negative value).
|
|
|
*
|
|
|
* @param {Number} shift Offset shift. Can be a negative value.
|
|
|
* @returns {module:engine/model/position~Position} Shifted position.
|
|
|
*/
|
|
|
getShiftedBy( shift ) {
|
|
|
- const newOffset = this.offset + shift;
|
|
|
+ const shifted = Position.createFromPosition( this );
|
|
|
+
|
|
|
+ const offset = shifted.offset + shift;
|
|
|
+ shifted.offset = offset < 0 ? 0 : offset;
|
|
|
|
|
|
- return this.getShiftedTo( newOffset < 0 ? 0 : newOffset );
|
|
|
+ return shifted;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -460,13 +433,13 @@ export default class Position {
|
|
|
return true;
|
|
|
|
|
|
case 'before':
|
|
|
- left = this; // eslint-disable-line consistent-this
|
|
|
- right = otherPosition;
|
|
|
+ left = Position.createFromPosition( this );
|
|
|
+ right = Position.createFromPosition( otherPosition );
|
|
|
break;
|
|
|
|
|
|
case 'after':
|
|
|
- left = otherPosition;
|
|
|
- right = this; // eslint-disable-line consistent-this
|
|
|
+ left = Position.createFromPosition( otherPosition );
|
|
|
+ right = Position.createFromPosition( this );
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
@@ -486,34 +459,20 @@ export default class Position {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- const path = left.getParentPath();
|
|
|
- path[ path.length - 1 ]++;
|
|
|
- left = new Position( left.root, path );
|
|
|
-
|
|
|
+ left.path = left.path.slice( 0, -1 );
|
|
|
leftParent = leftParent.parent;
|
|
|
+ left.offset++;
|
|
|
} else {
|
|
|
if ( right.offset !== 0 ) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- right = new Position( right.root, right.getParentPath() );
|
|
|
+ right.path = right.path.slice( 0, -1 );
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Converts `Position` to plain object and returns it.
|
|
|
- *
|
|
|
- * @returns {Object} `Position` converted to plain object.
|
|
|
- */
|
|
|
- toJSON() {
|
|
|
- return {
|
|
|
- root: this.root.toJSON(),
|
|
|
- path: this.path
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
* Returns a copy of this position that is updated by removing `howMany` nodes starting from `deletePosition`.
|
|
|
* It may happen that this position is in a removed node. If that is the case, `null` is returned instead.
|
|
|
*
|
|
|
@@ -523,14 +482,14 @@ export default class Position {
|
|
|
* @returns {module:engine/model/position~Position|null} Transformed position or `null`.
|
|
|
*/
|
|
|
_getTransformedByDeletion( deletePosition, howMany ) {
|
|
|
+ const transformed = Position.createFromPosition( this );
|
|
|
+
|
|
|
// This position can't be affected if deletion was in a different root.
|
|
|
if ( this.root != deletePosition.root ) {
|
|
|
- return Position.createFromPosition( this );
|
|
|
+ return transformed;
|
|
|
}
|
|
|
|
|
|
- const comparisonResult = compareArrays( deletePosition.getParentPath(), this.getParentPath() );
|
|
|
-
|
|
|
- if ( comparisonResult == 'same' ) {
|
|
|
+ if ( compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == 'same' ) {
|
|
|
// If nodes are removed from the node that is pointed by this position...
|
|
|
if ( deletePosition.offset < this.offset ) {
|
|
|
// And are removed from before an offset of that position...
|
|
|
@@ -538,10 +497,11 @@ export default class Position {
|
|
|
// Position is in removed range, it's no longer in the tree.
|
|
|
return null;
|
|
|
} else {
|
|
|
- return this.getShiftedBy( -howMany );
|
|
|
+ // Decrement the offset accordingly.
|
|
|
+ transformed.offset -= howMany;
|
|
|
}
|
|
|
}
|
|
|
- } else if ( comparisonResult == 'prefix' ) {
|
|
|
+ } else if ( compareArrays( deletePosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
|
|
|
// If nodes are removed from a node that is on a path to this position...
|
|
|
const i = deletePosition.path.length - 1;
|
|
|
|
|
|
@@ -553,16 +513,12 @@ export default class Position {
|
|
|
return null;
|
|
|
} else {
|
|
|
// Otherwise, decrement index on that path.
|
|
|
- const path = this.path.slice();
|
|
|
-
|
|
|
- path[ i ] -= howMany;
|
|
|
-
|
|
|
- return new Position( this.root, path );
|
|
|
+ transformed.path[ i ] -= howMany;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return Position.createFromPosition( this );
|
|
|
+ return transformed;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -577,9 +533,11 @@ export default class Position {
|
|
|
* @returns {module:engine/model/position~Position} Transformed position.
|
|
|
*/
|
|
|
_getTransformedByInsertion( insertPosition, howMany, insertBefore ) {
|
|
|
+ const transformed = Position.createFromPosition( this );
|
|
|
+
|
|
|
// This position can't be affected if insertion was in a different root.
|
|
|
if ( this.root != insertPosition.root ) {
|
|
|
- return Position.createFromPosition( this );
|
|
|
+ return transformed;
|
|
|
}
|
|
|
|
|
|
if ( compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == 'same' ) {
|
|
|
@@ -587,7 +545,7 @@ export default class Position {
|
|
|
if ( insertPosition.offset < this.offset || ( insertPosition.offset == this.offset && insertBefore ) ) {
|
|
|
// And are inserted before an offset of that position...
|
|
|
// "Push" this positions offset.
|
|
|
- return this.getShiftedBy( howMany );
|
|
|
+ transformed.offset += howMany;
|
|
|
}
|
|
|
} else if ( compareArrays( insertPosition.getParentPath(), this.getParentPath() ) == 'prefix' ) {
|
|
|
// If nodes are inserted in a node that is on a path to this position...
|
|
|
@@ -596,15 +554,11 @@ export default class Position {
|
|
|
if ( insertPosition.offset <= this.path[ i ] ) {
|
|
|
// And are inserted before next node of that path...
|
|
|
// "Push" the index on that path.
|
|
|
- const path = this.path.slice();
|
|
|
-
|
|
|
- path[ i ] += howMany;
|
|
|
-
|
|
|
- return new Position( this.root, path );
|
|
|
+ transformed.path[ i ] += howMany;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return Position.createFromPosition( this );
|
|
|
+ return transformed;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -672,20 +626,18 @@ export default class Position {
|
|
|
const i = source.path.length - 1;
|
|
|
|
|
|
// The first part of a path to combined position is a path to the place where nodes were moved.
|
|
|
- let combinedPath = target.path.slice();
|
|
|
+ const combined = Position.createFromPosition( target );
|
|
|
|
|
|
// Then we have to update the rest of the path.
|
|
|
|
|
|
// Fix the offset because this position might be after `from` position and we have to reflect that.
|
|
|
- const oldOffset = last( combinedPath );
|
|
|
- const newOffset = oldOffset + this.path[ i ] - source.offset;
|
|
|
- combinedPath[ combinedPath.length - 1 ] = newOffset;
|
|
|
+ combined.offset = combined.offset + this.path[ i ] - source.offset;
|
|
|
|
|
|
// Then, add the rest of the path.
|
|
|
// If this position is at the same level as `from` position nothing will get added.
|
|
|
- combinedPath = combinedPath.concat( this.path.slice( i + 1 ) );
|
|
|
+ combined.path = combined.path.concat( this.path.slice( i + 1 ) );
|
|
|
|
|
|
- return new Position( target.root, combinedPath );
|
|
|
+ return combined;
|
|
|
}
|
|
|
|
|
|
/**
|