markerscollection.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import LiveRange from './liverange';
  6. import Range from './range';
  7. import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
  8. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  9. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  10. /**
  11. * Manages and stores markers.
  12. *
  13. * Markers are simply {@link module:engine/model/liverange~LiveRange live ranges} that were added to `MarkersCollection`.
  14. * Markers are used to represent information connected with model document. In contrary to
  15. * {@link module:engine/model/node~Node nodes}, which are bits of data, markers are marking a part of model document.
  16. * Each live range is added with `name` parameter. Name is used to group and identify markers. Names have to be unique, but
  17. * markers can be grouped by using common prefixes, separated with `:`, for example: `user:john` or `search:3`.
  18. *
  19. * Whenever live range is added or removed from `MarkersCollection`,
  20. * {@link module:engine/model/markerscollection~MarkersCollection#event:addMarker addMarker event} and
  21. * {@link module:engine/model/markerscollection~MarkersCollection#event:addMarker removeMarker event} are fired.
  22. *
  23. * Markers can be converted to view by adding appropriate converters for
  24. * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:addMarker addMarker} and
  25. * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#event:removeMarker removeMarker}
  26. * events, or by building converters for {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher}
  27. * using {@link module:engine/conversion/buildmodelconverter~buildModelConverter model converter builder}.
  28. *
  29. * Markers are similar to adding and converting attributes on nodes. The difference is that attribute is connected to
  30. * a given node (e.g. a character is bold no matter if it gets moved or content around it changes). Markers on the
  31. * other hand are continuous ranges (e.g. if a character from inside of marker range is moved somewhere else, marker
  32. * range is shrunk and the character does not have any attribute or information that it was in the marked range). Another
  33. * upside of markers is that finding marked text is fast and easy. Using attributes to mark some nodes and then trying to
  34. * find that part of document would require traversing whole document tree. For markers, only marker name is needed
  35. * and a proper range can {@link module:engine/model/markerscollection~MarkersCollection#get be obtained} from the collection.
  36. */
  37. export default class MarkersCollection {
  38. /**
  39. * Creates a markers collection.
  40. */
  41. constructor() {
  42. /**
  43. * Stores marker name to range bindings for added ranges.
  44. *
  45. * @private
  46. * @member {Map} #_nameToRange
  47. */
  48. this._nameToRange = new Map();
  49. }
  50. /**
  51. * Returns an iterator that iterates over all markers added to the collection. Each item returned by the iterator is an array
  52. * containing two elements, first is a marker {String name} and second is a marker {@link module:engine/model/range~Range range}.
  53. *
  54. * @returns {Iterator}
  55. */
  56. [ Symbol.iterator ]() {
  57. return this._nameToRange.entries();
  58. }
  59. /**
  60. * Sets a name for given live range and adds it to the markers collection.
  61. *
  62. * Throws, if given `markerName` was already used.
  63. *
  64. * Throws, if given `liveRange` is not an instance of {@link module:engine/model/liverange~LiveRange}.
  65. *
  66. * @param {String} markerName Name to be associated with given `liveRange`.
  67. * @param {module:engine/model/liverange~LiveRange} liveRange Live range to be added as a marker to markers collection.
  68. */
  69. add( markerName, liveRange ) {
  70. if ( this._nameToRange.has( markerName ) ) {
  71. /**
  72. * Marker with given name is already added.
  73. *
  74. * @error markers-collection-add-name-exists
  75. */
  76. throw new CKEditorError( 'markers-collection-add-name-exists: Marker with given name is already added.' );
  77. }
  78. if ( !( liveRange instanceof LiveRange ) ) {
  79. /**
  80. * Added range is not an instance of LiveRange.
  81. *
  82. * @error markers-collection-add-range-not-live-range
  83. */
  84. throw new CKEditorError( 'markers-collection-add-range-not-live-range: Added range is not an instance of LiveRange.' );
  85. }
  86. this._nameToRange.set( markerName, liveRange );
  87. this.fire( 'add', markerName, Range.createFromRange( liveRange ) );
  88. }
  89. /**
  90. * Returns the live range that was added to `MarkersCollection` under given `markerName`.
  91. *
  92. * @param {String} markerName Name of range to get.
  93. * @returns {module:engine/model/liverange~LiveRange|null} Range added to collection under given name or `null` if
  94. * no range was added with that name.
  95. */
  96. get( markerName ) {
  97. return this._nameToRange.get( markerName ) || null;
  98. }
  99. /**
  100. * Removes a live range having given `name` from markers collection.
  101. *
  102. * @param {String} name Name of live range to remove.
  103. * @returns {Boolean} `true` is passed if range was found and removed from the markers collection, `false` otherwise.
  104. */
  105. remove( name ) {
  106. const range = this._nameToRange.get( name );
  107. if ( range ) {
  108. this._nameToRange.delete( name );
  109. this.fire( 'remove', name, Range.createFromRange( range ) );
  110. return true;
  111. }
  112. return false;
  113. }
  114. /**
  115. * Substitutes range having given `name`, that was already added to the markers collection, with given `newLiveRange`.
  116. *
  117. * This method is basically a wrapper for using {@link module:engine/model/markerscollection~MarkersCollection#removeRange removeRange}
  118. * followed by using {@link module:engine/model/markerscollection~MarkersCollection#addRange addRange}.
  119. *
  120. * @param {String} name Name of a range to be changed.
  121. * @param {module:engine/model/liverange~LiveRange} newLiveRange Live range to be added.
  122. * @returns {Boolean} `true` if range for given `name` was found and changed, `false` otherwise.
  123. */
  124. update( name, newLiveRange ) {
  125. const removed = this.remove( name );
  126. if ( removed ) {
  127. this.add( name, newLiveRange );
  128. }
  129. return removed;
  130. }
  131. /**
  132. * Destroys markers collection.
  133. */
  134. destroy() {
  135. this.stopListening();
  136. }
  137. }
  138. mix( MarkersCollection, EmitterMixin );