UNPKG

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