highlightui.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 highlight/highlightui
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  10. import markerIcon from './../theme/icons/marker.svg';
  11. import penIcon from './../theme/icons/pen.svg';
  12. import eraserIcon from '@ckeditor/ckeditor5-core/theme/icons/eraser.svg';
  13. import ToolbarSeparatorView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview';
  14. import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview';
  15. import { createDropdown, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  16. import './../theme/highlight.css';
  17. /**
  18. * The default highlight UI plugin. It introduces:
  19. *
  20. * * The `'highlight'` dropdown,
  21. * * The `'removeHighlight'` and `'highlight:*'` buttons.
  22. *
  23. * The default configuration includes the following buttons:
  24. *
  25. * * `'highlight:yellowMarker'`
  26. * * `'highlight:greenMarker'`
  27. * * `'highlight:pinkMarker'`
  28. * * `'highlight:blueMarker'`
  29. * * `'highlight:redPen'`
  30. * * `'highlight:greenPen'`
  31. *
  32. * See the {@link module:highlight/highlight~HighlightConfig#options configuration} to learn more
  33. * about the defaults.
  34. *
  35. * @extends module:core/plugin~Plugin
  36. */
  37. export default class HighlightUI extends Plugin {
  38. /**
  39. * Returns the localized option titles provided by the plugin.
  40. *
  41. * The following localized titles corresponding with default
  42. * {@link module:highlight/highlight~HighlightConfig#options} are available:
  43. *
  44. * * `'Yellow marker'`,
  45. * * `'Green marker'`,
  46. * * `'Pink marker'`,
  47. * * `'Blue marker'`,
  48. * * `'Red pen'`,
  49. * * `'Green pen'`.
  50. *
  51. * @readonly
  52. * @type {Object.<String,String>}
  53. */
  54. get localizedOptionTitles() {
  55. const t = this.editor.t;
  56. return {
  57. 'Yellow marker': t( 'Yellow marker' ),
  58. 'Green marker': t( 'Green marker' ),
  59. 'Pink marker': t( 'Pink marker' ),
  60. 'Blue marker': t( 'Blue marker' ),
  61. 'Red pen': t( 'Red pen' ),
  62. 'Green pen': t( 'Green pen' )
  63. };
  64. }
  65. /**
  66. * @inheritDoc
  67. */
  68. static get pluginName() {
  69. return 'HighlightUI';
  70. }
  71. /**
  72. * @inheritDoc
  73. */
  74. init() {
  75. const options = this.editor.config.get( 'highlight.options' );
  76. for ( const option of options ) {
  77. this._addHighlighterButton( option );
  78. }
  79. this._addRemoveHighlightButton();
  80. this._addDropdown( options );
  81. }
  82. /**
  83. * Creates the "Remove highlight" button.
  84. *
  85. * @private
  86. */
  87. _addRemoveHighlightButton() {
  88. const t = this.editor.t;
  89. this._addButton( 'removeHighlight', t( 'Remove highlight' ), eraserIcon );
  90. }
  91. /**
  92. * Creates a toolbar button from the provided highlight option.
  93. *
  94. * @param {module:highlight/highlight~HighlightOption} option
  95. * @private
  96. */
  97. _addHighlighterButton( option ) {
  98. const command = this.editor.commands.get( 'highlight' );
  99. // TODO: change naming
  100. this._addButton( 'highlight:' + option.model, option.title, getIconForType( option.type ), option.model, decorateHighlightButton );
  101. function decorateHighlightButton( button ) {
  102. button.bind( 'isEnabled' ).to( command, 'isEnabled' );
  103. button.bind( 'isOn' ).to( command, 'value', value => value === option.model );
  104. button.iconView.fillColor = option.color;
  105. button.isToggleable = true;
  106. }
  107. }
  108. /**
  109. * Internal method for creating highlight buttons.
  110. *
  111. * @param {String} name The name of the button.
  112. * @param {String} label The label for the button.
  113. * @param {String} icon The button icon.
  114. * @param {Function} [decorateButton=()=>{}] Additional method for extending the button.
  115. * @private
  116. */
  117. _addButton( name, label, icon, value, decorateButton = () => {} ) {
  118. const editor = this.editor;
  119. editor.ui.componentFactory.add( name, locale => {
  120. const buttonView = new ButtonView( locale );
  121. const localized = this.localizedOptionTitles[ label ] ? this.localizedOptionTitles[ label ] : label;
  122. buttonView.set( {
  123. label: localized,
  124. icon,
  125. tooltip: true
  126. } );
  127. buttonView.on( 'execute', () => {
  128. editor.execute( 'highlight', { value } );
  129. editor.editing.view.focus();
  130. } );
  131. // Add additional behavior for buttonView.
  132. decorateButton( buttonView );
  133. return buttonView;
  134. } );
  135. }
  136. /**
  137. * Creates the split button dropdown UI from the provided highlight options.
  138. *
  139. * @param {Array.<module:highlight/highlight~HighlightOption>} options
  140. * @private
  141. */
  142. _addDropdown( options ) {
  143. const editor = this.editor;
  144. const t = editor.t;
  145. const componentFactory = editor.ui.componentFactory;
  146. const startingHighlighter = options[ 0 ];
  147. const optionsMap = options.reduce( ( retVal, option ) => {
  148. retVal[ option.model ] = option;
  149. return retVal;
  150. }, {} );
  151. componentFactory.add( 'highlight', locale => {
  152. const command = editor.commands.get( 'highlight' );
  153. const dropdownView = createDropdown( locale, SplitButtonView );
  154. const splitButtonView = dropdownView.buttonView;
  155. splitButtonView.set( {
  156. tooltip: t( 'Highlight' ),
  157. // Holds last executed highlighter.
  158. lastExecuted: startingHighlighter.model,
  159. // Holds current highlighter to execute (might be different then last used).
  160. commandValue: startingHighlighter.model,
  161. isToggleable: true
  162. } );
  163. // Dropdown button changes to selection (command.value):
  164. // - If selection is in highlight it get active highlight appearance (icon, color) and is activated.
  165. // - Otherwise it gets appearance (icon, color) of last executed highlight.
  166. splitButtonView.bind( 'icon' ).to( command, 'value', value => getIconForType( getActiveOption( value, 'type' ) ) );
  167. splitButtonView.bind( 'color' ).to( command, 'value', value => getActiveOption( value, 'color' ) );
  168. splitButtonView.bind( 'commandValue' ).to( command, 'value', value => getActiveOption( value, 'model' ) );
  169. splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
  170. splitButtonView.delegate( 'execute' ).to( dropdownView );
  171. // Create buttons array.
  172. const buttons = options.map( option => {
  173. // Get existing highlighter button.
  174. const buttonView = componentFactory.create( 'highlight:' + option.model );
  175. // Update lastExecutedHighlight on execute.
  176. this.listenTo( buttonView, 'execute', () => dropdownView.buttonView.set( { lastExecuted: option.model } ) );
  177. return buttonView;
  178. } );
  179. // Make toolbar button enabled when any button in dropdown is enabled before adding separator and eraser.
  180. dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) );
  181. // Add separator and eraser buttons to dropdown.
  182. buttons.push( new ToolbarSeparatorView() );
  183. buttons.push( componentFactory.create( 'removeHighlight' ) );
  184. addToolbarToDropdown( dropdownView, buttons );
  185. bindToolbarIconStyleToActiveColor( dropdownView );
  186. dropdownView.toolbarView.ariaLabel = t( 'Text highlight toolbar' );
  187. // Execute current action from dropdown's split button action button.
  188. splitButtonView.on( 'execute', () => {
  189. editor.execute( 'highlight', { value: splitButtonView.commandValue } );
  190. editor.editing.view.focus();
  191. } );
  192. // Returns active highlighter option depending on current command value.
  193. // If current is not set or it is the same as last execute this method will return the option key (like icon or color)
  194. // of last executed highlighter. Otherwise it will return option key for current one.
  195. function getActiveOption( current, key ) {
  196. const whichHighlighter = !current ||
  197. current === splitButtonView.lastExecuted ? splitButtonView.lastExecuted : current;
  198. return optionsMap[ whichHighlighter ][ key ];
  199. }
  200. return dropdownView;
  201. } );
  202. }
  203. }
  204. // Extends split button icon style to reflect last used button style.
  205. function bindToolbarIconStyleToActiveColor( dropdownView ) {
  206. const actionView = dropdownView.buttonView.actionView;
  207. actionView.iconView.bind( 'fillColor' ).to( dropdownView.buttonView, 'color' );
  208. }
  209. // Returns icon for given highlighter type.
  210. function getIconForType( type ) {
  211. return type === 'marker' ? markerIcon : penIcon;
  212. }