From 30e97966206d0bbad14bec209fa0bab33c99cea4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Tue, 30 May 2023 13:29:11 +0200 Subject: [PATCH 1/6] Set image width and height on upload. --- .../src/imageupload/imageuploadediting.ts | 2 + packages/ckeditor5-image/src/imageutils.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index a2350e312c5..18f92ae24aa 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -225,12 +225,14 @@ export default class ImageUploadEditing extends Plugin { } ); // Set the default handler for feeding the image element with `src` and `srcset` attributes. + // Load the image, read and set `width` and `height` attributes (original sizes). this.on( 'uploadComplete', ( evt, { imageElement, data } ) => { const urls = data.urls ? data.urls as Record : data; this.editor.model.change( writer => { writer.setAttribute( 'src', urls.default, imageElement ); this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer ); + imageUtils.loadImageAndSetSizeAttributes( imageElement ); } ); }, { priority: 'low' } ); } diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index d8432b33501..1f8d243b6ad 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -24,11 +24,17 @@ import type { import { Plugin, type Editor } from 'ckeditor5/src/core'; import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget'; import { determineImageTypeForInsertionAtSelection } from './image/utils'; +import { DomEmitterMixin, type DomEmitter } from 'ckeditor5/src/utils'; /** * A set of helpers related to images. */ export default class ImageUtils extends Plugin { + /** + * DOM Emitter. + */ + private _domEmitter: DomEmitter = new ( DomEmitterMixin() )(); + /** * @inheritDoc */ @@ -121,6 +127,8 @@ export default class ImageUtils extends Plugin { // Inserting an image might've failed due to schema regulations. if ( imageElement.parent ) { + this.loadImageAndSetSizeAttributes( imageElement ); + return imageElement; } @@ -128,6 +136,42 @@ export default class ImageUtils extends Plugin { } ); } + /** + * Loads image file based on `src`, reads original image sizes and sets them as `width` and `height`. + * + * The `src` attribute may not be available if the user is using an upload adapter. In such a case, + * this method is called again after the upload process is complete and the `src` attribute is available. + */ + public loadImageAndSetSizeAttributes( imageElement: Element ): void { + const src = imageElement.getAttribute( 'src' ) as string; + + if ( !src ) { + return; + } + + const img = new Image(); + + this._domEmitter.listenTo( img, 'load', ( evt, data ) => { + this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); + } ); + + this._domEmitter.listenTo( img, 'error', ( evt, data ) => { + console.warn( `Failed to download image with src: ${ src }.` ); + } ); + + img.src = src; + } + + /** + * Sets image `width` and `height` attributes. + */ + private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void { + this.editor.model.enqueueChange( { isUndoable: false }, writer => { + writer.setAttribute( 'width', width.toString(), imageElement ); + writer.setAttribute( 'height', height.toString(), imageElement ); + } ); + } + /** * Returns an image widget editing view element if one is selected or is among the selection's ancestors. */ From cb64f2cabe0f74fe152df0dce651fa747da3dc1a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 31 May 2023 21:50:19 +0200 Subject: [PATCH 2/6] Test: set image width and height after image insert. --- packages/ckeditor5-image/tests/imageutils.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-image/tests/imageutils.js b/packages/ckeditor5-image/tests/imageutils.js index ffb86c9d679..f808daeb7af 100644 --- a/packages/ckeditor5-image/tests/imageutils.js +++ b/packages/ckeditor5-image/tests/imageutils.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* global console */ +/* global console, setTimeout */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ViewDowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwriter'; @@ -706,6 +706,20 @@ describe( 'ImageUtils plugin', () => { expect( imageElement ).to.be.null; } ); + + it( 'should set image width and height', done => { + setModelData( model, 'f[o]o' ); + + imageUtils.insertImage( { src: '/assets/sample.png' } ); + + setTimeout( () => { + expect( getModelData( model ) ).to.equal( + 'f[]o' + ); + + done(); + }, 100 ); + } ); } ); describe( 'findViewImgElement()', () => { From 0b0af828ad89a6f01272dfc92e77b89a3000845a Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Wed, 31 May 2023 21:51:22 +0200 Subject: [PATCH 3/6] Test: set image width and height after upload. --- .../tests/imageupload/imageuploadediting.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js index e05ceb20a0a..fab20c27cc0 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js @@ -487,6 +487,32 @@ describe( 'ImageUploadEditing', () => { expect( loader.status ).to.equal( 'idle' ); } ); + it( 'should set image width and height after server response', async () => { + const file = createNativeFileMock(); + setModelData( model, '{}foo bar' ); + editor.execute( 'uploadImage', { file } ); + + await new Promise( res => { + model.document.once( 'change', res ); + loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) ); + } ); + + await new Promise( res => { + model.document.once( 'change', res, { priority: 'lowest' } ); + loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: '/assets/sample.png' } ) ); + } ); + + await timeout( 100 ); + + expect( getModelData( model ) ).to.equal( + '[]foo bar' + ); + + function timeout( ms ) { + return new Promise( res => setTimeout( res, ms ) ); + } + } ); + it( 'should support adapter response with the normalized `urls` property', async () => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); From 0e99f8a6c52a1d2bdef68fdc358c477307386382 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 11:24:08 +0200 Subject: [PATCH 4/6] Remove error handler. --- packages/ckeditor5-image/src/imageutils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 1f8d243b6ad..6ecd26af348 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -155,10 +155,6 @@ export default class ImageUtils extends Plugin { this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); } ); - this._domEmitter.listenTo( img, 'error', ( evt, data ) => { - console.warn( `Failed to download image with src: ${ src }.` ); - } ); - img.src = src; } From 318833670b4757c956fd3819b185b6bf5056eb3b Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 16:42:42 +0200 Subject: [PATCH 5/6] Improve listener for loading image and setting width and height. --- packages/ckeditor5-image/src/imageutils.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index 6ecd26af348..fab28123f21 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -24,7 +24,7 @@ import type { import { Plugin, type Editor } from 'ckeditor5/src/core'; import { findOptimalInsertionRange, isWidget, toWidget } from 'ckeditor5/src/widget'; import { determineImageTypeForInsertionAtSelection } from './image/utils'; -import { DomEmitterMixin, type DomEmitter } from 'ckeditor5/src/utils'; +import { DomEmitterMixin, type DomEmitter, global } from 'ckeditor5/src/utils'; /** * A set of helpers related to images. @@ -149,10 +149,11 @@ export default class ImageUtils extends Plugin { return; } - const img = new Image(); + const img = new global.window.Image(); this._domEmitter.listenTo( img, 'load', ( evt, data ) => { this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight ); + this._domEmitter.stopListening( img, 'load' ); } ); img.src = src; @@ -163,8 +164,8 @@ export default class ImageUtils extends Plugin { */ private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void { this.editor.model.enqueueChange( { isUndoable: false }, writer => { - writer.setAttribute( 'width', width.toString(), imageElement ); - writer.setAttribute( 'height', height.toString(), imageElement ); + writer.setAttribute( 'width', width, imageElement ); + writer.setAttribute( 'height', height, imageElement ); } ); } @@ -279,6 +280,15 @@ export default class ImageUtils extends Plugin { } } } + + /** + * @inheritDoc + */ + public override destroy(): void { + this._domEmitter.stopListening(); + + return super.destroy(); + } } /** From 3eacd50eecb5ecdb6bd7ee52278edeed5220d4d4 Mon Sep 17 00:00:00 2001 From: Marta Motyczynska Date: Thu, 1 Jun 2023 18:10:55 +0200 Subject: [PATCH 6/6] Do not override width and height if they were provided. --- packages/ckeditor5-image/src/imageutils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ckeditor5-image/src/imageutils.ts b/packages/ckeditor5-image/src/imageutils.ts index fab28123f21..73f584e28df 100644 --- a/packages/ckeditor5-image/src/imageutils.ts +++ b/packages/ckeditor5-image/src/imageutils.ts @@ -149,6 +149,10 @@ export default class ImageUtils extends Plugin { return; } + if ( imageElement.getAttribute( 'width' ) || imageElement.getAttribute( 'height' ) ) { + return; + } + const img = new global.window.Image(); this._domEmitter.listenTo( img, 'load', ( evt, data ) => {