diff --git a/packages/ckeditor5-core/theme/icons/object-size-full.svg b/packages/ckeditor5-core/theme/icons/object-size-full.svg new file mode 100644 index 00000000000..9120563d753 --- /dev/null +++ b/packages/ckeditor5-core/theme/icons/object-size-full.svg @@ -0,0 +1 @@ + diff --git a/packages/ckeditor5-core/theme/icons/object-size-large.svg b/packages/ckeditor5-core/theme/icons/object-size-large.svg new file mode 100644 index 00000000000..f5e5a37f76f --- /dev/null +++ b/packages/ckeditor5-core/theme/icons/object-size-large.svg @@ -0,0 +1 @@ + diff --git a/packages/ckeditor5-core/theme/icons/object-size-medium.svg b/packages/ckeditor5-core/theme/icons/object-size-medium.svg new file mode 100644 index 00000000000..69713e30376 --- /dev/null +++ b/packages/ckeditor5-core/theme/icons/object-size-medium.svg @@ -0,0 +1 @@ + diff --git a/packages/ckeditor5-core/theme/icons/object-size-small.svg b/packages/ckeditor5-core/theme/icons/object-size-small.svg new file mode 100644 index 00000000000..661c575fd73 --- /dev/null +++ b/packages/ckeditor5-core/theme/icons/object-size-small.svg @@ -0,0 +1 @@ + diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resizeui.js b/packages/ckeditor5-image/docs/_snippets/features/image-resizeui.js index 2327d7c1167..f2f8b1c827d 100644 --- a/packages/ckeditor5-image/docs/_snippets/features/image-resizeui.js +++ b/packages/ckeditor5-image/docs/_snippets/features/image-resizeui.js @@ -17,26 +17,29 @@ ClassicEditor resizeOptions: [ { name: 'imageResize:original', - label: 'Original size', - value: null + label: 'Original', + value: null, + icon: 'original' }, { name: 'imageResize:50', label: '50%', - value: '50' + value: '50', + icon: 'medium' }, { name: 'imageResize:75', label: '75%', - value: '75' + value: '75', + icon: 'large' } ], toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', - 'imageResize:original', 'imageResize:50', - 'imageResize:75' + 'imageResize:75', + 'imageResize:original' ] }, cloudServices: CS_CONFIG diff --git a/packages/ckeditor5-image/docs/_snippets/features/image-resizeuidropdown.js b/packages/ckeditor5-image/docs/_snippets/features/image-resizeuidropdown.js index 15675720c49..3314067ecea 100644 --- a/packages/ckeditor5-image/docs/_snippets/features/image-resizeuidropdown.js +++ b/packages/ckeditor5-image/docs/_snippets/features/image-resizeuidropdown.js @@ -17,7 +17,7 @@ ClassicEditor resizeOptions: [ { name: 'imageResize:original', - label: 'Original size', + label: 'Original', value: null }, { diff --git a/packages/ckeditor5-image/lang/contexts.json b/packages/ckeditor5-image/lang/contexts.json index 80bba6e2d8d..da6c3337c43 100644 --- a/packages/ckeditor5-image/lang/contexts.json +++ b/packages/ckeditor5-image/lang/contexts.json @@ -11,7 +11,9 @@ "Insert image": "Label for the insert image toolbar button.", "Upload failed": "Title of the notification displayed when upload fails.", "Image toolbar": "The label used by assistive technologies describing an image toolbar attached to an image widget.", - "Resize image to": "The label used for the standalone resize option buttons in the image toolbar", - "Resize image": "The label used for the dropdown in the image toolbar, containing defined resize options", - "Resize image to the original size": "The label used for the standalone resize reset option button in the image toolbar, when user set the value to `null`" + "Resize image": "The label used for the dropdown in the image toolbar containing defined resize options", + "Resize image to %0": "The label used for the standalone resize options buttons in the image toolbar", + "Resize image to the original size": "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers", + "Original": "Default label for the resize option that resets the size of the image.", + "Image resize list": "The accessibility label of the image resize dropdown list for the screen readers." } diff --git a/packages/ckeditor5-image/src/imageresize.js b/packages/ckeditor5-image/src/imageresize.js index d32eab59bea..faad8b2ae76 100644 --- a/packages/ckeditor5-image/src/imageresize.js +++ b/packages/ckeditor5-image/src/imageresize.js @@ -58,16 +58,21 @@ export default class ImageResize extends Plugin { */ /** - * The resize options. + * The image resize options. * - * Each option should have its `name`, which is a component name definition that will be - * used in the {@link module:image/imageresize/imageresizeui~ImageResizeUI} plugin. - * Other properties like `label` and `value` define the following: - * a text label for the option button and the value that will be applied to the image's width. + * Each option should have at least these two properties: * - * The value property is combined with the `resizeUnit` (`%` by default), eg: `value: '50'` and `resizeUnit: '%'` is `50%`. + * * name: The name of the UI component registered in the global + * {@link module:core/editor/editorui~EditorUI#componentFactory component factory} of the editor, + * representing the button a user can click to change the size of an image, + * * value: An actual image width applied when a user clicks the mentioned button + * ({@link module:image/imageresize/imageresizecommand~ImageResizeCommand} gets executed). + * The value property is combined with the {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`} (`%` by default). + * For instance: `value: '50'` and `resizeUnit: '%'` will render as `'50%'` in the UI. * - * **NOTE:** If you want to set an option that will reset image to its original size, you need to pass a `null` value + * **Resetting the image size** + * + * If you want to set an option that will reset image to its original size, you need to pass a `null` value * to one of the options. The `:original` token is not mandatory, you can call it anything you wish, but it must reflect * in the standalone buttons configuration for the image toolbar. * @@ -77,17 +82,14 @@ export default class ImageResize extends Plugin { * resizeUnit: "%", * resizeOptions: [ { * name: 'imageResize:original', - * label: 'Original size', * value: null * }, * { * name: 'imageResize:50', - * label: '50%', * value: '50' * }, * { * name: 'imageResize:75', - * label: '75%', * value: '75' * } ] * } @@ -95,27 +97,26 @@ export default class ImageResize extends Plugin { * .then( ... ) * .catch( ... ); * + * **Resizing images using a dropdown** + * * With resize options defined, you can decide whether you want to display them as a dropdown or as standalone buttons. - * For the dropdown, you need to pass only the `imageResize` token to the `image.toolbar`. - * The dropdown contains all defined options by default: + * For the dropdown, you need to pass only the `imageResize` token to the +{@link module:image/image~ImageConfig#toolbar `config.image.toolbar`}. The dropdown contains all defined options by default: * - * ClassicEditor + * ClassicEditor * .create( editorElement, { * image: { * resizeUnit: "%", * resizeOptions: [ { * name: 'imageResize:original', - * label: 'Original size', * value: null * }, * { * name: 'imageResize:50', - * label: '50%', * value: '50' * }, * { * name: 'imageResize:75', - * label: '75%', * value: '75' * } ], * toolbar: [ 'imageResize', ... ], @@ -124,33 +125,76 @@ export default class ImageResize extends Plugin { * .then( ... ) * .catch( ... ); * - * If you want to have separate buttons for each option, pass their names instead: + * **Resizing images using individual buttons** + * + * If you want to have separate buttons for {@link module:image/imageresize/imageresizeui~ImageResizeOption each option}, + * pass their names to the {@link module:image/image~ImageConfig#toolbar `config.image.toolbar`} instead. Please keep in mind + * that this time **you must define the additional + * {@link module:image/imageresize/imageresizeui~ImageResizeOption `icon` property}**: * - * ClassicEditor + * ClassicEditor * .create( editorElement, { * image: { * resizeUnit: "%", * resizeOptions: [ { * name: 'imageResize:original', - * label: 'Original size', * value: null + * icon: 'original' + * }, + * { + * name: 'imageResize:25', + * value: '25' + * icon: 'small' * }, * { * name: 'imageResize:50', - * label: '50%', * value: '50' + * icon: 'medium' * }, * { * name: 'imageResize:75', - * label: '75%', * value: '75' + * icon: 'large' * } ], - * toolbar: [ 'imageResize:original', 'imageResize:50', 'imageResize:75', ... ], + * toolbar: [ 'imageResize:25', 'imageResize:50', 'imageResize:75', 'imageResize:original', ... ], * } * } ) * .then( ... ) * .catch( ... ); * + * **Customizing resize button labels** + * + * You can set your own label for each resize button. To do that, add the `label` property like in the example below. + * + * * When using the **dropdown**, the labels are displayed on the list of all options when you open the dropdown. + * * When using **standalone buttons**, the labels will are displayed as tooltips when a user hovers over the button. + * + * ClassicEditor + * .create( editorElement, { + * image: { + * resizeUnit: "%", + * resizeOptions: [ { + * name: 'imageResize:original', + * value: null, + * label: 'Original size' + * // Note: add the "icon" property if you're configuring a standalone button. + * }, + * { + * name: 'imageResize:50', + * value: '50', + * label: 'Medium size' + * // Note: add the "icon" property if you're configuring a standalone button. + * }, + * { + * name: 'imageResize:75', + * value: '75', + * label: 'Large size' + * // Note: add the "icon" property if you're configuring a standalone button. + * } ] + * } + * } ) + * .then( ... ) + * .catch( ... ); * * @member {Array.} module:image/image~ImageConfig#resizeOptions */ diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.js b/packages/ckeditor5-image/src/imageresize/imageresizeediting.js index 4e6bd1fa94e..6022a87e122 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.js @@ -4,7 +4,7 @@ */ /** - * @module image/imageresizeediting + * @module image/imageresize/imageresizeediting */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeui.js b/packages/ckeditor5-image/src/imageresize/imageresizeui.js index abf1f909ef0..813a8857afa 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeui.js +++ b/packages/ckeditor5-image/src/imageresize/imageresizeui.js @@ -16,10 +16,24 @@ import DropdownButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/dropd import Model from '@ckeditor/ckeditor5-ui/src/model'; import Collection from '@ckeditor/ckeditor5-utils/src/collection'; +import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; + +import iconSmall from '@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg'; +import iconMedium from '@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg'; +import iconLarge from '@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg'; +import iconFull from '@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg'; + +const RESIZE_ICONS = { + small: iconSmall, + medium: iconMedium, + large: iconLarge, + original: iconFull +}; + /** * The `ImageResizeUI` plugin. * - * It adds a possibility to resize each image using toolbar dropdown or separate buttons, depends on the plugin configuration. + * It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration. * * @extends module:core/plugin~Plugin */ @@ -38,6 +52,23 @@ export default class ImageResizeUI extends Plugin { return 'ImageResizeUI'; } + /** + * @inheritDoc + */ + constructor( editor ) { + super( editor ); + + /** + * The resize unit. + * + * @readonly + * @private + * @type {module:image/image~ImageConfig#resizeUnit} + * @default '%' + */ + this._resizeUnit = editor.config.get( 'image.resizeUnit' ) || '%'; + } + /** * @inheritDoc */ @@ -45,7 +76,6 @@ export default class ImageResizeUI extends Plugin { const editor = this.editor; const options = editor.config.get( 'image.resizeOptions' ); const command = editor.commands.get( 'imageResize' ); - const resizeUnit = editor.config.get( 'image.resizeUnit' ) || '%'; if ( !options ) { return; @@ -54,44 +84,62 @@ export default class ImageResizeUI extends Plugin { this.bind( 'isEnabled' ).to( command ); for ( const option of options ) { - this._addButton( option, resizeUnit ); + this._registerImageResizeButton( option ); } - this._addDropdown( options, resizeUnit ); + this._registerImageResizeDropdown( options ); } /** * A helper function that creates a standalone button component for the plugin. * * @private - * * @param {module:image/imageresize/imageresizeui~ImageResizeOption} resizeOption A model of resize option. - * @param {String} unit A resize unit. */ - _addButton( { name, label, value }, unit ) { + _registerImageResizeButton( option ) { const editor = this.editor; - const t = editor.t; - const parsedValue = value ? value + unit : null; + const { name, value, icon } = option; + const optionValueWithUnit = value ? value + this._resizeUnit : null; editor.ui.componentFactory.add( name, locale => { const button = new ButtonView( locale ); const command = editor.commands.get( 'imageResize' ); - const commandCallback = setOptionOn( parsedValue ); + const labelText = this._getOptionLabelValue( option, true ); + + if ( !RESIZE_ICONS[ icon ] ) { + /** + * When configuring {@link module:image/image~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone + * buttons, a valid `icon` token must be set for each option. + * + * See all valid options described in the + * {@link module:image/imageresize/imageresizeui~ImageResizeOption plugin configuration}. + * + * @error imageresizeui-missing-icon + * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option Invalid image resize option. + */ + throw new CKEditorError( + 'imageresizeui-missing-icon: ' + + 'The resize option "' + name + '" misses the "icon" property ' + + 'or the property value doesn\'t match any of available icons.', + editor, + option + ); + } button.set( { - label: t( label ), - withText: true, - tooltip: parsedValue ? t( 'Resize image to' ) + ' ' + parsedValue : t( 'Resize image to the original size' ), - isToggleable: true, - commandValue: parsedValue + // Use the `label` property for a verbose description (because of ARIA). + label: labelText, + icon: RESIZE_ICONS[ icon ], + tooltip: labelText, + isToggleable: true } ); // Bind button to the command. button.bind( 'isEnabled' ).to( this ); - button.bind( 'isOn' ).to( command, 'value', commandCallback ); + button.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) ); - this.listenTo( button, 'execute', evt => { - editor.execute( 'imageResize', { width: evt.source.commandValue } ); + this.listenTo( button, 'execute', () => { + editor.execute( 'imageResize', { width: optionValueWithUnit } ); } ); return button; @@ -99,19 +147,16 @@ export default class ImageResizeUI extends Plugin { } /** - * A helper function that creates a dropdown component for the plugin containing all resize options created through the - * plugin configuration settings. + * A helper function that creates a dropdown component for the plugin containing all resize options defined in + * the editor configuration. * * @private - * - * @param {Array.} options An array of the configured options. - * @param {String} unit A resize unit. + * @param {Array.} options An array of configured options. */ - _addDropdown( options, unit ) { + _registerImageResizeDropdown( options ) { const editor = this.editor; const t = editor.t; - const firstOption = options[ 0 ]; - const resetOption = options.find( option => option.value === null ); + const originalSizeOption = options.find( option => !option.value ); // Register dropdown. editor.ui.componentFactory.add( 'imageResize', locale => { @@ -121,19 +166,25 @@ export default class ImageResizeUI extends Plugin { dropdownButton.set( { tooltip: t( 'Resize image' ), - commandValue: firstOption.value, + commandValue: originalSizeOption.value, + icon: iconMedium, isToggleable: true, - label: firstOption.label, - withText: true + label: this._getOptionLabelValue( originalSizeOption ), + withText: true, + class: 'ck-resize-image-button' } ); dropdownButton.bind( 'label' ).to( command, 'value', commandValue => { - return commandValue && commandValue.width || resetOption.label; + if ( commandValue && commandValue.width ) { + return commandValue.width; + } else { + return this._getOptionLabelValue( originalSizeOption ); + } } ); dropdownView.bind( 'isOn' ).to( command ); dropdownView.bind( 'isEnabled' ).to( this ); - addListToDropdown( dropdownView, prepareListDefinitions( options, command, unit ) ); + addListToDropdown( dropdownView, this._getResizeDropdownListItemDefinitions( options, command ) ); dropdownView.listView.ariaLabel = t( 'Image resize list' ); @@ -146,40 +197,73 @@ export default class ImageResizeUI extends Plugin { return dropdownView; } ); } -} -// A helper function for parsing resize options definitions. -function prepareListDefinitions( definitions, command, resizeUnit ) { - const itemDefinitions = new Collection(); - - definitions.map( itemDefinition => { - const parsedValue = itemDefinition.value ? itemDefinition.value + resizeUnit : null; - const definition = { - type: 'button', - model: new Model( { - commandName: 'imageResize', - commandValue: parsedValue, - label: itemDefinition.label, - withText: true, - icon: null - } ) - }; - - const commandCallback = setOptionOn( parsedValue ); - - definition.model.bind( 'isOn' ).to( command, 'value', commandCallback ); + /** + * A helper function for creating an option label value string. + * + * @private + * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option A resize option object. + * @param {Boolean} [forTooltip] An optional flag for creating a tooltip label. + * @returns {String} A user-defined label, a label combined from the value and resize unit or the default label + * for reset options (`Original`). + */ + _getOptionLabelValue( option, forTooltip ) { + const t = this.editor.t; + + if ( option.label ) { + return option.label; + } else if ( forTooltip ) { + if ( option.value ) { + return t( 'Resize image to %0', option.value + this._resizeUnit ); + } else { + return t( 'Resize image to the original size' ); + } + } else { + if ( option.value ) { + return option.value + this._resizeUnit; + } else { + return t( 'Original' ); + } + } + } - itemDefinitions.add( definition ); - } ); + /** + * A helper function that parses resize options and returns list item definitions ready for use in a dropdown. + * + * @private + * @param {Array.} options The resize options. + * @param {module:image/imageresize/imageresizecommand~ImageResizeCommand} command A resize image command. + * @returns {Iterable.} Dropdown item definitions. + */ + _getResizeDropdownListItemDefinitions( options, command ) { + const itemDefinitions = new Collection(); + + options.map( option => { + const optionValueWithUnit = option.value ? option.value + this._resizeUnit : null; + const definition = { + type: 'button', + model: new Model( { + commandName: 'imageResize', + commandValue: optionValueWithUnit, + label: this._getOptionLabelValue( option ), + withText: true, + icon: null + } ) + }; + + definition.model.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) ); + + itemDefinitions.add( definition ); + } ); - return itemDefinitions; + return itemDefinitions; + } } -// A helper function for setting the `isOn` state used for creating a callback function in a value binding. -function setOptionOn( value ) { +// A helper function for setting the `isOn` state of buttons in value bindings. +function getIsOnButtonCallback( value ) { return commandValue => { - // Set reseting option on when command equals `null`. - if ( commandValue === value ) { + if ( value === null && commandValue === value ) { return true; } @@ -188,12 +272,19 @@ function setOptionOn( value ) { } /** - * A resize option type. + * An image resize option used in the {@link module:image/image~ImageConfig#resizeOptions image resize configuration}. * * @typedef {Object} module:image/imageresize/imageresizeui~ImageResizeOption - * - * @property {String} resizeOption.name A name of the option used for creating a component. - * You refer to that name later in the {@link module:image/image~ImageConfig#toolbar}. - * @property {String} resizeOption.label A label to be displayed with a button. - * @property {String} resizeOption.value A value of a resize option. `null` value is for resetting an image to its original size. + * @property {String} name A name of the UI component that changes the image size. + * * If you configure the feature using individual resize buttons, you can refer to this name in the + * {@link module:image/image~ImageConfig#toolbar image toolbar configuration}. + * * If you configure the feature using the resize dropdown, this name will be used for a list item in the dropdown. + * @property {String} value A value of a resize option without the unit + * ({@link module:image/image~ImageConfig#resizeUnit configured separately}). `null` resets an image to its original size. + * @property {String} [resizeOptions.icon] An icon used by an individual resize button (see the `name` property to learn more). + * Available icons are: `'small'`, `'medium'`, `'large'`, `'original'`. + * @property {String} [label] A label of the option displayed in the dropdown or, if the feature is configured using + * individual buttons, a {@link module:ui/button/buttonview~ButtonView#tooltip} and an ARIA attribute of a button. + * If not specified, the label is generated automatically based on the option `value` and the + * {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`}. */ diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeui.js b/packages/ckeditor5-image/tests/imageresize/imageresizeui.js index aeacf1340d5..aa49d04a06d 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeui.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeui.js @@ -17,23 +17,30 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo'; import Table from '@ckeditor/ckeditor5-table/src/table'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +import iconSmall from '@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg'; +import iconMedium from '@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg'; +import iconLarge from '@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg'; +import iconFull from '@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg'; describe( 'ImageResizeUI', () => { let plugin, command, editor, editorElement; const resizeOptions = [ { name: 'imageResize:original', - label: 'Original size', value: null }, + { + name: 'imageResize:25', + value: '25' + }, { name: 'imageResize:50', - label: '50%', value: '50' }, { name: 'imageResize:75', - label: '75%', value: '75' } ]; @@ -47,7 +54,6 @@ describe( 'ImageResizeUI', () => { .create( editorElement, { plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], image: { - resizeUnit: '%', resizeOptions } } ); @@ -70,66 +76,25 @@ describe( 'ImageResizeUI', () => { it( 'should be named', () => { expect( ImageResizeUI.pluginName ).to.equal( 'ImageResizeUI' ); } ); - - it( 'should be disabled when command is disabled', () => { - command.isEnabled = true; - - expect( plugin.isEnabled ).to.be.true; - - command.isEnabled = false; - - expect( plugin.isEnabled ).to.be.false; - } ); } ); - describe( 'init()', () => { - it( 'should have set "%" resize unit', () => { - const unit = editor.config.get( 'image.resizeUnit' ); + describe( 'constructor()', () => { + it( 'should create `_resizeUnit` with default value of `%`', () => { + const unit = plugin._resizeUnit; expect( unit ).to.equal( '%' ); } ); + } ); - it( 'should have set "%" resize unit if not defined', async () => { - const editor = await ClassicTestEditor - .create( editorElement, { - plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], - image: { - resizeOptions - } - } ); - - const button = editor.ui.componentFactory.create( 'imageResize:50' ); - const command = editor.commands.get( 'imageResize' ); - - command.isEnabled = true; - - button.fire( 'execute' ); - - expect( command.value.width.includes( '%' ) ).to.be.true; - - await editor.destroy(); - } ); - - it( 'should have set "px" resize unit', async () => { - const editor = await ClassicTestEditor - .create( editorElement, { - plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], - image: { - resizeUnit: 'px', - resizeOptions - } - } ); - - const button = editor.ui.componentFactory.create( 'imageResize:50' ); - const command = editor.commands.get( 'imageResize' ); - + describe( 'init()', () => { + it( 'should be disabled when command is disabled', () => { command.isEnabled = true; - button.fire( 'execute' ); + expect( plugin.isEnabled ).to.be.true; - expect( command.value.width.includes( 'px' ) ).to.be.true; + command.isEnabled = false; - await editor.destroy(); + expect( plugin.isEnabled ).to.be.false; } ); it( 'should not register a dropdown or buttons if no resize options passed', async () => { @@ -167,12 +132,12 @@ describe( 'ImageResizeUI', () => { expect( editor.ui.componentFactory.create( 'imageResize' ) ).to.be.instanceof( DropdownView ); } ); - it( 'should have 3 resize options in the `imageResize` dropdown', () => { + it( 'should have 4 resize options in the `imageResize` dropdown', () => { const dropdownView = editor.ui.componentFactory.create( 'imageResize' ); - expect( dropdownView.listView.items.length ).to.equal( 3 ); - expect( dropdownView.listView.items.first.element.textContent ).to.equal( 'Original size' ); - expect( dropdownView.listView.items._items[ 1 ].element.textContent ).to.equal( '50%' ); + expect( dropdownView.listView.items.length ).to.equal( 4 ); + expect( dropdownView.listView.items.first.element.textContent ).to.equal( 'Original' ); + expect( dropdownView.listView.items._items[ 1 ].element.textContent ).to.equal( '25%' ); expect( dropdownView.listView.items.last.element.textContent ).to.equal( '75%' ); } ); @@ -192,11 +157,56 @@ describe( 'ImageResizeUI', () => { resizeBy50Percent.fire( 'execute' ); sinon.assert.calledOnce( commandSpy ); - expect( command.value.width ).to.equal( '50%' ); + expect( command.value.width ).to.equal( '25%' ); } ); } ); describe( 'resize option button', () => { + let editor, plugin; + + beforeEach( async () => { + editor = await ClassicTestEditor + .create( editorElement, { + plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], + image: { + resizeUnit: '%', + resizeOptions: [ { + name: 'imageResize:original', + value: null, + icon: 'original' + }, + { + name: 'imageResize:25', + value: '25', + icon: 'small' + }, + { + name: 'imageResize:50', + value: '50', + icon: 'medium' + }, + { + name: 'imageResize:75', + value: '75', + icon: 'large' + } ], + toolbar: [ 'imageResize:original', 'imageResize:25', 'imageResize:50', 'imageResize:75' ] + } + } ); + + plugin = editor.plugins.get( 'ImageResizeUI' ); + } ); + + afterEach( async () => { + if ( editorElement ) { + editorElement.remove(); + } + + if ( editor ) { + await editor.destroy(); + } + } ); + it( 'should be bound to `#isEnabled`', () => { const buttonView = editor.ui.componentFactory.create( 'imageResize:50' ); @@ -213,13 +223,41 @@ describe( 'ImageResizeUI', () => { expect( editor.ui.componentFactory.create( 'imageResize:50' ) ).to.be.instanceof( ButtonView ); } ); - it( 'should be created with visible "50%" label', () => { + it( 'should be created with invisible "Resize image: 30%" label when is provided', async () => { + const editor = await ClassicTestEditor + .create( editorElement, { + plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], + image: { + resizeUnit: '%', + resizeOptions: [ { + name: 'imageResize:30', + value: '30', + label: 'Resize image: 30%', + icon: 'small' + } ], + toolbar: [ 'imageResize:30' ] + } + } ); + + const buttonView = editor.ui.componentFactory.create( 'imageResize:30' ); + buttonView.render(); + + expect( buttonView.withText ).to.be.false; + expect( buttonView.label ).to.equal( 'Resize image: 30%' ); + expect( buttonView.labelView ).to.be.instanceOf( View ); + + editor.destroy(); + } ); + + it( 'should be created with invisible "Resize image to 50%" label when is not provided', async () => { const buttonView = editor.ui.componentFactory.create( 'imageResize:50' ); buttonView.render(); - expect( buttonView.withText ).to.be.true; - expect( buttonView.label ).to.equal( '50%' ); + expect( buttonView.withText ).to.be.false; + expect( buttonView.label ).to.equal( 'Resize image to 50%' ); expect( buttonView.labelView ).to.be.instanceOf( View ); + + editor.destroy(); } ); it( 'should be created with a proper tooltip, depends on the set value', () => { @@ -233,12 +271,6 @@ describe( 'ImageResizeUI', () => { expect( buttonView50.tooltip ).to.equal( 'Resize image to 50%' ); } ); - it( 'should have `commandValue` equal "50%"', () => { - const buttonView = editor.ui.componentFactory.create( 'imageResize:50' ); - - expect( buttonView.commandValue ).to.equal( '50%' ); - } ); - it( 'should execute `imageResize` command with "50%" value', () => { const buttonView = editor.ui.componentFactory.create( 'imageResize:50' ); const command = editor.commands.get( 'imageResize' ); @@ -251,5 +283,41 @@ describe( 'ImageResizeUI', () => { sinon.assert.calledOnce( commandSpy ); expect( command.value.width ).to.equal( '50%' ); } ); + + it( 'should have set a proper icon', () => { + const buttonOriginal = editor.ui.componentFactory.create( 'imageResize:original' ); + const button25 = editor.ui.componentFactory.create( 'imageResize:25' ); + const button50 = editor.ui.componentFactory.create( 'imageResize:50' ); + const button75 = editor.ui.componentFactory.create( 'imageResize:75' ); + + expect( buttonOriginal.icon ).to.deep.equal( iconFull ); + expect( button25.icon ).to.deep.equal( iconSmall ); + expect( button50.icon ).to.deep.equal( iconMedium ); + expect( button75.icon ).to.deep.equal( iconLarge ); + } ); + + it( 'should throw the CKEditorError if no `icon` is provided', async () => { + const editor = await ClassicTestEditor + .create( editorElement, { + plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ], + image: { + resizeUnit: '%', + resizeOptions: [ { + name: 'imageResize:noicon', + value: '100' + } ], + toolbar: [ 'imageResize:noicon' ] + } + } ); + + const errMsg = 'The resize option "imageResize:noicon" misses the "icon" property ' + + 'or the property value doesn\'t match any of available icons.'; + + expectToThrowCKEditorError( () => { + editor.ui.componentFactory.create( 'imageResize:noicon' ); + }, errMsg, editor ); + + editor.destroy(); + } ); } ); } ); diff --git a/packages/ckeditor5-image/tests/manual/imageresizeui.js b/packages/ckeditor5-image/tests/manual/imageresizeui.js index 015043ef6a8..dec851c7626 100644 --- a/packages/ckeditor5-image/tests/manual/imageresizeui.js +++ b/packages/ckeditor5-image/tests/manual/imageresizeui.js @@ -36,17 +36,14 @@ const imageConfig1 = { resizeOptions: [ { name: 'imageResize:original', - label: 'Original size', value: null }, { name: 'imageResize:50', - label: '50%', value: '50' }, { name: 'imageResize:75', - label: '75%', value: '75' } ], @@ -66,7 +63,7 @@ const config1 = { ClassicEditor .create( document.querySelector( '#editor1' ), config1 ) .then( editor => { - window.editor = editor; + window.editor1 = editor; } ) .catch( err => { console.error( err.stack ); @@ -77,27 +74,27 @@ const imageConfig2 = { resizeOptions: [ { name: 'imageResize:original', - label: 'Original size', - value: null + value: null, + icon: 'original' }, { name: 'imageResize:50', - label: '50%', - value: '50' + value: '50', + icon: 'medium' }, { name: 'imageResize:75', - label: '75%', - value: '75' + value: '75', + icon: 'large' } ], toolbar: [ 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:side', '|', - 'imageResize:original', 'imageResize:50', - 'imageResize:75' + 'imageResize:75', + 'imageResize:original' ], styles: [ 'full', @@ -114,7 +111,7 @@ const config2 = { ClassicEditor .create( document.querySelector( '#editor2' ), config2 ) .then( editor => { - window.editor = editor; + window.editor2 = editor; } ) .catch( err => { console.error( err.stack ); diff --git a/packages/ckeditor5-image/tests/manual/imageresizeui.md b/packages/ckeditor5-image/tests/manual/imageresizeui.md index 2b4205a8569..861014a9a87 100644 --- a/packages/ckeditor5-image/tests/manual/imageresizeui.md +++ b/packages/ckeditor5-image/tests/manual/imageresizeui.md @@ -1,11 +1,20 @@ ## Image Resize UI -The tests for manual image resizing. -- The first test should have the dropdown with configured options in the image toolbar (using `imageResize`). - - Plugin icon should appear only in the dropbdown button. - - Each option should have a label text represented an option value defined in the plugin configuration. - - Selected options should be set "on" when dropdown is open. -- The second one should have the standalone buttons instead of dropdown (from the first test) in the image toolbar (using -`imageResize:option`). - - Each option should have the plugin icon "stretched" over the label text which represents an option value defined in the plugin configuration. - - Selected option should be set "on". +Tests for manual image resizing using the UI in the image toolbar. + +## Dropdown + +1. Select an image in the editor. +2. A dropdown with configured options (`config.image.resizeOptions`) should be visible in the toolbar. + - The plugin icon should appear only next to the dropdown button. + - Each option should have a label text corresponding to an option value defined in the configuration. + - The selected option should be "on" when the dropdown is open. + +## Buttons + +1. Select an image in the editor. +2. Standalone buttons should be displayed in the image toolbar (corresponding to `config.image.resizeOptions`). + - Each button should have an icon as in the configuration (`small`, `medium`, `large` or `original`). + - No label should be rendered, + - The tooltip text and the `aria-label` attribute should be the same (and more verbose). + - The selected option button should be "on". diff --git a/packages/ckeditor5-image/theme/imageresize.css b/packages/ckeditor5-image/theme/imageresize.css index 1ba212cfd88..4c9c336c5e5 100644 --- a/packages/ckeditor5-image/theme/imageresize.css +++ b/packages/ckeditor5-image/theme/imageresize.css @@ -23,3 +23,15 @@ display: block; } } + +[dir="ltr"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon { + margin-right: var(--ck-spacing-standard); +} + +[dir="rtl"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon { + margin-left: var(--ck-spacing-standard); +} + +.ck.ck-dropdown .ck-button.ck-resize-image-button .ck-button__label { + width: 4em; +}