8
0

selection.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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 LiveRange from './liverange.js';
  7. import EmitterMixin from '../../utils/emittermixin.js';
  8. import CKEditorError from '../../utils/ckeditorerror.js';
  9. import utils from '../../utils/utils.js';
  10. /**
  11. * Represents a selection that is made on nodes in {@link core.treeModel.Document}. Selection instance is
  12. * created by {@link core.treeModel.Document}. In most scenarios you should not need to create an instance of Selection.
  13. *
  14. * @memberOf core.treeModel
  15. */
  16. export default class Selection {
  17. /**
  18. * Creates an empty selection.
  19. */
  20. constructor() {
  21. /**
  22. * List of attributes set on current selection.
  23. *
  24. * @protected
  25. * @member {Map} core.treeModel.Selection#_attrs
  26. */
  27. this._attrs = new Map();
  28. /**
  29. * Stores all ranges that are selected.
  30. *
  31. * @private
  32. * @member {Array.<core.treeModel.LiveRange>} core.treeModel.Selection#_ranges
  33. */
  34. this._ranges = [];
  35. /**
  36. * Specifies whether the last added range was added as a backward or forward range.
  37. *
  38. * @private
  39. * @member {Boolean} core.treeModel.Selection#_lastRangeBackward
  40. */
  41. this._lastRangeBackward = false;
  42. }
  43. /**
  44. * Selection anchor. Anchor may be described as a position where the selection starts.
  45. * Together with {@link #focus} they define the direction of selection, which is important
  46. * when expanding/shrinking selection. When there are no ranges in selection anchor is null.
  47. * Anchor is always a start or end of the most recent added range. It may be a bit unintuitive when
  48. * there are multiple ranges in selection.
  49. *
  50. * @type {core.treeModel.LivePosition|null}
  51. */
  52. get anchor() {
  53. if ( this._ranges.length > 0 ) {
  54. let range = this._ranges[ this._ranges.length - 1 ];
  55. return this._lastRangeBackward ? range.end : range.start;
  56. }
  57. return null;
  58. }
  59. /**
  60. * Selection focus. Focus is a position where the selection ends. When there are no ranges in selection,
  61. * focus is null.
  62. *
  63. * @link {#anchor}
  64. * @type {core.treeModel.LivePosition|null}
  65. */
  66. get focus() {
  67. if ( this._ranges.length > 0 ) {
  68. let range = this._ranges[ this._ranges.length - 1 ];
  69. return this._lastRangeBackward ? range.start : range.end;
  70. }
  71. return null;
  72. }
  73. /**
  74. * Returns whether the selection is collapsed. Selection is collapsed when all it's ranges are collapsed.
  75. *
  76. * @type {Boolean}
  77. */
  78. get isCollapsed() {
  79. for ( let i = 0; i < this._ranges.length; i++ ) {
  80. if ( !this._ranges[ i ].isCollapsed ) {
  81. return false;
  82. }
  83. }
  84. return true;
  85. }
  86. /**
  87. * Adds a range to the selection. Added range is copied and converted to {@link core.treeModel.LiveRange}. This means
  88. * that passed range is not saved in the Selection instance and you can safely operate on it. Accepts a flag
  89. * describing in which way the selection is made - passed range might be selected from {@link core.treeModel.Range#start}
  90. * to {@link core.treeModel.Range#end} or from {@link core.treeModel.Range#start} to {@link core.treeModel.Range#end}. The flag
  91. * is used to set {@link #anchor} and {@link #focus} properties.
  92. *
  93. * @param {core.treeModel.Range} range Range to add.
  94. * @param {Boolean} [isBackward] Flag describing if added range was selected forward - from start to end (`false`)
  95. * or backward - from end to start (`true`). Defaults to `false`.
  96. */
  97. addRange( range, isBackward ) {
  98. pushRange.call( this, range );
  99. this._lastRangeBackward = !!isBackward;
  100. this.fire( 'update' );
  101. }
  102. /**
  103. * Unbinds all events previously bound by this selection or objects created by this selection.
  104. */
  105. detach() {
  106. for ( let i = 0; i < this._ranges.length; i++ ) {
  107. this._ranges[ i ].detach();
  108. }
  109. }
  110. /**
  111. * Returns an array of ranges added to the selection. The method returns a copy of internal array, so
  112. * it will not change when ranges get added or removed from selection.
  113. *
  114. * @returns {Array.<LiveRange>}
  115. */
  116. getRanges() {
  117. return this._ranges.slice();
  118. }
  119. /**
  120. * Removes all ranges that were added to the selection. Fires update event.
  121. *
  122. */
  123. removeAllRanges() {
  124. this.detach();
  125. this._ranges = [];
  126. this.fire( 'update' );
  127. }
  128. /**
  129. * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
  130. * is treated like the last added range and is used to set {@link #anchor} and {@link #focus}. Accepts a flag
  131. * describing in which way the selection is made (see {@link #addRange}).
  132. *
  133. * @param {Array.<core.treeModel.Range>} newRanges Array of ranges to set.
  134. * @param {Boolean} [isLastBackward] Flag describing if last added range was selected forward - from start to end (`false`)
  135. * or backward - from end to start (`true`). Defaults to `false`.
  136. */
  137. setRanges( newRanges, isLastBackward ) {
  138. this.detach();
  139. this._ranges = [];
  140. for ( let i = 0; i < newRanges.length; i++ ) {
  141. pushRange.call( this, newRanges[ i ] );
  142. }
  143. this._lastRangeBackward = !!isLastBackward;
  144. this.fire( 'update' );
  145. }
  146. /**
  147. * Checks if the selection has an attribute for given key.
  148. *
  149. * @param {String} key Key of attribute to check.
  150. * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
  151. */
  152. hasAttribute( key ) {
  153. return this._attrs.has( key );
  154. }
  155. /**
  156. * Gets an attribute value for given key or undefined it that attribute is not set on selection.
  157. *
  158. * @param {String} key Key of attribute to look for.
  159. * @returns {*} Attribute value or null.
  160. */
  161. getAttribute( key ) {
  162. return this._attrs.get( key );
  163. }
  164. /**
  165. * Returns iterator that iterates over this selection attributes.
  166. *
  167. * @returns {Iterable.<*>}
  168. */
  169. getAttributes() {
  170. return this._attrs[ Symbol.iterator ]();
  171. }
  172. /**
  173. * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
  174. *
  175. * @param {String} key Key of attribute to set.
  176. * @param {*} value Attribute value.
  177. */
  178. setAttribute( key, value ) {
  179. this._attrs.set( key, value );
  180. }
  181. /**
  182. * Removes all attributes from the selection and sets given attributes.
  183. *
  184. * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
  185. */
  186. setAttributesTo( attrs ) {
  187. this._attrs = utils.toMap( attrs );
  188. }
  189. /**
  190. * Removes an attribute with given key from the selection.
  191. *
  192. * @param {String} key Key of attribute to remove.
  193. * @returns {Boolean} `true` if the attribute was set on the selection, `false` otherwise.
  194. */
  195. removeAttribute( key ) {
  196. return this._attrs.delete( key );
  197. }
  198. /**
  199. * Removes all attributes from the selection.
  200. */
  201. clearAttributes() {
  202. this._attrs.clear();
  203. }
  204. }
  205. /**
  206. * Converts given range to {@link core.treeModel.LiveRange} and adds it to internal ranges array. Throws an error
  207. * if given range is intersecting with any range that is already stored in this selection.
  208. *
  209. * @private
  210. * @method pushRange
  211. * @memberOf {core.treeModel.Selection}
  212. * @param {core.treeModel.Range} range Range to add.
  213. */
  214. function pushRange( range ) {
  215. /* jshint validthis: true */
  216. for ( let i = 0; i < this._ranges.length ; i++ ) {
  217. if ( range.isIntersecting( this._ranges[ i ] ) ) {
  218. /**
  219. * Trying to add a range that intersects with another range from selection.
  220. *
  221. * @error selection-range-intersects
  222. * @param {core.treeModel.Range} addedRange Range that was added to the selection.
  223. * @param {core.treeModel.Range} intersectingRange Range from selection that intersects with `addedRange`.
  224. */
  225. throw new CKEditorError(
  226. 'selection-range-intersects: Trying to add a range that intersects with another range from selection.',
  227. { addedRange: range, intersectingRange: this._ranges[ i ] }
  228. );
  229. }
  230. }
  231. this._ranges.push( LiveRange.createFromRange( range ) );
  232. }
  233. utils.mix( Selection, EmitterMixin );