| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- 'use strict';
- import Text from './text.js';
- import compareArrays from '../../utils/comparearrays.js';
- import CKEditorError from '../../utils/ckeditorerror.js';
- /**
- * Position in the tree. Position is always located before or after a node.
- *
- * @memberOf engine.treeView
- */
- export default class Position {
- /**
- * Creates a position.
- *
- * @param {engine.treeView.Node} parent Position parent node.
- * @param {Number} offset Position offset.
- */
- constructor( parent, offset ) {
- /**
- * Position parent node.
- *
- * @member {engine.treeView.Node} engine.treeView.Position#parent
- */
- this.parent = parent;
- /**
- * Position offset.
- *
- * @member {Number} engine.treeView.Position#offset
- */
- this.offset = offset;
- }
- /**
- * Node directly after the position. Equals `null` when there is no node after position or position is located
- * inside text node.
- *
- * @readonly
- * @type {engine.treeView.Node|null}
- */
- get nodeAfter() {
- if ( this.parent instanceof Text ) {
- return null;
- }
- return this.parent.getChild( this.offset ) || null;
- }
- /**
- * Node directly before the position. Equals `null` when there is no node before position or position is located
- * inside text node.
- *
- * @readonly
- * @type {engine.treeView.Node|null}
- */
- get nodeBefore() {
- if ( this.parent instanceof Text ) {
- return null;
- }
- return this.parent.getChild( this.offset - 1 ) || null;
- }
- /**
- * Returns a new instance of Position with offset incremented by `shift` value.
- *
- * @param {Number} shift How position offset should get changed. Accepts negative values.
- * @returns {engine.treeView.Position} Shifted position.
- */
- getShiftedBy( shift ) {
- let shifted = Position.createFromPosition( this );
- let offset = shifted.offset + shift;
- shifted.offset = offset < 0 ? 0 : offset;
- return shifted;
- }
- /**
- * Checks whether this position equals given position.
- *
- * @param {engine.treeView.Position} otherPosition Position to compare with.
- * @returns {Boolean} True if positions are same.
- */
- isEqual( otherPosition ) {
- return this == otherPosition || ( this.parent == otherPosition.parent && this.offset == otherPosition.offset );
- }
- /**
- * Checks whether this position is located before given position. When method returns `false` it does not mean that
- * this position is after give one. Two positions may be located inside separate roots and in that situation this
- * method will still return `false`.
- *
- * @see engine.treeView.Position#isAfter
- * @see engine.treeView.Position#compareWith
- * @param {engine.treeView.Position} otherPosition Position to compare with.
- * @returns {Boolean} Returns `true` if this position is before given position.
- */
- isBefore( otherPosition ) {
- return this.compareWith( otherPosition ) == 'BEFORE';
- }
- /**
- * Checks whether this position is located after given position. When method returns `false` it does not mean that
- * this position is before give one. Two positions may be located inside separate roots and in that situation this
- * method will still return `false`.
- *
- * @see engine.treeView.Position#isBefore
- * @see engine.treeView.Position#compareWith
- * @param {engine.treeView.Position} otherPosition Position to compare with.
- * @returns {Boolean} Returns `true` if this position is after given position.
- */
- isAfter( otherPosition ) {
- return this.compareWith( otherPosition ) == 'AFTER';
- }
- /**
- * Checks whether this position is before, after or in same position that other position. Two positions may be also
- * different when they are located in separate roots.
- *
- * @param {engine.treeView.Position} otherPosition Position to compare with.
- * @returns {engine.treeView.PositionRelation}
- */
- compareWith( otherPosition ) {
- if ( this.isEqual( otherPosition ) ) {
- return 'SAME';
- }
- // If positions have same parent.
- if ( this.parent === otherPosition.parent ) {
- return this.offset - otherPosition.offset < 0 ? 'BEFORE' : 'AFTER';
- }
- // Get path from root to position's parent element.
- const path = this.parent.getAncestors( { includeNode: true } );
- const otherPath = otherPosition.parent.getAncestors( { includeNode: true } );
- // Compare both path arrays to find common ancestor.
- const result = compareArrays( path, otherPath );
- let commonAncestorIndex;
- switch ( result ) {
- case 0:
- // No common ancestors found.
- return 'DIFFERENT';
- case 'PREFIX':
- commonAncestorIndex = path.length - 1;
- break;
- case 'EXTENSION':
- commonAncestorIndex = otherPath.length - 1;
- break;
- default:
- commonAncestorIndex = result - 1;
- }
- // Common ancestor of two positions.
- const commonAncestor = path[ commonAncestorIndex ];
- const nextAncestor1 = path[ commonAncestorIndex + 1 ];
- const nextAncestor2 = otherPath[ commonAncestorIndex + 1 ];
- // Check if common ancestor is not one of the parents.
- if ( commonAncestor === this.parent ) {
- const index = this.offset - nextAncestor2.getIndex();
- return index <= 0 ? 'BEFORE' : 'AFTER';
- } else if ( commonAncestor === otherPosition.parent ) {
- const index = nextAncestor1.getIndex() - otherPosition.offset;
- return index < 0 ? 'BEFORE' : 'AFTER';
- }
- const index = nextAncestor1.getIndex() - nextAncestor2.getIndex();
- // Compare indexes of next ancestors inside common one.
- return index < 0 ? 'BEFORE' : 'AFTER';
- }
- /**
- * Creates a new position after given node.
- *
- * @param {engine.treeView.Node} node Node the position should be directly after.
- * @returns {engine.treeView.Position}
- */
- static createAfter( node ) {
- if ( !node.parent ) {
- /**
- * You can not make position after root.
- *
- * @error position-after-root
- * @param {engine.treeView.Node} root
- */
- throw new CKEditorError( 'position-after-root: You can not make position after root.', { root: node } );
- }
- return new Position( node.parent, node.getIndex() + 1 );
- }
- /**
- * Creates a new position before the given node.
- *
- * @param {engine.treeView.node} node Node the position should be directly before.
- * @returns {engine.treeView.Position}
- */
- static createBefore( node ) {
- if ( !node.parent ) {
- /**
- * You can not make position before root.
- *
- * @error position-before-root
- * @param {engine.treeView.Node} root
- */
- throw new CKEditorError( 'position-before-root: You can not make position before root.', { root: node } );
- }
- return new Position( node.parent, node.getIndex() );
- }
- /**
- * Creates and returns a new instance of Position, which is equal to passed position.
- *
- * @param {engine.treeView.Position} position Position to be cloned.
- * @returns {engine.treeView.Position}
- */
- static createFromPosition( position ) {
- return new this( position.parent, position.offset );
- }
- }
- /**
- * A flag indicating whether this position is `'BEFORE'` or `'AFTER'` or `'SAME'` as given position.
- * If positions are in different roots `'DIFFERENT'` flag is returned.
- *
- * @typedef {String} engine.treeView.PositionRelation
- */
|