highlightui.js 8.4 KB

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