8
0

highlightui.js 7.8 KB

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