Skip to content

Commit

Permalink
Merge pull request #14305 from ckeditor/ck/14202-set-image-width-and-…
Browse files Browse the repository at this point in the history
…height-on-upload

Feature (image): After inserting an image, the width and height attributes should be set automatically. Closes #14202.
  • Loading branch information
mmotyczynska committed Jun 26, 2023
2 parents 7f84ec2 + 961bbfe commit 1f602a7
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImageUploadCompleteEvent>( 'uploadComplete', ( evt, { imageElement, data } ) => {
const urls = data.urls ? data.urls as Record<string, unknown> : data;

this.editor.model.change( writer => {
writer.setAttribute( 'src', urls.default, imageElement );
this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer );
imageUtils.loadImageAndSetSizeAttributes( imageElement );
} );
}, { priority: 'low' } );
}
Expand Down
54 changes: 54 additions & 0 deletions packages/ckeditor5-image/src/imageutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, global } 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
*/
Expand Down Expand Up @@ -121,13 +127,52 @@ export default class ImageUtils extends Plugin {

// Inserting an image might've failed due to schema regulations.
if ( imageElement.parent ) {
this.loadImageAndSetSizeAttributes( imageElement );

return imageElement;
}

return null;
} );
}

/**
* 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;
}

if ( imageElement.getAttribute( 'width' ) || imageElement.getAttribute( 'height' ) ) {
return;
}

const img = new global.window.Image();

this._domEmitter.listenTo( img, 'load', ( evt, data ) => {
this._setWidthAndHeight( imageElement, img.naturalWidth, img.naturalHeight );
this._domEmitter.stopListening( img, 'load' );
} );

img.src = src;
}

/**
* Sets image `width` and `height` attributes.
*/
private _setWidthAndHeight( imageElement: Element, width: number, height: number ): void {
this.editor.model.enqueueChange( { isUndoable: false }, writer => {
writer.setAttribute( 'width', width, imageElement );
writer.setAttribute( 'height', height, imageElement );
} );
}

/**
* Returns an image widget editing view element if one is selected or is among the selection's ancestors.
*/
Expand Down Expand Up @@ -239,6 +284,15 @@ export default class ImageUtils extends Plugin {
}
}
}

/**
* @inheritDoc
*/
public override destroy(): void {
this._domEmitter.stopListening();

return super.destroy();
}
}

/**
Expand Down
26 changes: 26 additions & 0 deletions packages/ckeditor5-image/tests/imageupload/imageuploadediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<paragraph>{}foo bar</paragraph>' );
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(
'<paragraph>[<imageInline height="96" src="/assets/sample.png" width="96"></imageInline>]foo bar</paragraph>'
);

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, '<paragraph>{}foo bar</paragraph>' );
Expand Down
16 changes: 15 additions & 1 deletion packages/ckeditor5-image/tests/imageutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -706,6 +706,20 @@ describe( 'ImageUtils plugin', () => {

expect( imageElement ).to.be.null;
} );

it( 'should set image width and height', done => {
setModelData( model, '<paragraph>f[o]o</paragraph>' );

imageUtils.insertImage( { src: '/assets/sample.png' } );

setTimeout( () => {
expect( getModelData( model ) ).to.equal(
'<paragraph>f[<imageInline height="96" src="/assets/sample.png" width="96"></imageInline>]o</paragraph>'
);

done();
}, 100 );
} );
} );

describe( 'findViewImgElement()', () => {
Expand Down

0 comments on commit 1f602a7

Please sign in to comment.