highlightui.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  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. }
  106. }
  107. /**
  108. * Internal method for creating highlight buttons.
  109. *
  110. * @param {String} name The name of the button.
  111. * @param {String} label The label for the button.
  112. * @param {String} icon The button icon.
  113. * @param {Function} [decorateButton=()=>{}] Additional method for extending the button.
  114. * @private
  115. */
  116. _addButton( name, label, icon, value, decorateButton = () => {} ) {
  117. const editor = this.editor;
  118. editor.ui.componentFactory.add( name, locale => {
  119. const buttonView = new ButtonView( locale );
  120. const localized = this.localizedOptionTitles[ label ] ? this.localizedOptionTitles[ label ] : label;
  121. buttonView.set( {
  122. label: localized,
  123. icon,
  124. tooltip: true
  125. } );
  126. buttonView.on( 'execute', () => {
  127. editor.execute( 'highlight', { value } );
  128. editor.editing.view.focus();
  129. } );
  130. // Add additional behavior for buttonView.
  131. decorateButton( buttonView );
  132. return buttonView;
  133. } );
  134. }
  135. /**
  136. * Creates the split button dropdown UI from the provided highlight options.
  137. *
  138. * @param {Array.<module:highlight/highlight~HighlightOption>} options
  139. * @private
  140. */
  141. _addDropdown( options ) {
  142. const editor = this.editor;
  143. const t = editor.t;
  144. const componentFactory = editor.ui.componentFactory;
  145. const startingHighlighter = options[ 0 ];
  146. const optionsMap = options.reduce( ( retVal, option ) => {
  147. retVal[ option.model ] = option;
  148. return retVal;
  149. }, {} );
  150. componentFactory.add( 'highlight', locale => {
  151. const command = editor.commands.get( 'highlight' );
  152. const dropdownView = createDropdown( locale, SplitButtonView );
  153. const splitButtonView = dropdownView.buttonView;
  154. splitButtonView.set( {
  155. tooltip: t( 'Highlight' ),
  156. // Holds last executed highlighter.
  157. lastExecuted: startingHighlighter.model,
  158. // Holds current highlighter to execute (might be different then last used).
  159. commandValue: startingHighlighter.model
  160. } );
  161. // Dropdown button changes to selection (command.value):
  162. // - If selection is in highlight it get active highlight appearance (icon, color) and is activated.
  163. // - Otherwise it gets appearance (icon, color) of last executed highlight.
  164. splitButtonView.bind( 'icon' ).to( command, 'value', value => getIconForType( getActiveOption( value, 'type' ) ) );
  165. splitButtonView.bind( 'color' ).to( command, 'value', value => getActiveOption( value, 'color' ) );
  166. splitButtonView.bind( 'commandValue' ).to( command, 'value', value => getActiveOption( value, 'model' ) );
  167. splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
  168. splitButtonView.delegate( 'execute' ).to( dropdownView );
  169. // Create buttons array.
  170. const buttons = options.map( option => {
  171. // Get existing highlighter button.
  172. const buttonView = componentFactory.create( 'highlight:' + option.model );
  173. // Update lastExecutedHighlight on execute.
  174. this.listenTo( buttonView, 'execute', () => dropdownView.buttonView.set( { lastExecuted: option.model } ) );
  175. return buttonView;
  176. } );
  177. // Make toolbar button enabled when any button in dropdown is enabled before adding separator and eraser.
  178. dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) );
  179. // Add separator and eraser buttons to dropdown.
  180. buttons.push( new ToolbarSeparatorView() );
  181. buttons.push( componentFactory.create( 'removeHighlight' ) );
  182. addToolbarToDropdown( dropdownView, buttons );
  183. bindToolbarIconStyleToActiveColor( dropdownView );
  184. // Execute current action from dropdown's split button action button.
  185. splitButtonView.on( 'execute', () => {
  186. editor.execute( 'highlight', { value: splitButtonView.commandValue } );
  187. editor.editing.view.focus();
  188. } );
  189. // Returns active highlighter option depending on current command value.
  190. // If current is not set or it is the same as last execute this method will return the option key (like icon or color)
  191. // of last executed highlighter. Otherwise it will return option key for current one.
  192. function getActiveOption( current, key ) {
  193. const whichHighlighter = !current ||
  194. current === splitButtonView.lastExecuted ? splitButtonView.lastExecuted : current;
  195. return optionsMap[ whichHighlighter ][ key ];
  196. }
  197. return dropdownView;
  198. } );
  199. }
  200. }
  201. // Extends split button icon style to reflect last used button style.
  202. function bindToolbarIconStyleToActiveColor( dropdownView ) {
  203. const actionView = dropdownView.buttonView.actionView;
  204. actionView.iconView.bind( 'fillColor' ).to( dropdownView.buttonView, 'color' );
  205. }
  206. // Returns icon for given highlighter type.
  207. function getIconForType( type ) {
  208. return type === 'marker' ? markerIcon : penIcon;
  209. }