From 82975fec610760353549d4f033e88b074d0f126b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 7 Jun 2023 14:11:35 +0200 Subject: [PATCH 01/43] 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 02/43] 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 03/43] 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 04/43] 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 05/43] 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 06/43] 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 07/43] 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 08/43] 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 09/43] 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 10/43] 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 11/43] 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 12/43] 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 13/43] 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 14/43] 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 15/43] 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 16/43] 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 17/43] 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 18/43] 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 19/43] 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 20/43] 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 21/43] 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 22/43] 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 23/43] 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 24/43] 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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 38/43] 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 32104408ac4e8910715b4685b369d66a6dda8c34 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 5 Jul 2023 12:19:20 +0200 Subject: [PATCH 39/43] 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 40/43] 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 41/43] 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 42/43] 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 43/43] 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 );