diff --git a/docs/_snippets/features/image-style-custom.js b/docs/_snippets/features/image-style-custom.js index 600dca4d..3652af8a 100644 --- a/docs/_snippets/features/image-style-custom.js +++ b/docs/_snippets/features/image-style-custom.js @@ -7,42 +7,21 @@ import './image-style-custom.scss'; -import fullSizeIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; -import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg'; -import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; - ClassicEditor .create( document.querySelector( '#snippet-image-style-custom' ), { image: { styles: [ // This option is equal to a situation where no style is applied. - { - name: 'imageStyleFull', - title: 'Full size image', - icon: fullSizeIcon, - value: null - }, + 'imageStyleFull', // This represents an image aligned to left. - { - name: 'imageStyleLeft', - title: 'Left aligned image', - icon: alignLeftIcon, - value: 'left', - className: 'image-style-left' - }, + 'imageStyleAlignLeft', // This represents an image aligned to right. - { - name: 'imageStyleRight', - title: 'Right aligned image', - icon: alignRightIcon, - value: 'right', - className: 'image-style-right' - } + 'imageStyleAlignRight' ], - toolbar: [ 'imageTextAlternative', '|', 'imageStyleLeft', 'imageStyleFull', 'imageStyleRight' ] + toolbar: [ 'imageTextAlternative', '|', 'imageStyleAlignLeft', 'imageStyleFull', 'imageStyleAlignRight' ] } } ) .then( editor => { diff --git a/docs/_snippets/features/image-style-custom.scss b/docs/_snippets/features/image-style-custom.scss index f141c701..ca2dcd0b 100644 --- a/docs/_snippets/features/image-style-custom.scss +++ b/docs/_snippets/features/image-style-custom.scss @@ -1,13 +1,13 @@ // Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. // For licensing, see LICENSE.md or http://ckeditor.com/license -.image-style-left { +.image-style-align-left { float: left; width: 50%; margin: 1em 1em 1em 0; } -.image-style-right { +.image-style-align-right { float: right; width: 50%; margin: 1em 0 1em 1em; diff --git a/docs/features/image.md b/docs/features/image.md index 6eaf3859..92de1d05 100644 --- a/docs/features/image.md +++ b/docs/features/image.md @@ -111,45 +111,24 @@ Below you can see a demo of the editor with the image styles feature enabled. Th The available image styles can be configured using the {@link module:image/image~ImageConfig#styles `image.styles`} option. -The following editor supports the default style plus left- and right-aligned images: +The following editor supports the default full style plus left- and right-aligned images: ```js -import fullSizeIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; -import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg'; -import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; - ClassicEditor .create( document.querySelector( '#editor' ), { image: { // You need to configure the image toolbar too, so it uses the new style buttons. - toolbar: [ 'imageTextAlternative', '|', 'imageStyleLeft', 'imageStyleFull', 'imageStyleRight' ], + toolbar: [ 'imageTextAlternative', '|', 'imageStyleAlignLeft', 'imageStyleFull', 'imageStyleAlignRight' ], styles: [ // This option is equal to a situation where no style is applied. - { - name: 'imageStyleFull', - title: 'Full size image', - icon: fullSizeIcon, - value: null - }, + 'imageStyleFull', // This represents an image aligned to left. - { - name: 'imageStyleLeft', - title: 'Left aligned image', - icon: alignLeftIcon, - value: 'left', - className: 'image-style-left' - }, + 'imageStyleAlignLeft', // This represents an image aligned to right. - { - name: 'imageStyleRight', - title: 'Right aligned image', - icon: alignRightIcon, - value: 'right', - className: 'image-style-right' - } + 'imageStyleAlignRight' ] } } ) @@ -158,13 +137,13 @@ ClassicEditor ``` ```css -.image-style-left { +.image-style-align-left { float: left; width: 50%; margin: 1em 1em 1em 0; } -.image-style-right { +.image-style-align-right { float: right; width: 50%; margin: 1em 0 1em 1em; diff --git a/src/imagestyle.js b/src/imagestyle.js index 6300d492..3aed8042 100644 --- a/src/imagestyle.js +++ b/src/imagestyle.js @@ -37,7 +37,8 @@ export default class ImageStyle extends Plugin { * @inheritDoc */ init() { - const styles = this.editor.config.get( 'image.styles' ); + const editor = this.editor; + const styles = editor.plugins.get( ImageStyleEngine ).imageStyles; for ( const style of styles ) { this._createButton( style ); @@ -79,6 +80,39 @@ export default class ImageStyle extends Plugin { * * The default value is: * + * const imageConfig = { + * styles: [ 'imageStyleFull', 'imageStyleSide' ] + * }; + * + * which configures two default styles: + * + * * the "full" style which doesn't apply any class, e.g. for images styled to span 100% width of the content, + * * the "side" style with the `.image-style-side` CSS class. + * + * See {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles} to learn more about default + * styles provided by the image feature. + * + * The {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles default styles} can be customized, + * e.g. to change the icon, title or CSS class of the style. The feature also provides several + * {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultIcons default icons} to chose from. + * + * import customIcon from 'custom-icon.svg'; + * + * // ... + * + * const imageConfig = { + * styles: [ + * // This will only customize the icon of the "full" style. + * // Note: 'right' is one of default icons provided by the feature. + * { name: 'imageStyleFull', icon: 'right' }, + * + * // This will customize the icon, title and CSS class of the default "side" style. + * { name: 'imageStyleSide', icon: customIcon, title: 'My side style', class: 'custom-side-image' } + * ] + * }; + * + * If none of the default styles is good enough, it is possible to define independent custom styles too: + * * import fullSizeIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; * import sideIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; * @@ -86,15 +120,16 @@ export default class ImageStyle extends Plugin { * * const imageConfig = { * styles: [ - * // Option which defines a style which doesn't apply any class. - * // The style is titled "full" because such images are often styled to take 100% width of the content. - * { name: 'imageStyleFull', title: t( 'Full size image' ), icon: fullSizeIcon, value: null }, + * // A completely custom full size style with no class, used as a default. + * { name: 'fullSize', title: 'Full size', icon: fullSizeIcon, isDefault: true }, * - * // Option which represents a side image. - * { name: 'imageStyleSide', title: t( 'Side image' ), icon: sideIcon, value: 'side', className: 'image-style-side' } + * { name: 'side', title: 'To the side', icon: sideIcon, className: 'side-image' } * ] * }; * + * Note: Setting `title` to one of {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine#localizedDefaultStylesTitles} + * will automatically translate it to the language of the editor. + * * Read more about styling images in the {@linkTODO Image styles guide}. * * The feature creates commands based on defined styles, so you can change the style of a selected image by executing diff --git a/src/imagestyle/converters.js b/src/imagestyle/converters.js index f12577f8..7124573e 100644 --- a/src/imagestyle/converters.js +++ b/src/imagestyle/converters.js @@ -26,8 +26,8 @@ export function modelToViewStyleAttribute( styles ) { } // Check if there is class name associated with given value. - const newStyle = getStyleByValue( data.attributeNewValue, styles ); - const oldStyle = getStyleByValue( data.attributeOldValue, styles ); + const newStyle = getStyleByName( data.attributeNewValue, styles ); + const oldStyle = getStyleByName( data.attributeOldValue, styles ); const viewElement = conversionApi.mapper.toViewElement( data.item ); const isRemovalHandled = handleRemoval( eventType, oldStyle, viewElement ); @@ -47,8 +47,8 @@ export function modelToViewStyleAttribute( styles ) { * @returns {Function} A view-to-model converter. */ export function viewToModelStyleAttribute( styles ) { - // Convert only styles without `null` value. - const filteredStyles = styles.filter( style => style.value !== null ); + // Convert only non–default styles. + const filteredStyles = styles.filter( style => !style.isDefault ); return ( evt, data, consumable, conversionApi ) => { for ( const style of filteredStyles ) { @@ -88,17 +88,17 @@ function viewToModelImageStyle( style, data, consumable, conversionApi ) { // *** Step2: Convert to model. consumable.consume( viewFigureElement, { class: style.className } ); - modelImageElement.setAttribute( 'imageStyle', style.value ); + modelImageElement.setAttribute( 'imageStyle', style.name ); } -// Returns style with given `value` from array of styles. +// Returns style with given `name` from array of styles. // -// @param {String} value +// @param {String} name // @param {Array. } styles // @return {module:image/imagestyle/imagestyleengine~ImageStyleFormat|undefined} -function getStyleByValue( value, styles ) { +function getStyleByName( name, styles ) { for ( const style of styles ) { - if ( style.value === value ) { + if ( style.name === name ) { return style; } } diff --git a/src/imagestyle/imagestylecommand.js b/src/imagestyle/imagestylecommand.js index 3d7834ef..3a9dd3d7 100644 --- a/src/imagestyle/imagestylecommand.js +++ b/src/imagestyle/imagestylecommand.js @@ -53,10 +53,10 @@ export default class ImageStyleCommand extends Command { if ( !element ) { this.value = false; - } else if ( this.style.value === null ) { + } else if ( this.style.isDefault ) { this.value = !element.hasAttribute( 'imageStyle' ); } else { - this.value = ( element.getAttribute( 'imageStyle' ) == this.style.value ); + this.value = ( element.getAttribute( 'imageStyle' ) == this.style.name ); } } @@ -79,7 +79,7 @@ export default class ImageStyleCommand extends Command { doc.enqueueChanges( () => { const batch = options.batch || doc.batch(); - batch.setAttribute( imageElement, 'imageStyle', this.style.value ); + batch.setAttribute( imageElement, 'imageStyle', this.style.name ); } ); } } diff --git a/src/imagestyle/imagestyleengine.js b/src/imagestyle/imagestyleengine.js index f61a6555..06036007 100644 --- a/src/imagestyle/imagestyleengine.js +++ b/src/imagestyle/imagestyleengine.js @@ -11,8 +11,12 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import ImageStyleCommand from './imagestylecommand'; import ImageEngine from '../image/imageengine'; import { viewToModelStyleAttribute, modelToViewStyleAttribute } from './converters'; -import fullSizeIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; -import sideIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; +import log from '@ckeditor/ckeditor5-utils/src/log'; + +import fullWidthIcon from '@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg'; +import leftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg'; +import centerIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; +import rightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; /** * The image style engine plugin. It sets the default configuration, creates converters and registers @@ -28,28 +32,28 @@ export default class ImageStyleEngine extends Plugin { return [ ImageEngine ]; } + /** + * @inheritDoc + */ + static get pluginName() { + return 'ImageStyleEngine'; + } + /** * @inheritDoc */ init() { const editor = this.editor; - const t = editor.t; const doc = editor.document; const schema = doc.schema; const data = editor.data; const editing = editor.editing; // Define default configuration. - editor.config.define( 'image.styles', [ - // This option is equal to situation when no style is applied. - { name: 'imageStyleFull', title: t( 'Full size image' ), icon: fullSizeIcon, value: null }, - - // This represents side image. - { name: 'imageStyleSide', title: t( 'Side image' ), icon: sideIcon, value: 'side', className: 'image-style-side' } - ] ); + editor.config.define( 'image.styles', [ 'imageStyleFull', 'imageStyleSide' ] ); // Get configuration. - const styles = editor.config.get( 'image.styles' ); + const styles = this.imageStyles; // Allow imageStyle attribute in image. // We could call it 'style' but https://github.com/ckeditor/ckeditor5-engine/issues/559. @@ -72,28 +76,222 @@ export default class ImageStyleEngine extends Plugin { editor.commands.add( style.name, new ImageStyleCommand( editor, style ) ); } } + + /** + * Returns {@link module:image/image~ImageConfig#styles} array with items normalized in the + * {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat} format, translated + * `title` and a complete `icon` markup for each style. + * + * @readonly + * @type {Array.} + */ + get imageStyles() { + // Return cached value if there is one to improve the performance. + if ( this._cachedImageStyles ) { + return this._cachedImageStyles; + } + + const styles = []; + const editor = this.editor; + const titles = this.localizedDefaultStylesTitles; + const configuredStyles = editor.config.get( 'image.styles' ); + + for ( let style of configuredStyles ) { + style = normalizeStyle( style ); + + // Localize the titles of the styles, if a title corresponds with + // a localized default provided by the plugin. + if ( titles[ style.title ] ) { + style.title = titles[ style.title ]; + } + + // Don't override the user-defined styles array, clone it instead. + styles.push( style ); + } + + return ( this._cachedImageStyles = styles ); + } + + /** + * Returns the default localized style titles provided by the plugin e.g. ready to + * use in the {@link #imageStyles}. + * + * The following localized titles corresponding with + * {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles} are available: + * + * * `'Full size image'`, + * * `'Side image'`, + * * `'Left aligned image'`, + * * `'Centered image'`, + * * `'Right aligned image'` + * + * @readonly + * @type {Object.} + */ + get localizedDefaultStylesTitles() { + const t = this.editor.t; + + return { + 'Full size image': t( 'Full size image' ), + 'Side image': t( 'Side image' ), + 'Left aligned image': t( 'Left aligned image' ), + 'Centered image': t( 'Centered image' ), + 'Right aligned image': t( 'Right aligned image' ), + }; + } +} + +/** + * Default image styles provided by the plugin, which can be referred in the + * {@link module:image/image~ImageConfig#styles} config. + * + * Among them, 2 default semantic content styles are available: + * + * * `imageStyleFull` is a full–width image without any CSS class, + * * `imageStyleSide` is a side image styled with the `image-style-side` CSS class + * + * There are also 3 styles focused on formatting: + * + * * `imageStyleAlignLeft` aligns the image to the left using the `image-style-align-left` class, + * * `imageStyleAlignCenter` centers the image to the left using the `image-style-align-center` class, + * * `imageStyleAlignRight` aligns the image to the right using the `image-style-align-right` class, + * + * @member {Object.} + */ +ImageStyleEngine.defaultStyles = { + // This option is equal to situation when no style is applied. + imageStyleFull: { + name: 'imageStyleFull', + title: 'Full size image', + icon: fullWidthIcon, + isDefault: true + }, + + // This represents side image. + imageStyleSide: { + name: 'imageStyleSide', + title: 'Side image', + icon: rightIcon, + className: 'image-style-side' + }, + + // This style represents an imaged aligned to the left. + imageStyleAlignLeft: { + name: 'imageStyleAlignLeft', + title: 'Left aligned image', + icon: leftIcon, + className: 'image-style-align-left' + }, + + // This style represents a centered imaged. + imageStyleAlignCenter: { + name: 'imageStyleAlignCenter', + title: 'Centered image', + icon: centerIcon, + className: 'image-style-align-center' + }, + + // This style represents an imaged aligned to the right. + imageStyleAlignRight: { + name: 'imageStyleAlignRight', + title: 'Right aligned image', + icon: rightIcon, + className: 'image-style-align-right' + } +}; + +/** + * Default image style icons provided by the plugin, which can be referred in the + * {@link module:image/image~ImageConfig#styles} config. + * + * There are 3 icons available: `'full'`, `'left'`, `'center'` and `'right'`. + * + * @member {Object.} + */ +ImageStyleEngine.defaultIcons = { + full: fullWidthIcon, + left: leftIcon, + right: rightIcon, + center: centerIcon, +}; + +// Normalizes an image style provided in the {@link module:image/image~ImageConfig#styles} +// and returns it in a {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat}. +// +// @private +// @param {Object} style +// @returns {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat} +function normalizeStyle( style ) { + const defaultStyles = ImageStyleEngine.defaultStyles; + const defaultIcons = ImageStyleEngine.defaultIcons; + + // Just the name of the style has been passed. + if ( typeof style == 'string' ) { + // If it's one of the defaults, just use it. + // Clone the style to avoid overriding defaults. + if ( defaultStyles[ style ] ) { + style = Object.assign( {}, defaultStyles[ style ] ); + } + // If it's just a name but none of the defaults, warn because probably it's a mistake. + else { + log.warn( + 'image-style-not-found: There is no such image style of given name.', + { name: style } ); + + // Normalize the style anyway to prevent errors. + style = { + name: style + }; + } + } + + // If an object style has been passed and if the name matches one of the defaults, + // extend it with defaults – the user wants to customize a default style. + // Note: Don't override the user–defined style object, clone it instead. + else if ( defaultStyles[ style.name ] ) { + const defaultStyle = defaultStyles[ style.name ]; + const extendedStyle = Object.assign( {}, style ); + + for ( const prop in defaultStyle ) { + if ( !style.hasOwnProperty( prop ) ) { + extendedStyle[ prop ] = defaultStyle[ prop ]; + } + } + + style = extendedStyle; + } + + // If an icon is defined as a string and correspond with a name + // in default icons, use the default icon provided by the plugin. + if ( typeof style.icon == 'string' && defaultIcons[ style.icon ] ) { + style.icon = defaultIcons[ style.icon ]; + } + + return style; } /** * Image style format descriptor. * - * import fullIcon from 'path/to/icon.svg`; + * import fullWidthIcon from 'path/to/icon.svg`; * * const imageStyleFormat = { * name: 'fullSizeImage', - * value: 'full', - * icon: fullIcon, + * icon: fullWidthIcon, * title: 'Full size image', - * class: 'image-full-size' + * className: 'image-full-size' * } * * @typedef {Object} module:image/imagestyle/imagestyleengine~ImageStyleFormat - * @property {String} name The name of the style. It will be used to: + * @property {String} name The unique name of the style. It will be used to: * * register the {@link module:core/command~Command command} which will apply this style, - * * store the style's button in the editor {@link module:ui/componentfactory~ComponentFactory}. - * @property {String} value A value used to store this style in the model attribute. - * When the value is `null`, the style will be used as the default one. A default style does not apply any CSS class to the view element. - * @property {String} icon An SVG icon source (as XML string) to use when creating the style's button. + * * store the style's button in the editor {@link module:ui/componentfactory~ComponentFactory}, + * * store the style in the `imageStyle` model attribute. + * @property {Boolean} [isDefault] When set, the style will be used as the default one. + * A default style does not apply any CSS class to the view element. + * @property {String} icon One of the following to be used when creating the style's button: + * * An SVG icon source (as an XML string), + * * One of {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultIcons} to use a default icon provided by the plugin. * @property {String} title The style's title. * @property {String} className The CSS class used to represent the style in view. */ diff --git a/tests/imagestyle.js b/tests/imagestyle.js index 02951c0c..89621903 100644 --- a/tests/imagestyle.js +++ b/tests/imagestyle.js @@ -12,9 +12,9 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global'; describe( 'ImageStyle', () => { let editor; const styles = [ - { name: 'style 1', title: 'Style 1 title', icon: 'style1-icon', value: null }, - { name: 'style 2', title: 'Style 2 title', icon: 'style2-icon', value: 'style2', cssClass: 'style2-class' }, - { name: 'style 3', title: 'Style 3 title', icon: 'style3-icon', value: 'style3', cssClass: 'style3-class' } + { name: 'style 1', title: 'Style 1 title', icon: 'style1-icon', isDefault: true }, + { name: 'style 2', title: 'Style 2 title', icon: 'style2-icon', cssClass: 'style2-class' }, + { name: 'style 3', title: 'Style 3 title', icon: 'style3-icon', cssClass: 'style3-class' } ]; beforeEach( () => { diff --git a/tests/imagestyle/imagestylecommand.js b/tests/imagestyle/imagestylecommand.js index 92fb8655..e3a795b4 100644 --- a/tests/imagestyle/imagestylecommand.js +++ b/tests/imagestyle/imagestylecommand.js @@ -8,8 +8,8 @@ import ImageStyleCommand from '../../src/imagestyle/imagestylecommand'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; describe( 'ImageStyleCommand', () => { - const defaultStyle = { name: 'defaultStyle', title: 'foo bar', icon: 'icon-1', value: null }; - const otherStyle = { name: 'otherStyle', title: 'baz', icon: 'icon-2', value: 'other', className: 'other-class-name' }; + const defaultStyle = { name: 'defaultStyle', title: 'foo bar', icon: 'icon-1', isDefault: true }; + const otherStyle = { name: 'otherStyle', title: 'baz', icon: 'icon-2', className: 'other-class-name' }; let document, defaultStyleCommand, otherStyleCommand; @@ -44,7 +44,7 @@ describe( 'ImageStyleCommand', () => { } ); it( 'proper command should have true value when imageStyle attribute is present', () => { - setData( document, '[]' ); + setData( document, '[]' ); expect( defaultStyleCommand.value ).to.be.false; expect( otherStyleCommand.value ).to.be.true; @@ -62,15 +62,15 @@ describe( 'ImageStyleCommand', () => { otherStyleCommand.execute(); - expect( getData( document ) ).to.equal( '[]' ); + expect( getData( document ) ).to.equal( '[]' ); } ); it( 'should do nothing when attribute already present', () => { - setData( document, '[]' ); + setData( document, '[]' ); otherStyleCommand.execute(); - expect( getData( document ) ).to.equal( '[]' ); + expect( getData( document ) ).to.equal( '[]' ); } ); it( 'should allow to provide batch instance', () => { @@ -81,7 +81,7 @@ describe( 'ImageStyleCommand', () => { otherStyleCommand.execute( { batch } ); - expect( getData( document ) ).to.equal( '[]' ); + expect( getData( document ) ).to.equal( '[]' ); sinon.assert.calledOnce( spy ); } ); diff --git a/tests/imagestyle/imagestyleengine.js b/tests/imagestyle/imagestyleengine.js index 1b59c85d..c360d64a 100644 --- a/tests/imagestyle/imagestyleengine.js +++ b/tests/imagestyle/imagestyleengine.js @@ -7,241 +7,509 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest import ImageStyleEngine from '../../src/imagestyle/imagestyleengine'; import ImageEngine from '../../src/image/imageengine'; import ImageStyleCommand from '../../src/imagestyle/imagestylecommand'; +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import log from '@ckeditor/ckeditor5-utils/src/log'; import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; +import fullWidthIcon from '@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg'; +import leftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg'; +import centerIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; +import rightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; + describe( 'ImageStyleEngine', () => { - let editor, document, viewDocument; - - beforeEach( () => { - return VirtualTestEditor - .create( { - plugins: [ ImageStyleEngine ], - image: { - styles: [ - { name: 'fullStyle', title: 'foo', icon: 'object-center', value: null }, - { name: 'sideStyle', title: 'bar', icon: 'object-right', value: 'side', className: 'side-class' }, - { name: 'dummyStyle', title: 'baz', icon: 'object-dummy', value: 'dummy', className: 'dummy-class' } - ] - } - } ) - .then( newEditor => { - editor = newEditor; - document = editor.document; - viewDocument = editor.editing.view; - } ); - } ); + let editor, plugin, document, viewDocument; - it( 'should be loaded', () => { - expect( editor.plugins.get( ImageStyleEngine ) ).to.be.instanceOf( ImageStyleEngine ); + afterEach( () => { + editor.destroy(); } ); - it( 'should load image engine', () => { - expect( editor.plugins.get( ImageEngine ) ).to.be.instanceOf( ImageEngine ); - } ); + describe( 'plugin', () => { + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); - it( 'should set schema rules for image style', () => { - const schema = document.schema; + it( 'should be loaded', () => { + expect( editor.plugins.get( ImageStyleEngine ) ).to.be.instanceOf( ImageStyleEngine ); + } ); - expect( schema.check( { name: 'image', attributes: [ 'imageStyle', 'src' ], inside: '$root' } ) ).to.be.true; + it( 'should load image engine', () => { + expect( editor.plugins.get( ImageEngine ) ).to.be.instanceOf( ImageEngine ); + } ); } ); - it( 'should register separate command for each style', () => { - expect( editor.commands.get( 'fullStyle' ) ).to.be.instanceOf( ImageStyleCommand ); - expect( editor.commands.get( 'sideStyle' ) ).to.be.instanceOf( ImageStyleCommand ); - expect( editor.commands.get( 'dummyStyle' ) ).to.be.instanceOf( ImageStyleCommand ); - } ); + describe( 'init', () => { + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + image: { + styles: [ + { name: 'fullStyle', title: 'foo', icon: 'object-center', isDefault: true }, + { name: 'sideStyle', title: 'bar', icon: 'object-right', className: 'side-class' }, + { name: 'dummyStyle', title: 'baz', icon: 'object-dummy', className: 'dummy-class' }, + ] + } + } ) + .then( newEditor => { + editor = newEditor; + document = editor.document; + viewDocument = editor.editing.view; + } ); + } ); - it( 'should convert from view to model', () => { - editor.setData( '
' ); + it( 'should define image.styles config', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ] + } ) + .then( newEditor => { + editor = newEditor; - expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' + - '' + - '
' ); - } ); + expect( newEditor.config.get( 'image.styles' ) ).to.deep.equal( [ 'imageStyleFull', 'imageStyleSide' ] ); + } ); + } ); - it( 'should not convert from view to model if class is not defined', () => { - editor.setData( '
' ); + it( 'should set schema rules for image style', () => { + const schema = document.schema; - expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + expect( schema.check( { name: 'image', attributes: [ 'imageStyle', 'src' ], inside: '$root' } ) ).to.be.true; + } ); + + it( 'should register separate command for each style', () => { + expect( editor.commands.get( 'fullStyle' ) ).to.be.instanceOf( ImageStyleCommand ); + expect( editor.commands.get( 'sideStyle' ) ).to.be.instanceOf( ImageStyleCommand ); + expect( editor.commands.get( 'dummyStyle' ) ).to.be.instanceOf( ImageStyleCommand ); + } ); - it( 'should not convert from view to model when not in image figure', () => { - editor.setData( '
' ); + it( 'should convert from view to model', () => { + editor.setData( '
' ); - expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( '' ); - } ); + expect( getModelData( document, { withoutSelection: true } ) ) + .to.equal( '' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '
' ); + } ); - it( 'should not convert from view to model if schema prevents it', () => { - document.schema.disallow( { name: 'image', attributes: 'imageStyle' } ); - editor.setData( '
' ); + it( 'should not convert from view to model if class is not defined', () => { + editor.setData( '
' ); - expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); - it( 'should convert model to view: adding attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + it( 'should not convert from view to model when not in image figure', () => { + editor.setData( '
' ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'side' ); + expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( '' ); } ); - expect( editor.getData() ).to.equal( '
' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + it( 'should not convert from view to model if schema prevents it', () => { + document.schema.disallow( { name: 'image', attributes: 'imageStyle' } ); + editor.setData( '
' ); + + expect( getModelData( document, { withoutSelection: true } ) ).to.equal( '' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); - it( 'should convert model to view: removing attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + it( 'should convert model to view: adding attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); + + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'sideStyle' ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', null ); + expect( editor.getData() ).to.equal( '
' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( editor.getData() ).to.equal( '
' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + it( 'should convert model to view: removing attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); - it( 'should convert model to view: change attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', null ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'side' ); + expect( editor.getData() ).to.equal( '
' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( editor.getData() ).to.equal( '
' ); + it( 'should convert model to view: change attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); + + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'sideStyle' ); + } ); + + expect( editor.getData() ).to.equal( '
' ); - // https://github.com/ckeditor/ckeditor5-image/issues/132 - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); + // https://github.com/ckeditor/ckeditor5-image/issues/132 + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'dummy' ); + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'dummyStyle' ); + } ); + + expect( editor.getData() ).to.equal( '
' ); + + // https://github.com/ckeditor/ckeditor5-image/issues/132 + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( editor.getData() ).to.equal( '
' ); + it( 'should not convert from model to view if already consumed: adding attribute', () => { + editor.editing.modelToView.on( 'addAttribute:imageStyle', ( evt, data, consumable ) => { + consumable.consume( data.item, 'addAttribute:imageStyle' ); + }, { priority: 'high' } ); - // https://github.com/ckeditor/ckeditor5-image/issues/132 - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); + + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'sideStyle' ); + } ); + + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); + + it( 'should not convert from model to view if already consumed: removing attribute', () => { + editor.editing.modelToView.on( 'removeAttribute:imageStyle', ( evt, data, consumable ) => { + consumable.consume( data.item, 'removeAttribute:imageStyle' ); + }, { priority: 'high' } ); - it( 'should not convert from model to view if already consumed: adding attribute', () => { - editor.editing.modelToView.on( 'addAttribute:imageStyle', ( evt, data, consumable ) => { - consumable.consume( data.item, 'addAttribute:imageStyle' ); - }, { priority: 'high' } ); + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', null ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'side' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + it( 'should not convert from model to view if already consumed: change attribute', () => { + editor.editing.modelToView.on( 'changeAttribute:imageStyle', ( evt, data, consumable ) => { + consumable.consume( data.item, 'changeAttribute:imageStyle' ); + }, { priority: 'high' } ); - it( 'should not convert from model to view if already consumed: removing attribute', () => { - editor.editing.modelToView.on( 'removeAttribute:imageStyle', ( evt, data, consumable ) => { - consumable.consume( data.item, 'removeAttribute:imageStyle' ); - }, { priority: 'high' } ); + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'sideStyle' ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', null ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + it( 'should not convert from model to view if style is not present: adding attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); + + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'foo' ); + } ); - it( 'should not convert from model to view if already consumed: change attribute', () => { - editor.editing.modelToView.on( 'changeAttribute:imageStyle', ( evt, data, consumable ) => { - consumable.consume( data.item, 'changeAttribute:imageStyle' ); - }, { priority: 'high' } ); + expect( editor.getData() ).to.equal( '
' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + it( 'should not convert from model to view if style is not present: change attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'side' ); + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', 'foo' ); + } ); + + expect( editor.getData() ).to.equal( '
' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); } ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); + it( 'should not convert from model to view if style is not present: remove attribute', () => { + setModelData( document, '' ); + const image = document.getRoot().getChild( 0 ); + const batch = document.batch(); + + document.enqueueChanges( () => { + batch.setAttribute( image, 'imageStyle', null ); + } ); + + expect( editor.getData() ).to.equal( '
' ); + expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); } ); - it( 'should not convert from model to view if style is not present: adding attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + describe( 'imageStyles()', () => { + it( 'should fall back to defaults when no image.styles', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ] + } ) + .then( newEditor => { + editor = newEditor; + + expect( newEditor.config.get( 'image.styles' ) ).to.deep.equal( [ 'imageStyleFull', 'imageStyleSide' ] ); + } ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'foo' ); + it( 'should not alter the image.styles config', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + image: { + styles: [ + 'imageStyleSide' + ] + } + } ) + .then( newEditor => { + editor = newEditor; + + expect( newEditor.config.get( 'image.styles' ) ).to.deep.equal( [ 'imageStyleSide' ] ); + } ); } ); - expect( editor.getData() ).to.equal( '
' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); - } ); + it( 'should not alter object definitions in the image.styles config', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + image: { + styles: [ + { name: 'imageStyleSide' } + ] + } + } ) + .then( newEditor => { + editor = newEditor; + + expect( newEditor.config.get( 'image.styles' ) ).to.deep.equal( [ { name: 'imageStyleSide' } ] ); + } ); + } ); - it( 'should not convert from model to view if style is not present: change attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + it( 'should cache the styles', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ] + } ) + .then( newEditor => { + editor = newEditor; + plugin = editor.plugins.get( ImageStyleEngine ); + + expect( plugin.imageStyles ).to.equal( plugin.imageStyles ); + } ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', 'foo' ); + describe( 'object format', () => { + beforeEach( () => { + class TranslationMock extends Plugin { + init() { + sinon.stub( this.editor, 't' ).returns( 'Default translation' ); + } + } + + return VirtualTestEditor + .create( { + plugins: [ TranslationMock, ImageStyleEngine ], + image: { + styles: [ + // Custom user styles. + { name: 'foo', title: 'foo', icon: 'custom', isDefault: true, className: 'foo-class' }, + { name: 'bar', title: 'bar', icon: 'right', className: 'bar-class' }, + { name: 'baz', title: 'Side image', icon: 'custom', className: 'baz-class' }, + + // Customized default styles. + { name: 'imageStyleFull', icon: 'left', title: 'Custom title' } + ] + } + } ) + .then( newEditor => { + editor = newEditor; + plugin = editor.plugins.get( ImageStyleEngine ); + } ); + } ); + + it( 'should pass through if #name not found in default styles', () => { + expect( plugin.imageStyles[ 0 ] ).to.deep.equal( { + name: 'foo', + title: 'foo', + icon: 'custom', + isDefault: true, + className: 'foo-class' + } ); + } ); + + it( 'should use one of default icons if #icon matches', () => { + expect( plugin.imageStyles[ 1 ].icon ).to.equal( ImageStyleEngine.defaultIcons.right ); + } ); + + it( 'should use one of default translations if #title matches', () => { + expect( plugin.imageStyles[ 2 ].title ).to.deep.equal( 'Default translation' ); + } ); + + it( 'should extend one of default styles if #name matches', () => { + expect( plugin.imageStyles[ 3 ] ).to.deep.equal( { + name: 'imageStyleFull', + title: 'Custom title', + icon: ImageStyleEngine.defaultIcons.left, + isDefault: true + } ); + } ); } ); - expect( editor.getData() ).to.equal( '
' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); + describe( 'string format', () => { + it( 'should use one of default styles if #name matches', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + image: { + styles: [ 'imageStyleFull' ] + } + } ) + .then( newEditor => { + editor = newEditor; + plugin = editor.plugins.get( ImageStyleEngine ); + expect( plugin.imageStyles[ 0 ] ).to.deep.equal( ImageStyleEngine.defaultStyles.imageStyleFull ); + } ); + } ); + + it( 'should warn if a #name not found in default styles', () => { + sinon.stub( log, 'warn' ); + + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ], + image: { + styles: [ 'foo' ] + } + } ) + .then( newEditor => { + editor = newEditor; + plugin = editor.plugins.get( ImageStyleEngine ); + + expect( plugin.imageStyles[ 0 ] ).to.deep.equal( { + name: 'foo' + } ); + + sinon.assert.calledOnce( log.warn ); + sinon.assert.calledWithExactly( log.warn, + sinon.match( /^image-style-not-found/ ), + { name: 'foo' } + ); + } ); + } ); + } ); } ); - it( 'should not convert from model to view if style is not present: remove attribute', () => { - setModelData( document, '' ); - const image = document.getRoot().getChild( 0 ); - const batch = document.batch(); + describe( 'localizedDefaultStylesTitles()', () => { + it( 'should return localized titles of default styles', () => { + return VirtualTestEditor + .create( { + plugins: [ ImageStyleEngine ] + } ) + .then( newEditor => { + editor = newEditor; + plugin = editor.plugins.get( ImageStyleEngine ); + + expect( plugin.localizedDefaultStylesTitles ).to.deep.equal( { + 'Full size image': 'Full size image', + 'Side image': 'Side image', + 'Left aligned image': 'Left aligned image', + 'Centered image': 'Centered image', + 'Right aligned image': 'Right aligned image' + } ); + } ); + } ); + } ); - document.enqueueChanges( () => { - batch.setAttribute( image, 'imageStyle', null ); + describe( 'defaultStyles', () => { + it( 'should be defined', () => { + expect( ImageStyleEngine.defaultStyles ).to.deep.equal( { + imageStyleFull: { + name: 'imageStyleFull', + title: 'Full size image', + icon: fullWidthIcon, + isDefault: true + }, + imageStyleSide: { + name: 'imageStyleSide', + title: 'Side image', + icon: rightIcon, + className: 'image-style-side' + }, + imageStyleAlignLeft: { + name: 'imageStyleAlignLeft', + title: 'Left aligned image', + icon: leftIcon, + className: 'image-style-align-left' + }, + imageStyleAlignCenter: { + name: 'imageStyleAlignCenter', + title: 'Centered image', + icon: centerIcon, + className: 'image-style-align-center' + }, + imageStyleAlignRight: { + name: 'imageStyleAlignRight', + title: 'Right aligned image', + icon: rightIcon, + className: 'image-style-align-right' + } + } ); } ); + } ); - expect( editor.getData() ).to.equal( '
' ); - expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( - '
' - ); + describe( 'defaultIcons', () => { + it( 'should be defined', () => { + expect( ImageStyleEngine.defaultIcons ).to.deep.equal( { + full: fullWidthIcon, + left: leftIcon, + right: rightIcon, + center: centerIcon, + } ); + } ); } ); } ); diff --git a/tests/manual/imagestyle.html b/tests/manual/imagestyle.html index 204ae70e..3a625515 100644 --- a/tests/manual/imagestyle.html +++ b/tests/manual/imagestyle.html @@ -1,10 +1,20 @@ -
+

Semantic–oriented images

+ +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

+
+ +

Formatting–oriented images

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.

diff --git a/tests/manual/imagestyle.js b/tests/manual/imagestyle.js index 5510e922..2f931393 100644 --- a/tests/manual/imagestyle.js +++ b/tests/manual/imagestyle.js @@ -17,7 +17,7 @@ import ImageStyle from '../../src/imagestyle'; import ImageToolbar from '../../src/imagetoolbar'; ClassicEditor - .create( document.querySelector( '#editor' ), { + .create( document.querySelector( '#editor-semantic' ), { plugins: [ ImageToolbar, EnterPlugin, @@ -35,7 +35,33 @@ ClassicEditor } } ) .then( editor => { - window.editor = editor; + window.editorSemantic = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); + +ClassicEditor + .create( document.querySelector( '#editor-formatting' ), { + plugins: [ + ImageToolbar, + EnterPlugin, + TypingPlugin, + ParagraphPlugin, + HeadingPlugin, + ImagePlugin, + UndoPlugin, + ClipboardPlugin, + ImageStyle + ], + toolbar: [ 'headings', 'undo', 'redo' ], + image: { + styles: [ 'imageStyleAlignLeft', 'imageStyleAlignCenter', 'imageStyleAlignRight' ], + toolbar: [ 'imageStyleAlignLeft', 'imageStyleAlignCenter', 'imageStyleAlignRight' ] + } + } ) + .then( editor => { + window.editorFormatting = editor; } ) .catch( err => { console.error( err.stack ); diff --git a/tests/manual/imagestyle.md b/tests/manual/imagestyle.md index e7403a3b..51e6178f 100644 --- a/tests/manual/imagestyle.md +++ b/tests/manual/imagestyle.md @@ -1,6 +1,13 @@ ## ImageStyle feature +### The "semantic–oriented" editor + * Click on image - toolbar with icons should appear. "Full size image" icon should be selected. * Click on "Side image" icon. Image should be aligned to right. * Click on "Full size image" icon. Image should be back to its original state. * Resize the browser window so the scrollbar is visible. Click on image and scroll editor contents - check if toolbar is placed in proper position. + +### The "formatting–oriented" editor + +* Click on image - toolbar with icons should appear. "Centered image" icon should be selected. +* Check if other icons icons change the alignment of the image. diff --git a/theme/theme.scss b/theme/theme.scss index 89803bba..7dbf7260 100644 --- a/theme/theme.scss +++ b/theme/theme.scss @@ -1,15 +1,41 @@ // Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. // For licensing, see LICENSE.md or http://ckeditor.com/license +@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/_spacing'; + +$ck-image-spacing: 5 * ck-spacing( 'small' ); +$ck-image-max-width: 50%; + .ck-editor__editable { .image { text-align: center; clear: both; + &.image-style-side, + &.image-style-align-left, + &.image-style-align-center, + &.image-style-align-right { + max-width: $ck-image-max-width; + } + &.image-style-side { float: right; - margin-left: 0.8em; - max-width: 50%; + margin-left: $ck-image-spacing; + } + + &.image-style-align-left { + float: left; + margin-right: $ck-image-spacing; + } + + &.image-style-align-center { + margin-left: auto; + margin-right: auto; + } + + &.image-style-align-right { + float: right; + margin-left: $ck-image-spacing; } }