From 7a114f1461a57f25b2ceb30c53027a1e735e3274 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 17 May 2023 11:38:26 +0200 Subject: [PATCH 001/118] Introduce the ImageSizeAttributes plugin. --- packages/ckeditor5-image/src/augmentation.ts | 2 + .../src/imagesizeattributes.ts | 123 ++++++++++++++++++ packages/ckeditor5-image/src/index.ts | 1 + 3 files changed, 126 insertions(+) create mode 100644 packages/ckeditor5-image/src/imagesizeattributes.ts 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/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts new file mode 100644 index 00000000000..d111ec5fa19 --- /dev/null +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -0,0 +1,123 @@ +/** + * @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 { ViewElement } from 'ckeditor5/src/engine'; +import ImageUtils from './imageutils'; + +/** + * TODO + */ +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 init(): void { + this._registerSchema(); + this._registerConverters( 'imageBlock' ); + this._registerConverters( 'imageInline' ); + } + + /** + * TODO + */ + private _registerSchema(): void { + if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'widthAttribute', 'heightAttribute' ] } ); + } + + if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'widthAttribute', 'heightAttribute' ] } ); + } + } + + /** + * 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: 'widthAttribute', + value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' ) + } + } ) + .attributeToAttribute( { + view: { + name: viewElementName, + attributes: { + height: /.+/ + } + }, + model: { + key: 'heightAttribute', + value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'height' ) + } + } ); + + // Dedicated converter to propagate attributes to the element. + editor.conversion.for( 'downcast' ).add( dispatcher => { + dispatcher.on( `attribute:widthAttribute:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const viewElement = conversionApi.mapper.toViewElement( data.item ); + const img = imageUtils.findViewImgElement( viewElement ); + + if ( data.attributeNewValue !== null ) { + viewWriter.setAttribute( 'width', data.attributeNewValue, img ); + } else { + viewWriter.removeAttribute( 'width', img ); + } + } ); + + dispatcher.on( `attribute:heightAttribute:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const viewElement = conversionApi.mapper.toViewElement( data.item ); + const img = imageUtils.findViewImgElement( viewElement ); + + if ( data.attributeNewValue !== null ) { + viewWriter.setAttribute( 'height', data.attributeNewValue, img ); + } else { + viewWriter.removeAttribute( 'height', img ); + } + } ); + } ); + } +} 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'; From 55d3c9bc7db8e6ac54679e4e01bb64ea3b257fd5 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 17 May 2023 11:46:16 +0200 Subject: [PATCH 002/118] Image size attributes manual test. --- .../tests/manual/imagesizeattributes.html | 60 +++++++++++++++++++ .../tests/manual/imagesizeattributes.js | 51 ++++++++++++++++ .../tests/manual/imagesizeattributes.md | 3 + 3 files changed, 114 insertions(+) create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributes.html create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributes.js create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributes.md 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..c716f2e9e32 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.md @@ -0,0 +1,3 @@ +## Image size attributes + +TODO From 2f301e65216f139fafdd14e6d6ceb124d04da94e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 17 May 2023 12:32:47 +0200 Subject: [PATCH 003/118] Decoupled srcset and width. Made ImageSizeAttributes a mandatory dependency. --- .../ckeditor5-image/src/image/converters.ts | 8 ----- .../src/image/imageblockediting.ts | 3 +- .../ckeditor5-image/src/image/imageediting.ts | 14 ++------ .../src/image/imageinlineediting.ts | 3 +- .../src/imagesizeattributes.ts | 35 +++++++------------ .../src/imageupload/imageuploadediting.ts | 8 +++-- 6 files changed, 24 insertions(+), 47 deletions(-) diff --git a/packages/ckeditor5-image/src/image/converters.ts b/packages/ckeditor5-image/src/image/converters.ts index 1ea58c6f498..4ead1a353f0 100644 --- a/packages/ckeditor5-image/src/image/converters.ts +++ b/packages/ckeditor5-image/src/image/converters.ts @@ -202,10 +202,6 @@ export function downcastSrcsetAttribute( if ( srcset && srcset.data ) { writer.removeAttribute( 'srcset', img ); writer.removeAttribute( 'sizes', img ); - - if ( srcset.width ) { - writer.removeAttribute( 'width', img ); - } } } else { const srcset = data.attributeNewValue as SrcsetAttributeType; @@ -214,10 +210,6 @@ export function downcastSrcsetAttribute( writer.setAttribute( 'srcset', srcset.data, 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..c9748de98c3 100644 --- a/packages/ckeditor5-image/src/image/imageediting.ts +++ b/packages/ckeditor5-image/src/image/imageediting.ts @@ -61,17 +61,9 @@ export default class ImageEditing extends Plugin { }, 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 ) => ( { + data: 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/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index d111ec5fa19..77d7e77ac23 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -8,7 +8,7 @@ */ import { Plugin } from 'ckeditor5/src/core'; -import type { ViewElement } from 'ckeditor5/src/engine'; +import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; import ImageUtils from './imageutils'; /** @@ -32,7 +32,7 @@ export default class ImageSizeAttributes extends Plugin { /** * @inheritDoc */ - public init(): void { + public afterInit(): void { this._registerSchema(); this._registerConverters( 'imageBlock' ); this._registerConverters( 'imageInline' ); @@ -87,37 +87,26 @@ export default class ImageSizeAttributes extends Plugin { // Dedicated converter to propagate attributes to the element. editor.conversion.for( 'downcast' ).add( dispatcher => { - dispatcher.on( `attribute:widthAttribute:${ imageType }`, ( evt, data, conversionApi ) => { - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } - - const viewWriter = conversionApi.writer; - const viewElement = conversionApi.mapper.toViewElement( data.item ); - const img = imageUtils.findViewImgElement( viewElement ); - - if ( data.attributeNewValue !== null ) { - viewWriter.setAttribute( 'width', data.attributeNewValue, img ); - } else { - viewWriter.removeAttribute( 'width', img ); - } - } ); + attachDowncastConverter( dispatcher, 'widthAttribute', 'width' ); + attachDowncastConverter( dispatcher, 'heightAttribute', 'height' ); + } ); - dispatcher.on( `attribute:heightAttribute:${ imageType }`, ( evt, data, conversionApi ) => { + 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 ); - const img = imageUtils.findViewImgElement( viewElement ); + const viewElement = conversionApi.mapper.toViewElement( data.item as Element )!; + const img = imageUtils.findViewImgElement( viewElement )!; if ( data.attributeNewValue !== null ) { - viewWriter.setAttribute( 'height', data.attributeNewValue, img ); + viewWriter.setAttribute( viewAttributeName, data.attributeNewValue, img ); } else { - viewWriter.removeAttribute( 'height', img ); + viewWriter.removeAttribute( viewAttributeName, img ); } } ); - } ); + } } } diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 15a754bd2e0..f589fef8961 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -396,9 +396,11 @@ export default class ImageUploadEditing extends Plugin { .join( ', ' ); if ( srcsetAttribute != '' ) { - writer.setAttribute( 'srcset', { - data: srcsetAttribute, - width: maxWidth + writer.setAttributes( { + srcset: { + data: srcsetAttribute + }, + widthAttribute: maxWidth }, image ); } } From 79ab7c39d34dfc5ab29700e3470e7850095dc8c3 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 17 May 2023 13:06:51 +0200 Subject: [PATCH 004/118] Tests: Clean up after srcset and width decoupling. --- .../tests/image/imageblockediting.js | 83 +--------- .../tests/image/imageediting.js | 156 +----------------- .../tests/image/imageinlineediting.js | 81 +-------- .../tests/imageresize/imageresizehandles.js | 4 +- 4 files changed, 11 insertions(+), 313 deletions(-) diff --git a/packages/ckeditor5-image/tests/image/imageblockediting.js b/packages/ckeditor5-image/tests/image/imageblockediting.js index 31d5b66b4db..b90136e97d2 100644 --- a/packages/ckeditor5-image/tests/image/imageblockediting.js +++ b/packages/ckeditor5-image/tests/image/imageblockediting.js @@ -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=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); @@ -349,22 +327,6 @@ describe( 'ImageBlockEditing', () => { ); } ); - 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( '
' + @@ -582,27 +544,6 @@ 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, '' @@ -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=\'{"data":"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..83204fa13e3 100644 --- a/packages/ckeditor5-image/tests/image/imageediting.js +++ b/packages/ckeditor5-image/tests/image/imageediting.js @@ -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=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); @@ -255,7 +213,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); @@ -485,34 +443,6 @@ describe( 'ImageEditing', () => { ); } ); - 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( '
' + @@ -903,46 +833,6 @@ 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, '' + @@ -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=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); @@ -1047,7 +899,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset=\'{"data":"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..825ae3ad2b9 100644 --- a/packages/ckeditor5-image/tests/image/imageinlineediting.js +++ b/packages/ckeditor5-image/tests/image/imageinlineediting.js @@ -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,7 +168,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); @@ -329,20 +307,6 @@ describe( 'ImageInlineEditing', () => { ); } ); - 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

' @@ -609,27 +573,6 @@ 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, '' + @@ -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=\'{"data":"small.png 148w, big.png 1024w"}\'>' + '' ); diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index f5d08ce0546..8818aa96f94 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -384,7 +384,9 @@ describe( 'ImageResizeHandles', () => { writer.removeAttribute( 'srcset', model ); } ); - const expectedHtml = '
'; + const expectedHtml = '
' + + '' + + '
'; expect( editor.getData() ).to.equal( expectedHtml ); } ); From e8b9a687ae3cc58112c27a1d705a96ee58668d85 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 17 May 2023 17:00:52 +0200 Subject: [PATCH 005/118] Streamlined image srcset attribute (object -> string). --- .../ckeditor5-image/src/image/converters.ts | 14 +++------ .../ckeditor5-image/src/image/imageediting.ts | 8 ++--- .../src/imageupload/imageuploadediting.ts | 4 +-- .../tests/image/imageblockediting.js | 18 +++++------ .../tests/image/imageediting.js | 30 +++++++++---------- .../tests/image/imageinlineediting.js | 18 +++++------ .../tests/image/imagetypecommand.js | 8 ++--- .../tests/imageresize/imageresizehandles.js | 11 ++++--- .../ckeditor5-link/tests/linkimageediting.js | 2 +- 9 files changed, 54 insertions(+), 59 deletions(-) diff --git a/packages/ckeditor5-image/src/image/converters.ts b/packages/ckeditor5-image/src/image/converters.ts index 4ead1a353f0..a1b40af3ad8 100644 --- a/packages/ckeditor5-image/src/image/converters.ts +++ b/packages/ckeditor5-image/src/image/converters.ts @@ -197,17 +197,11 @@ 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 ); - } + 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 ); } diff --git a/packages/ckeditor5-image/src/image/imageediting.ts b/packages/ckeditor5-image/src/image/imageediting.ts index c9748de98c3..d449005209c 100644 --- a/packages/ckeditor5-image/src/image/imageediting.ts +++ b/packages/ckeditor5-image/src/image/imageediting.ts @@ -57,13 +57,13 @@ export default class ImageEditing extends Plugin { .attributeToAttribute( { view: { name: 'img', - key: 'srcset' + attributes: { + srcset: /.+/ + } }, model: { key: 'srcset', - value: ( viewImage: ViewElement ) => ( { - data: viewImage.getAttribute( 'srcset' ) - } ) + value: ( viewImage: ViewElement ) => viewImage.getAttribute( 'srcset' ) } } ); diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index f589fef8961..504fa283d50 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -397,9 +397,7 @@ export default class ImageUploadEditing extends Plugin { if ( srcsetAttribute != '' ) { writer.setAttributes( { - srcset: { - data: srcsetAttribute - }, + srcset: srcsetAttribute, widthAttribute: maxWidth }, image ); } diff --git a/packages/ckeditor5-image/tests/image/imageblockediting.js b/packages/ckeditor5-image/tests/image/imageblockediting.js index b90136e97d2..fb5eed7f610 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, - '' + + '' + '' ); @@ -173,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 ); @@ -322,7 +322,7 @@ describe( 'ImageBlockEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); } ); @@ -336,7 +336,7 @@ describe( 'ImageBlockEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); } ); @@ -514,7 +514,7 @@ describe( 'ImageBlockEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -524,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 ); @@ -546,7 +546,7 @@ describe( 'ImageBlockEditing', () => { it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, - '' + '' ); const image = doc.getRoot().getChild( 0 ); diff --git a/packages/ckeditor5-image/tests/image/imageediting.js b/packages/ckeditor5-image/tests/image/imageediting.js index 83204fa13e3..f4f27ea1462 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, '' + - '' + + '' + '' ); @@ -220,12 +220,12 @@ describe( 'ImageEditing', () => { 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 ); @@ -243,7 +243,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="">' + '' ); const imageInline = doc.getRoot().getChild( 0 ); @@ -426,7 +426,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); @@ -437,7 +437,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -452,7 +452,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( - '' + + '' + '' ); @@ -463,7 +463,7 @@ describe( 'ImageEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -772,7 +772,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -785,7 +785,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -795,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 ); @@ -835,7 +835,7 @@ describe( 'ImageEditing', () => { it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, - '' + + '' + '' ); let image = doc.getRoot().getChild( 0 ); @@ -852,7 +852,7 @@ describe( 'ImageEditing', () => { setModelData( model, '' + - '' + + '' + '' ); image = doc.getRoot().getChild( 0 ).getChild( 0 ); diff --git a/packages/ckeditor5-image/tests/image/imageinlineediting.js b/packages/ckeditor5-image/tests/image/imageinlineediting.js index 825ae3ad2b9..04cb9824f1c 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, '' + - '' + + '' + '' ); @@ -175,12 +175,12 @@ describe( 'ImageInlineEditing', () => { 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 ); @@ -301,7 +301,7 @@ describe( 'ImageInlineEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -315,7 +315,7 @@ describe( 'ImageInlineEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' + - '' + + '' + '' + '' ); @@ -543,7 +543,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( @@ -553,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 ); @@ -576,7 +576,7 @@ describe( 'ImageInlineEditing', () => { it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, '' + - '' + + '' + '' ); const image = doc.getRoot().getChild( 0 ).getChild( 0 ); 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/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index 8818aa96f94..c5e92181f45 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -807,9 +807,12 @@ describe( 'ImageResizeHandles', () => { await setModelAndWaitForImages( editor, '' + - '[]' + + '[' + + ']' + '' ); @@ -844,7 +847,7 @@ describe( 'ImageResizeHandles', () => { writer.removeAttribute( 'srcset', model ); } ); - const expectedHtml = '

'; + const expectedHtml = '

'; expect( editor.getData() ).to.equal( expectedHtml ); } ); 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">' + '' ); From 868c5f4de9e836b4cc1b652b7821b87e23dc4207 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 22 May 2023 10:58:06 +0200 Subject: [PATCH 006/118] Add height to img CSS. --- packages/ckeditor5-image/theme/image.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 09b5f9b82d4..27a0490d7ab 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -28,7 +28,9 @@ 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%; + + height: auto; } } @@ -60,6 +62,8 @@ /* Prevents overflowing the editing root boundaries when an inline image is very wide. */ max-width: 100%; + + height: auto; } } } From 0458e845d1b4ee1536d7fc9f968009f4c3a3fd76 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 23 May 2023 12:56:22 +0200 Subject: [PATCH 007/118] Change image resize model attribute: width -> resizedWidth. --- .../src/imageresize/imageresizeediting.ts | 8 ++-- .../src/imageresize/resizeimagecommand.ts | 6 +-- .../tests/image/imageinlineediting.js | 2 +- .../tests/imageresize/imageresizeediting.js | 48 +++++++++---------- .../tests/imageresize/imageresizehandles.js | 10 ++-- .../tests/imageresize/resizeimagecommand.js | 36 +++++++------- .../ckeditor5-image/tests/pictureediting.js | 10 ++-- 7 files changed, 60 insertions(+), 60 deletions(-) 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/tests/image/imageinlineediting.js b/packages/ckeditor5-image/tests/image/imageinlineediting.js index 04cb9824f1c..bdec92ac502 100644 --- a/packages/ckeditor5-image/tests/image/imageinlineediting.js +++ b/packages/ckeditor5-image/tests/image/imageinlineediting.js @@ -834,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/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 c5e92181f45..4369939f95d 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 () => { @@ -652,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; } ); } ); @@ -830,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 () => { 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/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' + ']' From f33dc43fe74855e62a509ad300e68fbd4deb77b3 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 24 May 2023 15:05:47 +0200 Subject: [PATCH 008/118] Tests (PFW, GHS) update after introducing image width & height. --- packages/ckeditor5-html-support/tests/integrations/image.js | 4 ++++ .../_data/image/adjacent-groups/model.edge.word2016.html | 2 +- .../_data/image/adjacent-groups/model.safari.word2016.html | 2 +- .../tests/_data/image/adjacent-groups/model.word2016.html | 2 +- .../_data/image/alternative-text/model.safari.word2016.html | 2 +- .../tests/_data/image/alternative-text/model.word2016.html | 2 +- .../tests/_data/image/linked/model.safari.word2016.html | 2 +- .../tests/_data/image/linked/model.word2016.html | 2 +- .../tests/_data/image/offline/model.safari.word2016.html | 4 ++-- .../tests/_data/image/offline/model.word2016.html | 4 ++-- .../_data/image/online-offline/model.safari.word2016.html | 4 ++-- .../tests/_data/image/online-offline/model.word2016.html | 4 ++-- .../tests/_data/image/reflection/model.safari.word2016.html | 2 +- .../tests/_data/image/reflection/model.word2016.html | 2 +- .../tests/_data/image/rotated/model.safari.word2016.html | 2 +- .../tests/_data/image/rotated/model.word2016.html | 2 +- .../image/shapes-online-offline/model.safari.word2016.html | 6 +++--- .../_data/image/shapes-online-offline/model.word2016.html | 4 ++-- .../tests/_data/image/wrapped/model.safari.word2016.html | 2 +- .../tests/_data/image/wrapped/model.word2016.html | 2 +- 20 files changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/ckeditor5-html-support/tests/integrations/image.js b/packages/ckeditor5-html-support/tests/integrations/image.js index 340627d3490..482f930ac2f 100644 --- a/packages/ckeditor5-html-support/tests/integrations/image.js +++ b/packages/ckeditor5-html-support/tests/integrations/image.js @@ -2403,6 +2403,8 @@ describe( 'ImageElementSupport', () => { 'src', 'srcset', 'linkHref', + 'widthAttribute', + 'heightAttribute', 'htmlAttributes', 'htmlFigureAttributes', 'htmlLinkAttributes' @@ -2437,6 +2439,8 @@ describe( 'ImageElementSupport', () => { 'alt', 'src', 'srcset', + 'widthAttribute', + 'heightAttribute', 'htmlA', 'htmlAttributes' ] ); 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..e25104aab48 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..914d7cc0356 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..184ceadeb40 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..345a098eb4d 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..6b04d02d5c9 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..15618322b25 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..168d67679d1 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..2b90e1d6e95 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..53a4c45210b 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..d044fae71ef 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..3d4ea54bfa3 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..f6d61e7145e 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..3ba8ab50bce 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..debd3e2f7ea 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..06dcd919e75 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..6dcbd46f547 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..918d0280736 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..43bba331630 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..4fc7fcaff56 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 @@ - + From 4da8be57d864314d0c3cf81fac528c48be0689e2 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 12:09:26 +0200 Subject: [PATCH 009/118] Tests: cc for ImageSizeAttributes. --- .../tests/imagesizeattributes.js | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 packages/ckeditor5-image/tests/imagesizeattributes.js diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js new file mode 100644 index 00000000000..22f94d5faa1 --- /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 "widthAttribute" and "heightAttribute" attributes on the imageBlock element', () => { + expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'widthAttribute' ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'heightAttribute' ) ).to.be.true; + } ); + + it( 'should allow the "widthAttribute" and "heightAttribute" attributes on the imageInline element', () => { + expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'widthAttribute' ) ).to.be.true; + expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'heightAttribute' ) ).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:widthAttribute:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:widthAttribute: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:heightAttribute:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:heightAttribute: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( 'widthAttribute', 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( 'heightAttribute', 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:widthAttribute:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:widthAttribute: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:heightAttribute:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:heightAttribute: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( 'widthAttribute', 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( 'heightAttribute', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( '
' ); + } ); + } ); + } ); + } ); +} ); From df3894fcbe639ac72855532a38784cb6a3f0c59b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 13:39:01 +0200 Subject: [PATCH 010/118] Description for added height style in image. --- packages/ckeditor5-image/theme/image.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 27a0490d7ab..4fa08564a0c 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -30,6 +30,7 @@ /* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */ min-width: 100%; + /* Preserve aspect ratio after introducing width and height attributes for image element. */ height: auto; } } @@ -63,6 +64,7 @@ /* 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; } } From fb9b0614dfd773d3217fcbd53b19951117f35171 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 13:39:33 +0200 Subject: [PATCH 011/118] Missing api docs. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 77d7e77ac23..dada080f752 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -12,7 +12,7 @@ import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } import ImageUtils from './imageutils'; /** - * TODO + * This plugin enables `width` and `size` attributes in inline and block image elements. */ export default class ImageSizeAttributes extends Plugin { /** @@ -39,7 +39,7 @@ export default class ImageSizeAttributes extends Plugin { } /** - * TODO + * Registers the `width` and `height` attributes for inline and block images. */ private _registerSchema(): void { if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { From 3c7f6e2a23da688a5901874147c5e325f2be3fa6 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 13:40:00 +0200 Subject: [PATCH 012/118] Missing manual test description. --- packages/ckeditor5-image/tests/manual/imagesizeattributes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.md b/packages/ckeditor5-image/tests/manual/imagesizeattributes.md index c716f2e9e32..7b5a3700001 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributes.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.md @@ -1,3 +1,4 @@ ## Image size attributes -TODO +* Images (inline and block) should have `width` and `heigth` attributes set. +* Resized images should preserve aspect ratio. From d1f1679597412e608b4d55916f18f1298a896eb3 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 13:54:39 +0200 Subject: [PATCH 013/118] Remove unnecessary type. --- packages/ckeditor5-image/src/image/converters.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ckeditor5-image/src/image/converters.ts b/packages/ckeditor5-image/src/image/converters.ts index a1b40af3ad8..abf9f122122 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: * From 597cb0ea53478dd53e356c4541ae5931a4367441 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 13:55:11 +0200 Subject: [PATCH 014/118] Update api docs. --- packages/ckeditor5-image/src/image/converters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/image/converters.ts b/packages/ckeditor5-image/src/image/converters.ts index abf9f122122..5a11f85bd8b 100644 --- a/packages/ckeditor5-image/src/image/converters.ts +++ b/packages/ckeditor5-image/src/image/converters.ts @@ -176,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. From dd229db0031cf846cafe887c21711d46f629eeab Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 25 May 2023 14:25:56 +0200 Subject: [PATCH 015/118] Change image attributes: widthAttribute -> width, heightAttribute -> height. --- .../tests/integrations/image.js | 8 +-- .../src/imagesizeattributes.ts | 12 ++-- .../src/imageupload/imageuploadediting.ts | 2 +- .../tests/imageresize/imageresizehandles.js | 2 +- .../tests/imagesizeattributes.js | 60 +++++++++---------- .../adjacent-groups/model.edge.word2016.html | 2 +- .../model.safari.word2016.html | 2 +- .../image/adjacent-groups/model.word2016.html | 2 +- .../model.safari.word2016.html | 2 +- .../alternative-text/model.word2016.html | 2 +- .../image/linked/model.safari.word2016.html | 2 +- .../_data/image/linked/model.word2016.html | 2 +- .../image/offline/model.safari.word2016.html | 4 +- .../_data/image/offline/model.word2016.html | 4 +- .../online-offline/model.safari.word2016.html | 4 +- .../image/online-offline/model.word2016.html | 4 +- .../reflection/model.safari.word2016.html | 2 +- .../image/reflection/model.word2016.html | 2 +- .../image/rotated/model.safari.word2016.html | 2 +- .../_data/image/rotated/model.word2016.html | 2 +- .../model.safari.word2016.html | 6 +- .../shapes-online-offline/model.word2016.html | 4 +- .../image/wrapped/model.safari.word2016.html | 2 +- .../_data/image/wrapped/model.word2016.html | 2 +- 24 files changed, 68 insertions(+), 68 deletions(-) diff --git a/packages/ckeditor5-html-support/tests/integrations/image.js b/packages/ckeditor5-html-support/tests/integrations/image.js index 482f930ac2f..ad7c9cee7a6 100644 --- a/packages/ckeditor5-html-support/tests/integrations/image.js +++ b/packages/ckeditor5-html-support/tests/integrations/image.js @@ -2403,8 +2403,8 @@ describe( 'ImageElementSupport', () => { 'src', 'srcset', 'linkHref', - 'widthAttribute', - 'heightAttribute', + 'width', + 'height', 'htmlAttributes', 'htmlFigureAttributes', 'htmlLinkAttributes' @@ -2439,8 +2439,8 @@ describe( 'ImageElementSupport', () => { 'alt', 'src', 'srcset', - 'widthAttribute', - 'heightAttribute', + 'width', + 'height', 'htmlA', 'htmlAttributes' ] ); diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index dada080f752..e1c67109a78 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -43,11 +43,11 @@ export default class ImageSizeAttributes extends Plugin { */ private _registerSchema(): void { if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { - this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'widthAttribute', 'heightAttribute' ] } ); + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height' ] } ); } if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { - this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'widthAttribute', 'heightAttribute' ] } ); + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height' ] } ); } } @@ -68,7 +68,7 @@ export default class ImageSizeAttributes extends Plugin { } }, model: { - key: 'widthAttribute', + key: 'width', value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' ) } } ) @@ -80,15 +80,15 @@ export default class ImageSizeAttributes extends Plugin { } }, model: { - key: 'heightAttribute', + key: 'height', value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'height' ) } } ); // Dedicated converter to propagate attributes to the element. editor.conversion.for( 'downcast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'widthAttribute', 'width' ); - attachDowncastConverter( dispatcher, 'heightAttribute', 'height' ); + attachDowncastConverter( dispatcher, 'width', 'width' ); + attachDowncastConverter( dispatcher, 'height', 'height' ); } ); function attachDowncastConverter( dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string ) { diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 504fa283d50..a2350e312c5 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -398,7 +398,7 @@ export default class ImageUploadEditing extends Plugin { if ( srcsetAttribute != '' ) { writer.setAttributes( { srcset: srcsetAttribute, - widthAttribute: maxWidth + width: maxWidth }, image ); } } diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index 4369939f95d..8c3233a3c81 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -811,7 +811,7 @@ describe( 'ImageResizeHandles', () => { `src="${ imageBaseUrl }" ` + `srcset="${ imageBaseUrl }?a 110w, ${ imageBaseUrl }?b 440w, ${ imageBaseUrl }?c 1025w" ` + 'sizes="100vw" ' + - 'widthAttribute="96">' + + 'width="96">' + ']' + '
' ); diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index 22f94d5faa1..e46acfaf402 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -47,14 +47,14 @@ describe( 'ImageSizeAttributes', () => { } ); describe( 'schema', () => { - it( 'should allow the "widthAttribute" and "heightAttribute" attributes on the imageBlock element', () => { - expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'widthAttribute' ) ).to.be.true; - expect( model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'heightAttribute' ) ).to.be.true; + 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 "widthAttribute" and "heightAttribute" attributes on the imageInline element', () => { - expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'widthAttribute' ) ).to.be.true; - expect( model.schema.checkAttribute( [ '$root', 'imageInline' ], 'heightAttribute' ) ).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; } ); } ); @@ -69,7 +69,7 @@ describe( 'ImageSizeAttributes', () => { expect( getData( model, { withoutSelection: true } ) ).to.equal( '' + 'Lorem ' + - '' + + '' + ' ipsum' + '' ); @@ -83,7 +83,7 @@ describe( 'ImageSizeAttributes', () => { expect( getData( model, { withoutSelection: true } ) ).to.equal( '' + 'Lorem ' + - '' + + '' + ' ipsum' + '' ); @@ -97,7 +97,7 @@ describe( 'ImageSizeAttributes', () => { ); expect( getData( model, { withoutSelection: true } ) ).to.equal( - '' + '' ); } ); @@ -107,7 +107,7 @@ describe( 'ImageSizeAttributes', () => { ); expect( getData( model, { withoutSelection: true } ) ).to.equal( - '' + '' ); } ); } ); @@ -149,11 +149,11 @@ describe( 'ImageSizeAttributes', () => { it( 'should not downcast consumed tokens for width attribute', () => { editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:widthAttribute:imageInline', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:widthAttribute:imageInline' ); + dispatcher.on( 'attribute:width:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:width:imageInline' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '

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

' @@ -174,12 +174,12 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove width attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'widthAttribute', imageModel ); + writer.removeAttribute( 'width', imageModel ); } ); expect( editor.getData() ) @@ -187,12 +187,12 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove height attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'heightAttribute', imageModel ); + writer.removeAttribute( 'height', imageModel ); } ); expect( editor.getData() ) @@ -235,11 +235,11 @@ describe( 'ImageSizeAttributes', () => { it( 'should not downcast consumed tokens for width attribute', () => { editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:widthAttribute:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:widthAttribute:imageBlock' ); + dispatcher.on( 'attribute:width:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:width:imageBlock' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '
' @@ -248,11 +248,11 @@ describe( 'ImageSizeAttributes', () => { it( 'should not downcast consumed tokens for height attribute', () => { editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:heightAttribute:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:heightAttribute:imageBlock' ); + dispatcher.on( 'attribute:height:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:height:imageBlock' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '
' @@ -260,12 +260,12 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove width attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'widthAttribute', imageModel ); + writer.removeAttribute( 'width', imageModel ); } ); expect( editor.getData() ) @@ -273,12 +273,12 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove height attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ); editor.model.change( writer => { - writer.removeAttribute( 'heightAttribute', imageModel ); + writer.removeAttribute( 'height', imageModel ); } ); expect( editor.getData() ) 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 e25104aab48..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 914d7cc0356..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 184ceadeb40..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 345a098eb4d..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 6b04d02d5c9..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 15618322b25..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 168d67679d1..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 2b90e1d6e95..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 53a4c45210b..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 d044fae71ef..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 3d4ea54bfa3..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 f6d61e7145e..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 3ba8ab50bce..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 debd3e2f7ea..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 06dcd919e75..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 6dcbd46f547..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 918d0280736..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 43bba331630..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 4fc7fcaff56..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 @@ - + From b3899675270aa8251a7125d5c1313fa3ea07123e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 26 May 2023 13:50:49 +0200 Subject: [PATCH 016/118] Tests: update the srcset attribute to the correct format. --- packages/ckeditor5-image/tests/image/imageblockediting.js | 4 ++-- packages/ckeditor5-image/tests/image/imageediting.js | 8 ++++---- .../ckeditor5-image/tests/image/imageinlineediting.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-image/tests/image/imageblockediting.js b/packages/ckeditor5-image/tests/image/imageblockediting.js index fb5eed7f610..ddb0d4e66e1 100644 --- a/packages/ckeditor5-image/tests/image/imageblockediting.js +++ b/packages/ckeditor5-image/tests/image/imageblockediting.js @@ -162,7 +162,7 @@ describe( 'ImageBlockEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -572,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 f4f27ea1462..5119bbee9f9 100644 --- a/packages/ckeditor5-image/tests/image/imageediting.js +++ b/packages/ckeditor5-image/tests/image/imageediting.js @@ -193,7 +193,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -213,7 +213,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -879,7 +879,7 @@ describe( 'ImageEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -899,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 bdec92ac502..9fdc7cb4c57 100644 --- a/packages/ckeditor5-image/tests/image/imageinlineediting.js +++ b/packages/ckeditor5-image/tests/image/imageinlineediting.js @@ -168,7 +168,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); @@ -603,7 +603,7 @@ describe( 'ImageInlineEditing', () => { '' + + 'srcset="small.png 148w, big.png 1024w">' + '' ); From 30e97966206d0bbad14bec209fa0bab33c99cea4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 30 May 2023 13:29:11 +0200 Subject: [PATCH 017/118] Set image width and height on upload. --- .../src/imageupload/imageuploadediting.ts | 2 + packages/ckeditor5-image/src/imageutils.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index a2350e312c5..18f92ae24aa 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -225,12 +225,14 @@ export default class ImageUploadEditing extends Plugin { } ); // Set the default handler for feeding the image element with `src` and `srcset` attributes. + // Load the image, read and set `width` and `height` attributes (original sizes). this.on( 'uploadComplete', ( evt, { imageElement, data } ) => { const urls = data.urls ? data.urls as Record : data; this.editor.model.change( writer => { writer.setAttribute( 'src', urls.default, imageElement ); this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer ); + imageUtils.loadImageAndSetSizeAttributes( imageElement ); } ); }, { priority: 'low' } ); } diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index d8432b33501..1f8d243b6ad 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -24,11 +24,17 @@ import type { import { Plugin, type Editor } from 'ckeditor5/src/core'; import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget'; import { determineImageTypeForInsertionAtSelection } from './image/utils'; +import { DomEmitterMixin, type DomEmitter } from 'ckeditor5/src/utils'; /** * A set of helpers related to images. */ export default class ImageUtils extends Plugin { + /** + * DOM Emitter. + */ + private _domEmitter: DomEmitter = new ( DomEmitterMixin() )(); + /** * @inheritDoc */ @@ -121,6 +127,8 @@ export default class ImageUtils extends Plugin { // Inserting an image might've failed due to schema regulations. if ( imageElement.parent ) { + this.loadImageAndSetSizeAttributes( imageElement ); + return imageElement; } @@ -128,6 +136,42 @@ export default class ImageUtils extends Plugin { } ); } + /** + * Loads image file based on `src`, reads original image sizes and sets them as `width` and `height`. + * + * The `src` attribute may not be available if the user is using an upload adapter. In such a case, + * this method is called again after the upload process is complete and the `src` attribute is available. + */ + public loadImageAndSetSizeAttributes( imageElement: Element ): void { + const src = imageElement.getAttribute( 'src' ) as string; + + if ( !src ) { + return; + } + + const img = new Image(); + + this._domEmitter.listenTo( img, 'load', ( evt, data ) => { + this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); + } ); + + this._domEmitter.listenTo( img, 'error', ( evt, data ) => { + console.warn( `Failed to download image with src: ${ src }.` ); + } ); + + img.src = src; + } + + /** + * Sets image `width` and `height` attributes. + */ + private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void { + this.editor.model.enqueueChange( { isUndoable: false }, writer => { + writer.setAttribute( 'width', width.toString(), imageElement ); + writer.setAttribute( 'height', height.toString(), imageElement ); + } ); + } + /** * Returns an image widget editing view element if one is selected or is among the selection's ancestors. */ From cb64f2cabe0f74fe152df0dce651fa747da3dc1a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 31 May 2023 21:50:19 +0200 Subject: [PATCH 018/118] Test: set image width and height after image insert. --- packages/ckeditor5-image/tests/imageutils.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/imageutils.js b/packages/ckeditor5-image/tests/imageutils.js index ffb86c9d679..f808daeb7af 100644 --- a/packages/ckeditor5-image/tests/imageutils.js +++ b/packages/ckeditor5-image/tests/imageutils.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* global console */ +/* global console, setTimeout */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ViewDowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwriter'; @@ -706,6 +706,20 @@ describe( 'ImageUtils plugin', () => { expect( imageElement ).to.be.null; } ); + + it( 'should set image width and height', done => { + setModelData( model, 'f[o]o' ); + + imageUtils.insertImage( { src: '/assets/sample.png' } ); + + setTimeout( () => { + expect( getModelData( model ) ).to.equal( + 'f[]o' + ); + + done(); + }, 100 ); + } ); } ); describe( 'findViewImgElement()', () => { From 0b0af828ad89a6f01272dfc92e77b89a3000845a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 31 May 2023 21:51:22 +0200 Subject: [PATCH 019/118] Test: set image width and height after upload. --- .../tests/imageupload/imageuploadediting.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js index e05ceb20a0a..fab20c27cc0 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js @@ -487,6 +487,32 @@ describe( 'ImageUploadEditing', () => { expect( loader.status ).to.equal( 'idle' ); } ); + it( 'should set image width and height after server response', async () => { + const file = createNativeFileMock(); + setModelData( model, '{}foo bar' ); + editor.execute( 'uploadImage', { file } ); + + await new Promise( res => { + model.document.once( 'change', res ); + loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) ); + } ); + + await new Promise( res => { + model.document.once( 'change', res, { priority: 'lowest' } ); + loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: '/assets/sample.png' } ) ); + } ); + + await timeout( 100 ); + + expect( getModelData( model ) ).to.equal( + '[]foo bar' + ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } + } ); + it( 'should support adapter response with the normalized `urls` property', async () => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); From 0e99f8a6c52a1d2bdef68fdc358c477307386382 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 11:24:08 +0200 Subject: [PATCH 020/118] Remove error handler. --- packages/ckeditor5-image/src/imageutils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 1f8d243b6ad..6ecd26af348 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -155,10 +155,6 @@ export default class ImageUtils extends Plugin { this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); } ); - this._domEmitter.listenTo( img, 'error', ( evt, data ) => { - console.warn( `Failed to download image with src: ${ src }.` ); - } ); - img.src = src; } From 318833670b4757c956fd3819b185b6bf5056eb3b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 16:42:42 +0200 Subject: [PATCH 021/118] Improve listener for loading image and setting width and height. --- packages/ckeditor5-image/src/imageutils.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 6ecd26af348..fab28123f21 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -24,7 +24,7 @@ import type { import { Plugin, type Editor } from 'ckeditor5/src/core'; import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget'; import { determineImageTypeForInsertionAtSelection } from './image/utils'; -import { DomEmitterMixin, type DomEmitter } from 'ckeditor5/src/utils'; +import { DomEmitterMixin, type DomEmitter, global } from 'ckeditor5/src/utils'; /** * A set of helpers related to images. @@ -149,10 +149,11 @@ export default class ImageUtils extends Plugin { return; } - const img = new Image(); + const img = new global.window.Image(); this._domEmitter.listenTo( img, 'load', ( evt, data ) => { this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); + this._domEmitter.stopListening( img, 'load' ); } ); img.src = src; @@ -163,8 +164,8 @@ export default class ImageUtils extends Plugin { */ private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void { this.editor.model.enqueueChange( { isUndoable: false }, writer => { - writer.setAttribute( 'width', width.toString(), imageElement ); - writer.setAttribute( 'height', height.toString(), imageElement ); + writer.setAttribute( 'width', width, imageElement ); + writer.setAttribute( 'height', height, imageElement ); } ); } @@ -279,6 +280,15 @@ export default class ImageUtils extends Plugin { } } } + + /** + * @inheritDoc + */ + public override destroy(): void { + this._domEmitter.stopListening(); + + return super.destroy(); + } } /** From 3eacd50eecb5ecdb6bd7ee52278edeed5220d4d4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 18:10:55 +0200 Subject: [PATCH 022/118] Do not override width and height if they were provided. --- packages/ckeditor5-image/src/imageutils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index fab28123f21..73f584e28df 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -149,6 +149,10 @@ export default class ImageUtils extends Plugin { return; } + if ( imageElement.getAttribute( 'width' ) || imageElement.getAttribute( 'height' ) ) { + return; + } + const img = new global.window.Image(); this._domEmitter.listenTo( img, 'load', ( evt, data ) => { From 82975fec610760353549d4f033e88b074d0f126b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 7 Jun 2023 14:11:35 +0200 Subject: [PATCH 023/118] Add converter for image resizedHeight. --- .../src/imageresize/imageresizeediting.ts | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 4e0adbfa3cd..e0333ebf04a 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: 'resizedWidth' } ); + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'resizedWidth', 'resizedHeight' ] } ); } if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { - this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'resizedWidth' } ); + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'resizedWidth', 'resizedHeight' ] } ); } } @@ -118,6 +118,25 @@ export default class ImageResizeEditing extends Plugin { } ) ); + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const figure = conversionApi.mapper.toViewElement( data.item ); + + if ( data.attributeNewValue !== null ) { + viewWriter.setStyle( 'height', data.attributeNewValue, figure ); + viewWriter.addClass( 'image_resized', figure ); + } else { + viewWriter.removeStyle( 'height', figure ); + viewWriter.removeClass( 'image_resized', figure ); + } + } ) + ); + editor.conversion.for( 'upcast' ) .attributeToAttribute( { view: { @@ -131,5 +150,19 @@ export default class ImageResizeEditing extends Plugin { value: ( viewElement: ViewElement ) => viewElement.getStyle( 'width' ) } } ); + + editor.conversion.for( 'upcast' ) + .attributeToAttribute( { + view: { + name: imageType === 'imageBlock' ? 'figure' : 'img', + styles: { + height: /.+/ + } + }, + model: { + key: 'resizedHeight', + value: ( viewElement: ViewElement ) => viewElement.getStyle( 'height' ) + } + } ); } } From 65065ab2c7ad704431fac0ffb40c8d333e08b88d Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 7 Jun 2023 15:17:45 +0200 Subject: [PATCH 024/118] Remove resizedHeight on image resize. --- packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts | 1 + packages/ckeditor5-widget/src/widgetresize/resizer.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index cb424e2af27..df14fef73f0 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -72,6 +72,7 @@ export default class ResizeImageCommand extends Command { if ( imageElement ) { model.change( writer => { writer.setAttribute( 'resizedWidth', options.width, imageElement ); + writer.removeAttribute( 'resizedHeight', imageElement ); } ); } } diff --git a/packages/ckeditor5-widget/src/widgetresize/resizer.ts b/packages/ckeditor5-widget/src/widgetresize/resizer.ts index ccf7054881a..791b608ea8b 100644 --- a/packages/ckeditor5-widget/src/widgetresize/resizer.ts +++ b/packages/ckeditor5-widget/src/widgetresize/resizer.ts @@ -208,6 +208,7 @@ export default class Resizer extends ObservableMixin() { const newWidth = ( unit === '%' ? newSize.widthPercents : newSize.width ) + unit; writer.setStyle( 'width', newWidth, this._options.viewElement ); + writer.removeStyle( 'height', this._options.viewElement ); } ); // Get an actual image width, and: From c80b762ca61fe7fcd6015a02583e54573bdaa120 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 12 Jun 2023 12:44:34 +0200 Subject: [PATCH 025/118] Set inline aspect-ratio on image in editing downcast. --- .../src/imageresize/imageresizeediting.ts | 32 ++++++++++++++++++- .../src/imagesizeattributes.ts | 27 +++++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index e0333ebf04a..c915997d872 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -97,6 +97,7 @@ export default class ImageResizeEditing extends Plugin { */ private _registerConverters( imageType: 'imageBlock' | 'imageInline' ) { const editor = this.editor; + const imageUtils = editor.plugins.get( 'ImageUtils' ); // Dedicated converter to propagate image's attribute to the img tag. editor.conversion.for( 'downcast' ).add( dispatcher => @@ -118,7 +119,7 @@ export default class ImageResizeEditing extends Plugin { } ) ); - editor.conversion.for( 'downcast' ).add( dispatcher => + editor.conversion.for( 'dataDowncast' ).add( dispatcher => dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; @@ -137,6 +138,35 @@ export default class ImageResizeEditing extends Plugin { } ) ); + editor.conversion.for( 'editingDowncast' ).add( dispatcher => + dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const figure = conversionApi.mapper.toViewElement( data.item ); + + if ( data.attributeNewValue !== null ) { + viewWriter.setStyle( 'height', data.attributeNewValue, figure ); + viewWriter.addClass( 'image_resized', figure ); + } else { + viewWriter.removeStyle( 'height', figure ); + viewWriter.removeClass( 'image_resized', figure ); + } + + const viewElement = conversionApi.mapper.toViewElement( data.item as Element )!; + const img = imageUtils.findViewImgElement( viewElement )!; + + const resizedWidth = parseInt( data.item.getAttribute( 'resizedWidth' ) ); + const resizedHeight = parseInt( data.item.getAttribute( 'resizedHeight' ) ); + + if ( resizedWidth && resizedHeight ) { + viewWriter.setStyle( 'aspect-ratio', `${ resizedWidth }/${ resizedHeight }`, img ); + } + } ) + ); + editor.conversion.for( 'upcast' ) .attributeToAttribute( { view: { diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index e1c67109a78..74a19e5a684 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -86,12 +86,19 @@ export default class ImageSizeAttributes extends Plugin { } ); // Dedicated converter to propagate attributes to the element. - editor.conversion.for( 'downcast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'width', 'width' ); - attachDowncastConverter( dispatcher, 'height', 'height' ); + editor.conversion.for( 'dataDowncast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width', false ); + attachDowncastConverter( dispatcher, 'height', 'height', false ); } ); - function attachDowncastConverter( dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string ) { + editor.conversion.for( 'editingDowncast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width', true ); + attachDowncastConverter( dispatcher, 'height', 'height', true ); + } ); + + function attachDowncastConverter( + dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string, setAspectRatio: boolean + ) { dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; @@ -106,6 +113,18 @@ export default class ImageSizeAttributes extends Plugin { } else { viewWriter.removeAttribute( viewAttributeName, img ); } + + if ( !setAspectRatio ) { + return; + } + + const width = data.item.getAttribute( 'width' ); + const height = data.item.getAttribute( 'height' ); + const aspectRatio = img.getStyle( 'aspect-ratio' ); + + if ( width && height && !aspectRatio ) { + viewWriter.setStyle( 'aspect-ratio', `${ width }/${ height }`, img ); + } } ); } } From c2b8cff26571ddbe9e6de4472c172a5bf5cc16e1 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 13 Jun 2023 17:30:00 +0200 Subject: [PATCH 026/118] Update resizedHeight on image resize (when resize unit is px). --- .../src/imageresize/imageresizehandles.ts | 4 ++-- .../src/imageresize/resizeimagecommand.ts | 11 ++++++++--- packages/ckeditor5-widget/src/widgetresize.ts | 2 +- packages/ckeditor5-widget/src/widgetresize/resizer.ts | 5 +++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 04d3880b600..209db201865 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -111,7 +111,7 @@ export default class ImageResizeHandles extends Plugin { return !imageStyle || imageStyle == 'block' || imageStyle == 'alignCenter'; }, - onCommit( newValue ) { + onCommit( newWidth, newHeight ) { // Get rid of the CSS class in case the command execution that follows is unsuccessful // (e.g. Track Changes can override it and the new dimensions will not apply). Otherwise, // the presence of the class and the absence of the width style will cause it to take 100% @@ -120,7 +120,7 @@ export default class ImageResizeHandles extends Plugin { writer.removeClass( RESIZED_IMAGE_CLASS, widgetView ); } ); - editor.execute( 'resizeImage', { width: newValue } ); + editor.execute( 'resizeImage', { width: newWidth, height: newHeight } ); } } ); diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index df14fef73f0..9027900a085 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -58,7 +58,7 @@ export default class ResizeImageCommand extends Command { * @param options.width The new width of the image. * @fires execute */ - public override execute( options: { width: string | null } ): void { + public override execute( options: { width: string | null; height: string | null } ): void { const editor = this.editor; const model = editor.model; const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); @@ -66,13 +66,18 @@ export default class ResizeImageCommand extends Command { this.value = { width: options.width, - height: null + height: options.height }; if ( imageElement ) { model.change( writer => { writer.setAttribute( 'resizedWidth', options.width, imageElement ); - writer.removeAttribute( 'resizedHeight', imageElement ); + + if ( options.height ) { + writer.setAttribute( 'resizedHeight', options.height, imageElement ); + } else { + writer.removeAttribute( 'resizedHeight', imageElement ); + } } ); } } diff --git a/packages/ckeditor5-widget/src/widgetresize.ts b/packages/ckeditor5-widget/src/widgetresize.ts index 4b981639fe1..17ee65bc1ba 100644 --- a/packages/ckeditor5-widget/src/widgetresize.ts +++ b/packages/ckeditor5-widget/src/widgetresize.ts @@ -321,7 +321,7 @@ export interface ResizerOptions { * }; * ``` */ - onCommit: ( newValue: string ) => void; + onCommit: ( newWidth: string, newHeight: string | null ) => void; getResizeHost: ( widgetWrapper: HTMLElement ) => HTMLElement; diff --git a/packages/ckeditor5-widget/src/widgetresize/resizer.ts b/packages/ckeditor5-widget/src/widgetresize/resizer.ts index 791b608ea8b..f847967bbd1 100644 --- a/packages/ckeditor5-widget/src/widgetresize/resizer.ts +++ b/packages/ckeditor5-widget/src/widgetresize/resizer.ts @@ -242,12 +242,13 @@ export default class Resizer extends ObservableMixin() { */ public commit(): void { const unit = this._options.unit || '%'; - const newValue = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit; + const newWidth = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit; + const newHeight = ( unit === '%' ) ? null : this.state.proposedHeight + unit; // Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step. this._options.editor.editing.view.change( () => { this._cleanup(); - this._options.onCommit( newValue ); + this._options.onCommit( newWidth, newHeight ); } ); } From 834533499cd0c8cd90f81eaf843cf0a07ef4601f Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 13 Jun 2023 17:34:49 +0200 Subject: [PATCH 027/118] Add PFO to manual test with image size attributes. --- packages/ckeditor5-image/package.json | 2 ++ packages/ckeditor5-image/tests/manual/imagesizeattributes.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/package.json b/packages/ckeditor5-image/package.json index 61589e3dac9..04c7057610d 100644 --- a/packages/ckeditor5-image/package.json +++ b/packages/ckeditor5-image/package.json @@ -33,11 +33,13 @@ "@ckeditor/ckeditor5-essentials": "^38.0.1", "@ckeditor/ckeditor5-heading": "^38.0.1", "@ckeditor/ckeditor5-html-embed": "^38.0.1", + "@ckeditor/ckeditor5-html-support": "^38.0.1", "@ckeditor/ckeditor5-indent": "^38.0.1", "@ckeditor/ckeditor5-link": "^38.0.1", "@ckeditor/ckeditor5-list": "^38.0.1", "@ckeditor/ckeditor5-media-embed": "^38.0.1", "@ckeditor/ckeditor5-paragraph": "^38.0.1", + "@ckeditor/ckeditor5-paste-from-office": "^38.0.1", "@ckeditor/ckeditor5-table": "^38.0.1", "@ckeditor/ckeditor5-theme-lark": "^38.0.1", "@ckeditor/ckeditor5-typing": "^38.0.1", diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.js b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js index 999f0365676..8c0e86fc074 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js @@ -15,6 +15,7 @@ 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 PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; @@ -28,7 +29,8 @@ const commonConfig = { Indent, IndentBlock, CloudServices, - EasyImage + EasyImage, + PasteFromOffice ], toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', 'undo', 'redo', 'outdent', 'indent' ], From d3a2752305f3bd85612f19fc872c94ecdc2e4f76 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 13 Jun 2023 17:38:15 +0200 Subject: [PATCH 028/118] Add manual test for image size attributes with ghs. --- .../tests/manual/imagesizeattributesghs.html | 2 + .../tests/manual/imagesizeattributesghs.js | 74 +++++++++++++++++++ .../tests/manual/imagesizeattributesghs.md | 1 + 3 files changed, 77 insertions(+) create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html new file mode 100644 index 00000000000..d09b7adffe8 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html @@ -0,0 +1,2 @@ +
+
diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js new file mode 100644 index 00000000000..a776fb35cfd --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js @@ -0,0 +1,74 @@ +/** + * @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 CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices'; +import ImageResize from '../../src/imageresize'; +import ImageSizeAttributes from '../../src/imagesizeattributes'; +import ImageUpload from '../../src/imageupload'; +import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; +import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport'; + +import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; + +const commonConfig = { + plugins: [ + ArticlePluginSet, + ImageResize, + Code, + ImageSizeAttributes, + ImageUpload, + Indent, + IndentBlock, + CloudServices, + PasteFromOffice, + GeneralHtmlSupport + ], + 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' ] + }, + htmlSupport: { + allow: [ + // Enables all HTML features. + { + name: /.*/, + attributes: true, + classes: true, + styles: true + } + ], + disallow: [ + { + attributes: [ + { key: /^on(.*)/i, value: true }, + { key: /.*/, value: /(\b)(on\S+)(\s*)=|javascript:|(<\s*)(\/*)script/i }, + { key: /.*/, value: /data:(?!image\/(png|jpeg|gif|webp))/i } + ] + }, + { name: 'script' } + ] + }, + cloudServices: CS_CONFIG +}; + +( async function initTest() { + window.editor = await ClassicEditor + .create( document.querySelector( '#editor-ghs-with-width-height-attributes' ), commonConfig ) + .catch( err => { + console.error( err.stack ); + } ); +}() ); diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md new file mode 100644 index 00000000000..c581c9eac3b --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md @@ -0,0 +1 @@ +## Image size attributes From 226d1cd43624d81657ecc74970c5607f850be2f8 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 16 Jun 2023 13:05:15 +0200 Subject: [PATCH 029/118] Revert "Update resizedHeight on image resize (when resize unit is px)." This reverts commit c2b8cff26571ddbe9e6de4472c172a5bf5cc16e1. --- .../src/imageresize/imageresizehandles.ts | 4 ++-- .../src/imageresize/resizeimagecommand.ts | 11 +++-------- packages/ckeditor5-widget/src/widgetresize.ts | 2 +- packages/ckeditor5-widget/src/widgetresize/resizer.ts | 5 ++--- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 209db201865..04d3880b600 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -111,7 +111,7 @@ export default class ImageResizeHandles extends Plugin { return !imageStyle || imageStyle == 'block' || imageStyle == 'alignCenter'; }, - onCommit( newWidth, newHeight ) { + onCommit( newValue ) { // Get rid of the CSS class in case the command execution that follows is unsuccessful // (e.g. Track Changes can override it and the new dimensions will not apply). Otherwise, // the presence of the class and the absence of the width style will cause it to take 100% @@ -120,7 +120,7 @@ export default class ImageResizeHandles extends Plugin { writer.removeClass( RESIZED_IMAGE_CLASS, widgetView ); } ); - editor.execute( 'resizeImage', { width: newWidth, height: newHeight } ); + editor.execute( 'resizeImage', { width: newValue } ); } } ); diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index 9027900a085..df14fef73f0 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -58,7 +58,7 @@ export default class ResizeImageCommand extends Command { * @param options.width The new width of the image. * @fires execute */ - public override execute( options: { width: string | null; height: string | null } ): void { + public override execute( options: { width: string | null } ): void { const editor = this.editor; const model = editor.model; const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); @@ -66,18 +66,13 @@ export default class ResizeImageCommand extends Command { this.value = { width: options.width, - height: options.height + height: null }; if ( imageElement ) { model.change( writer => { writer.setAttribute( 'resizedWidth', options.width, imageElement ); - - if ( options.height ) { - writer.setAttribute( 'resizedHeight', options.height, imageElement ); - } else { - writer.removeAttribute( 'resizedHeight', imageElement ); - } + writer.removeAttribute( 'resizedHeight', imageElement ); } ); } } diff --git a/packages/ckeditor5-widget/src/widgetresize.ts b/packages/ckeditor5-widget/src/widgetresize.ts index 17ee65bc1ba..4b981639fe1 100644 --- a/packages/ckeditor5-widget/src/widgetresize.ts +++ b/packages/ckeditor5-widget/src/widgetresize.ts @@ -321,7 +321,7 @@ export interface ResizerOptions { * }; * ``` */ - onCommit: ( newWidth: string, newHeight: string | null ) => void; + onCommit: ( newValue: string ) => void; getResizeHost: ( widgetWrapper: HTMLElement ) => HTMLElement; diff --git a/packages/ckeditor5-widget/src/widgetresize/resizer.ts b/packages/ckeditor5-widget/src/widgetresize/resizer.ts index f847967bbd1..791b608ea8b 100644 --- a/packages/ckeditor5-widget/src/widgetresize/resizer.ts +++ b/packages/ckeditor5-widget/src/widgetresize/resizer.ts @@ -242,13 +242,12 @@ export default class Resizer extends ObservableMixin() { */ public commit(): void { const unit = this._options.unit || '%'; - const newWidth = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit; - const newHeight = ( unit === '%' ) ? null : this.state.proposedHeight + unit; + const newValue = ( unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth ) + unit; // Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step. this._options.editor.editing.view.change( () => { this._cleanup(); - this._options.onCommit( newWidth, newHeight ); + this._options.onCommit( newValue ); } ); } From 6ba54a61236cb31f87f0788ab3443094609a4624 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 21 Jun 2023 14:17:13 +0200 Subject: [PATCH 030/118] Reverse changes in image.css. --- packages/ckeditor5-image/theme/image.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 4fa08564a0c..1068658b33c 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -31,7 +31,7 @@ min-width: 100%; /* Preserve aspect ratio after introducing width and height attributes for image element. */ - height: auto; + /*height: auto;*/ } } @@ -65,7 +65,7 @@ max-width: 100%; /* Preserve aspect ratio after introducing width and height attributes for image element. */ - height: auto; + /*height: auto;*/ } } } From 603e9613a623096dcb626a671ecee5d70870be22 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 21 Jun 2023 14:43:40 +0200 Subject: [PATCH 031/118] Add height: auto for resized images (downcast). --- packages/ckeditor5-image/theme/imageresize.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/ckeditor5-image/theme/imageresize.css b/packages/ckeditor5-image/theme/imageresize.css index 1bb7afac7f5..54472f941d7 100644 --- a/packages/ckeditor5-image/theme/imageresize.css +++ b/packages/ckeditor5-image/theme/imageresize.css @@ -3,6 +3,12 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +/* TODO */ +.ck-content figure.image_resized img, +.ck-content img.image_resized { + height: auto; +} + .ck-content .image.image_resized { max-width: 100%; /* @@ -25,6 +31,12 @@ } .ck.ck-editor__editable { + /* TODO */ + & .image.image_resized img, + & .image-inline.image_resized img { + height: auto; + } + /* The resized inline image nested in the table should respect its parent size. See https://github.com/ckeditor/ckeditor5/issues/9117. */ & td, From 007ddec87cb33eb9369d7e016dc88164a09c5ca8 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 21 Jun 2023 14:50:17 +0200 Subject: [PATCH 032/118] Change upcast and downcast of image attributes. --- .../src/imageresize/imageresizeediting.ts | 53 +++++++--------- .../src/imagesizeattributes.ts | 63 ++++++++++++++----- packages/ckeditor5-image/src/imageutils.ts | 11 ++++ 3 files changed, 81 insertions(+), 46 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index c915997d872..7cc6700c50a 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -119,26 +119,7 @@ export default class ImageResizeEditing extends Plugin { } ) ); - editor.conversion.for( 'dataDowncast' ).add( dispatcher => - dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; - } - - const viewWriter = conversionApi.writer; - const figure = conversionApi.mapper.toViewElement( data.item ); - - if ( data.attributeNewValue !== null ) { - viewWriter.setStyle( 'height', data.attributeNewValue, figure ); - viewWriter.addClass( 'image_resized', figure ); - } else { - viewWriter.removeStyle( 'height', figure ); - viewWriter.removeClass( 'image_resized', figure ); - } - } ) - ); - - editor.conversion.for( 'editingDowncast' ).add( dispatcher => + editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; @@ -154,16 +135,6 @@ export default class ImageResizeEditing extends Plugin { viewWriter.removeStyle( 'height', figure ); viewWriter.removeClass( 'image_resized', figure ); } - - const viewElement = conversionApi.mapper.toViewElement( data.item as Element )!; - const img = imageUtils.findViewImgElement( viewElement )!; - - const resizedWidth = parseInt( data.item.getAttribute( 'resizedWidth' ) ); - const resizedHeight = parseInt( data.item.getAttribute( 'resizedHeight' ) ); - - if ( resizedWidth && resizedHeight ) { - viewWriter.setStyle( 'aspect-ratio', `${ resizedWidth }/${ resizedHeight }`, img ); - } } ) ); @@ -177,7 +148,16 @@ export default class ImageResizeEditing extends Plugin { }, model: { key: 'resizedWidth', - value: ( viewElement: ViewElement ) => viewElement.getStyle( 'width' ) + value: ( viewElement: ViewElement ) => { + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + if ( widthStyle && heightStyle ) { + return null; + } + + return viewElement.getStyle( 'width' ); + } } } ); @@ -191,7 +171,16 @@ export default class ImageResizeEditing extends Plugin { }, model: { key: 'resizedHeight', - value: ( viewElement: ViewElement ) => viewElement.getStyle( 'height' ) + value: ( viewElement: ViewElement ) => { + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + if ( widthStyle && heightStyle ) { + return null; + } + + return viewElement.getStyle( 'height' ); + } } } ); } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 74a19e5a684..c438e2eb72a 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -60,6 +60,27 @@ export default class ImageSizeAttributes extends Plugin { const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img'; editor.conversion.for( 'upcast' ) + .attributeToAttribute( { + view: { + name: imageType === 'imageBlock' ? 'figure' : 'img', + styles: { + width: /.+/ + } + }, + model: { + key: 'width', + value: ( viewElement: ViewElement ) => { + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + if ( widthStyle && heightStyle ) { + return widthStyle; + } + + return null; + } + } + } ) .attributeToAttribute( { view: { name: viewElementName, @@ -72,6 +93,27 @@ export default class ImageSizeAttributes extends Plugin { value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' ) } } ) + .attributeToAttribute( { + view: { + name: imageType === 'imageBlock' ? 'figure' : 'img', + styles: { + height: /.+/ + } + }, + model: { + key: 'height', + value: ( viewElement: ViewElement ) => { + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + if ( widthStyle && heightStyle ) { + return heightStyle; + } + + return null; + } + } + } ) .attributeToAttribute( { view: { name: viewElementName, @@ -86,18 +128,13 @@ export default class ImageSizeAttributes extends Plugin { } ); // Dedicated converter to propagate attributes to the element. - editor.conversion.for( 'dataDowncast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'width', 'width', false ); - attachDowncastConverter( dispatcher, 'height', 'height', false ); - } ); - - editor.conversion.for( 'editingDowncast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'width', 'width', true ); - attachDowncastConverter( dispatcher, 'height', 'height', true ); + editor.conversion.for( 'downcast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width' ); + attachDowncastConverter( dispatcher, 'height', 'height' ); } ); function attachDowncastConverter( - dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string, setAspectRatio: boolean + dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string ) { dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { @@ -114,18 +151,16 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } - if ( !setAspectRatio ) { - return; - } - const width = data.item.getAttribute( 'width' ); const height = data.item.getAttribute( 'height' ); + const isResized = data.item.hasAttribute( 'resizedWidth' ); const aspectRatio = img.getStyle( 'aspect-ratio' ); - if ( width && height && !aspectRatio ) { + if ( width && height && !aspectRatio && isResized ) { viewWriter.setStyle( 'aspect-ratio', `${ width }/${ height }`, img ); } } ); } } } + diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 73f584e28df..c1f57694ec0 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -293,6 +293,17 @@ export default class ImageUtils extends Plugin { return super.destroy(); } + + /** + * Returns parsed value of the size, but only if it contains unit: px. + */ + public getSizeInPx( size: string | undefined ): number | null { + if ( size && size.endsWith( 'px' ) ) { + return parseInt( size ); + } + + return null; + } } /** From 7d429e3b819f8f2fe59bbf383a1b32c17739ea19 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 21 Jun 2023 14:52:00 +0200 Subject: [PATCH 033/118] Set aspect-ratio for image view when resizing begins. --- .../src/imageresize/imageresizehandles.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 04d3880b600..68f76923346 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -10,6 +10,7 @@ import type { Element, ViewContainerElement, ViewElement } from 'ckeditor5/src/engine'; import { Plugin } from 'ckeditor5/src/core'; import { WidgetResize } from 'ckeditor5/src/widget'; +import ImageUtils from '../imageutils'; import ImageLoadObserver, { type ImageLoadedEvent } from '../image/imageloadobserver'; import type ResizeImageCommand from './resizeimagecommand'; @@ -37,7 +38,7 @@ export default class ImageResizeHandles extends Plugin { * @inheritDoc */ public static get requires() { - return [ WidgetResize ] as const; + return [ WidgetResize, ImageUtils ] as const; } /** @@ -63,6 +64,7 @@ export default class ImageResizeHandles extends Plugin { private _setupResizerCreator(): void { const editor = this.editor; const editingView = editor.editing.view; + const imageUtils = editor.plugins.get( 'ImageUtils' ); editingView.addObserver( ImageLoadObserver ); @@ -132,6 +134,19 @@ export default class ImageResizeHandles extends Plugin { } } ); + resizer.on( 'begin', () => { + const img = imageUtils.findViewImgElement( imageView )!; + const aspectRatio = img.getStyle( 'aspect-ratio' ); + const widthAttr = imageModel.getAttribute( 'width' ); + const heightAttr = imageModel.getAttribute( 'height' ); + + if ( widthAttr && heightAttr && !aspectRatio ) { + editingView.change( writer => { + writer.setStyle( 'aspect-ratio', `${ widthAttr }/${ heightAttr }`, img ); + } ); + } + } ); + resizer.bind( 'isEnabled' ).to( this ); } ); } From 1f1be62eb1d66790e1a3159c1b8a9ffb16619234 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 21 Jun 2023 15:37:53 +0200 Subject: [PATCH 034/118] Manual test with many image resize attributes test cases. --- .../manual/imagesizeattributesallcases.html | 50 +++ .../manual/imagesizeattributesallcases.js | 385 ++++++++++++++++++ .../manual/imagesizeattributesallcases.md | 1 + 3 files changed, 436 insertions(+) create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.html create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js create mode 100644 packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.html b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.html new file mode 100644 index 00000000000..0e06131a879 --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.html @@ -0,0 +1,50 @@ + + diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js new file mode 100644 index 00000000000..08d97268d5f --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -0,0 +1,385 @@ +/** + * @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, CKEditorInspector */ + +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 PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; +import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; + +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, + PasteFromOffice + ], + 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 +}; + +const configPx = { + plugins: [ + ArticlePluginSet, + ImageResize, + Code, + ImageSizeAttributes, + ImageUpload, + Indent, + IndentBlock, + CloudServices, + EasyImage, + PasteFromOffice + ], + toolbar: [ 'heading', '|', 'bold', 'italic', 'link', + 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', 'undo', 'redo', 'outdent', 'indent' ], + image: { + resizeUnit: 'px', + toolbar: [ 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', '|', 'toggleImageCaption', 'resizeImage' ] + }, + table: { + contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ], + tableToolbar: [ 'bold', 'italic' ] + }, + cloudServices: CS_CONFIG +}; + +const editors = [ + { + id: 'inline1', + title: '[Inline] plain (no attributes, no styles)', + config: commonConfig, + data: '

' + }, + { + id: 'inline2', + title: '[Inline] natural size | width + height attributes: Resize in %', + config: commonConfig, + data: '

' + }, + { + id: 'inline3', + config: commonConfig, + data: '

', + title: '[Inline] natural size | width + height attributes: Resized (width % style)' + }, + { + id: 'inline4', + config: commonConfig, + data: '

', + title: '[Inline] natural size | width + height attributes: Resized (width % style)' + }, + { + id: 'inline5', + title: '[Inline] natural size | width + height attributes: Resize in px', + config: configPx, + data: '

' + }, + { + id: 'inline6', + title: '[Inline] natural size | width + height attributes: Resized (width px style only)', + config: configPx, + data: '

' + }, + { + id: 'inline7', + title: '[Inline] natural size | width + height attributes: Resized (width and height px style)', + config: configPx, + data: '

' + }, + { + id: 'inline8', + title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in %', + config: commonConfig, + data: '

' + }, + { + id: 'inline9', + title: '[Inline] natural size | only resize in % (only width style)', + config: commonConfig, + data: '

' + }, + { + id: 'inline10', + title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in px', + config: configPx, + data: '

' + }, + { + id: 'inline11', + title: '[Inline] broken aspect ratio | width + height attributes', + config: commonConfig, + data: '

' + }, + { + id: 'inline12', + title: '[Inline] broken aspect ratio | styles only (w/o width & height attributes)', + config: commonConfig, + data: '

' + }, + { + id: 'block1', + title: '[Block] plain (no attributes, no styles)', + config: commonConfig, + data: '
' + }, + { + id: 'block2', + title: '[Block] natural size | width + height attributes: Resize in %', + config: commonConfig, + data: '
' + }, + { + id: 'block3', + title: '[Block] natural size | width + height attributes: Resized (width % style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block4', + title: '[Block] natural size | width + height attributes: Resized (width % style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block5', + title: '[Block] natural size | width + height attributes: Resize in px', + config: configPx, + data: '
' + }, + { + id: 'block6', + title: '[Block] natural size | width + height attributes: Resized (width px style only)', + config: configPx, + data: '
' + + '
' + }, + { + id: 'block7', + title: '[Block] natural size | width + height attributes: Resized (width and height px style)', + config: configPx, + data: '
' + + '
' + }, + { + id: 'block8', + title: '[Block] natural size | styles only (w/o width & height attributes): Resize in %', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block9', + title: '[Block] natural size | only resize in % (only width style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block10', + title: '[Block] natural size | styles only (w/o width & height attributes): Resize in px', + config: configPx, + data: '
' + + '
' + }, + { + id: 'block11', + title: '[Block] broken aspect ratio | width + height attributes', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block12', + title: '[Block] broken aspect ratio | styles only (w/o width & height attributes)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'inline101', + title: '[Inline] natural size | width + height attributes: Resized (height % style)', + config: commonConfig, + data: '

' + }, + { + id: 'inline102', + title: '[Inline] natural size | width + height attributes: Resized (height px style)', + config: configPx, + data: '

' + }, + { + id: 'inline103', + title: '[Inline] natural size | only resize in % (only height style)', + config: commonConfig, + data: '

' + }, + { + id: 'inline104', + title: '[Inline] natural size | only resize in px (only height style)', + config: configPx, + data: '

' + }, + { + id: 'inline105', + title: '[Inline] width + height attributes: Resized (height & width % style)', + config: commonConfig, + data: '

' + }, + { + id: 'inline106', + title: '[Inline] only resize in % (height & width % style)', + config: commonConfig, + data: '

' + }, + { + id: 'block101', + title: '[Block] natural size | width + height attributes: Resized (height % style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block102', + title: '[Block] natural size | width + height attributes: Resized (height px style)', + config: configPx, + data: '
' + + '
' + }, + { + id: 'block103', + title: '[Block] natural size | only resize in % (only height style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block104', + title: '[Block] natural size | only resize in px (only height style)', + config: configPx, + data: '
' + + '
' + }, + { + id: 'block105', + title: '[Block] width + height attributes: Resized (height & width % style)', + config: commonConfig, + data: '
' + + '
' + }, + { + id: 'block106', + title: '[Block] only resize in % (height & width % style)', + config: commonConfig, + data: '
' + + '
' + } +]; + +for ( const editorObj of editors ) { + insertEditorStructure( editorObj ); + + ( async function initTest() { + const domElement = document.querySelector( `#${ editorObj.id }` ); + + await ClassicEditor + .create( domElement, { ...editorObj.config, initialData: editorObj.data } ) + .then( editor => { + window[ editorObj.id ] = editor; + + editor.model.document.on( 'change:data', () => { + updateLogsAndData( domElement, editor ); + } ); + + logInitialData( domElement, editorObj ); + updateLogsAndData( domElement, editor ); + + CKEditorInspector.attach( { [ editorObj.id ]: editor } ); + } ) + .catch( err => { + console.error( err.stack ); + } ); + }() ); +} + +function insertEditorStructure( editorObj ) { + const colorClass = editorObj.id.startsWith( 'inline' ) ? 'inlineColor' : 'blockColor'; + + document.body.insertAdjacentHTML( 'beforeend', + `

${ editorObj.title }

` + + `Editor id: ${ editorObj.id }` + + '
' + + `
` + + '
' + + `

Initial data:

` + + `

Model:

` + + '
' + ); +} + +function logInitialData( domElement, editorObj ) { + const editorDataText = domElement.parentElement.querySelector( '.editor-data-text' ); + + editorDataText.insertAdjacentText( 'beforeend', editorObj.data ); + editorDataText.insertAdjacentHTML( 'beforeend', '

Output data:

' ); +} + +function updateLogsAndData( domElement, editor ) { + const editorModel = domElement.parentElement.querySelector( '.editor-model' ); + const editorDataHtml = domElement.parentElement.querySelector( '.editor-data' ); + const editorDataText = domElement.parentElement.querySelector( '.editor-data-text' ); + + // model + editorModel.insertAdjacentText( 'beforeend', getData( editor.model, { withoutSelection: true } ) ); + editorModel.insertAdjacentHTML( 'beforeend', '

---

' ); + + // data (html) + editorDataHtml.innerHTML = editor.getData(); + + // data (output data) + editorDataText.insertAdjacentText( 'beforeend', editor.getData() ); + editorDataText.insertAdjacentHTML( 'beforeend', '

---

' ); +} diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md new file mode 100644 index 00000000000..c581c9eac3b --- /dev/null +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md @@ -0,0 +1 @@ +## Image size attributes From fc13ab70c2b4ecb6553ebeb2f615a865306864d3 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 23 Jun 2023 13:33:11 +0200 Subject: [PATCH 035/118] Upcast height style to height attribute if no other styles/attributes are provided. --- .../src/imageresize/imageresizeediting.ts | 4 ++++ packages/ckeditor5-image/src/imagesizeattributes.ts | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 7cc6700c50a..018a0063f58 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -179,6 +179,10 @@ export default class ImageResizeEditing extends Plugin { return null; } + if ( heightStyle ) { + return null; + } + return viewElement.getStyle( 'height' ); } } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index c438e2eb72a..edaf12d5cfc 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -110,6 +110,15 @@ export default class ImageSizeAttributes extends Plugin { return heightStyle; } + const img = imageUtils.findViewImgElement( viewElement )!; + const imgHasAttributes = img.getAttribute( 'width' ) || img.getAttribute( 'height' ); + + if ( heightStyle && !viewElement.getStyle( 'width' ) ) { + if ( !imgHasAttributes ) { + return heightStyle; + } + } + return null; } } From e29e786844be92ba89980ddbe9848af5a8e9125d Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 23 Jun 2023 15:24:07 +0200 Subject: [PATCH 036/118] Convert resizedHeight to resizedWidth if there are attributes. --- .../src/imageresize/imageresizeediting.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 018a0063f58..e53d0a6207b 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -133,7 +133,9 @@ export default class ImageResizeEditing extends Plugin { viewWriter.addClass( 'image_resized', figure ); } else { viewWriter.removeStyle( 'height', figure ); - viewWriter.removeClass( 'image_resized', figure ); + if ( !figure.getStyle( 'width' ) ) { + viewWriter.removeClass( 'image_resized', figure ); + } } } ) ); @@ -186,6 +188,38 @@ export default class ImageResizeEditing extends Plugin { return viewElement.getStyle( 'height' ); } } + } ) + .attributeToAttribute( { + view: { + name: imageType === 'imageBlock' ? 'figure' : 'img', + styles: { + height: /.+/ + } + }, + model: { + key: 'resizedWidth', + value: ( viewElement: ViewElement ) => { + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + if ( !widthStyle && !heightStyle ) { + return null; + } + + const img = imageUtils.findViewImgElement( viewElement )!; + const widthAttr = img.getAttribute( 'width' ); + const heightAttr = img.getAttribute( 'height' ); + const imgHasAttributes = widthAttr || heightAttr; + + if ( heightStyle && !viewElement.getStyle( 'width' ) ) { + if ( imgHasAttributes ) { + return Math.round( parseInt( widthAttr! ) * heightStyle / parseInt( heightAttr! ) ) + 'px'; + } + } + + return null; + } + } } ); } } From 02578a876dc2fa99b49bf23e37cfb7c76d17cc36 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 12:50:13 +0200 Subject: [PATCH 037/118] Correct manual test for image resize in px style only. --- .../ckeditor5-image/tests/manual/imagesizeattributesallcases.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index 08d97268d5f..d1be3ef7eb7 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -259,7 +259,7 @@ const editors = [ id: 'inline104', title: '[Inline] natural size | only resize in px (only height style)', config: configPx, - data: '

' + data: '

' }, { id: 'inline105', From 470be3feaf7c16d5b830c91d38b61ffec655220e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 12:50:59 +0200 Subject: [PATCH 038/118] Upcast image aspect-ratio. --- .../src/imagesizeattributes.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index edaf12d5cfc..ee349bd1812 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -43,11 +43,11 @@ export default class ImageSizeAttributes extends Plugin { */ private _registerSchema(): void { if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { - this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height' ] } ); + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height', 'aspectRatio' ] } ); } if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { - this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height' ] } ); + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height', 'aspectRatio' ] } ); } } @@ -60,6 +60,18 @@ export default class ImageSizeAttributes extends Plugin { const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img'; editor.conversion.for( 'upcast' ) + .attributeToAttribute( { + view: { + name: viewElementName, + styles: { + 'aspect-ratio': /.+/ + } + }, + model: { + key: 'aspectRatio', + value: ( viewElement: ViewElement ) => viewElement.getStyle( 'aspect-ratio' ) + } + } ) .attributeToAttribute( { view: { name: imageType === 'imageBlock' ? 'figure' : 'img', @@ -136,6 +148,24 @@ export default class ImageSizeAttributes extends Plugin { } } ); + editor.conversion.for( 'downcast' ).add( dispatcher => { + dispatcher.on( `attribute:aspectRatio:${ 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.setStyle( 'aspect-ratio', data.attributeNewValue as string, img ); + } else { + viewWriter.removeAttribute( 'aspect-ratio', img ); + } + } ); + } ); + // Dedicated converter to propagate attributes to the element. editor.conversion.for( 'downcast' ).add( dispatcher => { attachDowncastConverter( dispatcher, 'width', 'width' ); @@ -160,6 +190,10 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } + if ( img.getAttribute( 'aspectRatio' ) ) { + return; + } + const width = data.item.getAttribute( 'width' ); const height = data.item.getAttribute( 'height' ); const isResized = data.item.hasAttribute( 'resizedWidth' ); From 02b1fb03d66e5d7121b34ab599bc4acb59027801 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 12:51:12 +0200 Subject: [PATCH 039/118] Revert "Upcast image aspect-ratio." This reverts commit 470be3feaf7c16d5b830c91d38b61ffec655220e. --- .../src/imagesizeattributes.ts | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index ee349bd1812..edaf12d5cfc 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -43,11 +43,11 @@ export default class ImageSizeAttributes extends Plugin { */ private _registerSchema(): void { if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) { - this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height', 'aspectRatio' ] } ); + this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height' ] } ); } if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) { - this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height', 'aspectRatio' ] } ); + this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height' ] } ); } } @@ -60,18 +60,6 @@ export default class ImageSizeAttributes extends Plugin { const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img'; editor.conversion.for( 'upcast' ) - .attributeToAttribute( { - view: { - name: viewElementName, - styles: { - 'aspect-ratio': /.+/ - } - }, - model: { - key: 'aspectRatio', - value: ( viewElement: ViewElement ) => viewElement.getStyle( 'aspect-ratio' ) - } - } ) .attributeToAttribute( { view: { name: imageType === 'imageBlock' ? 'figure' : 'img', @@ -148,24 +136,6 @@ export default class ImageSizeAttributes extends Plugin { } } ); - editor.conversion.for( 'downcast' ).add( dispatcher => { - dispatcher.on( `attribute:aspectRatio:${ 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.setStyle( 'aspect-ratio', data.attributeNewValue as string, img ); - } else { - viewWriter.removeAttribute( 'aspect-ratio', img ); - } - } ); - } ); - // Dedicated converter to propagate attributes to the element. editor.conversion.for( 'downcast' ).add( dispatcher => { attachDowncastConverter( dispatcher, 'width', 'width' ); @@ -190,10 +160,6 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } - if ( img.getAttribute( 'aspectRatio' ) ) { - return; - } - const width = data.item.getAttribute( 'width' ); const height = data.item.getAttribute( 'height' ); const isResized = data.item.hasAttribute( 'resizedWidth' ); From 941e9d14ba84e15d60fe16c6d988823905bd8451 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 12:59:41 +0200 Subject: [PATCH 040/118] Revert "Convert resizedHeight to resizedWidth if there are attributes." This reverts commit e29e786844be92ba89980ddbe9848af5a8e9125d. --- .../src/imageresize/imageresizeediting.ts | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index e53d0a6207b..018a0063f58 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -133,9 +133,7 @@ export default class ImageResizeEditing extends Plugin { viewWriter.addClass( 'image_resized', figure ); } else { viewWriter.removeStyle( 'height', figure ); - if ( !figure.getStyle( 'width' ) ) { - viewWriter.removeClass( 'image_resized', figure ); - } + viewWriter.removeClass( 'image_resized', figure ); } } ) ); @@ -188,38 +186,6 @@ export default class ImageResizeEditing extends Plugin { return viewElement.getStyle( 'height' ); } } - } ) - .attributeToAttribute( { - view: { - name: imageType === 'imageBlock' ? 'figure' : 'img', - styles: { - height: /.+/ - } - }, - model: { - key: 'resizedWidth', - value: ( viewElement: ViewElement ) => { - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - if ( !widthStyle && !heightStyle ) { - return null; - } - - const img = imageUtils.findViewImgElement( viewElement )!; - const widthAttr = img.getAttribute( 'width' ); - const heightAttr = img.getAttribute( 'height' ); - const imgHasAttributes = widthAttr || heightAttr; - - if ( heightStyle && !viewElement.getStyle( 'width' ) ) { - if ( imgHasAttributes ) { - return Math.round( parseInt( widthAttr! ) * heightStyle / parseInt( heightAttr! ) ) + 'px'; - } - } - - return null; - } - } } ); } } From 9ccaf9e52ac902b25e6f8961f8dea2bf3623b758 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 12:59:57 +0200 Subject: [PATCH 041/118] Revert "Upcast height style to height attribute if no other styles/attributes are provided." This reverts commit fc13ab70c2b4ecb6553ebeb2f615a865306864d3. --- .../src/imageresize/imageresizeediting.ts | 4 ---- packages/ckeditor5-image/src/imagesizeattributes.ts | 9 --------- 2 files changed, 13 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 018a0063f58..7cc6700c50a 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -179,10 +179,6 @@ export default class ImageResizeEditing extends Plugin { return null; } - if ( heightStyle ) { - return null; - } - return viewElement.getStyle( 'height' ); } } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index edaf12d5cfc..c438e2eb72a 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -110,15 +110,6 @@ export default class ImageSizeAttributes extends Plugin { return heightStyle; } - const img = imageUtils.findViewImgElement( viewElement )!; - const imgHasAttributes = img.getAttribute( 'width' ) || img.getAttribute( 'height' ); - - if ( heightStyle && !viewElement.getStyle( 'width' ) ) { - if ( !imgHasAttributes ) { - return heightStyle; - } - } - return null; } } From bbb9f4a91618631d1596ba41cb674d31b36443ed Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 13:57:54 +0200 Subject: [PATCH 042/118] Do not remove image_resized class when removing style height but style width is still set. --- .../ckeditor5-image/src/imageresize/imageresizeediting.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 7cc6700c50a..7192a58a857 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -133,7 +133,10 @@ export default class ImageResizeEditing extends Plugin { viewWriter.addClass( 'image_resized', figure ); } else { viewWriter.removeStyle( 'height', figure ); - viewWriter.removeClass( 'image_resized', figure ); + + if ( !figure.getStyle( 'width' ) ) { + viewWriter.removeClass( 'image_resized', figure ); + } } } ) ); From 58ec57d7280d31eb2934eb4cf5f31d1d3b781d36 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 14:04:12 +0200 Subject: [PATCH 043/118] Remove unused styles. --- packages/ckeditor5-image/theme/image.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 1068658b33c..0d857998e19 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -29,9 +29,6 @@ /* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */ min-width: 100%; - - /* Preserve aspect ratio after introducing width and height attributes for image element. */ - /*height: auto;*/ } } @@ -63,9 +60,6 @@ /* 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;*/ } } } From aafb59ee3c44726fc5be24f20be6e5cb81996a7a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 26 Jun 2023 18:16:16 +0200 Subject: [PATCH 044/118] For image inline in editing set height style on img instead of span. --- .../src/imageresize/imageresizeediting.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 7192a58a857..002fa80535c 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -119,7 +119,20 @@ export default class ImageResizeEditing extends Plugin { } ) ); - editor.conversion.for( 'downcast' ).add( dispatcher => + editor.conversion.for( 'dataDowncast' ).add( dispatcher => + dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { + if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { + return; + } + + const viewWriter = conversionApi.writer; + const viewElement = conversionApi.mapper.toViewElement( data.item ); + + viewWriter.setStyle( 'height', data.attributeNewValue, viewElement ); + } ) + ); + + editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; @@ -127,12 +140,13 @@ export default class ImageResizeEditing extends Plugin { const viewWriter = conversionApi.writer; const figure = conversionApi.mapper.toViewElement( data.item ); + const img = imageUtils.findViewImgElement( figure ); + const target = imageType === 'imageInline' ? img : figure; if ( data.attributeNewValue !== null ) { - viewWriter.setStyle( 'height', data.attributeNewValue, figure ); - viewWriter.addClass( 'image_resized', figure ); + viewWriter.setStyle( 'height', data.attributeNewValue, target ); } else { - viewWriter.removeStyle( 'height', figure ); + viewWriter.removeStyle( 'height', target ); if ( !figure.getStyle( 'width' ) ) { viewWriter.removeClass( 'image_resized', figure ); From e4faaa894110516172e7ef48347f9a8d7c6ad2b7 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 28 Jun 2023 15:02:42 +0200 Subject: [PATCH 045/118] Remove style height from img after starting resizing. --- .../ckeditor5-image/src/imageresize/imageresizehandles.ts | 8 ++++++++ packages/ckeditor5-widget/src/widgetresize/resizer.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 68f76923346..78c92f4b493 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -132,6 +132,14 @@ export default class ImageResizeHandles extends Plugin { writer.addClass( RESIZED_IMAGE_CLASS, widgetView ); } ); } + + const img = imageUtils.findViewImgElement( imageView )!; + + if ( img.getStyle( 'height' ) ) { + editingView.change( writer => { + writer.removeStyle( 'height', img ); + } ); + } } ); resizer.on( 'begin', () => { diff --git a/packages/ckeditor5-widget/src/widgetresize/resizer.ts b/packages/ckeditor5-widget/src/widgetresize/resizer.ts index 791b608ea8b..ccf7054881a 100644 --- a/packages/ckeditor5-widget/src/widgetresize/resizer.ts +++ b/packages/ckeditor5-widget/src/widgetresize/resizer.ts @@ -208,7 +208,6 @@ export default class Resizer extends ObservableMixin() { const newWidth = ( unit === '%' ? newSize.widthPercents : newSize.width ) + unit; writer.setStyle( 'width', newWidth, this._options.viewElement ); - writer.removeStyle( 'height', this._options.viewElement ); } ); // Get an actual image width, and: From 5a851e530c3f54c22426cfb2cf0c0272f49d1c1f Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 28 Jun 2023 17:09:25 +0200 Subject: [PATCH 046/118] Remove style height after starting resizing - fix for block images. --- .../ckeditor5-image/src/imageresize/imageresizehandles.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 78c92f4b493..636b29b1dfe 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -133,11 +133,11 @@ export default class ImageResizeHandles extends Plugin { } ); } - const img = imageUtils.findViewImgElement( imageView )!; + const target = imageModel.name === 'imageInline' ? imageView : widgetView; - if ( img.getStyle( 'height' ) ) { + if ( target.getStyle( 'height' ) ) { editingView.change( writer => { - writer.removeStyle( 'height', img ); + writer.removeStyle( 'height', target ); } ); } } ); From d8cea9a39eaca1ec83fbd89e1e2cd931652547eb Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 10:38:55 +0200 Subject: [PATCH 047/118] Tests: image resize schema allowed attributes. --- .../tests/imageresize/imageresizeediting.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index ad10f15334b..cb42f22204e 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -208,11 +208,23 @@ describe( 'ImageResizeEditing', () => { await newEditor.destroy(); } ); + it( 'allows the resizedHeight attribute when ImageBlock plugin is enabled', async () => { + const newEditor = await ClassicEditor.create( editorElement, { plugins: [ ImageBlockEditing, ImageResizeEditing ] } ); + expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageBlock' ], 'resizedHeight' ) ).to.be.true; + await newEditor.destroy(); + } ); + 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' ], 'resizedWidth' ) ).to.be.true; await newEditor.destroy(); } ); + + it( 'allows the resizedHeight attribute when ImageInline plugin is enabled', async () => { + const newEditor = await ClassicEditor.create( editorElement, { plugins: [ ImageInlineEditing, ImageResizeEditing ] } ); + expect( newEditor.model.schema.checkAttribute( [ '$root', 'imageInline' ], 'resizedHeight' ) ).to.be.true; + await newEditor.destroy(); + } ); } ); describe( 'command', () => { From 7b3e614325a64bd78363a67c737f76fb13d83b58 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 11:43:45 +0200 Subject: [PATCH 048/118] Refactor editing downcast for resizedHeight. --- .../ckeditor5-image/src/imageresize/imageresizeediting.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 002fa80535c..d24b5e8105c 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -145,12 +145,6 @@ export default class ImageResizeEditing extends Plugin { if ( data.attributeNewValue !== null ) { viewWriter.setStyle( 'height', data.attributeNewValue, target ); - } else { - viewWriter.removeStyle( 'height', target ); - - if ( !figure.getStyle( 'width' ) ) { - viewWriter.removeClass( 'image_resized', figure ); - } } } ) ); From ecbd6e95216bf6217e34269b24013ef984208d0f Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 12:00:02 +0200 Subject: [PATCH 049/118] Tests: image block resizedHeight downcast. --- .../tests/imageresize/imageresizeediting.js | 141 +++++++++++++----- 1 file changed, 107 insertions(+), 34 deletions(-) diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index cb42f22204e..681b14d71b8 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -18,6 +18,7 @@ import ImageBlockEditing from '../../src/image/imageblockediting'; import ImageInlineEditing from '../../src/image/imageinlineediting'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; import { focusEditor } from '@ckeditor/ckeditor5-widget/tests/widgetresize/_utils/utils'; import { IMAGE_SRC_FIXTURE } from './_utils/utils'; @@ -84,55 +85,127 @@ describe( 'ImageResizeEditing', () => { editor = await createEditor(); } ); - it( 'upcasts 100px width correctly', () => { - editor.setData( `
` ); + describe( 'width', () => { + it( 'upcasts 100px width correctly', () => { + editor.setData( `
` ); - expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '100px' ); - } ); + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '100px' ); + } ); - it( 'upcasts 50% width correctly', () => { - editor.setData( `
` ); + it( 'upcasts 50% width correctly', () => { + editor.setData( `
` ); - expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); - } ); + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); + } ); - it( 'downcasts 100px width correctly', () => { - setData( editor.model, `` ); + it( 'downcasts 100px width correctly', () => { + setData( editor.model, `` ); - expect( editor.getData() ) - .to.equal( `
` ); - } ); + expect( editor.getData() ) + .to.equal( `
` ); + } ); - it( 'downcasts 50% width correctly', () => { - setData( editor.model, `` ); + it( 'downcasts 50% width correctly', () => { + setData( editor.model, `` ); - expect( editor.getData() ) - .to.equal( `
` ); - } ); + expect( editor.getData() ) + .to.equal( `
` ); + } ); - it( 'removes style and extra class when no longer resized', () => { - setData( editor.model, `` ); + it( 'removes style and extra class when no longer resized', () => { + setData( editor.model, `` ); - const imageModel = editor.model.document.getRoot().getChild( 0 ); + const imageModel = editor.model.document.getRoot().getChild( 0 ); - editor.model.change( writer => { - writer.removeAttribute( 'resizedWidth', imageModel ); + editor.model.change( writer => { + writer.removeAttribute( 'resizedWidth', imageModel ); + } ); + + expect( editor.getData() ) + .to.equal( `
` ); } ); - expect( editor.getData() ) - .to.equal( `
` ); + it( 'doesn\'t downcast consumed tokens', () => { + editor.conversion.for( 'downcast' ).add( dispatcher => + dispatcher.on( 'attribute:resizedWidth:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedWidth:imageBlock' ); + }, { priority: 'high' } ) + ); + setData( editor.model, `` ); + + expect( editor.getData() ) + .to.equal( `
` ); + } ); } ); - it( 'doesn\'t downcast consumed tokens', () => { - editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:resizedWidth:imageBlock', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:resizedWidth:imageBlock' ); - }, { priority: 'high' } ) - ); - setData( editor.model, `` ); + describe( 'height', () => { + describe( 'data downcast', () => { + it( 'downcasts 100px height correctly', () => { + setData( editor.model, `` ); + + expect( editor.getData() ) + .to.equal( `
` ); + } ); + + it( 'downcasts 50% height correctly', () => { + setData( editor.model, `` ); + + expect( editor.getData() ) + .to.equal( `
` ); + } ); + + it( 'doesn\'t downcast consumed tokens', () => { + editor.conversion.for( 'dataDowncast' ).add( dispatcher => + dispatcher.on( 'attribute:resizedHeight:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedHeight:imageBlock' ); + }, { priority: 'high' } ) + ); + setData( editor.model, `` ); + + expect( editor.getData() ) + .to.equal( `
` ); + } ); + } ); - expect( editor.getData() ) - .to.equal( `
` ); + describe( 'editing downcast', () => { + it( 'downcasts 100px height correctly', () => { + setData( editor.model, `` ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '
' + + `` + + '
' + + '
' + ); + } ); + + it( 'downcasts 50% height correctly', () => { + setData( editor.model, `` ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '
' + + `` + + '
' + + '
' + ); + } ); + + it( 'doesn\'t downcast consumed tokens', () => { + editor.conversion.for( 'editingDowncast' ).add( dispatcher => + dispatcher.on( 'attribute:resizedHeight:imageBlock', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedHeight:imageBlock' ); + }, { priority: 'high' } ) + ); + setData( editor.model, `` ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '
' + + `` + + '
' + + '
' + ); + } ); + } ); } ); } ); From 4c7108625083e9a2daef1a68893e64cb5797e9e8 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 12:25:57 +0200 Subject: [PATCH 050/118] Tests: image inline resizedHeight downcast. --- .../tests/imageresize/imageresizeediting.js | 167 ++++++++++++++---- 1 file changed, 130 insertions(+), 37 deletions(-) diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index 681b14d71b8..2e6f8bcc1c9 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -214,59 +214,152 @@ describe( 'ImageResizeEditing', () => { editor = await createEditor(); } ); - it( 'upcasts 100px width correctly', () => { - editor.setData( - `

Lorem ipsum

` - ); + describe( 'width', () => { + it( 'upcasts 100px width correctly', () => { + editor.setData( + `

Lorem ipsum

` + ); - expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedWidth' ) ).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

` ); + it( 'upcasts 50% width correctly', () => { + editor.setData( + `

Lorem ipsum

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

` + ); + } ); - expect( editor.getData() ) - .to.equal( - `

` + it( 'downcasts 50% resizedWidth correctly', () => { + setData( editor.model, + `` ); - } ); - it( 'downcasts 50% resizedWidth correctly', () => { - setData( editor.model, `` ); + expect( editor.getData() ) + .to.equal( `

` ); + } ); - expect( editor.getData() ) - .to.equal( `

` ); - } ); + it( 'removes style and extra class when no longer resized', () => { + setData( editor.model, + `` + ); - it( 'removes style and extra class when no longer resized', () => { - setData( editor.model, `` ); + const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); - const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); + editor.model.change( writer => { + writer.removeAttribute( 'resizedWidth', imageModel ); + } ); - editor.model.change( writer => { - writer.removeAttribute( 'resizedWidth', imageModel ); + expect( editor.getData() ) + .to.equal( `

` ); } ); - expect( editor.getData() ) - .to.equal( `

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

` ); + } ); } ); - it( 'doesn\'t downcast consumed tokens', () => { - editor.conversion.for( 'downcast' ).add( dispatcher => - dispatcher.on( 'attribute:resizedWidth:imageInline', ( evt, data, conversionApi ) => { - conversionApi.consumable.consume( data.item, 'attribute:resizedWidth:imageInline' ); - }, { priority: 'high' } ) - ); - setData( editor.model, `` ); + describe( 'height', () => { + describe( 'data downcast', () => { + it( 'downcasts 100px resizedHeight correctly', () => { + setData( editor.model, + `` + ); - expect( editor.getData() ) - .to.equal( `

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

` + ); + } ); + + it( 'downcasts 50% resizedHeight correctly', () => { + setData( editor.model, + `` + ); + + expect( editor.getData() ) + .to.equal( `

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

` ); + } ); + } ); + + describe( 'editing downcast', () => { + it( 'downcasts 100px resizedHeight correctly', () => { + setData( editor.model, + `` + ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '

' + + `` + + '

' + ); + } ); + + it( 'downcasts 50% resizedHeight correctly', () => { + setData( editor.model, + `` + ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '

' + + `` + + '

' + ); + } ); + + it( 'doesn\'t downcast consumed tokens', () => { + editor.conversion.for( 'editingDowncast' ).add( dispatcher => + dispatcher.on( 'attribute:resizedHeight:imageInline', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.item, 'attribute:resizedHeight:imageInline' ); + }, { priority: 'high' } ) + ); + setData( editor.model, + `` + ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '

' + + `` + + '

' + ); + } ); + } ); } ); } ); From eb3bf54538fc2bdbc1f32fa570006768d67f6afe Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 13:14:00 +0200 Subject: [PATCH 051/118] Tests: image resizedWidth & resizedHeight upcast. --- .../tests/imageresize/imageresizeediting.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index 2e6f8bcc1c9..4ba2c179c67 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -98,6 +98,12 @@ describe( 'ImageResizeEditing', () => { expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); } ); + it( 'does not upcast width if height is set too', () => { + editor.setData( `
` ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedWidth' ) ).to.be.undefined; + } ); + it( 'downcasts 100px width correctly', () => { setData( editor.model, `` ); @@ -139,6 +145,26 @@ describe( 'ImageResizeEditing', () => { } ); describe( 'height', () => { + describe( 'upcast', () => { + it( 'upcasts 100px height correctly', () => { + editor.setData( `
` ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedHeight' ) ).to.equal( '100px' ); + } ); + + it( 'upcasts 50% height correctly', () => { + editor.setData( `
` ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedHeight' ) ).to.equal( '50%' ); + } ); + + it( 'does not upcast height if width is set too', () => { + editor.setData( `
` ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'resizedHeight' ) ).to.be.undefined; + } ); + } ); + describe( 'data downcast', () => { it( 'downcasts 100px height correctly', () => { setData( editor.model, `` ); @@ -231,6 +257,16 @@ describe( 'ImageResizeEditing', () => { expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedWidth' ) ).to.equal( '50%' ); } ); + it( 'does not upcast width if height is set too', () => { + editor.setData( + '

Lorem ' + + `` + + ' ipsum

' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedWidth' ) ).to.be.undefined; + } ); + it( 'downcasts 100px resizedWidth correctly', () => { setData( editor.model, `` @@ -282,6 +318,35 @@ describe( 'ImageResizeEditing', () => { } ); describe( 'height', () => { + describe( 'upcast', () => { + it( 'upcasts 100px height correctly', () => { + editor.setData( + `

Lorem ipsum

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

Lorem ipsum

` + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedHeight' ) ).to.equal( '50%' ); + } ); + + it( 'does not upcast height if width is set too', () => { + editor.setData( + '

Lorem ' + + `` + + ' ipsum

' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'resizedHeight' ) ).to.be.undefined; + } ); + } ); + describe( 'data downcast', () => { it( 'downcasts 100px resizedHeight correctly', () => { setData( editor.model, From 93d0efe796585f8dc7c43b433ce25fb2500e126a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 30 Jun 2023 14:51:41 +0200 Subject: [PATCH 052/118] Tests: image upcast from styles to attributes. --- .../tests/imagesizeattributes.js | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index e46acfaf402..b5990395670 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -88,6 +88,36 @@ describe( 'ImageSizeAttributes', () => { '' ); } ); + + it( 'should upcast width & height styles if they both are set', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + + 'Lorem ' + + '' + + ' ipsum' + + '' + ); + } ); + + it( 'should not upcast width style if height style is missing', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'width' ) ).to.be.undefined; + } ); + + it( 'should not upcast height style if width style is missing', () => { + editor.setData( + '

Lorem ipsum

' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getChild( 1 ).getAttribute( 'height' ) ).to.be.undefined; + } ); } ); describe( 'block images', () => { @@ -110,6 +140,32 @@ describe( 'ImageSizeAttributes', () => { '' ); } ); + + it( 'should upcast width & height styles if they both are set', () => { + editor.setData( + '
' + ); + + expect( getData( model, { withoutSelection: true } ) ).to.equal( + '' + ); + } ); + + it( 'should not upcast width style if height style is missing', () => { + editor.setData( + '
' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'width' ) ).to.be.undefined; + } ); + + it( 'should not upcast height style if width style is missing', () => { + editor.setData( + '
' + ); + + expect( editor.model.document.getRoot().getChild( 0 ).getAttribute( 'height' ) ).to.be.undefined; + } ); } ); } ); From 452bcf307bd7f5b1ea72f5ee8eee0734ab0be3c7 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Sat, 1 Jul 2023 13:51:32 +0200 Subject: [PATCH 053/118] Tests: image downcast aspect-ration inline style. --- .../tests/imagesizeattributes.js | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index b5990395670..df1b1bb089d 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -10,6 +10,7 @@ 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 ImageResizeEditing from '../src/imageresize/imageresizeediting'; import ImageUtils from '../src/imageutils'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; @@ -254,6 +255,55 @@ describe( 'ImageSizeAttributes', () => { expect( editor.getData() ) .to.equal( '

' ); } ); + + describe( 'with image resize plugin', () => { + let editor, view; + + beforeEach( async () => { + editor = await VirtualTestEditor.create( { + plugins: [ Paragraph, ImageInlineEditing, ImageSizeAttributes, ImageResizeEditing ] + } ); + + view = editor.editing.view; + } ); + + afterEach( async () => { + await editor.destroy(); + } ); + + it( 'should add aspect-ratio if attributes are set and image is resized', () => { + editor.setData( + '

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

' + + '' + + '

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

' + ); + } ); + + it( 'should not add aspect-ratio if attributes are set but image is not resized', () => { + editor.setData( + '

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

' + + '' + + '

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

' + ); + } ); + } ); } ); describe( 'block images', () => { @@ -340,6 +390,58 @@ describe( 'ImageSizeAttributes', () => { expect( editor.getData() ) .to.equal( '
' ); } ); + + describe( 'with image resize plugin', () => { + let editor, view; + + beforeEach( async () => { + editor = await VirtualTestEditor.create( { + plugins: [ Paragraph, ImageBlockEditing, ImageSizeAttributes, ImageResizeEditing ] + } ); + + view = editor.editing.view; + } ); + + afterEach( async () => { + await editor.destroy(); + } ); + + it( 'should add aspect-ratio if attributes are set and image is resized', () => { + editor.setData( + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '
' + ); + } ); + + it( 'should not add aspect-ratio if attributes are set but image is not resized', () => { + editor.setData( + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '
' + ); + } ); + } ); } ); } ); } ); From 5280b8580cd0606b7feaa93189a54448bd4f6c85 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 13:40:01 +0200 Subject: [PATCH 054/118] Tests: style height and aspect-ratio after starting resizing. --- .../tests/imageresize/imageresizehandles.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index 8c3233a3c81..89de2711284 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -656,6 +656,68 @@ describe( 'ImageResizeHandles', () => { } ); } ); + describe( 'height style', () => { + beforeEach( async () => { + editor = await createEditor(); + + await setModelAndWaitForImages( editor, + '[' + + `` + + ']' + ); + + widget = viewDocument.getRoot().getChild( 0 ).getChild( 0 ); + } ); + + it( 'is removed after starting resizing', () => { + const resizerPosition = 'bottom-left'; + const domParts = getWidgetDomParts( editor, widget, resizerPosition ); + const initialPointerPosition = getHandleCenterPoint( domParts.widget, resizerPosition ); + const viewImage = widget.getChild( 0 ); + + expect( viewImage.getStyle( 'height' ) ).to.equal( '50px' ); + + resizerMouseSimulator.down( editor, domParts.resizeHandle ); + + resizerMouseSimulator.move( editor, domParts.resizeHandle, null, initialPointerPosition ); + + expect( viewImage.getStyle( 'height' ) ).to.be.undefined; + + resizerMouseSimulator.up( editor ); + } ); + } ); + + describe( 'aspect-ratio style', () => { + beforeEach( async () => { + editor = await createEditor(); + + await setModelAndWaitForImages( editor, + '[' + + `` + + ']' + ); + + widget = viewDocument.getRoot().getChild( 0 ).getChild( 0 ); + } ); + + it( 'is added after starting resizing, if width & height attributes are set', () => { + const resizerPosition = 'bottom-left'; + const domParts = getWidgetDomParts( editor, widget, resizerPosition ); + const initialPointerPosition = getHandleCenterPoint( domParts.widget, resizerPosition ); + const viewImage = widget.getChild( 0 ); + + expect( viewImage.getStyle( 'aspec-ratio' ) ).to.be.undefined; + + resizerMouseSimulator.down( editor, domParts.resizeHandle ); + + resizerMouseSimulator.move( editor, domParts.resizeHandle, null, initialPointerPosition ); + + expect( viewImage.getStyle( 'aspect-ratio' ) ).to.equal( '100/50' ); + + resizerMouseSimulator.up( editor ); + } ); + } ); + describe( 'undo integration', () => { beforeEach( async () => { editor = await createEditor(); From 86789e1119f3f07d106e0996ca0aa62ec06453c6 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 16:22:05 +0200 Subject: [PATCH 055/118] Add description for new ck-content resized image css rules. --- packages/ckeditor5-image/theme/imageresize.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/theme/imageresize.css b/packages/ckeditor5-image/theme/imageresize.css index 54472f941d7..15351410a77 100644 --- a/packages/ckeditor5-image/theme/imageresize.css +++ b/packages/ckeditor5-image/theme/imageresize.css @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* TODO */ +/* Preserve aspect ratio of the resized image after introducing image height attribute. */ .ck-content figure.image_resized img, .ck-content img.image_resized { height: auto; @@ -31,7 +31,7 @@ } .ck.ck-editor__editable { - /* TODO */ + /* Preserve aspect ratio of the resized image after introducing image height attribute. */ & .image.image_resized img, & .image-inline.image_resized img { height: auto; From c301b21e8854d69680240dc6692f2b5bd55af1d6 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 16:29:16 +0200 Subject: [PATCH 056/118] Tests: correction for image size attributes. --- .../tests/imagesizeattributes.js | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index df1b1bb089d..adf13c5d061 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -64,13 +64,13 @@ describe( 'ImageSizeAttributes', () => { describe( 'inline images', () => { it( 'should upcast width attribute correctly', () => { editor.setData( - '

Lorem ipsum

' + '

Lorem ipsum

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

Lorem ipsum

' + '

Lorem ipsum

' ); expect( getData( model, { withoutSelection: true } ) ).to.equal( '' + 'Lorem ' + - '' + + '' + ' ipsum' + '' ); @@ -124,21 +124,21 @@ describe( 'ImageSizeAttributes', () => { 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( - '' + '' ); } ); @@ -174,33 +174,33 @@ describe( 'ImageSizeAttributes', () => { describe( 'inline images', () => { it( 'should downcast width attribute correctly', () => { editor.setData( - '

Lorem ipsum

' + '

Lorem ipsum

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

Lorem ' + - '' + + '' + ' ipsum

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

Lorem ipsum

' + '

Lorem ipsum

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

Lorem ipsum

' + '

Lorem ipsum

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

Lorem ' + - '' + + '' + ' ipsum

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

Lorem ipsum

' + '

Lorem ipsum

' ); } ); @@ -210,7 +210,7 @@ describe( 'ImageSizeAttributes', () => { conversionApi.consumable.consume( data.item, 'attribute:width:imageInline' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '

' @@ -223,7 +223,7 @@ describe( 'ImageSizeAttributes', () => { conversionApi.consumable.consume( data.item, 'attribute:height:imageInline' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '

' @@ -231,7 +231,7 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove width attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); @@ -244,7 +244,7 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove height attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); @@ -309,33 +309,33 @@ describe( 'ImageSizeAttributes', () => { 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( - '
' + '
' ); } ); @@ -345,7 +345,7 @@ describe( 'ImageSizeAttributes', () => { conversionApi.consumable.consume( data.item, 'attribute:width:imageBlock' ); }, { priority: 'high' } ) ); - setData( model, '' ); + setData( model, '' ); expect( editor.getData() ).to.equal( '
' @@ -366,7 +366,7 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove width attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ); @@ -379,7 +379,7 @@ describe( 'ImageSizeAttributes', () => { } ); it( 'should remove height attribute properly', () => { - setData( model, '' ); + setData( model, '' ); const imageModel = editor.model.document.getRoot().getChild( 0 ); From a49fee64e64c4f30292bcd1572ae059682f08520 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 16:35:21 +0200 Subject: [PATCH 057/118] Tests: image resize command should remove resizedHeight. --- .../tests/imageresize/resizeimagecommand.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js index b127a7cdf66..c7a0bba1f5a 100644 --- a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js +++ b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js @@ -115,5 +115,14 @@ describe( 'ResizeImageCommand', () => { expect( getData( model ) ).to.equal( '[]' ); expect( model.document.getRoot().getChild( 0 ).hasAttribute( 'resizedWidth' ) ).to.be.false; } ); + + it( 'removes image resizedHeight', () => { + setData( model, '[]' ); + + command.execute( { width: '100%' } ); + + expect( getData( model ) ).to.equal( '[]' ); + expect( model.document.getRoot().getChild( 0 ).hasAttribute( 'resizedHeight' ) ).to.be.false; + } ); } ); } ); From d7ea66ad91100d0f51256a5b6c2cfe2c14347502 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 17:02:45 +0200 Subject: [PATCH 058/118] Tests: improve manual test for image attributes & styles with full ghs integration. --- .../tests/manual/imagesizeattributesghs.html | 13 +++++++++++++ .../tests/manual/imagesizeattributesghs.md | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html index d09b7adffe8..9904fd1b1a2 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.html @@ -1,2 +1,15 @@
+

[Inline] width + height attributes:

+

+

[Inline] width style:

+

+

[Inline] height style:

+

+ +

[Block] width + height attributes:

+
+

[Block] width style:

+
+

[Block] height style:

+
diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md index c581c9eac3b..b5f7eb3716c 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.md @@ -1 +1,7 @@ -## Image size attributes +## Image size attributes with full GHS + +A manual test to check image attributes and styles with GHS integration: +* `width` (image width attribute) +* `height` (image height attribute) +* `resizedWidth` (image width style) +* `resizedHeight` (image height style) From 412de18d58596b336faefd87584e6c9c5cb378db Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 17:13:17 +0200 Subject: [PATCH 059/118] Tests: add description to manual test for image attributes. --- .../tests/manual/imagesizeattributesallcases.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md index c581c9eac3b..3d333f57b57 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md @@ -1 +1,14 @@ ## Image size attributes + +This manual tests consists of many use cases for different combinations of image attributes and styles: +* `width` (image width attribute) +* `height` (image height attribute) +* `resizedWidth` (image width style) +* `resizedHeight` (image height style) + +Image in the editor should look like the image next to the editor (created from editor's output data): +* after initial editor load +* after resizing image in the editor + +Everytime an image is resized, the code below is updated with refreshed output data and model. +It's then easier to compare what has changed after resizing. From fc0300dcb3b88c6406fdcaa078983d8ae66bd2b6 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 3 Jul 2023 17:32:26 +0200 Subject: [PATCH 060/118] Tests: manual test for image attributes - typo. --- .../ckeditor5-image/tests/manual/imagesizeattributesallcases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md index 3d333f57b57..51db2f6b45e 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md @@ -10,5 +10,5 @@ Image in the editor should look like the image next to the editor (created from * after initial editor load * after resizing image in the editor -Everytime an image is resized, the code below is updated with refreshed output data and model. +**Note**: Every time an image is resized, the code below is updated with refreshed output data and model. It's then easier to compare what has changed after resizing. From 47a97c66df99fa622aa72717ae430e3de66332ce Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 4 Jul 2023 18:59:55 +0200 Subject: [PATCH 061/118] Tests: manual test image sizes: remove easy image, add picture. --- .../tests/manual/imagesizeattributesallcases.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index d1be3ef7eb7..13e56911a5d 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -15,6 +15,7 @@ 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 PictureEditing from '../../src/pictureediting'; import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; @@ -26,11 +27,10 @@ const commonConfig = { ImageResize, Code, ImageSizeAttributes, - ImageUpload, Indent, IndentBlock, CloudServices, - EasyImage, + PictureEditing, PasteFromOffice ], toolbar: [ 'heading', '|', 'bold', 'italic', 'link', @@ -56,6 +56,7 @@ const configPx = { IndentBlock, CloudServices, EasyImage, + PictureEditing, PasteFromOffice ], toolbar: [ 'heading', '|', 'bold', 'italic', 'link', From a86f6ad65c6bafb8e19fdde39cfdfc5cc6338b21 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 4 Jul 2023 21:36:35 +0200 Subject: [PATCH 062/118] Tests: add picture cases to manual test for image sizes. --- .../manual/imagesizeattributesallcases.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index 13e56911a5d..3e28ee62ba9 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -317,6 +317,46 @@ const editors = [ config: commonConfig, data: '
' + '
' + }, + { + id: 'inline201', + title: '[Picture: Inline] plain (no styles)', + config: commonConfig, + data: '

' + + '' + + '' + + '' + + '

' + }, + { + id: 'inline202', + title: '[Picture: Inline] resized (width style %)', + config: commonConfig, + data: '

' + + '' + + '' + + '' + + '

' + }, + { + id: 'block201', + title: '[Picture: Block] plain (no styles)', + config: commonConfig, + data: '
' + + '' + + '' + + '' + + '
' + }, + { + id: 'block202', + title: '[Picture: Block] resized (width style %)', + config: commonConfig, + data: '
' + + '' + + '' + + '' + + '
' } ]; From 32104408ac4e8910715b4685b369d66a6dda8c34 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:19:20 +0200 Subject: [PATCH 063/118] Enforce importing ImageUtils. --- packages/ckeditor5-image/src/imageresize/imageresizeediting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index d24b5e8105c..0a9df2b2997 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -97,7 +97,7 @@ export default class ImageResizeEditing extends Plugin { */ private _registerConverters( imageType: 'imageBlock' | 'imageInline' ) { const editor = this.editor; - const imageUtils = editor.plugins.get( 'ImageUtils' ); + const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); // Dedicated converter to propagate image's attribute to the img tag. editor.conversion.for( 'downcast' ).add( dispatcher => From 19855582b831458d899a14a594331b292d21c352 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:22:35 +0200 Subject: [PATCH 064/118] Refactor. --- .../src/imageresize/imageresizeediting.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 0a9df2b2997..6016936e5c3 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -138,12 +138,11 @@ export default class ImageResizeEditing extends Plugin { return; } - const viewWriter = conversionApi.writer; - const figure = conversionApi.mapper.toViewElement( data.item ); - const img = imageUtils.findViewImgElement( figure ); - const target = imageType === 'imageInline' ? img : figure; - if ( data.attributeNewValue !== null ) { + const viewWriter = conversionApi.writer; + const figure = conversionApi.mapper.toViewElement( data.item ); + const target = imageType === 'imageInline' ? imageUtils.findViewImgElement( figure ) : figure; + viewWriter.setStyle( 'height', data.attributeNewValue, target ); } } ) From 4decf192d5c0d64c14b16a55c75963bc1386a10c Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:37:23 +0200 Subject: [PATCH 065/118] Comment upcasting image styles to width/height instead of resizedWidth/resizedHeight. --- .../ckeditor5-image/src/imageresize/imageresizeediting.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 6016936e5c3..80d1fca5bce 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -162,6 +162,9 @@ export default class ImageResizeEditing extends Plugin { const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + // If both image styles: width & height are set, they will override the image width & height attributes in the + // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. + // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. if ( widthStyle && heightStyle ) { return null; } @@ -185,6 +188,9 @@ export default class ImageResizeEditing extends Plugin { const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + // If both image styles: width & height are set, they will override the image width & height attributes in the + // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. + // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. if ( widthStyle && heightStyle ) { return null; } From 9d7fda9736481d527c688a1d3a44030541c4fb8a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:40:31 +0200 Subject: [PATCH 066/118] Tests: update comment. --- .../ckeditor5-image/tests/manual/imagesizeattributesallcases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md index 51db2f6b45e..2156997d8bf 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md @@ -10,5 +10,5 @@ Image in the editor should look like the image next to the editor (created from * after initial editor load * after resizing image in the editor -**Note**: Every time an image is resized, the code below is updated with refreshed output data and model. +**Note**: Every time an image is resized, the code blocks below the editor are updated with refreshed output data and model. It's then easier to compare what has changed after resizing. From 662472bc6059ad21a7938bc972ceb172666e77eb Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:49:47 +0200 Subject: [PATCH 067/118] Enforce importing ImageUtils. --- packages/ckeditor5-image/src/imageresize/imageresizehandles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 636b29b1dfe..eab0555cc22 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -64,7 +64,7 @@ export default class ImageResizeHandles extends Plugin { private _setupResizerCreator(): void { const editor = this.editor; const editingView = editor.editing.view; - const imageUtils = editor.plugins.get( 'ImageUtils' ); + const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); editingView.addObserver( ImageLoadObserver ); From eea68dc28cce5f84c3fbcb43836a2d2b91b3788e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 4 Jul 2023 21:48:07 +0200 Subject: [PATCH 068/118] Picture (inline): make sure the image does not exceed the width of the parent container. --- packages/ckeditor5-image/theme/image.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 0d857998e19..3fe96cf6917 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -4,6 +4,13 @@ */ .ck-content { + & picture { + /* The image should never exceed the width of the parent container */ + & img { + max-width: 100%; + } + } + & .image { display: table; clear: both; From fb117a51fce02d67e562b0f9c745970b15b52162 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 16:58:03 +0200 Subject: [PATCH 069/118] Do not set width after image upload if the image has width or height set. --- packages/ckeditor5-engine/src/index.ts | 2 +- .../src/imageupload/imageuploadediting.ts | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-engine/src/index.ts b/packages/ckeditor5-engine/src/index.ts index ae2a3c73452..de1bc11596e 100644 --- a/packages/ckeditor5-engine/src/index.ts +++ b/packages/ckeditor5-engine/src/index.ts @@ -92,7 +92,7 @@ export type { Marker } from './model/markercollection'; export type { default as Batch } from './model/batch'; export type { default as Differ, DiffItem, DiffItemAttribute, DiffItemInsert, DiffItemRemove } from './model/differ'; export type { default as Item } from './model/item'; -export type { default as Node } from './model/node'; +export type { default as Node, NodeAttributes } from './model/node'; export type { default as RootElement } from './model/rootelement'; export type { default as Schema, diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 18f92ae24aa..8628293935b 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -9,7 +9,15 @@ import { Plugin, type Editor } from 'ckeditor5/src/core'; -import { UpcastWriter, type Element, type Item, type Writer, type DataTransfer, type ViewElement } from 'ckeditor5/src/engine'; +import { + UpcastWriter, + type Element, + type Item, + type Writer, + type DataTransfer, + type ViewElement, + type NodeAttributes +} from 'ckeditor5/src/engine'; import { Notification } from 'ckeditor5/src/ui'; import { ClipboardPipeline, type ViewDocumentClipboardInputEvent } from 'ckeditor5/src/clipboard'; @@ -398,10 +406,15 @@ export default class ImageUploadEditing extends Plugin { .join( ', ' ); if ( srcsetAttribute != '' ) { - writer.setAttributes( { - srcset: srcsetAttribute, - width: maxWidth - }, image ); + const attributes: NodeAttributes = { + srcset: srcsetAttribute + }; + + if ( !image.hasAttribute( 'width' ) && !image.hasAttribute( 'height' ) ) { + attributes.width = maxWidth; + } + + writer.setAttributes( attributes, image ); } } } From ebe8d35395f0e29665f555b7c17508949e4b1900 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 11:22:27 +0200 Subject: [PATCH 070/118] Revert "Picture (inline): make sure the image does not exceed the width of the parent container." This reverts commit eea68dc28cce5f84c3fbcb43836a2d2b91b3788e. --- packages/ckeditor5-image/theme/image.css | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 3fe96cf6917..0d857998e19 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -4,13 +4,6 @@ */ .ck-content { - & picture { - /* The image should never exceed the width of the parent container */ - & img { - max-width: 100%; - } - } - & .image { display: table; clear: both; From 461ef559b3e7d31b600900fbc8f26d79830f2b32 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 12:52:59 +0200 Subject: [PATCH 071/118] Fix appearance of inline images in editing, which are wider than editor and have height set. --- packages/ckeditor5-image/theme/image.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index 0d857998e19..f8c43e4a432 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -105,6 +105,12 @@ } } + /* Keep proportions of the inline image if the height is set and the image is wider than the editor width. + See https://github.com/ckeditor/ckeditor5/issues/14542. */ + & .image-inline img { + height: auto; + } + /* The inline image nested in the table should have its original size if not resized. See https://github.com/ckeditor/ckeditor5/issues/9117. */ & td, From f22ff3cd6faa6d1e20bd2cce1df7bc76f99e48e5 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 12:59:57 +0200 Subject: [PATCH 072/118] Fix appearance of block images (editing & data), which are wider than editor and have height set. --- packages/ckeditor5-image/theme/image.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ckeditor5-image/theme/image.css b/packages/ckeditor5-image/theme/image.css index f8c43e4a432..8051a285114 100644 --- a/packages/ckeditor5-image/theme/image.css +++ b/packages/ckeditor5-image/theme/image.css @@ -29,6 +29,10 @@ /* Make sure the image is never smaller than the parent container (See: https://github.com/ckeditor/ckeditor5/issues/9300). */ min-width: 100%; + + /* Keep proportions of the block image if the height is set and the image is wider than the editor width. + See https://github.com/ckeditor/ckeditor5/issues/14542. */ + height: auto; } } From e18fa9531d47d6f1b5914305850bcb21dbee3922 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 13:04:21 +0200 Subject: [PATCH 073/118] Add inline aspect-ratio for inline (editing) and block (editing & data) images which have width & height set. --- .../src/imagesizeattributes.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index c438e2eb72a..c8886d340cd 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -127,14 +127,19 @@ export default class ImageSizeAttributes extends Plugin { } } ); - // Dedicated converter to propagate attributes to the element. - editor.conversion.for( 'downcast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'width', 'width' ); - attachDowncastConverter( dispatcher, 'height', 'height' ); + // Dedicated converters to propagate attributes to the element. + editor.conversion.for( 'editingDowncast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width', true ); + attachDowncastConverter( dispatcher, 'height', 'height', true ); + } ); + + editor.conversion.for( 'dataDowncast' ).add( dispatcher => { + attachDowncastConverter( dispatcher, 'width', 'width', false ); + attachDowncastConverter( dispatcher, 'height', 'height', false ); } ); function attachDowncastConverter( - dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string + dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string, setRatioForInlineImage: boolean ) { dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { @@ -151,12 +156,15 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } + if ( imageType === 'imageInline' && !setRatioForInlineImage ) { + return; + } + const width = data.item.getAttribute( 'width' ); const height = data.item.getAttribute( 'height' ); - const isResized = data.item.hasAttribute( 'resizedWidth' ); const aspectRatio = img.getStyle( 'aspect-ratio' ); - if ( width && height && !aspectRatio && isResized ) { + if ( width && height && !aspectRatio ) { viewWriter.setStyle( 'aspect-ratio', `${ width }/${ height }`, img ); } } ); From d74868b1a2a0d8b75a96ef7c58a2b20ac9430efc Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 13:25:41 +0200 Subject: [PATCH 074/118] Add aspect ratio for inline images which are resized (data). --- packages/ckeditor5-image/src/imagesizeattributes.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index c8886d340cd..5331cf081d7 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -156,7 +156,10 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } - if ( imageType === 'imageInline' && !setRatioForInlineImage ) { + const isResized = data.item.hasAttribute( 'resizedWidth' ); + + // Do not set aspect ratio for inline images which are not resized (data pipeline). + if ( imageType === 'imageInline' && !isResized && !setRatioForInlineImage ) { return; } From a1b88a54044ae7e0fc37ec8c2846607c03b47998 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 13:30:33 +0200 Subject: [PATCH 075/118] Remove css rules which are already covered in image.css file. --- packages/ckeditor5-image/theme/imageresize.css | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/ckeditor5-image/theme/imageresize.css b/packages/ckeditor5-image/theme/imageresize.css index 15351410a77..ad86a3b6b16 100644 --- a/packages/ckeditor5-image/theme/imageresize.css +++ b/packages/ckeditor5-image/theme/imageresize.css @@ -4,7 +4,6 @@ */ /* Preserve aspect ratio of the resized image after introducing image height attribute. */ -.ck-content figure.image_resized img, .ck-content img.image_resized { height: auto; } @@ -31,12 +30,6 @@ } .ck.ck-editor__editable { - /* Preserve aspect ratio of the resized image after introducing image height attribute. */ - & .image.image_resized img, - & .image-inline.image_resized img { - height: auto; - } - /* The resized inline image nested in the table should respect its parent size. See https://github.com/ckeditor/ckeditor5/issues/9117. */ & td, From 174443f0e57bc5abe580278ee7e275cd6d35186d Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 14:49:03 +0200 Subject: [PATCH 076/118] Tests: update after setting aspect-ratio for more use cases. --- packages/ckeditor5-image/tests/imagesizeattributes.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index adf13c5d061..903ecb4715a 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -288,14 +288,14 @@ describe( 'ImageSizeAttributes', () => { ); } ); - it( 'should not add aspect-ratio if attributes are set but image is not resized', () => { + it( 'should add aspect-ratio if attributes are set but image is not resized (but to editing view only)', () => { editor.setData( '

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

' + - '' + + '' + '

' ); @@ -424,20 +424,20 @@ describe( 'ImageSizeAttributes', () => { ); } ); - it( 'should not add aspect-ratio if attributes are set but image is not resized', () => { + it( 'should add aspect-ratio if attributes are set but image is not resized', () => { editor.setData( '
' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + - '' + + '' + '
' ); expect( editor.getData() ).to.equal( '
' + - '' + + '' + '
' ); } ); From afc03de10e03d910a43fd55377294e490b7ae877 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 7 Jul 2023 15:59:16 +0200 Subject: [PATCH 077/118] Remove code for setting aspect-ratio on starting resizng, as now we set it also for not resized images. --- .../src/imageresize/imageresizehandles.ts | 13 -------- .../tests/imageresize/imageresizehandles.js | 31 ------------------- 2 files changed, 44 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index eab0555cc22..3c611ceef46 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -142,19 +142,6 @@ export default class ImageResizeHandles extends Plugin { } } ); - resizer.on( 'begin', () => { - const img = imageUtils.findViewImgElement( imageView )!; - const aspectRatio = img.getStyle( 'aspect-ratio' ); - const widthAttr = imageModel.getAttribute( 'width' ); - const heightAttr = imageModel.getAttribute( 'height' ); - - if ( widthAttr && heightAttr && !aspectRatio ) { - editingView.change( writer => { - writer.setStyle( 'aspect-ratio', `${ widthAttr }/${ heightAttr }`, img ); - } ); - } - } ); - resizer.bind( 'isEnabled' ).to( this ); } ); } diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js index 89de2711284..8c596c15ba4 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizehandles.js @@ -687,37 +687,6 @@ describe( 'ImageResizeHandles', () => { } ); } ); - describe( 'aspect-ratio style', () => { - beforeEach( async () => { - editor = await createEditor(); - - await setModelAndWaitForImages( editor, - '[' + - `` + - ']' - ); - - widget = viewDocument.getRoot().getChild( 0 ).getChild( 0 ); - } ); - - it( 'is added after starting resizing, if width & height attributes are set', () => { - const resizerPosition = 'bottom-left'; - const domParts = getWidgetDomParts( editor, widget, resizerPosition ); - const initialPointerPosition = getHandleCenterPoint( domParts.widget, resizerPosition ); - const viewImage = widget.getChild( 0 ); - - expect( viewImage.getStyle( 'aspec-ratio' ) ).to.be.undefined; - - resizerMouseSimulator.down( editor, domParts.resizeHandle ); - - resizerMouseSimulator.move( editor, domParts.resizeHandle, null, initialPointerPosition ); - - expect( viewImage.getStyle( 'aspect-ratio' ) ).to.equal( '100/50' ); - - resizerMouseSimulator.up( editor ); - } ); - } ); - describe( 'undo integration', () => { beforeEach( async () => { editor = await createEditor(); From 8cab8c9f98df08197c41a9e723db0f4126e6e4b2 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 10 Jul 2023 15:08:06 +0200 Subject: [PATCH 078/118] Tests: do not set image width after image upload if width or height were set previously. --- .../tests/imageupload/imageuploadediting.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js index fab20c27cc0..24920821f39 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js @@ -513,6 +513,70 @@ describe( 'ImageUploadEditing', () => { } } ); + it( 'should not modify image width if width was set before server response', async () => { + setModelData( model, '[]foo' ); + + const clipboardHtml = ``; + const dataTransfer = mockDataTransfer( clipboardHtml ); + + const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) ); + const targetViewRange = editor.editing.mapper.toViewRange( targetRange ); + + viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } ); + + await new Promise( res => { + model.document.once( 'change', res ); + loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) ); + } ); + + await new Promise( res => { + model.document.once( 'change', res, { priority: 'lowest' } ); + loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: '/assets/sample.png', 800: 'image-800.png' } ) ); + } ); + + await timeout( 100 ); + + expect( getModelData( model ) ).to.equal( + '[]foo' + ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } + } ); + + it( 'should not modify image width if height was set before server response', async () => { + setModelData( model, '[]foo' ); + + const clipboardHtml = ``; + const dataTransfer = mockDataTransfer( clipboardHtml ); + + const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) ); + const targetViewRange = editor.editing.mapper.toViewRange( targetRange ); + + viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } ); + + await new Promise( res => { + model.document.once( 'change', res ); + loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) ); + } ); + + await new Promise( res => { + model.document.once( 'change', res, { priority: 'lowest' } ); + loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: '/assets/sample.png', 800: 'image-800.png' } ) ); + } ); + + await timeout( 100 ); + + expect( getModelData( model ) ).to.equal( + '[]foo' + ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } + } ); + it( 'should support adapter response with the normalized `urls` property', async () => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); From ed8a0a1655d3b5149f63e50e7cc4db5e57fef49f Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 11 Jul 2023 10:09:35 +0200 Subject: [PATCH 079/118] Tests: Refactor. --- .../tests/imageupload/imageuploadediting.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js index 24920821f39..ebb1343d6b7 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js @@ -507,10 +507,6 @@ describe( 'ImageUploadEditing', () => { expect( getModelData( model ) ).to.equal( '[]foo bar' ); - - function timeout( ms ) { - return new Promise( res => setTimeout( res, ms ) ); - } } ); it( 'should not modify image width if width was set before server response', async () => { @@ -539,10 +535,6 @@ describe( 'ImageUploadEditing', () => { expect( getModelData( model ) ).to.equal( '[]foo' ); - - function timeout( ms ) { - return new Promise( res => setTimeout( res, ms ) ); - } } ); it( 'should not modify image width if height was set before server response', async () => { @@ -571,10 +563,6 @@ describe( 'ImageUploadEditing', () => { expect( getModelData( model ) ).to.equal( '[]foo' ); - - function timeout( ms ) { - return new Promise( res => setTimeout( res, ms ) ); - } } ); it( 'should support adapter response with the normalized `urls` property', async () => { @@ -1573,3 +1561,7 @@ function base64ToBlob( base64Data ) { return new Blob( byteArrays, { type } ); } + +function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); +} From 4c2508cadee29385775d664cec51076a3a57c23e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 12 Jul 2023 15:06:46 +0200 Subject: [PATCH 080/118] Set image width & height on image attribute change. --- .../src/imagesizeattributes.ts | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 5331cf081d7..2d4839a9a52 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -8,8 +8,12 @@ */ import { Plugin } from 'ckeditor5/src/core'; -import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; +import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element, ViewContainerElement } from 'ckeditor5/src/engine'; import ImageUtils from './imageutils'; +import ImageLoadObserver, { type ImageLoadedEvent } from './image/imageloadobserver'; +import ImageTypeCommand from './image/imagetypecommand'; + +const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/; /** * This plugin enables `width` and `size` attributes in inline and block image elements. @@ -29,6 +33,48 @@ export default class ImageSizeAttributes extends Plugin { return 'ImageSizeAttributes'; } + /** + * @inheritDoc + */ + public init(): void { + const editor = this.editor; + const editing = editor.editing; + + this.listenTo( editing.view.document, 'imageLoaded', ( evt, domEvent ) => { + const image = domEvent.target as HTMLElement; + + if ( !image ) { + return; + } + + const domConverter = editing.view.domConverter; + const imageView = domConverter.domToView( image as HTMLElement ) as ViewElement; + const widgetView = imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ViewContainerElement; + const imageElement = editing.mapper.toModelElement( widgetView )!; + const imageUtils = editor.plugins.get( 'ImageUtils' ); + + if ( imageElement.hasAttribute( 'width' ) || imageElement.hasAttribute( 'height' ) ) { + return; + } + + const setImageSizesOnImageChange = () => { + const changes = Array.from( editor.model.document.differ.getChanges() ); + + for ( const entry of changes ) { + if ( entry.type === 'attribute' ) { + const imageElement = editing.mapper.toModelElement( widgetView )!; + + imageUtils.loadImageAndSetSizeAttributes( imageElement ); + widgetView.off( 'change:attributes', setImageSizesOnImageChange ); + break; + } + } + }; + + widgetView.on( 'change:attributes', setImageSizesOnImageChange ); + } ); + } + /** * @inheritDoc */ From ba9693d5a5197442a97e4b117823279dc93f9b47 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 12 Jul 2023 16:43:58 +0200 Subject: [PATCH 081/118] Refactor: extract getImageWidgetFromImageView function to ImageUtils. --- .../src/imageresize/imageresizehandles.ts | 6 ++---- .../ckeditor5-image/src/imagesizeattributes.ts | 16 ++++------------ packages/ckeditor5-image/src/imageutils.ts | 12 +++++++++++- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts index 3c611ceef46..61ed31120da 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizehandles.ts @@ -7,7 +7,7 @@ * @module image/imageresize/imageresizehandles */ -import type { Element, ViewContainerElement, ViewElement } from 'ckeditor5/src/engine'; +import type { Element, ViewElement } from 'ckeditor5/src/engine'; import { Plugin } from 'ckeditor5/src/core'; import { WidgetResize } from 'ckeditor5/src/widget'; import ImageUtils from '../imageutils'; @@ -23,8 +23,6 @@ const RESIZABLE_IMAGES_CSS_SELECTOR = 'span.image-inline.ck-widget > img,' + 'span.image-inline.ck-widget > picture > img'; -const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/; - const RESIZED_IMAGE_CLASS = 'image_resized'; /** @@ -76,7 +74,7 @@ export default class ImageResizeHandles extends Plugin { const domConverter = editor.editing.view.domConverter; const imageView = domConverter.domToView( domEvent.target as HTMLElement ) as ViewElement; - const widgetView = imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ViewContainerElement; + const widgetView = imageUtils.getImageWidgetFromImageView( imageView )!; let resizer = this.editor.plugins.get( WidgetResize ).getResizerByViewElement( widgetView ); if ( resizer ) { diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 2d4839a9a52..550315b1852 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -8,12 +8,9 @@ */ import { Plugin } from 'ckeditor5/src/core'; -import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element, ViewContainerElement } from 'ckeditor5/src/engine'; +import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; import ImageUtils from './imageutils'; -import ImageLoadObserver, { type ImageLoadedEvent } from './image/imageloadobserver'; -import ImageTypeCommand from './image/imagetypecommand'; - -const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/; +import { type ImageLoadedEvent } from './image/imageloadobserver'; /** * This plugin enables `width` and `size` attributes in inline and block image elements. @@ -42,16 +39,11 @@ export default class ImageSizeAttributes extends Plugin { this.listenTo( editing.view.document, 'imageLoaded', ( evt, domEvent ) => { const image = domEvent.target as HTMLElement; - - if ( !image ) { - return; - } - + const imageUtils = editor.plugins.get( 'ImageUtils' ); const domConverter = editing.view.domConverter; const imageView = domConverter.domToView( image as HTMLElement ) as ViewElement; - const widgetView = imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ViewContainerElement; + const widgetView = imageUtils.getImageWidgetFromImageView( imageView )!; const imageElement = editing.mapper.toModelElement( widgetView )!; - const imageUtils = editor.plugins.get( 'ImageUtils' ); if ( imageElement.hasAttribute( 'width' ) || imageElement.hasAttribute( 'height' ) ) { return; diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index c1f57694ec0..92b7a7c2715 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -19,13 +19,16 @@ import type { ViewDocumentFragment, DowncastWriter, Model, - Position + Position, + ViewContainerElement } from 'ckeditor5/src/engine'; import { Plugin, type Editor } from 'ckeditor5/src/core'; import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget'; import { determineImageTypeForInsertionAtSelection } from './image/utils'; import { DomEmitterMixin, type DomEmitter, global } from 'ckeditor5/src/utils'; +const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/; + /** * A set of helpers related to images. */ @@ -211,6 +214,13 @@ export default class ImageUtils extends Plugin { return this.isImage( selectedElement ) ? selectedElement : selection.getFirstPosition()!.findAncestor( 'imageBlock' ); } + /** + * Returns an image widget editing view based on the passed image view. + */ + public getImageWidgetFromImageView( imageView: ViewElement ): ViewContainerElement | null { + return imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ViewContainerElement; + } + /** * Checks if image can be inserted at current model selection. * From 635a03140d850bdb3c40b978779f2d31d469a060 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 13 Jul 2023 13:04:14 +0200 Subject: [PATCH 082/118] Tests: set image width and height on image change. --- .../tests/imagesizeattributes.js | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index 903ecb4715a..61fb67e6bac 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -4,6 +4,8 @@ */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; @@ -17,6 +19,8 @@ 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'; +/* global Event */ + describe( 'ImageSizeAttributes', () => { let editor, model, view; @@ -47,6 +51,166 @@ describe( 'ImageSizeAttributes', () => { expect( ImageSizeAttributes.requires ).to.have.members( [ ImageUtils ] ); } ); + describe( 'init()', () => { + let editor, model, modelRoot, element, domRoot, imageUtils; + + beforeEach( async () => { + element = global.document.createElement( 'div' ); + global.document.body.appendChild( element ); + + await createEditor(); + } ); + + afterEach( async () => { + element.remove(); + + await editor.destroy(); + } ); + + describe( 'inline image: set width and height on image change', () => { + it( 'should set image width and height on image attribute change', () => { + editor.setData( + '

' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + } ); + + it( 'should set image width and height only on first image attribute change', () => { + editor.setData( + '

' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '20%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + } ); + + it( 'should not try to set image width and height on image attribute change, if image already has width set', () => { + editor.setData( + '

' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.notCalled ).to.be.true; + } ); + } ); + + describe( 'block image: set width and height on image change', () => { + it( 'should set image width and height on image attribute change', () => { + editor.setData( + '
' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + } ); + + it( 'should set image width and height only on first image attribute change', () => { + editor.setData( + '
' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '20%', imageElement ); + } ); + + expect( spy.callCount ).to.equal( 1 ); + } ); + + it( 'should not try to set image width and height on image attribute change, if image already has width set', () => { + editor.setData( + '
' + ); + + const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); + const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); + + domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); + + expect( spy.notCalled ).to.be.true; + + model.change( writer => { + writer.setAttribute( 'resizedWidth', '50%', imageElement ); + } ); + + expect( spy.notCalled ).to.be.true; + } ); + } ); + + async function createEditor() { + editor = await ClassicEditor.create( element, { + plugins: [ + Paragraph, ImageInlineEditing, ImageSizeAttributes, ImageResizeEditing + ] + } ); + + model = editor.model; + modelRoot = editor.model.document.getRoot(); + domRoot = editor.editing.view.getDomRoot(); + imageUtils = editor.plugins.get( '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; From 6e5a1c667d5df68d085a16074e32d0fbedf582dd Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 13 Jul 2023 14:26:45 +0200 Subject: [PATCH 083/118] Make sure image widget view exists. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 550315b1852..d799523040d 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -42,7 +42,12 @@ export default class ImageSizeAttributes extends Plugin { const imageUtils = editor.plugins.get( 'ImageUtils' ); const domConverter = editing.view.domConverter; const imageView = domConverter.domToView( image as HTMLElement ) as ViewElement; - const widgetView = imageUtils.getImageWidgetFromImageView( imageView )!; + const widgetView = imageUtils.getImageWidgetFromImageView( imageView ); + + if ( !widgetView ) { + return; + } + const imageElement = editing.mapper.toModelElement( widgetView )!; if ( imageElement.hasAttribute( 'width' ) || imageElement.hasAttribute( 'height' ) ) { From a6a1c345ff0f79e98acc56e97416cee8efc15f85 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 14 Jul 2023 12:03:52 +0200 Subject: [PATCH 084/118] Update return type. Co-authored-by: Arkadiusz Filipczak --- packages/ckeditor5-image/src/imageutils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 92b7a7c2715..39a0170c799 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -218,7 +218,7 @@ export default class ImageUtils extends Plugin { * Returns an image widget editing view based on the passed image view. */ public getImageWidgetFromImageView( imageView: ViewElement ): ViewContainerElement | null { - return imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ViewContainerElement; + return imageView.findAncestor( { classes: IMAGE_WIDGETS_CLASSES_MATCH_REGEXP } ) as ( ViewContainerElement | null ); } /** From 2963fd933ca8044fc268b8ab10cbbe162b7bb3c2 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Fri, 14 Jul 2023 13:00:51 +0200 Subject: [PATCH 085/118] Do not set aspect-ratio for pictures. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index d799523040d..d0254b12158 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -199,6 +199,11 @@ export default class ImageSizeAttributes extends Plugin { viewWriter.removeAttribute( viewAttributeName, img ); } + // Do not set aspect-ratio for pictures. See https://github.com/ckeditor/ckeditor5/issues/14579. + if ( img.parent!.is( 'element', 'picture' ) ) { + return; + } + const isResized = data.item.hasAttribute( 'resizedWidth' ); // Do not set aspect ratio for inline images which are not resized (data pipeline). From 564d1fd02ab9a0a2fb2f47e16474e8186790ec8e Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 25 Jul 2023 13:49:54 +0200 Subject: [PATCH 086/118] Modify not setting aspect-ratio for pictures. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index d0254b12158..8380acd1122 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -200,7 +200,7 @@ export default class ImageSizeAttributes extends Plugin { } // Do not set aspect-ratio for pictures. See https://github.com/ckeditor/ckeditor5/issues/14579. - if ( img.parent!.is( 'element', 'picture' ) ) { + if ( data.item.hasAttribute( 'sources' ) ) { return; } From 1fbf2b75f3e8c17ff167440082c38074ab081a7d Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 25 Jul 2023 14:11:29 +0200 Subject: [PATCH 087/118] Tests: do not set aspect-ratio for pictures. --- .../tests/imagesizeattributes.js | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index 61fb67e6bac..f3cebf16208 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -13,6 +13,7 @@ import ImageBlockEditing from '../src/image/imageblockediting'; import ImageInlineEditing from '../src/image/imageinlineediting'; import ImageSizeAttributes from '../src/imagesizeattributes'; import ImageResizeEditing from '../src/imageresize/imageresizeediting'; +import PictureEditing from '../src/pictureediting'; import ImageUtils from '../src/imageutils'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; @@ -425,7 +426,7 @@ describe( 'ImageSizeAttributes', () => { beforeEach( async () => { editor = await VirtualTestEditor.create( { - plugins: [ Paragraph, ImageInlineEditing, ImageSizeAttributes, ImageResizeEditing ] + plugins: [ Paragraph, ImageInlineEditing, PictureEditing, ImageResizeEditing ] } ); view = editor.editing.view; @@ -467,6 +468,70 @@ describe( 'ImageSizeAttributes', () => { '

' ); } ); + + it( 'should not add aspect-ratio if it is a picture', () => { + editor.setData( + '

' + + '' + + '' + + '' + + '' + + '

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

' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

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

' + + '' + + '' + + '' + + '' + + '

' + ); + } ); + + it( 'should not add aspect-ratio if it is a picture and image has width and height set', () => { + editor.setData( + '

' + + '' + + '' + + '' + + '' + + '

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

' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

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

' + + '' + + '' + + '' + + '' + + '

' + ); + } ); } ); } ); @@ -560,7 +625,7 @@ describe( 'ImageSizeAttributes', () => { beforeEach( async () => { editor = await VirtualTestEditor.create( { - plugins: [ Paragraph, ImageBlockEditing, ImageSizeAttributes, ImageResizeEditing ] + plugins: [ Paragraph, ImageBlockEditing, PictureEditing, ImageResizeEditing ] } ); view = editor.editing.view; @@ -605,6 +670,64 @@ describe( 'ImageSizeAttributes', () => { '
' ); } ); + + it( 'should not add aspect-ratio if it is a picture', () => { + editor.setData( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + } ); + + it( 'should not add aspect-ratio if it is a picture and image has width and height set', () => { + editor.setData( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '' + + '' + + '' + + '
' + ); + } ); } ); } ); } ); From 3b647b9bdeba01816282ce2dda9aa4202f3553c4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 2 Aug 2023 13:00:02 +0200 Subject: [PATCH 088/118] Use local images in imagesizeattributesallcases manual test. --- .../ckeditor5-image/tests/manual/game_boy.jpg | Bin 0 -> 74350 bytes .../manual/imagesizeattributesallcases.js | 72 +++++++++--------- .../ckeditor5-image/tests/manual/parrot_2.jpg | Bin 0 -> 37484 bytes 3 files changed, 36 insertions(+), 36 deletions(-) create mode 100644 packages/ckeditor5-image/tests/manual/game_boy.jpg create mode 100644 packages/ckeditor5-image/tests/manual/parrot_2.jpg diff --git a/packages/ckeditor5-image/tests/manual/game_boy.jpg b/packages/ckeditor5-image/tests/manual/game_boy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6d089a61687a3159468c09c475ebcb8ba852efad GIT binary patch literal 74350 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 BZZZG> literal 0 HcmV?d00001 diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index 3e28ee62ba9..861a1df28b7 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -77,18 +77,18 @@ const editors = [ id: 'inline1', title: '[Inline] plain (no attributes, no styles)', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline2', title: '[Inline] natural size | width + height attributes: Resize in %', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline3', config: commonConfig, - data: '

', title: '[Inline] natural size | width + height attributes: Resized (width % style)' }, @@ -96,34 +96,34 @@ const editors = [ id: 'inline4', config: commonConfig, data: '

', + 'src="game_boy.jpg" alt="" width="384" height="500">

', title: '[Inline] natural size | width + height attributes: Resized (width % style)' }, { id: 'inline5', title: '[Inline] natural size | width + height attributes: Resize in px', config: configPx, - data: '

' + data: '

' }, { id: 'inline6', title: '[Inline] natural size | width + height attributes: Resized (width px style only)', config: configPx, data: '

' + 'src="game_boy.jpg" alt="" width="384" height="500">

' }, { id: 'inline7', title: '[Inline] natural size | width + height attributes: Resized (width and height px style)', config: configPx, - data: '

' }, { id: 'inline8', title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in %', config: commonConfig, - data: '

' }, { @@ -131,39 +131,39 @@ const editors = [ title: '[Inline] natural size | only resize in % (only width style)', config: commonConfig, data: '

' + 'src="game_boy.jpg" alt="">

' }, { id: 'inline10', title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in px', config: configPx, data: '

' + 'src="game_boy.jpg" alt="">

' }, { id: 'inline11', title: '[Inline] broken aspect ratio | width + height attributes', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline12', title: '[Inline] broken aspect ratio | styles only (w/o width & height attributes)', config: commonConfig, - data: '

' }, { id: 'block1', title: '[Block] plain (no attributes, no styles)', config: commonConfig, - data: '
' + data: '
' }, { id: 'block2', title: '[Block] natural size | width + height attributes: Resize in %', config: commonConfig, - data: '
' }, { @@ -171,20 +171,20 @@ const editors = [ title: '[Block] natural size | width + height attributes: Resized (width % style)', config: commonConfig, data: '
' + - '
' + '
' }, { id: 'block4', title: '[Block] natural size | width + height attributes: Resized (width % style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block5', title: '[Block] natural size | width + height attributes: Resize in px', config: configPx, - data: '
' }, { @@ -192,88 +192,88 @@ const editors = [ title: '[Block] natural size | width + height attributes: Resized (width px style only)', config: configPx, data: '
' + - '
' + '' }, { id: 'block7', title: '[Block] natural size | width + height attributes: Resized (width and height px style)', config: configPx, data: '
' + - '
' + '' }, { id: 'block8', title: '[Block] natural size | styles only (w/o width & height attributes): Resize in %', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block9', title: '[Block] natural size | only resize in % (only width style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block10', title: '[Block] natural size | styles only (w/o width & height attributes): Resize in px', config: configPx, data: '
' + - '
' + '' }, { id: 'block11', title: '[Block] broken aspect ratio | width + height attributes', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block12', title: '[Block] broken aspect ratio | styles only (w/o width & height attributes)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'inline101', title: '[Inline] natural size | width + height attributes: Resized (height % style)', config: commonConfig, - data: '

' }, { id: 'inline102', title: '[Inline] natural size | width + height attributes: Resized (height px style)', config: configPx, - data: '

' }, { id: 'inline103', title: '[Inline] natural size | only resize in % (only height style)', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline104', title: '[Inline] natural size | only resize in px (only height style)', config: configPx, - data: '

' + data: '

' }, { id: 'inline105', title: '[Inline] width + height attributes: Resized (height & width % style)', config: commonConfig, - data: '

' }, { id: 'inline106', title: '[Inline] only resize in % (height & width % style)', config: commonConfig, - data: '

' }, { @@ -281,42 +281,42 @@ const editors = [ title: '[Block] natural size | width + height attributes: Resized (height % style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block102', title: '[Block] natural size | width + height attributes: Resized (height px style)', config: configPx, data: '
' + - '
' + '' }, { id: 'block103', title: '[Block] natural size | only resize in % (only height style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block104', title: '[Block] natural size | only resize in px (only height style)', config: configPx, data: '
' + - '
' + '' }, { id: 'block105', title: '[Block] width + height attributes: Resized (height & width % style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'block106', title: '[Block] only resize in % (height & width % style)', config: commonConfig, data: '
' + - '
' + '' }, { id: 'inline201', diff --git a/packages/ckeditor5-image/tests/manual/parrot_2.jpg b/packages/ckeditor5-image/tests/manual/parrot_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2ec03739ed848e42910d1414c4b4a8591a76e0a GIT binary patch literal 37484 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 Q&-v|JwXxMs3fmw5*`0j|n*aa+ literal 0 HcmV?d00001 From d0a3b1b55e2829f271b1a00440f493ba3d5d8900 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 2 Aug 2023 13:11:30 +0200 Subject: [PATCH 089/118] Tests: small refactor for imagesizeattributesallcases manual test. --- .../manual/imagesizeattributesallcases.js | 88 +++++++------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index 861a1df28b7..f56b4e1cdd6 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -87,17 +87,15 @@ const editors = [ }, { id: 'inline3', + title: '[Inline] natural size | width + height attributes: Resized (width % style)', config: commonConfig, - data: '

', - title: '[Inline] natural size | width + height attributes: Resized (width % style)' + data: '

' }, { id: 'inline4', + title: '[Inline] natural size | width + height attributes: Resized (width % style)', config: commonConfig, - data: '

', - title: '[Inline] natural size | width + height attributes: Resized (width % style)' + data: '

' }, { id: 'inline5', @@ -109,36 +107,31 @@ const editors = [ id: 'inline6', title: '[Inline] natural size | width + height attributes: Resized (width px style only)', config: configPx, - data: '

' + data: '

' }, { id: 'inline7', title: '[Inline] natural size | width + height attributes: Resized (width and height px style)', config: configPx, - data: '

' + data: '

' }, { id: 'inline8', title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in %', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline9', title: '[Inline] natural size | only resize in % (only width style)', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline10', title: '[Inline] natural size | styles only (w/o width & height attributes): Resize in px', config: configPx, - data: '

' + data: '

' }, { id: 'inline11', @@ -150,8 +143,7 @@ const editors = [ id: 'inline12', title: '[Inline] broken aspect ratio | styles only (w/o width & height attributes)', config: commonConfig, - data: '

' + data: '

' }, { id: 'block1', @@ -163,36 +155,31 @@ const editors = [ id: 'block2', title: '[Block] natural size | width + height attributes: Resize in %', config: commonConfig, - data: '
' + data: '
' }, { id: 'block3', title: '[Block] natural size | width + height attributes: Resized (width % style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block4', title: '[Block] natural size | width + height attributes: Resized (width % style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block5', title: '[Block] natural size | width + height attributes: Resize in px', config: configPx, - data: '
' + data: '
' }, { id: 'block6', title: '[Block] natural size | width + height attributes: Resized (width px style only)', config: configPx, - data: '
' + - '
' + data: '
' }, { id: 'block7', @@ -205,50 +192,43 @@ const editors = [ id: 'block8', title: '[Block] natural size | styles only (w/o width & height attributes): Resize in %', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block9', title: '[Block] natural size | only resize in % (only width style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block10', title: '[Block] natural size | styles only (w/o width & height attributes): Resize in px', config: configPx, - data: '
' + - '
' + data: '
' }, { id: 'block11', title: '[Block] broken aspect ratio | width + height attributes', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block12', title: '[Block] broken aspect ratio | styles only (w/o width & height attributes)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'inline101', title: '[Inline] natural size | width + height attributes: Resized (height % style)', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline102', title: '[Inline] natural size | width + height attributes: Resized (height px style)', config: configPx, - data: '

' + data: '

' }, { id: 'inline103', @@ -266,57 +246,49 @@ const editors = [ id: 'inline105', title: '[Inline] width + height attributes: Resized (height & width % style)', config: commonConfig, - data: '

' + data: '

' }, { id: 'inline106', title: '[Inline] only resize in % (height & width % style)', config: commonConfig, - data: '

' + data: '

' }, { id: 'block101', title: '[Block] natural size | width + height attributes: Resized (height % style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block102', title: '[Block] natural size | width + height attributes: Resized (height px style)', config: configPx, - data: '
' + - '
' + data: '
' }, { id: 'block103', title: '[Block] natural size | only resize in % (only height style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block104', title: '[Block] natural size | only resize in px (only height style)', config: configPx, - data: '
' + - '
' + data: '
' }, { id: 'block105', title: '[Block] width + height attributes: Resized (height & width % style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'block106', title: '[Block] only resize in % (height & width % style)', config: commonConfig, - data: '
' + - '
' + data: '
' }, { id: 'inline201', From 90499129a4763b4dd4dbc3b765df5fd533cb9bfe Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 2 Aug 2023 13:22:30 +0200 Subject: [PATCH 090/118] Tests: update description for imagesizeattributesallcases manual test. --- .../ckeditor5-image/tests/manual/imagesizeattributesallcases.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md index 2156997d8bf..0fdd068a07f 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.md @@ -8,6 +8,7 @@ This manual tests consists of many use cases for different combinations of image Image in the editor should look like the image next to the editor (created from editor's output data): * after initial editor load + * the exception to this are inline images that have not been resized (because in the editor they have `max-width: 100%`) * after resizing image in the editor **Note**: Every time an image is resized, the code blocks below the editor are updated with refreshed output data and model. From 9909482ff413526e17f123e17af0adfb96f594ae Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 7 Aug 2023 11:13:41 +0200 Subject: [PATCH 091/118] Fix package.json package versions. --- packages/ckeditor5-image/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/package.json b/packages/ckeditor5-image/package.json index 4ba63138d08..a35261a097e 100644 --- a/packages/ckeditor5-image/package.json +++ b/packages/ckeditor5-image/package.json @@ -26,8 +26,8 @@ "@ckeditor/ckeditor5-cloud-services": "39.0.0", "@ckeditor/ckeditor5-core": "39.0.0", "@ckeditor/ckeditor5-dev-utils": "39.0.0", - "@ckeditor/ckeditor5-html-support": "39.0.1", - "@ckeditor/ckeditor5-paste-from-office": "39.0.1", + "@ckeditor/ckeditor5-html-support": "39.0.0", + "@ckeditor/ckeditor5-paste-from-office": "39.0.0", "@ckeditor/ckeditor5-easy-image": "39.0.0", "@ckeditor/ckeditor5-editor-classic": "39.0.0", "@ckeditor/ckeditor5-engine": "39.0.0", From bc8ece6cd09a53aec998df095f245f0f361030b0 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Mon, 7 Aug 2023 11:38:28 +0200 Subject: [PATCH 092/118] Correct version for ckeditor5-dev-utils. --- packages/ckeditor5-image/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/package.json b/packages/ckeditor5-image/package.json index a35261a097e..b0ea5bed733 100644 --- a/packages/ckeditor5-image/package.json +++ b/packages/ckeditor5-image/package.json @@ -25,7 +25,7 @@ "@ckeditor/ckeditor5-clipboard": "39.0.0", "@ckeditor/ckeditor5-cloud-services": "39.0.0", "@ckeditor/ckeditor5-core": "39.0.0", - "@ckeditor/ckeditor5-dev-utils": "39.0.0", + "@ckeditor/ckeditor5-dev-utils": "^38.0.0", "@ckeditor/ckeditor5-html-support": "39.0.0", "@ckeditor/ckeditor5-paste-from-office": "39.0.0", "@ckeditor/ckeditor5-easy-image": "39.0.0", From 5df5ecbcf1c3ffb24e4cabd20f15963cec583dbd Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 8 Aug 2023 11:07:42 +0200 Subject: [PATCH 093/118] Add missing as const. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 8380acd1122..18fc8997a61 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -26,8 +26,8 @@ export default class ImageSizeAttributes extends Plugin { /** * @inheritDoc */ - public static get pluginName(): 'ImageSizeAttributes' { - return 'ImageSizeAttributes'; + public static get pluginName() { + return 'ImageSizeAttributes' as const; } /** From 15da2810c1ad8600fea61e470bc47bf58d9ea298 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 22 Aug 2023 12:07:42 +0200 Subject: [PATCH 094/118] Change setting image width and height on attribute change (will not be generic). --- .../src/imagesizeattributes.ts | 42 ----- packages/ckeditor5-image/src/imageutils.ts | 30 ++-- .../tests/imagesizeattributes.js | 164 ------------------ 3 files changed, 14 insertions(+), 222 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 18fc8997a61..fe822a83c26 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -30,48 +30,6 @@ export default class ImageSizeAttributes extends Plugin { return 'ImageSizeAttributes' as const; } - /** - * @inheritDoc - */ - public init(): void { - const editor = this.editor; - const editing = editor.editing; - - this.listenTo( editing.view.document, 'imageLoaded', ( evt, domEvent ) => { - const image = domEvent.target as HTMLElement; - const imageUtils = editor.plugins.get( 'ImageUtils' ); - const domConverter = editing.view.domConverter; - const imageView = domConverter.domToView( image as HTMLElement ) as ViewElement; - const widgetView = imageUtils.getImageWidgetFromImageView( imageView ); - - if ( !widgetView ) { - return; - } - - const imageElement = editing.mapper.toModelElement( widgetView )!; - - if ( imageElement.hasAttribute( 'width' ) || imageElement.hasAttribute( 'height' ) ) { - return; - } - - const setImageSizesOnImageChange = () => { - const changes = Array.from( editor.model.document.differ.getChanges() ); - - for ( const entry of changes ) { - if ( entry.type === 'attribute' ) { - const imageElement = editing.mapper.toModelElement( widgetView )!; - - imageUtils.loadImageAndSetSizeAttributes( imageElement ); - widgetView.off( 'change:attributes', setImageSizesOnImageChange ); - break; - } - } - }; - - widgetView.on( 'change:attributes', setImageSizesOnImageChange ); - } ); - } - /** * @inheritDoc */ diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index cba23ad41e9..8bd399744a0 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -156,23 +156,21 @@ export default class ImageUtils extends Plugin { return; } - const img = new global.window.Image(); - - this._domEmitter.listenTo( img, 'load', ( evt, data ) => { - this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); - this._domEmitter.stopListening( img, 'load' ); - } ); - - img.src = src; - } + this.editor.model.change( writer => { + const img = new global.window.Image(); + + this._domEmitter.listenTo( img, 'load', ( evt, data ) => { + if ( !imageElement.getAttribute( 'width' ) && !imageElement.getAttribute( 'height' ) ) { + this.editor.model.enqueueChange( writer.batch, writer => { + writer.setAttribute( 'width', img.naturalWidth, imageElement ); + writer.setAttribute( 'height', img.naturalHeight, imageElement ); + } ); + } + + this._domEmitter.stopListening( img, 'load' ); + } ); - /** - * Sets image `width` and `height` attributes. - */ - private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void { - this.editor.model.enqueueChange( { isUndoable: false }, writer => { - writer.setAttribute( 'width', width, imageElement ); - writer.setAttribute( 'height', height, imageElement ); + img.src = src; } ); } diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index f3cebf16208..d5ecdf575e3 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -4,8 +4,6 @@ */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; -import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; @@ -20,8 +18,6 @@ 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'; -/* global Event */ - describe( 'ImageSizeAttributes', () => { let editor, model, view; @@ -52,166 +48,6 @@ describe( 'ImageSizeAttributes', () => { expect( ImageSizeAttributes.requires ).to.have.members( [ ImageUtils ] ); } ); - describe( 'init()', () => { - let editor, model, modelRoot, element, domRoot, imageUtils; - - beforeEach( async () => { - element = global.document.createElement( 'div' ); - global.document.body.appendChild( element ); - - await createEditor(); - } ); - - afterEach( async () => { - element.remove(); - - await editor.destroy(); - } ); - - describe( 'inline image: set width and height on image change', () => { - it( 'should set image width and height on image attribute change', () => { - editor.setData( - '

' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - } ); - - it( 'should set image width and height only on first image attribute change', () => { - editor.setData( - '

' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '20%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - } ); - - it( 'should not try to set image width and height on image attribute change, if image already has width set', () => { - editor.setData( - '

' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.notCalled ).to.be.true; - } ); - } ); - - describe( 'block image: set width and height on image change', () => { - it( 'should set image width and height on image attribute change', () => { - editor.setData( - '
' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - } ); - - it( 'should set image width and height only on first image attribute change', () => { - editor.setData( - '
' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '20%', imageElement ); - } ); - - expect( spy.callCount ).to.equal( 1 ); - } ); - - it( 'should not try to set image width and height on image attribute change, if image already has width set', () => { - editor.setData( - '
' - ); - - const spy = sinon.spy( imageUtils, 'loadImageAndSetSizeAttributes' ); - const imageElement = modelRoot.getChild( 0 ).getChild( 0 ); - - domRoot.querySelector( 'img' ).dispatchEvent( new Event( 'load' ) ); - - expect( spy.notCalled ).to.be.true; - - model.change( writer => { - writer.setAttribute( 'resizedWidth', '50%', imageElement ); - } ); - - expect( spy.notCalled ).to.be.true; - } ); - } ); - - async function createEditor() { - editor = await ClassicEditor.create( element, { - plugins: [ - Paragraph, ImageInlineEditing, ImageSizeAttributes, ImageResizeEditing - ] - } ); - - model = editor.model; - modelRoot = editor.model.document.getRoot(); - domRoot = editor.editing.view.getDomRoot(); - imageUtils = editor.plugins.get( '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; From 256445ba6c78b6a478a0f4dd8e73642078e7bb83 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 31 Aug 2023 14:43:01 +0200 Subject: [PATCH 095/118] Change setting image width and height on attribute change (continued) so it's undoable in a single undo step. --- .../src/imageresize/resizeimagecommand.ts | 1 + .../src/imagestyle/imagestylecommand.ts | 2 + .../tests/imageresize/resizeimagecommand.js | 67 ++++++++++++++++++- .../tests/imagestyle/imagestylecommand.js | 39 +++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index df14fef73f0..272b0d97530 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -73,6 +73,7 @@ export default class ResizeImageCommand extends Command { model.change( writer => { writer.setAttribute( 'resizedWidth', options.width, imageElement ); writer.removeAttribute( 'resizedHeight', imageElement ); + imageUtils.loadImageAndSetSizeAttributes( imageElement ); } ); } } diff --git a/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts b/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts index 812c7bd707b..5a3342655ee 100644 --- a/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts +++ b/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts @@ -116,6 +116,8 @@ export default class ImageStyleCommand extends Command { } else { writer.setAttribute( 'imageStyle', requestedStyle, imageElement ); } + + imageUtils.loadImageAndSetSizeAttributes( imageElement ); } ); } diff --git a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js index c7a0bba1f5a..56c8c9e2623 100644 --- a/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js +++ b/packages/ckeditor5-image/tests/imageresize/resizeimagecommand.js @@ -4,9 +4,13 @@ */ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import ResizeImageCommand from '../../src/imageresize/resizeimagecommand'; import ImageResizeEditing from '../../src/imageresize/imageresizeediting'; -import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; + +/* eslint-disable no-undef */ describe( 'ResizeImageCommand', () => { let editor, model, command; @@ -124,5 +128,66 @@ describe( 'ResizeImageCommand', () => { expect( getData( model ) ).to.equal( '[]' ); expect( model.document.getRoot().getChild( 0 ).hasAttribute( 'resizedHeight' ) ).to.be.false; } ); + + describe( 'image width and height attributes', () => { + let editor, model, command, editorElement; + + beforeEach( async () => { + editorElement = document.createElement( 'div' ); + + document.body.appendChild( editorElement ); + + editor = await ClassicTestEditor.create( editorElement, { + plugins: [ ArticlePluginSet, ImageResizeEditing ], + image: { + toolbar: [ 'imageStyle:block' ] + } + } ); + + model = editor.model; + command = new ResizeImageCommand( editor ); + } ); + + afterEach( async () => { + editorElement.remove(); + return editor.destroy(); + } ); + + it( 'should set width and height when resizedWidth is set (and be undoable in single step)', async () => { + const initialData = '[]'; + + setData( model, initialData ); + + command.execute( { width: '100%' } ); + await timeout( 100 ); + + expect( getData( model ) ).to.equal( + '[]' + ); + + editor.execute( 'undo' ); + + expect( getData( model ) ).to.equal( initialData ); + } ); + + it( 'should set width and height when resizedWidth is removed (and be undoable in single step)', async () => { + const initialData = '[]'; + + setData( model, initialData ); + + command.execute( { width: null } ); + await timeout( 100 ); + + expect( getData( model ) ).to.equal( '[]' ); + + editor.execute( 'undo' ); + + expect( getData( model ) ).to.equal( initialData ); + } ); + } ); } ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } } ); diff --git a/packages/ckeditor5-image/tests/imagestyle/imagestylecommand.js b/packages/ckeditor5-image/tests/imagestyle/imagestylecommand.js index 51540875df9..4bc84cfbe16 100644 --- a/packages/ckeditor5-image/tests/imagestyle/imagestylecommand.js +++ b/packages/ckeditor5-image/tests/imagestyle/imagestylecommand.js @@ -367,6 +367,41 @@ describe( 'ImageStyleCommand', () => { expect( getData( model ) ).to.equal( '[]' ); expect( command.value ).to.equal( defaultInline.name ); } ); + + it( 'should set width and height when imageStyle is set (and be undoable in single step)', async () => { + const initialData = '[]'; + + setData( model, initialData ); + command.execute( { value: anyImage.name } ); + await timeout( 100 ); + + expect( getData( model ) ).to.equal( + `[]' + ); + + editor.execute( 'undo' ); + + expect( getData( model ) ) + .to.equal( initialData ); + } ); + + it( 'should set width and height when imageStyle is removed (and be undoable in single step)', async () => { + const initialData = + `[]`; + + setData( model, initialData ); + command.execute( { value: defaultInline.name } ); + await timeout( 100 ); + + expect( getData( model ) ) + .to.equal( '[]' ); + + editor.execute( 'undo' ); + + expect( getData( model ) ) + .to.equal( initialData ); + } ); } ); describe( 'when a block image is selected', () => { @@ -435,6 +470,10 @@ describe( 'ImageStyleCommand', () => { .to.equal( `Fo[o]` ); } ); } ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } } ); describe( 'shouldConvertImageType()', () => { From 864dca9dd1c528c7168053b14622dae36ce2b083 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 5 Sep 2023 12:17:10 +0200 Subject: [PATCH 096/118] Refactor. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index fe822a83c26..af2b6f66e67 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -63,7 +63,7 @@ export default class ImageSizeAttributes extends Plugin { editor.conversion.for( 'upcast' ) .attributeToAttribute( { view: { - name: imageType === 'imageBlock' ? 'figure' : 'img', + name: viewElementName, styles: { width: /.+/ } @@ -96,7 +96,7 @@ export default class ImageSizeAttributes extends Plugin { } ) .attributeToAttribute( { view: { - name: imageType === 'imageBlock' ? 'figure' : 'img', + name: viewElementName, styles: { height: /.+/ } From 00b291596c8b3df30f2e17220636d41c4c3f49c4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 5 Sep 2023 12:18:03 +0200 Subject: [PATCH 097/118] Improve regexp for image widget classes. --- packages/ckeditor5-image/src/imageutils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 8bd399744a0..b626485f305 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -27,7 +27,7 @@ import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/wid import { determineImageTypeForInsertionAtSelection } from './image/utils'; import { DomEmitterMixin, type DomEmitter, global } from 'ckeditor5/src/utils'; -const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /(image|image-inline)/; +const IMAGE_WIDGETS_CLASSES_MATCH_REGEXP = /^(image|image-inline)$/; /** * A set of helpers related to images. From 00df4c9b5d1cb9f28e6130de5f8d0fd7c4b8855a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 6 Sep 2023 15:03:13 +0200 Subject: [PATCH 098/118] Update plugins versions. --- packages/ckeditor5-image/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/package.json b/packages/ckeditor5-image/package.json index 2f50710c6f5..fa00c096047 100644 --- a/packages/ckeditor5-image/package.json +++ b/packages/ckeditor5-image/package.json @@ -26,8 +26,8 @@ "@ckeditor/ckeditor5-cloud-services": "39.0.2", "@ckeditor/ckeditor5-core": "39.0.2", "@ckeditor/ckeditor5-dev-utils": "^38.0.0", - "@ckeditor/ckeditor5-html-support": "39.0.1", - "@ckeditor/ckeditor5-paste-from-office": "39.0.1", + "@ckeditor/ckeditor5-html-support": "39.0.2", + "@ckeditor/ckeditor5-paste-from-office": "39.0.2", "@ckeditor/ckeditor5-easy-image": "39.0.2", "@ckeditor/ckeditor5-editor-classic": "39.0.2", "@ckeditor/ckeditor5-engine": "39.0.2", From 7077b7c21c93c03d55bd435fa7f7a68ad7b5560f Mon Sep 17 00:00:00 2001 From: Kuba Niegowski Date: Wed, 6 Sep 2023 19:07:18 +0200 Subject: [PATCH 099/118] Manual test init refactor. --- .../manual/imagesizeattributesallcases.js | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index f56b4e1cdd6..83e0d78f4ab 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -332,32 +332,31 @@ const editors = [ } ]; -for ( const editorObj of editors ) { - insertEditorStructure( editorObj ); +async function initEditors() { + await Promise.all( editors.map( async editorObj => { + insertEditorStructure( editorObj ); - ( async function initTest() { const domElement = document.querySelector( `#${ editorObj.id }` ); - await ClassicEditor - .create( domElement, { ...editorObj.config, initialData: editorObj.data } ) - .then( editor => { - window[ editorObj.id ] = editor; + const editor = await ClassicEditor.create( domElement, { ...editorObj.config, initialData: editorObj.data } ); - editor.model.document.on( 'change:data', () => { - updateLogsAndData( domElement, editor ); - } ); + window[ editorObj.id ] = editor; - logInitialData( domElement, editorObj ); - updateLogsAndData( domElement, editor ); + editor.model.document.on( 'change:data', () => { + updateLogsAndData( domElement, editor ); + } ); - CKEditorInspector.attach( { [ editorObj.id ]: editor } ); - } ) - .catch( err => { - console.error( err.stack ); - } ); - }() ); + logInitialData( domElement, editorObj ); + updateLogsAndData( domElement, editor ); + } ) ); + + CKEditorInspector.attach( Object.fromEntries( editors.map( editorObj => [ editorObj.id, window[ editorObj.id ] ] ) ) ); } +initEditors().catch( err => { + console.error( err.stack ); +} ); + function insertEditorStructure( editorObj ) { const colorClass = editorObj.id.startsWith( 'inline' ) ? 'inlineColor' : 'blockColor'; From 47e4d2b5ecfad4d75337d5f20264f55f0e994dd5 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 11:18:56 +0200 Subject: [PATCH 100/118] Give a variable a better name. --- .../ckeditor5-image/src/imageresize/imageresizeediting.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index 432a2f9af37..bc1fd3e95e0 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -140,8 +140,8 @@ export default class ImageResizeEditing extends Plugin { if ( data.attributeNewValue !== null ) { const viewWriter = conversionApi.writer; - const figure = conversionApi.mapper.toViewElement( data.item ); - const target = imageType === 'imageInline' ? imageUtils.findViewImgElement( figure ) : figure; + const viewImg = conversionApi.mapper.toViewElement( data.item ); + const target = imageType === 'imageInline' ? imageUtils.findViewImgElement( viewImg ) : viewImg; viewWriter.setStyle( 'height', data.attributeNewValue, target ); } From b7fe537f07aa0e48e4c1b764b3469cf0199ffdd7 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 11:44:26 +0200 Subject: [PATCH 101/118] Simplify downcast converter for resizedHeight attribute. --- .../src/imageresize/imageresizeediting.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index bc1fd3e95e0..d0deba646fc 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -119,18 +119,18 @@ export default class ImageResizeEditing extends Plugin { } ) ); - editor.conversion.for( 'dataDowncast' ).add( dispatcher => - dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { - if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { - return; + editor.conversion.for( 'dataDowncast' ).attributeToAttribute( { + model: { + name: imageType, + key: 'resizedHeight' + }, + view: modelAttributeValue => ( { + key: 'style', + value: { + 'height': modelAttributeValue } - - const viewWriter = conversionApi.writer; - const viewElement = conversionApi.mapper.toViewElement( data.item ); - - viewWriter.setStyle( 'height', data.attributeNewValue, viewElement ); } ) - ); + } ); editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( `attribute:resizedHeight:${ imageType }`, ( evt, data, conversionApi ) => { From a00c608838cd3910d09696dacee70f6d149be886 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:25:20 +0200 Subject: [PATCH 102/118] Update image view after removing `resizedHeight` attribute from model. --- .../src/imageresize/imageresizeediting.ts | 10 +++--- .../tests/imageresize/imageresizeediting.js | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index d0deba646fc..ffa951ba87b 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -138,12 +138,14 @@ export default class ImageResizeEditing extends Plugin { return; } - if ( data.attributeNewValue !== null ) { - const viewWriter = conversionApi.writer; - const viewImg = conversionApi.mapper.toViewElement( data.item ); - const target = imageType === 'imageInline' ? imageUtils.findViewImgElement( viewImg ) : viewImg; + const viewWriter = conversionApi.writer; + const viewImg = conversionApi.mapper.toViewElement( data.item ); + const target = imageType === 'imageInline' ? imageUtils.findViewImgElement( viewImg ) : viewImg; + if ( data.attributeNewValue !== null ) { viewWriter.setStyle( 'height', data.attributeNewValue, target ); + } else { + viewWriter.removeStyle( 'height', target ); } } ) ); diff --git a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js index 4ba2c179c67..4782da140d8 100644 --- a/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js +++ b/packages/ckeditor5-image/tests/imageresize/imageresizeediting.js @@ -216,6 +216,23 @@ describe( 'ImageResizeEditing', () => { ); } ); + it( 'removes `height` style in view if `resizedHeight` is removed from model', () => { + setData( editor.model, `` ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'resizedHeight', imageModel ); + } ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '
' + + `` + + '
' + + '
' + ); + } ); + it( 'doesn\'t downcast consumed tokens', () => { editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( 'attribute:resizedHeight:imageBlock', ( evt, data, conversionApi ) => { @@ -408,6 +425,24 @@ describe( 'ImageResizeEditing', () => { ); } ); + it( 'removes `height` style in view if `resizedHeight` is removed from model', () => { + setData( editor.model, + `` + ); + + const imageModel = editor.model.document.getRoot().getChild( 0 ).getChild( 0 ); + + editor.model.change( writer => { + writer.removeAttribute( 'resizedHeight', imageModel ); + } ); + + expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( + '

' + + `` + + '

' + ); + } ); + it( 'doesn\'t downcast consumed tokens', () => { editor.conversion.for( 'editingDowncast' ).add( dispatcher => dispatcher.on( 'attribute:resizedHeight:imageInline', ( evt, data, conversionApi ) => { From 041bb7db296b1f55eb765fa5a7677e0d905da720 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:47:45 +0200 Subject: [PATCH 103/118] Extract common logic to a helper function. --- .../src/imageresize/imageresizeediting.ts | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index ffa951ba87b..f89732815ad 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -161,17 +161,7 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedWidth', value: ( viewElement: ViewElement ) => { - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - // If both image styles: width & height are set, they will override the image width & height attributes in the - // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. - // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. - if ( widthStyle && heightStyle ) { - return null; - } - - return viewElement.getStyle( 'width' ); + return this._getStyleIfWidthAndHeightStylesSet( viewElement, 'width' ); } } } ); @@ -187,19 +177,28 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedHeight', value: ( viewElement: ViewElement ) => { - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - // If both image styles: width & height are set, they will override the image width & height attributes in the - // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. - // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. - if ( widthStyle && heightStyle ) { - return null; - } - - return viewElement.getStyle( 'height' ); + return this._getStyleIfWidthAndHeightStylesSet( viewElement, 'height' ); } } } ); } + + /** + * Returns style (width or height) from the view element, if both styles (width and height) are set. + */ + private _getStyleIfWidthAndHeightStylesSet( viewElement: ViewElement, style: string ): string | null { + const imageUtils: ImageUtils = this.editor.plugins.get( 'ImageUtils' ); + + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + // If both image styles: width & height are set, they will override the image width & height attributes in the + // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. + // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. + if ( widthStyle && heightStyle ) { + return null; + } + + return viewElement.getStyle( style )!; + } } From de853eddb447751fbf2edeebb1dd29cf238b24ce Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:50:36 +0200 Subject: [PATCH 104/118] Correct plugin description. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index af2b6f66e67..62f1f4706ac 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -13,7 +13,7 @@ import ImageUtils from './imageutils'; import { type ImageLoadedEvent } from './image/imageloadobserver'; /** - * This plugin enables `width` and `size` attributes in inline and block image elements. + * This plugin enables `width` and `height` attributes in inline and block image elements. */ export default class ImageSizeAttributes extends Plugin { /** From c8b7e38bed2ed7f01d3e6af2901385c25ee5ff37 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:53:42 +0200 Subject: [PATCH 105/118] Simplify upcast conversion for image for width attribute. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 62f1f4706ac..028ee2db838 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -85,14 +85,9 @@ export default class ImageSizeAttributes extends Plugin { .attributeToAttribute( { view: { name: viewElementName, - attributes: { - width: /.+/ - } + key: 'width' }, - model: { - key: 'width', - value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' ) - } + model: 'width' } ) .attributeToAttribute( { view: { From 0e2c084015c2bee057efa91ee139f828aae8c06c Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:55:24 +0200 Subject: [PATCH 106/118] Simplify upcast conversion for image for height attribute. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 028ee2db838..ab11e16e5da 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -113,14 +113,9 @@ export default class ImageSizeAttributes extends Plugin { .attributeToAttribute( { view: { name: viewElementName, - attributes: { - height: /.+/ - } + key: 'height' }, - model: { - key: 'height', - value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'height' ) - } + model: 'height' } ); // Dedicated converters to propagate attributes to the element. From aee4b43d82ebe81a89560dd8eb8074dbd8b977a5 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 12:57:37 +0200 Subject: [PATCH 107/118] Simplify upcast conversion for image srcset attribute. --- packages/ckeditor5-image/src/image/imageediting.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-image/src/image/imageediting.ts b/packages/ckeditor5-image/src/image/imageediting.ts index 2429f8b3115..71dbb7af6cb 100644 --- a/packages/ckeditor5-image/src/image/imageediting.ts +++ b/packages/ckeditor5-image/src/image/imageediting.ts @@ -57,14 +57,9 @@ export default class ImageEditing extends Plugin { .attributeToAttribute( { view: { name: 'img', - attributes: { - srcset: /.+/ - } + key: 'srcset' }, - model: { - key: 'srcset', - value: ( viewImage: ViewElement ) => viewImage.getAttribute( 'srcset' ) - } + model: 'srcset' } ); const insertImageCommand = new InsertImageCommand( editor ); From 021073fa6df9422935b26bb55f8074928b7a0767 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:00:25 +0200 Subject: [PATCH 108/118] Move helper for checking width & height styles to utils. Use it in additional converters. --- packages/ckeditor5-image/src/image/utils.ts | 15 +++++++++ .../src/imageresize/imageresizeediting.ts | 32 +++++++------------ .../src/imagesizeattributes.ts | 16 +++------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/ckeditor5-image/src/image/utils.ts b/packages/ckeditor5-image/src/image/utils.ts index a9b83d982bc..58405fb8266 100644 --- a/packages/ckeditor5-image/src/image/utils.ts +++ b/packages/ckeditor5-image/src/image/utils.ts @@ -135,3 +135,18 @@ export function determineImageTypeForInsertionAtSelection( // Otherwise insert an inline image. return 'imageInline'; } + +/** + * Returns true if both styles (width and height) are set. + * + * If both image styles: width & height are set, they will override the image width & height attributes in the + * browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. + * That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. + */ +export function widthAndHeightStylesAreBothSet( editor: Editor, viewElement: ViewElement ): boolean { + const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); + const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + + return !!( widthStyle && heightStyle ); +} diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index f89732815ad..c405b07ef5a 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -11,6 +11,7 @@ import type { ViewElement } from 'ckeditor5/src/engine'; import { type Editor, Plugin } from 'ckeditor5/src/core'; import ImageUtils from '../imageutils'; import ResizeImageCommand from './resizeimagecommand'; +import { widthAndHeightStylesAreBothSet } from '../image/utils'; /** * The image resize editing feature. @@ -161,7 +162,11 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedWidth', value: ( viewElement: ViewElement ) => { - return this._getStyleIfWidthAndHeightStylesSet( viewElement, 'width' ); + if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + return null; + } + + return viewElement.getStyle( 'width' ); } } } ); @@ -177,28 +182,13 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedHeight', value: ( viewElement: ViewElement ) => { - return this._getStyleIfWidthAndHeightStylesSet( viewElement, 'height' ); + if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + return null; + } + + return viewElement.getStyle( 'height' ); } } } ); } - - /** - * Returns style (width or height) from the view element, if both styles (width and height) are set. - */ - private _getStyleIfWidthAndHeightStylesSet( viewElement: ViewElement, style: string ): string | null { - const imageUtils: ImageUtils = this.editor.plugins.get( 'ImageUtils' ); - - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - // If both image styles: width & height are set, they will override the image width & height attributes in the - // browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. - // That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. - if ( widthStyle && heightStyle ) { - return null; - } - - return viewElement.getStyle( style )!; - } } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index ab11e16e5da..8af5d800650 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -10,7 +10,7 @@ import { Plugin } from 'ckeditor5/src/core'; import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; import ImageUtils from './imageutils'; -import { type ImageLoadedEvent } from './image/imageloadobserver'; +import { widthAndHeightStylesAreBothSet } from './image/utils'; /** * This plugin enables `width` and `height` attributes in inline and block image elements. @@ -71,11 +71,8 @@ export default class ImageSizeAttributes extends Plugin { model: { key: 'width', value: ( viewElement: ViewElement ) => { - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - if ( widthStyle && heightStyle ) { - return widthStyle; + if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + return imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); } return null; @@ -99,11 +96,8 @@ export default class ImageSizeAttributes extends Plugin { model: { key: 'height', value: ( viewElement: ViewElement ) => { - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); - - if ( widthStyle && heightStyle ) { - return heightStyle; + if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + return imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); } return null; From b1ba144e3564e541254dc320b00804d08b0e5a12 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:01:50 +0200 Subject: [PATCH 109/118] Make function parameters more readable. --- packages/ckeditor5-image/src/imagesizeattributes.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 8af5d800650..82c200a279c 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -124,7 +124,10 @@ export default class ImageSizeAttributes extends Plugin { } ); function attachDowncastConverter( - dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string, setRatioForInlineImage: boolean + dispatcher: DowncastDispatcher, + modelAttributeName: string, + viewAttributeName: string, + setRatioForInlineImage: boolean ) { dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { From d14fe1aeb142078bc04d96c140593888996e2936 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:06:18 +0200 Subject: [PATCH 110/118] Give a function a better name. --- .../ckeditor5-image/src/imageresize/resizeimagecommand.ts | 2 +- .../ckeditor5-image/src/imagestyle/imagestylecommand.ts | 2 +- .../ckeditor5-image/src/imageupload/imageuploadediting.ts | 2 +- packages/ckeditor5-image/src/imageutils.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts index 272b0d97530..0bf83c4b1ec 100644 --- a/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts +++ b/packages/ckeditor5-image/src/imageresize/resizeimagecommand.ts @@ -73,7 +73,7 @@ export default class ResizeImageCommand extends Command { model.change( writer => { writer.setAttribute( 'resizedWidth', options.width, imageElement ); writer.removeAttribute( 'resizedHeight', imageElement ); - imageUtils.loadImageAndSetSizeAttributes( imageElement ); + imageUtils.setImageNaturalSizeAttributes( imageElement ); } ); } } diff --git a/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts b/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts index 5a3342655ee..561bfb6b251 100644 --- a/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts +++ b/packages/ckeditor5-image/src/imagestyle/imagestylecommand.ts @@ -117,7 +117,7 @@ export default class ImageStyleCommand extends Command { writer.setAttribute( 'imageStyle', requestedStyle, imageElement ); } - imageUtils.loadImageAndSetSizeAttributes( imageElement ); + imageUtils.setImageNaturalSizeAttributes( imageElement ); } ); } diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 4679e83b819..418a7037e46 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -240,7 +240,7 @@ export default class ImageUploadEditing extends Plugin { this.editor.model.change( writer => { writer.setAttribute( 'src', urls.default, imageElement ); this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer ); - imageUtils.loadImageAndSetSizeAttributes( imageElement ); + imageUtils.setImageNaturalSizeAttributes( imageElement ); } ); }, { priority: 'low' } ); } diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index b626485f305..0a62a734315 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -130,7 +130,7 @@ export default class ImageUtils extends Plugin { // Inserting an image might've failed due to schema regulations. if ( imageElement.parent ) { - this.loadImageAndSetSizeAttributes( imageElement ); + this.setImageNaturalSizeAttributes( imageElement ); return imageElement; } @@ -140,12 +140,12 @@ export default class ImageUtils extends Plugin { } /** - * Loads image file based on `src`, reads original image sizes and sets them as `width` and `height`. + * Reads original image sizes and sets them as `width` and `height`. * * The `src` attribute may not be available if the user is using an upload adapter. In such a case, * this method is called again after the upload process is complete and the `src` attribute is available. */ - public loadImageAndSetSizeAttributes( imageElement: Element ): void { + public setImageNaturalSizeAttributes( imageElement: Element ): void { const src = imageElement.getAttribute( 'src' ) as string; if ( !src ) { From 078773410a98310a59d2e2d7cba60106be5de7f4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:07:23 +0200 Subject: [PATCH 111/118] Remove unsused function params. --- packages/ckeditor5-image/src/imageutils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 0a62a734315..db053997773 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -159,7 +159,7 @@ export default class ImageUtils extends Plugin { this.editor.model.change( writer => { const img = new global.window.Image(); - this._domEmitter.listenTo( img, 'load', ( evt, data ) => { + this._domEmitter.listenTo( img, 'load', () => { if ( !imageElement.getAttribute( 'width' ) && !imageElement.getAttribute( 'height' ) ) { this.editor.model.enqueueChange( writer.batch, writer => { writer.setAttribute( 'width', img.naturalWidth, imageElement ); From 1427f17b68f656fa68c1f5af6f8bf6df5fd8cfa7 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:17:54 +0200 Subject: [PATCH 112/118] Explain using writer.batch while setting natural image width and height attribute. --- packages/ckeditor5-image/src/imageutils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index db053997773..61e4bec56b0 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -161,6 +161,8 @@ export default class ImageUtils extends Plugin { this._domEmitter.listenTo( img, 'load', () => { if ( !imageElement.getAttribute( 'width' ) && !imageElement.getAttribute( 'height' ) ) { + // We use writer.batch to be able to undo (in a single step) width and height setting + // along with any change that triggered this action (e.g. image resize or image style change). this.editor.model.enqueueChange( writer.batch, writer => { writer.setAttribute( 'width', img.naturalWidth, imageElement ); writer.setAttribute( 'height', img.naturalHeight, imageElement ); From d7c05a24d34493ed19e4c15ce2453c304ce3642b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:24:42 +0200 Subject: [PATCH 113/118] Move internal function to utils file. --- packages/ckeditor5-image/src/image/utils.ts | 15 +++++++++++++-- .../ckeditor5-image/src/imagesizeattributes.ts | 6 +++--- packages/ckeditor5-image/src/imageutils.ts | 11 ----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/ckeditor5-image/src/image/utils.ts b/packages/ckeditor5-image/src/image/utils.ts index 58405fb8266..aaf1406f7f9 100644 --- a/packages/ckeditor5-image/src/image/utils.ts +++ b/packages/ckeditor5-image/src/image/utils.ts @@ -136,6 +136,17 @@ export function determineImageTypeForInsertionAtSelection( return 'imageInline'; } +/** + * Returns parsed value of the size, but only if it contains unit: px. + */ +export function getSizeValueIfInPx( size: string | undefined ): number | null { + if ( size && size.endsWith( 'px' ) ) { + return parseInt( size ); + } + + return null; +} + /** * Returns true if both styles (width and height) are set. * @@ -145,8 +156,8 @@ export function determineImageTypeForInsertionAtSelection( */ export function widthAndHeightStylesAreBothSet( editor: Editor, viewElement: ViewElement ): boolean { const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); - const widthStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); - const heightStyle = imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + const widthStyle = getSizeValueIfInPx( viewElement.getStyle( 'width' ) ); + const heightStyle = getSizeValueIfInPx( viewElement.getStyle( 'height' ) ); return !!( widthStyle && heightStyle ); } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 82c200a279c..51ae2f9b571 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -10,7 +10,7 @@ import { Plugin } from 'ckeditor5/src/core'; import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine'; import ImageUtils from './imageutils'; -import { widthAndHeightStylesAreBothSet } from './image/utils'; +import { widthAndHeightStylesAreBothSet, getSizeValueIfInPx } from './image/utils'; /** * This plugin enables `width` and `height` attributes in inline and block image elements. @@ -72,7 +72,7 @@ export default class ImageSizeAttributes extends Plugin { key: 'width', value: ( viewElement: ViewElement ) => { if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { - return imageUtils.getSizeInPx( viewElement.getStyle( 'width' ) ); + return getSizeValueIfInPx( viewElement.getStyle( 'width' ) ); } return null; @@ -97,7 +97,7 @@ export default class ImageSizeAttributes extends Plugin { key: 'height', value: ( viewElement: ViewElement ) => { if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { - return imageUtils.getSizeInPx( viewElement.getStyle( 'height' ) ); + return getSizeValueIfInPx( viewElement.getStyle( 'height' ) ); } return null; diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 61e4bec56b0..eebab43f061 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -303,17 +303,6 @@ export default class ImageUtils extends Plugin { return super.destroy(); } - - /** - * Returns parsed value of the size, but only if it contains unit: px. - */ - public getSizeInPx( size: string | undefined ): number | null { - if ( size && size.endsWith( 'px' ) ) { - return parseInt( size ); - } - - return null; - } } /** From 44250e26ffbd5e1a9d84f75f489541ff8b118981 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:28:54 +0200 Subject: [PATCH 114/118] Change editor initialization in manual tests. --- .../tests/manual/imagesizeattributes.js | 15 ++++++++------- .../tests/manual/imagesizeattributesghs.js | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributes.js b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js index 8c0e86fc074..77d42ce2c89 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributes.js @@ -44,10 +44,11 @@ const commonConfig = { cloudServices: CS_CONFIG }; -( async function initTest() { - window.editor = await ClassicEditor - .create( document.querySelector( '#editor-width-height-attributes' ), commonConfig ) - .catch( err => { - console.error( err.stack ); - } ); -}() ); +ClassicEditor + .create( document.querySelector( '#editor-width-height-attributes' ), commonConfig ) + .then( editor => { + window.editor = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js index a776fb35cfd..3202f35df72 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesghs.js @@ -65,10 +65,11 @@ const commonConfig = { cloudServices: CS_CONFIG }; -( async function initTest() { - window.editor = await ClassicEditor - .create( document.querySelector( '#editor-ghs-with-width-height-attributes' ), commonConfig ) - .catch( err => { - console.error( err.stack ); - } ); -}() ); +ClassicEditor + .create( document.querySelector( '#editor-ghs-with-width-height-attributes' ), commonConfig ) + .then( editor => { + window.editor = editor; + } ) + .catch( err => { + console.error( err.stack ); + } ); From 8be30ee4fc13d33cf4397395b41cc94ebf07b14c Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:42:34 +0200 Subject: [PATCH 115/118] Simplified imagesizeattributesallcases manual test. --- .../manual/imagesizeattributesallcases.js | 87 +++++++------------ 1 file changed, 32 insertions(+), 55 deletions(-) diff --git a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js index 83e0d78f4ab..9fd913591ad 100644 --- a/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js +++ b/packages/ckeditor5-image/tests/manual/imagesizeattributesallcases.js @@ -10,67 +10,14 @@ import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articleplugi 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 PictureEditing from '../../src/pictureediting'; import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'; import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; -import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; - -const commonConfig = { - plugins: [ - ArticlePluginSet, - ImageResize, - Code, - ImageSizeAttributes, - Indent, - IndentBlock, - CloudServices, - PictureEditing, - PasteFromOffice - ], - 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 -}; - -const configPx = { - plugins: [ - ArticlePluginSet, - ImageResize, - Code, - ImageSizeAttributes, - ImageUpload, - Indent, - IndentBlock, - CloudServices, - EasyImage, - PictureEditing, - PasteFromOffice - ], - toolbar: [ 'heading', '|', 'bold', 'italic', 'link', - 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', 'undo', 'redo', 'outdent', 'indent' ], - image: { - resizeUnit: 'px', - toolbar: [ 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', '|', 'toggleImageCaption', 'resizeImage' ] - }, - table: { - contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ], - tableToolbar: [ 'bold', 'italic' ] - }, - cloudServices: CS_CONFIG -}; +const commonConfig = getConfig(); +const configPx = getConfig( true ); const editors = [ { @@ -332,6 +279,36 @@ const editors = [ } ]; +function getConfig( resizeUnitInPx = false ) { + const config = { + plugins: [ + ArticlePluginSet, + ImageResize, + Code, + ImageSizeAttributes, + Indent, + IndentBlock, + PictureEditing, + PasteFromOffice + ], + 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' ] + } + }; + + if ( resizeUnitInPx ) { + config.image.resizeUnit = 'px'; + } + + return config; +} + async function initEditors() { await Promise.all( editors.map( async editorObj => { insertEditorStructure( editorObj ); From be765a06781b3e189520c24e9b699f3408a6ba95 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 14:56:43 +0200 Subject: [PATCH 116/118] Give a variable a more precise name. --- .../src/imageresize/imageresizeediting.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index c405b07ef5a..c8158ade8c0 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -108,14 +108,14 @@ export default class ImageResizeEditing extends Plugin { } const viewWriter = conversionApi.writer; - const figure = conversionApi.mapper.toViewElement( data.item ); + const viewImg = conversionApi.mapper.toViewElement( data.item ); if ( data.attributeNewValue !== null ) { - viewWriter.setStyle( 'width', data.attributeNewValue, figure ); - viewWriter.addClass( 'image_resized', figure ); + viewWriter.setStyle( 'width', data.attributeNewValue, viewImg ); + viewWriter.addClass( 'image_resized', viewImg ); } else { - viewWriter.removeStyle( 'width', figure ); - viewWriter.removeClass( 'image_resized', figure ); + viewWriter.removeStyle( 'width', viewImg ); + viewWriter.removeClass( 'image_resized', viewImg ); } } ) ); From 9cf1ed51c17f3292bf9646f9fcfaeb8e9205eec7 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 15:47:51 +0200 Subject: [PATCH 117/118] Simplify helper function. --- packages/ckeditor5-image/src/image/utils.ts | 3 +-- .../ckeditor5-image/src/imageresize/imageresizeediting.ts | 4 ++-- packages/ckeditor5-image/src/imagesizeattributes.ts | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-image/src/image/utils.ts b/packages/ckeditor5-image/src/image/utils.ts index aaf1406f7f9..2c0f1707bee 100644 --- a/packages/ckeditor5-image/src/image/utils.ts +++ b/packages/ckeditor5-image/src/image/utils.ts @@ -154,8 +154,7 @@ export function getSizeValueIfInPx( size: string | undefined ): number | null { * browser. In this case, the image looks the same as if these styles were applied to attributes instead of styles. * That's why we can upcast these styles to width & height attributes instead of resizedWidth and resizedHeight. */ -export function widthAndHeightStylesAreBothSet( editor: Editor, viewElement: ViewElement ): boolean { - const imageUtils: ImageUtils = editor.plugins.get( 'ImageUtils' ); +export function widthAndHeightStylesAreBothSet( viewElement: ViewElement ): boolean { const widthStyle = getSizeValueIfInPx( viewElement.getStyle( 'width' ) ); const heightStyle = getSizeValueIfInPx( viewElement.getStyle( 'height' ) ); diff --git a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts index c8158ade8c0..006c41fc909 100644 --- a/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts +++ b/packages/ckeditor5-image/src/imageresize/imageresizeediting.ts @@ -162,7 +162,7 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedWidth', value: ( viewElement: ViewElement ) => { - if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + if ( widthAndHeightStylesAreBothSet( viewElement ) ) { return null; } @@ -182,7 +182,7 @@ export default class ImageResizeEditing extends Plugin { model: { key: 'resizedHeight', value: ( viewElement: ViewElement ) => { - if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + if ( widthAndHeightStylesAreBothSet( viewElement ) ) { return null; } diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 51ae2f9b571..eec5a0e1cf1 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -71,7 +71,7 @@ export default class ImageSizeAttributes extends Plugin { model: { key: 'width', value: ( viewElement: ViewElement ) => { - if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + if ( widthAndHeightStylesAreBothSet( viewElement ) ) { return getSizeValueIfInPx( viewElement.getStyle( 'width' ) ); } @@ -96,7 +96,7 @@ export default class ImageSizeAttributes extends Plugin { model: { key: 'height', value: ( viewElement: ViewElement ) => { - if ( widthAndHeightStylesAreBothSet( this.editor, viewElement ) ) { + if ( widthAndHeightStylesAreBothSet( viewElement ) ) { return getSizeValueIfInPx( viewElement.getStyle( 'height' ) ); } From 84d77c69cd480773de3108909fc260d0f57c72be Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 7 Sep 2023 15:53:35 +0200 Subject: [PATCH 118/118] Update description for uploadComplete handler. --- packages/ckeditor5-image/src/imageupload/imageuploadediting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 418a7037e46..f93e374c484 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -233,7 +233,7 @@ export default class ImageUploadEditing extends Plugin { } ); // Set the default handler for feeding the image element with `src` and `srcset` attributes. - // Load the image, read and set `width` and `height` attributes (original sizes). + // Also set the natural `width` and `height` attributes (if not already set). this.on( 'uploadComplete', ( evt, { imageElement, data } ) => { const urls = data.urls ? data.urls as Record : data;