position.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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( [ 'utils', 'ckeditorerror' ], ( utils, CKEditorError ) => {
  7. /**
  8. * Position in the tree. Position is always located before or after a node.
  9. * See {@link #path} property for more information.
  10. *
  11. * @class document.Position
  12. */
  13. class Position {
  14. /**
  15. * Creates a position.
  16. *
  17. * @param {Array} path Position path. See {@link #path} property for more information.
  18. * @param {document.Element} root Root element for the path. Note that this element can not have a parent.
  19. * @constructor
  20. */
  21. constructor( path, root ) {
  22. /**
  23. * Position of the node it the tree. For example:
  24. *
  25. * root
  26. * |- p Before: [ 0 ] After: [ 1 ]
  27. * |- ul Before: [ 1 ] After: [ 2 ]
  28. * |- li Before: [ 1, 0 ] After: [ 1, 1 ]
  29. * | |- f Before: [ 1, 0, 0 ] After: [ 1, 0, 1 ]
  30. * | |- o Before: [ 1, 0, 1 ] After: [ 1, 0, 2 ]
  31. * | |- o Before: [ 1, 0, 2 ] After: [ 1, 0, 3 ]
  32. * |- li Before: [ 1, 1 ] After: [ 1, 2 ]
  33. * |- b Before: [ 1, 1, 0 ] After: [ 1, 1, 1 ]
  34. * |- a Before: [ 1, 1, 1 ] After: [ 1, 1, 2 ]
  35. * |- r Before: [ 1, 1, 2 ] After: [ 1, 1, 3 ]
  36. *
  37. * @type {Number[]}
  38. */
  39. this.path = path;
  40. /**
  41. * Root element for the path. Note that this element can not have a parent.
  42. *
  43. * @type {document.Element}
  44. */
  45. this.root = root;
  46. }
  47. /**
  48. * Parent element of the position. The position is located at {@link #offset} in this element.
  49. *
  50. * @readonly
  51. * @property {document.Element} parent
  52. */
  53. get parent() {
  54. let parent = this.root;
  55. let i, len;
  56. for ( i = 0, len = this.path.length - 1; i < len; i++ ) {
  57. parent = parent.getChild( this.path[ i ] );
  58. }
  59. return parent;
  60. }
  61. /**
  62. * Offset at which the position is located in the {@link #parent}.
  63. *
  64. * @readonly
  65. * @property {Number} offset
  66. */
  67. get offset() {
  68. return utils.last( this.path );
  69. }
  70. /**
  71. * Node directly before the position.
  72. *
  73. * @readonly
  74. * @type {document.Node}
  75. */
  76. get nodeBefore() {
  77. return this.parent.getChild( this.offset - 1 ) || null;
  78. }
  79. /**
  80. * Node directly after the position.
  81. *
  82. * @readonly
  83. * @property {document.Node}
  84. */
  85. get nodeAfter() {
  86. return this.parent.getChild( this.offset ) || null;
  87. }
  88. /**
  89. * Two positions equal if paths are equal.
  90. *
  91. * @param {document.Position} otherPosition Position to compare.
  92. * @returns {Boolean} True if positions equal.
  93. */
  94. isEqual( otherPosition ) {
  95. return utils.isEqual( this.path, otherPosition.path );
  96. }
  97. /**
  98. * Returns the path to the parent, which is the {@link document.Position#path} without the last element.
  99. *
  100. * This method returns the parent path even if the parent does not exists.
  101. *
  102. * @returns {Number[]} Path to the parent.
  103. */
  104. getParentPath() {
  105. return this.path.slice( 0, -1 );
  106. }
  107. /**
  108. * Creates a new position from the parent element and the offset in that element.
  109. *
  110. * @param {document.Element} parent Position parent element.
  111. * @param {Number} offset Position offset.
  112. * @returns {document.Position}
  113. */
  114. static createFromParentAndOffset( parent, offset ) {
  115. const path = parent.getPath();
  116. path.push( offset );
  117. return new Position( path, parent.root );
  118. }
  119. /**
  120. * Creates a new position before the given node.
  121. *
  122. * @param {document.node} node Node the position should be directly before.
  123. * @returns {document.Position}
  124. */
  125. static createBefore( node ) {
  126. if ( !node.parent ) {
  127. /**
  128. * You can not make position before root.
  129. *
  130. * @error position-before-root
  131. * @param {document.Node} root
  132. */
  133. throw new CKEditorError( 'position-before-root: You can not make position before root.', { root: node } );
  134. }
  135. return Position.createFromParentAndOffset( node.parent, node.getIndex() );
  136. }
  137. /**
  138. * Creates a new position after given node.
  139. *
  140. * @param {document.Node} node Node the position should be directly after.
  141. * @returns {document.Position}
  142. */
  143. static createAfter( node ) {
  144. if ( !node.parent ) {
  145. /**
  146. * You can not make position after root.
  147. *
  148. * @error position-after-root
  149. * @param {document.Node} root
  150. */
  151. throw new CKEditorError( 'position-after-root: You can not make position after root.', { root: node } );
  152. }
  153. return Position.createFromParentAndOffset( node.parent, node.getIndex() + 1 );
  154. }
  155. }
  156. return Position;
  157. } );