imageresizeui.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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 image/imageresize/imageresizeui
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  10. import ImageResizeEditing from './imageresizeediting';
  11. import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  12. import DropdownButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview';
  13. import Model from '@ckeditor/ckeditor5-ui/src/model';
  14. import Collection from '@ckeditor/ckeditor5-utils/src/collection';
  15. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  16. import iconSmall from '@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg';
  17. import iconMedium from '@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg';
  18. import iconLarge from '@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg';
  19. import iconFull from '@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg';
  20. const RESIZE_ICONS = {
  21. small: iconSmall,
  22. medium: iconMedium,
  23. large: iconLarge,
  24. original: iconFull
  25. };
  26. /**
  27. * The `ImageResizeUI` plugin.
  28. *
  29. * It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration.
  30. *
  31. * @extends module:core/plugin~Plugin
  32. */
  33. export default class ImageResizeUI extends Plugin {
  34. /**
  35. * @inheritDoc
  36. */
  37. static get requires() {
  38. return [ ImageResizeEditing ];
  39. }
  40. /**
  41. * @inheritDoc
  42. */
  43. static get pluginName() {
  44. return 'ImageResizeUI';
  45. }
  46. /**
  47. * @inheritDoc
  48. */
  49. constructor( editor ) {
  50. super( editor );
  51. /**
  52. * The resize unit.
  53. *
  54. * @readonly
  55. * @private
  56. * @type {module:image/image~ImageConfig#resizeUnit}
  57. * @default '%'
  58. */
  59. this._resizeUnit = editor.config.get( 'image.resizeUnit' ) || '%';
  60. }
  61. /**
  62. * @inheritDoc
  63. */
  64. init() {
  65. const editor = this.editor;
  66. const options = editor.config.get( 'image.resizeOptions' );
  67. const command = editor.commands.get( 'imageResize' );
  68. if ( !options ) {
  69. return;
  70. }
  71. this.bind( 'isEnabled' ).to( command );
  72. for ( const option of options ) {
  73. this._registerImageResizeButton( option );
  74. }
  75. this._registerImageResizeDropdown( options );
  76. }
  77. /**
  78. * A helper function that creates a standalone button component for the plugin.
  79. *
  80. * @private
  81. * @param {module:image/imageresize/imageresizeui~ImageResizeOption} resizeOption A model of resize option.
  82. */
  83. _registerImageResizeButton( option ) {
  84. const editor = this.editor;
  85. const { name, value, icon } = option;
  86. const optionValueWithUnit = value ? value + this._resizeUnit : null;
  87. editor.ui.componentFactory.add( name, locale => {
  88. const button = new ButtonView( locale );
  89. const command = editor.commands.get( 'imageResize' );
  90. const labelText = this._getOptionLabelValue( option, true );
  91. if ( !RESIZE_ICONS[ icon ] ) {
  92. /**
  93. * When configuring {@link module:image/image~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
  94. * buttons, a valid `icon` token must be set for each option.
  95. *
  96. * See all valid options described in the
  97. * {@link module:image/imageresize/imageresizeui~ImageResizeOption plugin configuration}.
  98. *
  99. * @error imageresizeui-missing-icon
  100. * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option Invalid image resize option.
  101. */
  102. throw new CKEditorError(
  103. 'imageresizeui-missing-icon: ' +
  104. 'The resize option "' + name + '" misses the "icon" property ' +
  105. 'or the property value doesn\'t match any of available icons.',
  106. editor,
  107. option
  108. );
  109. }
  110. button.set( {
  111. // Use the `label` property for a verbose description (because of ARIA).
  112. label: labelText,
  113. icon: RESIZE_ICONS[ icon ],
  114. tooltip: labelText,
  115. isToggleable: true
  116. } );
  117. // Bind button to the command.
  118. button.bind( 'isEnabled' ).to( this );
  119. button.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
  120. this.listenTo( button, 'execute', () => {
  121. editor.execute( 'imageResize', { width: optionValueWithUnit } );
  122. } );
  123. return button;
  124. } );
  125. }
  126. /**
  127. * A helper function that creates a dropdown component for the plugin containing all resize options defined in
  128. * the editor configuration.
  129. *
  130. * @private
  131. * @param {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} options An array of configured options.
  132. */
  133. _registerImageResizeDropdown( options ) {
  134. const editor = this.editor;
  135. const t = editor.t;
  136. const originalSizeOption = options.find( option => !option.value );
  137. // Register dropdown.
  138. editor.ui.componentFactory.add( 'imageResize', locale => {
  139. const command = editor.commands.get( 'imageResize' );
  140. const dropdownView = createDropdown( locale, DropdownButtonView );
  141. const dropdownButton = dropdownView.buttonView;
  142. dropdownButton.set( {
  143. tooltip: t( 'Resize image' ),
  144. commandValue: originalSizeOption.value,
  145. icon: iconMedium,
  146. isToggleable: true,
  147. label: this._getOptionLabelValue( originalSizeOption ),
  148. withText: true,
  149. class: 'ck-resize-image-button'
  150. } );
  151. dropdownButton.bind( 'label' ).to( command, 'value', commandValue => {
  152. if ( commandValue && commandValue.width ) {
  153. return commandValue.width;
  154. } else {
  155. return this._getOptionLabelValue( originalSizeOption );
  156. }
  157. } );
  158. dropdownView.bind( 'isOn' ).to( command );
  159. dropdownView.bind( 'isEnabled' ).to( this );
  160. addListToDropdown( dropdownView, this._getResizeDropdownListItemDefinitions( options, command ) );
  161. dropdownView.listView.ariaLabel = t( 'Image resize list' );
  162. // Execute command when an item from the dropdown is selected.
  163. this.listenTo( dropdownView, 'execute', evt => {
  164. editor.execute( evt.source.commandName, { width: evt.source.commandValue } );
  165. editor.editing.view.focus();
  166. } );
  167. return dropdownView;
  168. } );
  169. }
  170. /**
  171. * A helper function for creating an option label value string.
  172. *
  173. * @private
  174. * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option A resize option object.
  175. * @param {Boolean} [forTooltip] An optional flag for creating a tooltip label.
  176. * @returns {String} A user-defined label, a label combined from the value and resize unit or the default label
  177. * for reset options (`Original`).
  178. */
  179. _getOptionLabelValue( option, forTooltip ) {
  180. const t = this.editor.t;
  181. if ( option.label ) {
  182. return option.label;
  183. } else if ( forTooltip ) {
  184. if ( option.value ) {
  185. return t( 'Resize image to %0', option.value + this._resizeUnit );
  186. } else {
  187. return t( 'Resize image to the original size' );
  188. }
  189. } else {
  190. if ( option.value ) {
  191. return option.value + this._resizeUnit;
  192. } else {
  193. return t( 'Original' );
  194. }
  195. }
  196. }
  197. /**
  198. * A helper function that parses resize options and returns list item definitions ready for use in a dropdown.
  199. *
  200. * @private
  201. * @param {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} options The resize options.
  202. * @param {module:image/imageresize/imageresizecommand~ImageResizeCommand} command A resize image command.
  203. * @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} Dropdown item definitions.
  204. */
  205. _getResizeDropdownListItemDefinitions( options, command ) {
  206. const itemDefinitions = new Collection();
  207. options.map( option => {
  208. const optionValueWithUnit = option.value ? option.value + this._resizeUnit : null;
  209. const definition = {
  210. type: 'button',
  211. model: new Model( {
  212. commandName: 'imageResize',
  213. commandValue: optionValueWithUnit,
  214. label: this._getOptionLabelValue( option ),
  215. withText: true,
  216. icon: null
  217. } )
  218. };
  219. definition.model.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
  220. itemDefinitions.add( definition );
  221. } );
  222. return itemDefinitions;
  223. }
  224. }
  225. // A helper function for setting the `isOn` state of buttons in value bindings.
  226. function getIsOnButtonCallback( value ) {
  227. return commandValue => {
  228. if ( value === null && commandValue === value ) {
  229. return true;
  230. }
  231. return commandValue && commandValue.width === value;
  232. };
  233. }
  234. /**
  235. * An image resize option used in the {@link module:image/image~ImageConfig#resizeOptions image resize configuration}.
  236. *
  237. * @typedef {Object} module:image/imageresize/imageresizeui~ImageResizeOption
  238. * @property {String} name A name of the UI component that changes the image size.
  239. * * If you configure the feature using individual resize buttons, you can refer to this name in the
  240. * {@link module:image/image~ImageConfig#toolbar image toolbar configuration}.
  241. * * If you configure the feature using the resize dropdown, this name will be used for a list item in the dropdown.
  242. * @property {String} value A value of a resize option without the unit
  243. * ({@link module:image/image~ImageConfig#resizeUnit configured separately}). `null` resets an image to its original size.
  244. * @property {String} [resizeOptions.icon] An icon used by an individual resize button (see the `name` property to learn more).
  245. * Available icons are: `'small'`, `'medium'`, `'large'`, `'original'`.
  246. * @property {String} [label] A label of the option displayed in the dropdown or, if the feature is configured using
  247. * individual buttons, a {@link module:ui/button/buttonview~ButtonView#tooltip} and an ARIA attribute of a button.
  248. * If not specified, the label is generated automatically based on the option `value` and the
  249. * {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`}.
  250. */