diff --git a/packages/ckeditor5-html-support/tests/integrations/image.js b/packages/ckeditor5-html-support/tests/integrations/image.js index ce351e07df4..878a7d3ef53 100644 --- a/packages/ckeditor5-html-support/tests/integrations/image.js +++ b/packages/ckeditor5-html-support/tests/integrations/image.js @@ -2406,6 +2406,8 @@ describe( 'ImageElementSupport', () => { 'src', 'srcset', 'linkHref', + 'width', + 'height', 'htmlImgAttributes', 'htmlFigureAttributes', 'htmlLinkAttributes' @@ -2440,6 +2442,8 @@ describe( 'ImageElementSupport', () => { 'alt', 'src', 'srcset', + 'width', + 'height', 'htmlA', 'htmlImgAttributes' ] ); diff --git a/packages/ckeditor5-image/src/augmentation.ts b/packages/ckeditor5-image/src/augmentation.ts index 2962b03ac01..202f63f0a93 100644 --- a/packages/ckeditor5-image/src/augmentation.ts +++ b/packages/ckeditor5-image/src/augmentation.ts @@ -26,6 +26,7 @@ import type { ImageCaptionUtils, ImageInsertUI, ImageResizeEditing, + ImageSizeAttributes, ImageStyleEditing, ImageStyleUI, ImageTextAlternativeEditing, @@ -76,6 +77,7 @@ declare module '@ckeditor/ckeditor5-core' { [ ImageCaptionUtils.pluginName ]: ImageCaptionUtils; [ ImageInsertUI.pluginName ]: ImageInsertUI; [ ImageResizeEditing.pluginName ]: ImageResizeEditing; + [ ImageSizeAttributes.pluginName ]: ImageSizeAttributes; [ ImageStyleEditing.pluginName ]: ImageStyleEditing; [ ImageStyleUI.pluginName ]: ImageStyleUI; [ ImageTextAlternativeEditing.pluginName ]: ImageTextAlternativeEditing; diff --git a/packages/ckeditor5-image/src/image/converters.ts b/packages/ckeditor5-image/src/image/converters.ts index 1ea58c6f498..5a11f85bd8b 100644 --- a/packages/ckeditor5-image/src/image/converters.ts +++ b/packages/ckeditor5-image/src/image/converters.ts @@ -19,8 +19,6 @@ import type { import { first, type GetCallback } from 'ckeditor5/src/utils'; import type ImageUtils from '../imageutils'; -type SrcsetAttributeType = null | { data: unknown; width: unknown }; - /** * Returns a function that converts the image view representation: * @@ -178,7 +176,7 @@ export function upcastPicture( imageUtils: ImageUtils ): ( dispatcher: UpcastDis } /** - * Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view. + * Converter used to convert the `srcset` model image attribute to the `srcset` and `sizes` attributes in the view. * * @internal * @param imageType The type of the image. @@ -197,27 +195,13 @@ export function downcastSrcsetAttribute( const img = imageUtils.findViewImgElement( element )!; if ( data.attributeNewValue === null ) { - const srcset = data.attributeOldValue as SrcsetAttributeType; - - if ( srcset && srcset.data ) { - writer.removeAttribute( 'srcset', img ); - writer.removeAttribute( 'sizes', img ); - - if ( srcset.width ) { - writer.removeAttribute( 'width', img ); - } - } + writer.removeAttribute( 'srcset', img ); + writer.removeAttribute( 'sizes', img ); } else { - const srcset = data.attributeNewValue as SrcsetAttributeType; - - if ( srcset && srcset.data ) { - writer.setAttribute( 'srcset', srcset.data, img ); + if ( data.attributeNewValue ) { + writer.setAttribute( 'srcset', data.attributeNewValue, img ); // Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2. writer.setAttribute( 'sizes', '100vw', img ); - - if ( srcset.width ) { - writer.setAttribute( 'width', srcset.width, img ); - } } } }; diff --git a/packages/ckeditor5-image/src/image/imageblockediting.ts b/packages/ckeditor5-image/src/image/imageblockediting.ts index 32a4300db39..fc867192b76 100644 --- a/packages/ckeditor5-image/src/image/imageblockediting.ts +++ b/packages/ckeditor5-image/src/image/imageblockediting.ts @@ -18,6 +18,7 @@ import { } from './converters'; import ImageEditing from './imageediting'; +import ImageSizeAttributes from '../imagesizeattributes'; import ImageTypeCommand from './imagetypecommand'; import ImageUtils from '../imageutils'; import { @@ -41,7 +42,7 @@ export default class ImageBlockEditing extends Plugin { * @inheritDoc */ public static get requires() { - return [ ImageEditing, ImageUtils, ClipboardPipeline ] as const; + return [ ImageEditing, ImageSizeAttributes, ImageUtils, ClipboardPipeline ] as const; } /** diff --git a/packages/ckeditor5-image/src/image/imageediting.ts b/packages/ckeditor5-image/src/image/imageediting.ts index 67f3f37fa74..d449005209c 100644 --- a/packages/ckeditor5-image/src/image/imageediting.ts +++ b/packages/ckeditor5-image/src/image/imageediting.ts @@ -57,21 +57,13 @@ export default class ImageEditing extends Plugin { .attributeToAttribute( { view: { name: 'img', - key: 'srcset' + attributes: { + srcset: /.+/ + } }, model: { key: 'srcset', - value: ( viewImage: ViewElement ) => { - const value: Record = { - data: viewImage.getAttribute( 'srcset' )! - }; - - if ( viewImage.hasAttribute( 'width' ) ) { - value.width = viewImage.getAttribute( 'width' )!; - } - - return value; - } + value: ( viewImage: ViewElement ) => viewImage.getAttribute( 'srcset' ) } } ); diff --git a/packages/ckeditor5-image/src/image/imageinlineediting.ts b/packages/ckeditor5-image/src/image/imageinlineediting.ts index 07b7e1994a8..75e1a827057 100644 --- a/packages/ckeditor5-image/src/image/imageinlineediting.ts +++ b/packages/ckeditor5-image/src/image/imageinlineediting.ts @@ -17,6 +17,7 @@ import { } from './converters'; import ImageEditing from './imageediting'; +import ImageSizeAttributes from '../imagesizeattributes'; import ImageTypeCommand from './imagetypecommand'; import ImageUtils from '../imageutils'; import { @@ -40,7 +41,7 @@ export default class ImageInlineEditing extends Plugin { * @inheritDoc */ public static get requires() { - return [ ImageEditing, ImageUtils, ClipboardPipeline ] as const; + return [ ImageEditing, ImageSizeAttributes, ImageUtils, ClipboardPipeline ] as const; } /** diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index cc9fed1ba66..4e0adbfa3cd 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -82,11 +82,11 @@ export default class ImageResizeEditing extends Plugin { private _registerSchema(): void { if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { - this.editor.model.schema.extend( 'imageBlock', { allowAttributes: 'width' } ); + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: 'resizedWidth' } ); } if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { - this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'width' } ); + this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'resizedWidth' } ); } } @@ -100,7 +100,7 @@ export default class ImageResizeEditing extends Plugin { // Dedicated converter to propagate image's attribute to the img tag. editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( `attribute:width:${ imageType }`, ( evt, data, conversionApi ) => { + dispatcher.on( `attribute:resizedWidth:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; } @@ -127,7 +127,7 @@ export default class ImageResizeEditing extends Plugin { } }, model: { - key: 'width', + key: 'resizedWidth', value: ( viewElement: ViewElement ) => viewElement.getStyle( 'width' ) } } ); diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index 5b2184e76fb..cb424e2af27 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -33,11 +33,11 @@ export default class ResizeImageCommand extends Command { this.isEnabled = !!element; - if ( !element || !element.hasAttribute( 'width' ) ) { + if ( !element || !element.hasAttribute( 'resizedWidth' ) ) { this.value = null; } else { this.value = { - width: element.getAttribute( 'width' ) as string, + width: element.getAttribute( 'resizedWidth' ) as string, height: null }; } @@ -71,7 +71,7 @@ export default class ResizeImageCommand extends Command { if ( imageElement ) { model.change( writer => { - writer.setAttribute( 'width', options.width, imageElement ); + writer.setAttribute( 'resizedWidth', options.width, imageElement ); } ); } } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts new file mode 100644 index 00000000000..e1c67109a78 --- /dev/null +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -0,0 +1,112 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module image/imagesizeattributes + */ + +import { Plugin } from 'ckeditor5/src/core'; +import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; +import ImageUtils from './imageutils'; + +/** + * This plugin enables `width` and `size` attributes in inline and block image elements. + */ +export default class ImageSizeAttributes extends Plugin { + /** + * @inheritDoc + */ + public static get requires() { + return [ ImageUtils ] as const; + } + + /** + * @inheritDoc + */ + public static get pluginName(): 'ImageSizeAttributes' { + return 'ImageSizeAttributes'; + } + + /** + * @inheritDoc + */ + public afterInit(): void { + this._registerSchema(); + this._registerConverters( 'imageBlock' ); + this._registerConverters( 'imageInline' ); + } + + /** + * Registers the `width` and `height` attributes for inline and block images. + */ + private _registerSchema(): void { + if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height' ] } ); + } + + if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height' ] } ); + } + } + + /** + * Registers converters for `width` and `height` attributes. + */ + private _registerConverters( imageType: 'imageBlock' | 'imageInline' ): void { + const editor = this.editor; + const imageUtils = editor.plugins.get( 'ImageUtils' ); + const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img'; + + editor.conversion.for( 'upcast' ) + .attributeToAttribute( { + view: { + name: viewElementName, + attributes: { + width: /.+/ + } + }, + model: { + key: 'width', + value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' ) + } + } ) + .attributeToAttribute( { + view: { + name: viewElementName, + attributes: { + height: /.+/ + } + }, + model: { + key: 'height', + value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'height' ) + } + } ); + + // Dedicated converter to propagate attributes to the element. + editor.conversion.for( 'downcast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width' ); + attachDowncastConverter( dispatcher, 'height', 'height' ); + } ); + + function attachDowncastConverter( dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string ) { + dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const viewElement = conversionApi.mapper.toViewElement( data.item as Element )!; + const img = imageUtils.findViewImgElement( viewElement )!; + + if ( data.attributeNewValue !== null ) { + viewWriter.setAttribute( viewAttributeName, data.attributeNewValue, img ); + } else { + viewWriter.removeAttribute( viewAttributeName, img ); + } + } ); + } + } +} diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 15a754bd2e0..a2350e312c5 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -396,8 +396,8 @@ export default class ImageUploadEditing extends Plugin { .join( ', ' ); if ( srcsetAttribute != '' ) { - writer.setAttribute( 'srcset', { - data: srcsetAttribute, + writer.setAttributes( { + srcset: srcsetAttribute, width: maxWidth }, image ); } diff --git a/packages/ckeditor5-image/src/index.ts b/packages/ckeditor5-image/src/index.ts index 9b23864448f..b8c45971529 100644 --- a/packages/ckeditor5-image/src/index.ts +++ b/packages/ckeditor5-image/src/index.ts @@ -19,6 +19,7 @@ export { default as ImageResize } from './imageresize'; export { default as ImageResizeButtons } from './imageresize/imageresizebuttons'; export { default as ImageResizeEditing } from './imageresize/imageresizeediting'; export { default as ImageResizeHandles } from './imageresize/imageresizehandles'; +export { default as ImageSizeAttributes } from './imagesizeattributes'; export { default as ImageStyle } from './imagestyle'; export { default as ImageStyleEditing } from './imagestyle/imagestyleediting'; export { default as ImageStyleUI } from './imagestyle/imagestyleui'; diff --git a/packages/ckeditor5-image/tests/image/imageblockediting.js b/packages/ckeditor5-image/tests/image/imageblockediting.js index 31d5b66b4db..ddb0d4e66e1 100644 --- a/packages/ckeditor5-image/tests/image/imageblockediting.js +++ b/packages/ckeditor5-image/tests/image/imageblockediting.js @@ -140,7 +140,7 @@ describe( 'ImageBlockEditing', () => { it( 'should convert srcset attribute to srcset and sizes attribute', () => { setModelData( model, - '' + + '' + '' ); @@ -151,28 +151,6 @@ describe( 'ImageBlockEditing', () => { ); } ); - it( 'should convert srcset attribute to width, srcset and add sizes attribute', () => { - setModelData( model, - '' + - '' - ); - - expect( normalizeHtml( editor.getData() ) ).to.equal( - '
' + - '' + - '' + - '
' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.data.downcastDispatcher.on( 'attribute:srcset:imageBlock', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -184,7 +162,7 @@ describe( 'ImageBlockEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -195,12 +173,12 @@ describe( 'ImageBlockEditing', () => { ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); const image = doc.getRoot().getChild( 0 ); @@ -344,27 +322,11 @@ describe( 'ImageBlockEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); } ); - it( 'should convert image with srcset and width attributes', () => { - editor.setData( - '
' + - 'alt text' + - '
' - ); - - expect( getModelData( model, { withoutSelection: true } ) ).to.equal( - '' + - '' - ); - } ); - it( 'should ignore sizes attribute', () => { editor.setData( '
' + @@ -374,7 +336,7 @@ describe( 'ImageBlockEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); } ); @@ -552,7 +514,7 @@ describe( 'ImageBlockEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -562,12 +524,12 @@ describe( 'ImageBlockEditing', () => { ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); const image = doc.getRoot().getChild( 0 ); @@ -582,30 +544,9 @@ describe( 'ImageBlockEditing', () => { ); } ); - it( 'should convert srcset attribute to srcset, width and sizes', () => { - setModelData( model, - '' + - '' ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '
' + - '' + - '' + - '
' - ); - } ); - it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, - '' + '' ); const image = doc.getRoot().getChild( 0 ); @@ -620,26 +561,6 @@ describe( 'ImageBlockEditing', () => { ); } ); - it( 'should remove width, sizes and srcsset attribute when srcset attribute is removed from model', () => { - setModelData( model, - '' + - '' - ); - const image = doc.getRoot().getChild( 0 ); - - model.change( writer => { - writer.removeAttribute( 'srcset', image ); - } ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '
' + - '' + - '
' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.editing.downcastDispatcher.on( 'attribute:srcset:imageBlock', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -651,7 +572,7 @@ describe( 'ImageBlockEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); diff --git a/packages/ckeditor5-image/tests/image/imageediting.js b/packages/ckeditor5-image/tests/image/imageediting.js index 6ad93d45d79..5119bbee9f9 100644 --- a/packages/ckeditor5-image/tests/image/imageediting.js +++ b/packages/ckeditor5-image/tests/image/imageediting.js @@ -161,7 +161,7 @@ describe( 'ImageEditing', () => { it( 'should convert srcset attribute to srcset and sizes attribute', () => { setModelData( model, - '' + + '' + '' ); @@ -173,7 +173,7 @@ describe( 'ImageEditing', () => { setModelData( model, '' + - '' + + '' + '' ); @@ -182,48 +182,6 @@ describe( 'ImageEditing', () => { ); } ); - it( 'should convert srcset attribute to width, srcset and add sizes attribute', () => { - setModelData( model, - '' + - '' - ); - - expect( normalizeHtml( editor.getData() ) ).to.equal( - '
' + - '' + - '' + - '
' - ); - - setModelData( model, - '' + - '' - ); - - expect( normalizeHtml( editor.getData() ) ).to.equal( - '

' + - '' + - '' + - '

' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.data.downcastDispatcher.on( 'attribute:srcset:imageBlock', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -235,7 +193,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -255,19 +213,19 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( editor.getData() ).to.equal( '

alt text

' ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); const image = doc.getRoot().getChild( 0 ); @@ -285,7 +243,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="">' + '' ); const imageInline = doc.getRoot().getChild( 0 ); @@ -468,7 +426,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); @@ -479,40 +437,12 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); } ); - it( 'should convert image with srcset and width attributes', () => { - editor.setData( - '
' + - 'alt text' + - '
' - ); - - expect( getModelData( model, { withoutSelection: true } ) ).to.equal( - '' + - '' - ); - - editor.setData( - '

alt text

' - ); - - expect( getModelData( model, { withoutSelection: true } ) ).to.equal( - '' + - '' - ); - } ); - it( 'should ignore sizes attribute', () => { editor.setData( '
' + @@ -522,7 +452,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); @@ -533,7 +463,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -842,7 +772,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -855,7 +785,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -865,12 +795,12 @@ describe( 'ImageEditing', () => { ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); let image = doc.getRoot().getChild( 0 ); @@ -903,49 +833,9 @@ describe( 'ImageEditing', () => { ); } ); - it( 'should convert srcset attribute to srcset, width and sizes', () => { - setModelData( model, - '' + - '' ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '
' + - '' + - '' + - '
' - ); - - setModelData( model, - '' + - '' ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '

' + - '' + - '' + - '

' - ); - } ); - it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, - '' + + '' + '' ); let image = doc.getRoot().getChild( 0 ); @@ -962,7 +852,7 @@ describe( 'ImageEditing', () => { setModelData( model, '' + - '' + + '' + '' ); image = doc.getRoot().getChild( 0 ).getChild( 0 ); @@ -978,44 +868,6 @@ describe( 'ImageEditing', () => { ); } ); - it( 'should remove width, sizes and srcsset attribute when srcset attribute is removed from model', () => { - setModelData( model, - '' + - '' - ); - let image = doc.getRoot().getChild( 0 ); - - model.change( writer => { - writer.removeAttribute( 'srcset', image ); - } ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '
' + - '' + - '
' - ); - - setModelData( model, - '' + - '' - ); - image = doc.getRoot().getChild( 0 ).getChild( 0 ); - - model.change( writer => { - writer.removeAttribute( 'srcset', image ); - } ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '

' + - '' + - '

' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.editing.downcastDispatcher.on( 'attribute:srcset:imageBlock', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -1027,7 +879,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -1047,7 +899,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); diff --git a/packages/ckeditor5-image/tests/image/imageinlineediting.js b/packages/ckeditor5-image/tests/image/imageinlineediting.js index 228b5993e85..9fdc7cb4c57 100644 --- a/packages/ckeditor5-image/tests/image/imageinlineediting.js +++ b/packages/ckeditor5-image/tests/image/imageinlineediting.js @@ -148,7 +148,7 @@ describe( 'ImageInlineEditing', () => { it( 'should convert srcset attribute to srcset and sizes attribute', () => { setModelData( model, '' + - '' + + '' + '' ); @@ -157,28 +157,6 @@ describe( 'ImageInlineEditing', () => { ); } ); - it( 'should convert srcset attribute to width, srcset and add sizes attribute', () => { - setModelData( model, - '' + - '' - ); - - expect( normalizeHtml( editor.getData() ) ).to.equal( - '

' + - '' + - '' + - '

' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.data.downcastDispatcher.on( 'attribute:srcset:imageInline', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -190,19 +168,19 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( editor.getData() ).to.equal( '

alt text

' ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); const imageInline = doc.getRoot().getChild( 0 ).getChild( 0 ); @@ -323,26 +301,12 @@ describe( 'ImageInlineEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); } ); - it( 'should convert image with srcset and width attributes', () => { - editor.setData( - '

alt text

' - ); - - expect( getModelData( model, { withoutSelection: true } ) ).to.equal( - '' + - '' - ); - } ); - it( 'should ignore sizes attribute', () => { editor.setData( '

alt text

' @@ -351,7 +315,7 @@ describe( 'ImageInlineEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -579,7 +543,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -589,12 +553,12 @@ describe( 'ImageInlineEditing', () => { ); } ); - it( 'should not convert srcset attribute if has wrong data', () => { + it( 'should not convert srcset attribute if has no data', () => { setModelData( model, '' + + 'srcset="">' + '' ); const image = doc.getRoot().getChild( 0 ).getChild( 0 ); @@ -609,31 +573,10 @@ describe( 'ImageInlineEditing', () => { ); } ); - it( 'should convert srcset attribute to srcset, width and sizes', () => { - setModelData( model, - '' + - '' ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '

' + - '' + - '' + - '

' - ); - } ); - it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, '' + - '' + + '' + '' ); const image = doc.getRoot().getChild( 0 ).getChild( 0 ); @@ -649,26 +592,6 @@ describe( 'ImageInlineEditing', () => { ); } ); - it( 'should remove width, sizes and srcsset attribute when srcset attribute is removed from model', () => { - setModelData( model, - '' + - '' - ); - const image = doc.getRoot().getChild( 0 ).getChild( 0 ); - - model.change( writer => { - writer.removeAttribute( 'srcset', image ); - } ); - - expect( getViewData( view, { withoutSelection: true } ) ).to.equal( - '

' + - '' + - '

' - ); - } ); - it( 'should not convert srcset attribute if is already consumed', () => { editor.editing.downcastDispatcher.on( 'attribute:srcset:imageInline', ( evt, data, conversionApi ) => { const modelImage = data.item; @@ -680,7 +603,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -911,7 +834,7 @@ describe( 'ImageInlineEditing', () => { viewDocument.fire( 'clipboardInput', { dataTransfer } ); expect( getModelData( model ) ).to.equal( - 'f[]oo' + 'f[]oo' ); } ); } ); diff --git a/packages/ckeditor5-image/tests/image/imagetypecommand.js b/packages/ckeditor5-image/tests/image/imagetypecommand.js index 77c6ec68bc6..5a2df222604 100644 --- a/packages/ckeditor5-image/tests/image/imagetypecommand.js +++ b/packages/ckeditor5-image/tests/image/imagetypecommand.js @@ -186,14 +186,14 @@ describe( 'ImageTypeCommand', () => { it( 'should convert inline image with srcset attribute to block image', () => { setModelData( model, ` - [] + [] ` ); blockCommand.execute(); expect( getModelData( model ) ).to.equal( - `[]` + `[]` ); } ); @@ -423,14 +423,14 @@ describe( 'ImageTypeCommand', () => { it( 'should convert block image with srcset attribute to inline image', () => { setModelData( model, - `[]` + `[]` ); inlineCommand.execute(); expect( getModelData( model ) ).to.equal( '' + - `[]` + + `[]` + '' ); } ); diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index 1f47d221bdd..ad10f15334b 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -87,36 +87,36 @@ describe( 'ImageResizeEditing', () => { it( 'upcasts 100px width correctly', () => { editor.setData( `
` ); - expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'width' ) ).to.equal( '100px' ); + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '100px' ); } ); it( 'upcasts 50% width correctly', () => { editor.setData( `
` ); - expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'width' ) ).to.equal( '50%' ); + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); } ); it( 'downcasts 100px width correctly', () => { - setData( editor.model, `` ); + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( `
` ); } ); it( 'downcasts 50% width correctly', () => { - setData( editor.model, `` ); + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( `
` ); } ); it( 'removes style and extra class when no longer resized', () => { - setData( editor.model, `` ); + setData( editor.model, `` ); const imageModel = editor.model.document.getRoot().getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'width', imageModel ); + writer.removeAttribute( 'resizedWidth', imageModel ); } ); expect( editor.getData() ) @@ -125,11 +125,11 @@ describe( 'ImageResizeEditing', () => { it( 'doesn\'t downcast consumed tokens', () => { editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:width:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:width:imageBlock' ); + dispatcher.on( 'attribute:resizedWidth:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedWidth:imageBlock' ); }, { priority: 'high' } ) ); - setData( editor.model, `` ); + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( `
` ); @@ -146,17 +146,17 @@ describe( 'ImageResizeEditing', () => { `

Lorem ipsum

` ); - expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'width' ) ).to.equal( '100px' ); + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedWidth' ) ).to.equal( '100px' ); } ); it( 'upcasts 50% width correctly', () => { editor.setData( `

Lorem ipsum

` ); - expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'width' ) ).to.equal( '50%' ); + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); } ); - it( 'downcasts 100px width correctly', () => { - setData( editor.model, `` ); + it( 'downcasts 100px resizedWidth correctly', () => { + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( @@ -164,20 +164,20 @@ describe( 'ImageResizeEditing', () => { ); } ); - it( 'downcasts 50% width correctly', () => { - setData( editor.model, `` ); + it( 'downcasts 50% resizedWidth correctly', () => { + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( `

` ); } ); it( 'removes style and extra class when no longer resized', () => { - setData( editor.model, `` ); + setData( editor.model, `` ); const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'width', imageModel ); + writer.removeAttribute( 'resizedWidth', imageModel ); } ); expect( editor.getData() ) @@ -186,11 +186,11 @@ describe( 'ImageResizeEditing', () => { it( 'doesn\'t downcast consumed tokens', () => { editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:width:imageInline', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:width:imageInline' ); + dispatcher.on( 'attribute:resizedWidth:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedWidth:imageInline' ); }, { priority: 'high' } ) ); - setData( editor.model, `` ); + setData( editor.model, `` ); expect( editor.getData() ) .to.equal( `

` ); @@ -202,15 +202,15 @@ describe( 'ImageResizeEditing', () => { editor = await createEditor(); } ); - it( 'allows the width attribute when ImageBlock plugin is enabled', async () => { + it( 'allows the resizedWidth attribute when ImageBlock plugin is enabled', async () => { const newEditor = await ClassicEditor.create( editorElement, { plugins: [ ImageBlockEditing, ImageResizeEditing ] } ); - expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'width' ) ).to.be.true; + expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'resizedWidth' ) ).to.be.true; await newEditor.destroy(); } ); - it( 'allows the width attribute when ImageInline plugin is enabled', async () => { + it( 'allows the resizedWidth attribute when ImageInline plugin is enabled', async () => { const newEditor = await ClassicEditor.create( editorElement, { plugins: [ ImageInlineEditing, ImageResizeEditing ] } ); - expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageInline' ], 'width' ) ).to.be.true; + expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageInline' ], 'resizedWidth' ) ).to.be.true; await newEditor.destroy(); } ); } ); diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index f5d08ce0546..8c3233a3c81 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -192,7 +192,7 @@ describe( 'ImageResizeHandles', () => { const modelItem = editor.model.document.getRoot().getChild( 1 ); - expect( modelItem.getAttribute( 'width' ), 'model width attribute' ).to.be.undefined; + expect( modelItem.getAttribute( 'resizedWidth' ), 'model width attribute' ).to.be.undefined; } ); } ); @@ -283,7 +283,7 @@ describe( 'ImageResizeHandles', () => { to: finalPointerPosition } ); - expect( model.getAttribute( 'width' ) ).to.equal( '60px' ); + expect( model.getAttribute( 'resizedWidth' ) ).to.equal( '60px' ); } ); } ); @@ -367,7 +367,7 @@ describe( 'ImageResizeHandles', () => { to: finalPointerPosition } ); - expect( model.getAttribute( 'width' ) ).to.equal( '76px' ); + expect( model.getAttribute( 'resizedWidth' ) ).to.equal( '76px' ); } ); it( 'retains width after removing srcset', async () => { @@ -384,7 +384,9 @@ describe( 'ImageResizeHandles', () => { writer.removeAttribute( 'srcset', model ); } ); - const expectedHtml = '
'; + const expectedHtml = '
' + + '' + + '
'; expect( editor.getData() ).to.equal( expectedHtml ); } ); @@ -650,7 +652,7 @@ describe( 'ImageResizeHandles', () => { const modelItem = editor.model.document.getRoot().getChild( 1 ).getChild( 0 ); - expect( modelItem.getAttribute( 'width' ), 'model width attribute' ).to.be.undefined; + expect( modelItem.getAttribute( 'resizedWidth' ), 'model width attribute' ).to.be.undefined; } ); } ); @@ -805,9 +807,12 @@ describe( 'ImageResizeHandles', () => { await setModelAndWaitForImages( editor, '' + - '[]' + + '[' + + ']' + '' ); @@ -825,7 +830,7 @@ describe( 'ImageResizeHandles', () => { to: finalPointerPosition } ); - expect( model.getAttribute( 'width' ) ).to.equal( '76px' ); + expect( model.getAttribute( 'resizedWidth' ) ).to.equal( '76px' ); } ); it( 'retains width after removing srcset', async () => { @@ -842,7 +847,7 @@ describe( 'ImageResizeHandles', () => { writer.removeAttribute( 'srcset', model ); } ); - const expectedHtml = '

'; + const expectedHtml = '

'; expect( editor.getData() ).to.equal( expectedHtml ); } ); diff --git a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js index e87f610869e..b127a7cdf66 100644 --- a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js +++ b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js @@ -24,7 +24,7 @@ describe( 'ResizeImageCommand', () => { isObject: true, isBlock: true, allowWhere: '$block', - allowAttributes: 'width' + allowAttributes: 'resizedWidth' } ); model.schema.register( 'caption', { @@ -40,25 +40,25 @@ describe( 'ResizeImageCommand', () => { describe( '#isEnabled', () => { it( 'is true when image is selected', () => { - setData( model, '

x

[]

x

' ); + setData( model, '

x

[]

x

' ); expect( command ).to.have.property( 'isEnabled', true ); } ); it( 'is true when the selection is inside a block image caption', () => { - setData( model, '[F]oo' ); + setData( model, '[F]oo' ); expect( command ).to.have.property( 'isEnabled', true ); } ); it( 'is false when image is not selected', () => { - setData( model, '

x[]

' ); + setData( model, '

x[]

' ); expect( command ).to.have.property( 'isEnabled', false ); } ); it( 'is false when more than one image is selected', () => { - setData( model, '

x

[]' ); + setData( model, '

x

[]' ); expect( command ).to.have.property( 'isEnabled', false ); } ); @@ -66,24 +66,24 @@ describe( 'ResizeImageCommand', () => { describe( '#value', () => { it( 'is null when image is not selected', () => { - setData( model, '

x[]

' ); + setData( model, '

x[]

' ); expect( command ).to.have.property( 'value', null ); } ); it( 'is set to an object with a width property (and height set to null) when a block image is selected', () => { - setData( model, '

x

[]

x

' ); + setData( model, '

x

[]

x

' ); expect( command ).to.have.deep.property( 'value', { width: '50px', height: null } ); } ); it( 'is set to an object with a width property (and height set to null) when the selection is in a block image caption', () => { - setData( model, '[]Foo' ); + setData( model, '[]Foo' ); expect( command ).to.have.deep.property( 'value', { width: '50px', height: null } ); } ); - it( 'is set to null if image does not have the width set', () => { + it( 'is set to null if image does not have the resizedWidth set', () => { setData( model, '

x

[]

x

' ); expect( command ).to.have.property( 'value', null ); @@ -91,29 +91,29 @@ describe( 'ResizeImageCommand', () => { } ); describe( 'execute()', () => { - it( 'sets image width', () => { - setData( model, '[]' ); + it( 'sets image resizedWidth', () => { + setData( model, '[]' ); command.execute( { width: '100%' } ); - expect( getData( model ) ).to.equal( '[]' ); + expect( getData( model ) ).to.equal( '[]' ); } ); - it( 'sets image width when selection is in a block image caption', () => { - setData( model, 'F[o]o' ); + it( 'sets image resizedWidth when selection is in a block image caption', () => { + setData( model, 'F[o]o' ); command.execute( { width: '100%' } ); - expect( getData( model ) ).to.equal( 'F[o]o' ); + expect( getData( model ) ).to.equal( 'F[o]o' ); } ); - it( 'removes image width when null passed', () => { - setData( model, '[]' ); + it( 'removes image resizedWidth when null passed', () => { + setData( model, '[]' ); command.execute( { width: null } ); expect( getData( model ) ).to.equal( '[]' ); - expect( model.document.getRoot().getChild( 0 ).hasAttribute( 'width' ) ).to.be.false; + expect( model.document.getRoot().getChild( 0 ).hasAttribute( 'resizedWidth' ) ).to.be.false; } ); } ); } ); diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js new file mode 100644 index 00000000000..e46acfaf402 --- /dev/null +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -0,0 +1,290 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; + +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; + +import ImageBlockEditing from '../src/image/imageblockediting'; +import ImageInlineEditing from '../src/image/imageinlineediting'; +import ImageSizeAttributes from '../src/imagesizeattributes'; +import ImageUtils from '../src/imageutils'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; + +describe( 'ImageSizeAttributes', () => { + let editor, model, view; + + testUtils.createSinonSandbox(); + + beforeEach( async () => { + editor = await VirtualTestEditor.create( { + plugins: [ Paragraph, ImageBlockEditing, ImageInlineEditing, ImageSizeAttributes ] + } ); + + model = editor.model; + view = editor.editing.view; + } ); + + afterEach( async () => { + await editor.destroy(); + } ); + + it( 'should be named', () => { + expect( ImageSizeAttributes.pluginName ).to.equal( 'ImageSizeAttributes' ); + } ); + + it( 'should be loaded', () => { + expect( editor.plugins.get( ImageSizeAttributes ) ).to.be.instanceOf( ImageSizeAttributes ); + } ); + + it( 'should require ImageUtils', () => { + expect( ImageSizeAttributes.requires ).to.have.members( [ ImageUtils ] ); + } ); + + describe( 'schema', () => { + it( 'should allow the "width" and "height" attributes on the imageBlock element', () => { + expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'width' ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'height' ) ).to.be.true; + } ); + + it( 'should allow the "width" and "height" attributes on the imageInline element', () => { + expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'width' ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'height' ) ).to.be.true; + } ); + } ); + + describe( 'conversion', () => { + describe( 'upcast', () => { + describe( 'inline images', () => { + it( 'should upcast width attribute correctly', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + + 'Lorem ' + + '' + + ' ipsum' + + '' + ); + } ); + + it( 'should upcast height attribute correctly', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + + 'Lorem ' + + '' + + ' ipsum' + + '' + ); + } ); + } ); + + describe( 'block images', () => { + it( 'should upcast width attribute correctly', () => { + editor.setData( + '
' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + ); + } ); + + it( 'should upcast height attribute correctly', () => { + editor.setData( + '
' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + ); + } ); + } ); + } ); + + describe( 'downcast', () => { + describe( 'inline images', () => { + it( 'should downcast width attribute correctly', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '

Lorem ' + + '' + + ' ipsum

' + ); + + expect( editor.getData() ).to.equal( + '

Lorem ipsum

' + ); + } ); + + it( 'should downcast height attribute correctly', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '

Lorem ' + + '' + + ' ipsum

' + ); + + expect( editor.getData() ).to.equal( + '

Lorem ipsum

' + ); + } ); + + it( 'should not downcast consumed tokens for width attribute', () => { + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( 'attribute:width:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:width:imageInline' ); + }, { priority: 'high' } ) + ); + setData( model, '' ); + + expect( editor.getData() ).to.equal( + '

' + ); + } ); + + it( 'should not downcast consumed tokens for height attribute', () => { + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( 'attribute:height:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:height:imageInline' ); + }, { priority: 'high' } ) + ); + setData( model, '' ); + + expect( editor.getData() ).to.equal( + '

' + ); + } ); + + it( 'should remove width attribute properly', () => { + setData( model, '' ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'width', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( '

' ); + } ); + + it( 'should remove height attribute properly', () => { + setData( model, '' ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'height', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( '

' ); + } ); + } ); + + describe( 'block images', () => { + it( 'should downcast width attribute correctly', () => { + editor.setData( + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + ); + } ); + + it( 'should downcast height attribute correctly', () => { + editor.setData( + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + ); + } ); + + it( 'should not downcast consumed tokens for width attribute', () => { + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( 'attribute:width:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:width:imageBlock' ); + }, { priority: 'high' } ) + ); + setData( model, '' ); + + expect( editor.getData() ).to.equal( + '
' + ); + } ); + + it( 'should not downcast consumed tokens for height attribute', () => { + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( 'attribute:height:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:height:imageBlock' ); + }, { priority: 'high' } ) + ); + setData( model, '' ); + + expect( editor.getData() ).to.equal( + '
' + ); + } ); + + it( 'should remove width attribute properly', () => { + setData( model, '' ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'width', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( '
' ); + } ); + + it( 'should remove height attribute properly', () => { + setData( model, '' ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'height', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( '
' ); + } ); + } ); + } ); + } ); +} ); diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.html b/packages/ckeditor5-image/tests/manual/imagesizeattributes.html new file mode 100644 index 00000000000..15392c753d6 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.html @@ -0,0 +1,60 @@ + + +
+

Just width and height attributes

+ +
+ Foo +
+ +

+ Lorem ipsum + Bar + dolor sit amet. +

+ +

width and height + sizes

+ +
+ Foo +
+ +

+ Lorem ipsum + Bar + dolor sit amet. +

+ +

width and height attributes + ImageResize (px)

+ +
+ Foo +
+ +

+ Lorem ipsum + Bar + dolor sit amet. +

+ +

width and height attributes + ImageResize (%)

+ +
+ Foo +
+ +

+ Lorem ipsum + Bar + dolor sit amet. +

+
diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.js b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js new file mode 100644 index 00000000000..999f0365676 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js @@ -0,0 +1,51 @@ +/** + * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* global document, console, window */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import Indent from '@ckeditor/ckeditor5-indent/src/indent'; +import Code from '@ckeditor/ckeditor5-basic-styles/src/code'; +import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock'; +import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage'; +import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices'; +import ImageResize from '../../src/imageresize'; +import ImageSizeAttributes from '../../src/imagesizeattributes'; +import ImageUpload from '../../src/imageupload'; + +import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; + +const commonConfig = { + plugins: [ + ArticlePluginSet, + ImageResize, + Code, + ImageSizeAttributes, + ImageUpload, + Indent, + IndentBlock, + CloudServices, + EasyImage + ], + toolbar: [ 'heading', '|', 'bold', 'italic', 'link', + 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', 'undo', 'redo', 'outdent', 'indent' ], + image: { + toolbar: [ 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', '|', 'toggleImageCaption', 'resizeImage' ] + }, + table: { + contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ], + tableToolbar: [ 'bold', 'italic' ] + }, + cloudServices: CS_CONFIG +}; + +( async function initTest() { + window.editor = await ClassicEditor + .create( document.querySelector( '#editor-width-height-attributes' ), commonConfig ) + .catch( err => { + console.error( err.stack ); + } ); +}() ); diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.md b/packages/ckeditor5-image/tests/manual/imagesizeattributes.md new file mode 100644 index 00000000000..7b5a3700001 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.md @@ -0,0 +1,4 @@ +## Image size attributes + +* Images (inline and block) should have `width` and `heigth` attributes set. +* Resized images should preserve aspect ratio. diff --git a/packages/ckeditor5-image/tests/pictureediting.js b/packages/ckeditor5-image/tests/pictureediting.js index 65bf88ed1c7..8f07e3a460b 100644 --- a/packages/ckeditor5-image/tests/pictureediting.js +++ b/packages/ckeditor5-image/tests/pictureediting.js @@ -230,7 +230,7 @@ describe( 'PictureEditing', () => { expect( getModelData( model ) ).to.equal( '[]' + 'foo' + - '' + + '' + 'bar' + '' ); @@ -449,9 +449,9 @@ describe( 'PictureEditing', () => { expect( getModelData( model ) ).to.equal( '[' + 'Text of the caption' + ']' @@ -492,9 +492,9 @@ describe( 'PictureEditing', () => { expect( getModelData( model ) ).to.equal( '[' + 'Text of the caption' + ']' diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 09b5f9b82d4..4fa08564a0c 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -28,7 +28,10 @@ max-width: 100%; /* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */ - min-width: 100% + min-width: 100%; + + /* Preserve aspect ratio after introducing width and height attributes for image element. */ + height: auto; } } @@ -60,6 +63,9 @@ /* Prevents overflowing the editing root boundaries when an inline image is very wide. */ max-width: 100%; + + /* Preserve aspect ratio after introducing width and height attributes for image element. */ + height: auto; } } } diff --git a/packages/ckeditor5-link/tests/linkimageediting.js b/packages/ckeditor5-link/tests/linkimageediting.js index cebd74f3d21..aae72ac98b2 100644 --- a/packages/ckeditor5-link/tests/linkimageediting.js +++ b/packages/ckeditor5-link/tests/linkimageediting.js @@ -100,7 +100,7 @@ describe( 'LinkImageEditing', () => { setModelData( model, '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.edge.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.edge.word2016.html index 75ae5e72d24..f3da2c79d4d 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.edge.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.edge.word2016.html @@ -37,4 +37,4 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.safari.word2016.html index 98450ab1c8e..3deb4f0e931 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.safari.word2016.html @@ -26,4 +26,4 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.word2016.html index 98b1777db06..1792d387f7c 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/adjacent-groups/model.word2016.html @@ -34,4 +34,4 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.safari.word2016.html index 84a291b292b..1c0e4fd2892 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.safari.word2016.html @@ -1 +1 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.word2016.html index 51c5d3dd9fd..b221ed55acb 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/alternative-text/model.word2016.html @@ -1 +1 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.safari.word2016.html index bb8d531a9e9..a0fd88da596 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.safari.word2016.html @@ -1,2 +1,2 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.word2016.html index 27b855fd5b5..054ee09c9a2 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/linked/model.word2016.html @@ -1,2 +1,2 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.safari.word2016.html index 0d54939a0df..e3302b2696b 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.safari.word2016.html @@ -1,3 +1,3 @@ This word contains some pictures: - - + + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.word2016.html index c1e0bd6df42..1c69545cd47 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/offline/model.word2016.html @@ -1,3 +1,3 @@ This word contains some pictures: - - + + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.safari.word2016.html index 8f9b8d6c9e0..d22e774e79c 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.safari.word2016.html @@ -1,4 +1,4 @@ -Kitty from internet: +Kitty from internet: -My drawing: hehehehe :D +My drawing: hehehehe :D diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.word2016.html index 9d1d30431b7..6486f412580 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/online-offline/model.word2016.html @@ -1,4 +1,4 @@ -Kitty from internet: +Kitty from internet: -My drawing: hehehehe :D +My drawing: hehehehe :D diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.safari.word2016.html index aad2cb6fb6c..7b0b1aaad8b 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.safari.word2016.html @@ -1,2 +1,2 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.word2016.html index 81a00edc22a..54d84e85032 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/reflection/model.word2016.html @@ -1,2 +1,2 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.safari.word2016.html index ed952781172..9b309d21240 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.safari.word2016.html @@ -1 +1 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.word2016.html index 5c138b0658f..a31989ebcb2 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/rotated/model.word2016.html @@ -1 +1 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.safari.word2016.html index 62079fa7ed0..5d820af343c 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.safari.word2016.html @@ -1,7 +1,7 @@ -Kitty from internet: +Kitty from internet: -My drawing: hehehehe :D +My drawing: hehehehe :D -Additional shape made within Word is added here: +Additional shape made within Word is added here: diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.word2016.html index a2ca325c7b3..35232fafeab 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/shapes-online-offline/model.word2016.html @@ -1,6 +1,6 @@ -Kitty from internet: +Kitty from internet: -My drawing: hehehehe :D +My drawing: hehehehe :D Additional shape made within Word is added here: diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.safari.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.safari.word2016.html index cb74abc3605..f8426d8b90a 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.safari.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.safari.word2016.html @@ -1 +1 @@ - + diff --git a/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.word2016.html b/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.word2016.html index 49847faff42..dade0ff3ef4 100644 --- a/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.word2016.html +++ b/packages/ckeditor5-paste-from-office/tests/_data/image/wrapped/model.word2016.html @@ -1 +1 @@ - +