8
0

highlightstack.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module widget/highlightstack
  7. */
  8. import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
  9. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  10. /**
  11. * Class used to handle correct order of highlights on elements.
  12. *
  13. * When different highlights are applied to same element correct order should be preserved:
  14. *
  15. * * highlight with highest priority should be applied,
  16. * * if two highlights have same priority - sort by CSS class provided in
  17. * {@link module:engine/conversion/downcasthelpers~HighlightDescriptor}.
  18. *
  19. * This way, highlight will be applied with the same rules it is applied on texts.
  20. */
  21. export default class HighlightStack {
  22. /**
  23. * Creates class instance.
  24. */
  25. constructor() {
  26. this._stack = [];
  27. }
  28. /**
  29. * Adds highlight descriptor to the stack.
  30. *
  31. * @fires change:top
  32. * @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
  33. * @param {module:engine/view/downcastwriter~DowncastWriter} writer
  34. */
  35. add( descriptor, writer ) {
  36. const stack = this._stack;
  37. // Save top descriptor and insert new one. If top is changed - fire event.
  38. const oldTop = stack[ 0 ];
  39. this._insertDescriptor( descriptor );
  40. const newTop = stack[ 0 ];
  41. // When new object is at the top and stores different information.
  42. if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
  43. this.fire( 'change:top', {
  44. oldDescriptor: oldTop,
  45. newDescriptor: newTop,
  46. writer
  47. } );
  48. }
  49. }
  50. /**
  51. * Removes highlight descriptor from the stack.
  52. *
  53. * @fires change:top
  54. * @param {String} id Id of the descriptor to remove.
  55. * @param {module:engine/view/downcastwriter~DowncastWriter} writer
  56. */
  57. remove( id, writer ) {
  58. const stack = this._stack;
  59. const oldTop = stack[ 0 ];
  60. this._removeDescriptor( id );
  61. const newTop = stack[ 0 ];
  62. // When new object is at the top and stores different information.
  63. if ( oldTop !== newTop && !compareDescriptors( oldTop, newTop ) ) {
  64. this.fire( 'change:top', {
  65. oldDescriptor: oldTop,
  66. newDescriptor: newTop,
  67. writer
  68. } );
  69. }
  70. }
  71. /**
  72. * Inserts given descriptor in correct place in the stack. It also takes care about updating information when
  73. * descriptor with same id is already present.
  74. *
  75. * @private
  76. * @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
  77. */
  78. _insertDescriptor( descriptor ) {
  79. const stack = this._stack;
  80. const index = stack.findIndex( item => item.id === descriptor.id );
  81. // Inserting exact same descriptor - do nothing.
  82. if ( compareDescriptors( descriptor, stack[ index ] ) ) {
  83. return;
  84. }
  85. // If descriptor with same id but with different information is on the stack - remove it.
  86. if ( index > -1 ) {
  87. stack.splice( index, 1 );
  88. }
  89. // Find correct place to insert descriptor in the stack.
  90. // It have different information (for example priority) so it must be re-inserted in correct place.
  91. let i = 0;
  92. while ( stack[ i ] && shouldABeBeforeB( stack[ i ], descriptor ) ) {
  93. i++;
  94. }
  95. stack.splice( i, 0, descriptor );
  96. }
  97. /**
  98. * Removes descriptor with given id from the stack.
  99. *
  100. * @private
  101. * @param {String} id Descriptor's id.
  102. */
  103. _removeDescriptor( id ) {
  104. const stack = this._stack;
  105. const index = stack.findIndex( item => item.id === id );
  106. // If descriptor with same id is on the list - remove it.
  107. if ( index > -1 ) {
  108. stack.splice( index, 1 );
  109. }
  110. }
  111. }
  112. mix( HighlightStack, EmitterMixin );
  113. // Compares two descriptors by checking their priority and class list.
  114. //
  115. // @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a
  116. // @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b
  117. // @returns {Boolean} Returns true if both descriptors are defined and have same priority and classes.
  118. function compareDescriptors( a, b ) {
  119. return a && b && a.priority == b.priority && classesToString( a.classes ) == classesToString( b.classes );
  120. }
  121. // Checks whenever first descriptor should be placed in the stack before second one.
  122. //
  123. // @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} a
  124. // @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} b
  125. // @returns {Boolean}
  126. function shouldABeBeforeB( a, b ) {
  127. if ( a.priority > b.priority ) {
  128. return true;
  129. } else if ( a.priority < b.priority ) {
  130. return false;
  131. }
  132. // When priorities are equal and names are different - use classes to compare.
  133. return classesToString( a.classes ) > classesToString( b.classes );
  134. }
  135. // Converts CSS classes passed with {@link module:engine/conversion/downcasthelpers~HighlightDescriptor} to
  136. // sorted string.
  137. //
  138. // @param {String|Array<String>} descriptor
  139. // @returns {String}
  140. function classesToString( classes ) {
  141. return Array.isArray( classes ) ? classes.sort().join( ',' ) : classes;
  142. }
  143. /**
  144. * Fired when top element on {@link module:widget/highlightstack~HighlightStack} has been changed
  145. *
  146. * @event change:top
  147. * @param {Object} data Additional information about the change.
  148. * @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.newDescriptor] New highlight
  149. * descriptor. It will be `undefined` when last descriptor is removed from the stack.
  150. * @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} [data.oldDescriptor] Old highlight
  151. * descriptor. It will be `undefined` when first descriptor is added to the stack.
  152. * @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that can be used to modify element.
  153. */