Skip to content

Commit

Permalink
Merge pull request #14233 from ckeditor/ck/14201-allow-and-preserve-i…
Browse files Browse the repository at this point in the history
…mage-width-and-height

Feature (image): It should be possible to load and preserve image width and height attributes. Closes #14201.

MAJOR BREAKING CHANGE (image): The name of the image resize width attribute has been changed to resizedWidth. The width and height attributes are now used to preserve image width and height.

MAJOR BREAKING CHANGE (image): The srcset attribute has been simplified. It is no longer an object { data: "...", width: "..." }, but a value previously stored in the data part.
  • Loading branch information
mmotyczynska authored Jun 7, 2023
2 parents a54ae4a + 0bfa0e2 commit 7f84ec2
Show file tree
Hide file tree
Showing 44 changed files with 688 additions and 479 deletions.
4 changes: 4 additions & 0 deletions packages/ckeditor5-html-support/tests/integrations/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,8 @@ describe( 'ImageElementSupport', () => {
'src',
'srcset',
'linkHref',
'width',
'height',
'htmlImgAttributes',
'htmlFigureAttributes',
'htmlLinkAttributes'
Expand Down Expand Up @@ -2440,6 +2442,8 @@ describe( 'ImageElementSupport', () => {
'alt',
'src',
'srcset',
'width',
'height',
'htmlA',
'htmlImgAttributes'
] );
Expand Down
2 changes: 2 additions & 0 deletions packages/ckeditor5-image/src/augmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
ImageCaptionUtils,
ImageInsertUI,
ImageResizeEditing,
ImageSizeAttributes,
ImageStyleEditing,
ImageStyleUI,
ImageTextAlternativeEditing,
Expand Down Expand Up @@ -76,6 +77,7 @@ declare module '@ckeditor/ckeditor5-core' {
[ ImageCaptionUtils.pluginName ]: ImageCaptionUtils;
[ ImageInsertUI.pluginName ]: ImageInsertUI;
[ ImageResizeEditing.pluginName ]: ImageResizeEditing;
[ ImageSizeAttributes.pluginName ]: ImageSizeAttributes;
[ ImageStyleEditing.pluginName ]: ImageStyleEditing;
[ ImageStyleUI.pluginName ]: ImageStyleUI;
[ ImageTextAlternativeEditing.pluginName ]: ImageTextAlternativeEditing;
Expand Down
26 changes: 5 additions & 21 deletions packages/ckeditor5-image/src/image/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import type {
import { first, type GetCallback } from 'ckeditor5/src/utils';
import type ImageUtils from '../imageutils';

type SrcsetAttributeType = null | { data: unknown; width: unknown };

/**
* Returns a function that converts the image view representation:
*
Expand Down Expand Up @@ -178,7 +176,7 @@ export function upcastPicture( imageUtils: ImageUtils ): ( dispatcher: UpcastDis
}

/**
* Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
* Converter used to convert the `srcset` model image attribute to the `srcset` and `sizes` attributes in the view.
*
* @internal
* @param imageType The type of the image.
Expand All @@ -197,27 +195,13 @@ export function downcastSrcsetAttribute(
const img = imageUtils.findViewImgElement( element )!;

if ( data.attributeNewValue === null ) {
const srcset = data.attributeOldValue as SrcsetAttributeType;

if ( srcset && srcset.data ) {
writer.removeAttribute( 'srcset', img );
writer.removeAttribute( 'sizes', img );

if ( srcset.width ) {
writer.removeAttribute( 'width', img );
}
}
writer.removeAttribute( 'srcset', img );
writer.removeAttribute( 'sizes', img );
} else {
const srcset = data.attributeNewValue as SrcsetAttributeType;

if ( srcset && srcset.data ) {
writer.setAttribute( 'srcset', srcset.data, img );
if ( data.attributeNewValue ) {
writer.setAttribute( 'srcset', data.attributeNewValue, img );
// Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2.
writer.setAttribute( 'sizes', '100vw', img );

if ( srcset.width ) {
writer.setAttribute( 'width', srcset.width, img );
}
}
}
};
Expand Down
3 changes: 2 additions & 1 deletion packages/ckeditor5-image/src/image/imageblockediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './converters';

import ImageEditing from './imageediting';
import ImageSizeAttributes from '../imagesizeattributes';
import ImageTypeCommand from './imagetypecommand';
import ImageUtils from '../imageutils';
import {
Expand All @@ -41,7 +42,7 @@ export default class ImageBlockEditing extends Plugin {
* @inheritDoc
*/
public static get requires() {
return [ ImageEditing, ImageUtils, ClipboardPipeline ] as const;
return [ ImageEditing, ImageSizeAttributes, ImageUtils, ClipboardPipeline ] as const;
}

/**
Expand Down
16 changes: 4 additions & 12 deletions packages/ckeditor5-image/src/image/imageediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,13 @@ export default class ImageEditing extends Plugin {
.attributeToAttribute( {
view: {
name: 'img',
key: 'srcset'
attributes: {
srcset: /.+/
}
},
model: {
key: 'srcset',
value: ( viewImage: ViewElement ) => {
const value: Record<string, string> = {
data: viewImage.getAttribute( 'srcset' )!
};

if ( viewImage.hasAttribute( 'width' ) ) {
value.width = viewImage.getAttribute( 'width' )!;
}

return value;
}
value: ( viewImage: ViewElement ) => viewImage.getAttribute( 'srcset' )
}
} );

Expand Down
3 changes: 2 additions & 1 deletion packages/ckeditor5-image/src/image/imageinlineediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from './converters';

import ImageEditing from './imageediting';
import ImageSizeAttributes from '../imagesizeattributes';
import ImageTypeCommand from './imagetypecommand';
import ImageUtils from '../imageutils';
import {
Expand All @@ -40,7 +41,7 @@ export default class ImageInlineEditing extends Plugin {
* @inheritDoc
*/
public static get requires() {
return [ ImageEditing, ImageUtils, ClipboardPipeline ] as const;
return [ ImageEditing, ImageSizeAttributes, ImageUtils, ClipboardPipeline ] as const;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ export default class ImageResizeEditing extends Plugin {

private _registerSchema(): void {
if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) {
this.editor.model.schema.extend( 'imageBlock', { allowAttributes: 'width' } );
this.editor.model.schema.extend( 'imageBlock', { allowAttributes: 'resizedWidth' } );
}

if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) {
this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'width' } );
this.editor.model.schema.extend( 'imageInline', { allowAttributes: 'resizedWidth' } );
}
}

Expand All @@ -100,7 +100,7 @@ export default class ImageResizeEditing extends Plugin {

// Dedicated converter to propagate image's attribute to the img tag.
editor.conversion.for( 'downcast' ).add( dispatcher =>
dispatcher.on( `attribute:width:${ imageType }`, ( evt, data, conversionApi ) => {
dispatcher.on( `attribute:resizedWidth:${ imageType }`, ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
Expand All @@ -127,7 +127,7 @@ export default class ImageResizeEditing extends Plugin {
}
},
model: {
key: 'width',
key: 'resizedWidth',
value: ( viewElement: ViewElement ) => viewElement.getStyle( 'width' )
}
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ export default class ResizeImageCommand extends Command {

this.isEnabled = !!element;

if ( !element || !element.hasAttribute( 'width' ) ) {
if ( !element || !element.hasAttribute( 'resizedWidth' ) ) {
this.value = null;
} else {
this.value = {
width: element.getAttribute( 'width' ) as string,
width: element.getAttribute( 'resizedWidth' ) as string,
height: null
};
}
Expand Down Expand Up @@ -71,7 +71,7 @@ export default class ResizeImageCommand extends Command {

if ( imageElement ) {
model.change( writer => {
writer.setAttribute( 'width', options.width, imageElement );
writer.setAttribute( 'resizedWidth', options.width, imageElement );
} );
}
}
Expand Down
112 changes: 112 additions & 0 deletions packages/ckeditor5-image/src/imagesizeattributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module image/imagesizeattributes
*/

import { Plugin } from 'ckeditor5/src/core';
import type { DowncastDispatcher, DowncastAttributeEvent, ViewElement, Element } from 'ckeditor5/src/engine';
import ImageUtils from './imageutils';

/**
* This plugin enables `width` and `size` attributes in inline and block image elements.
*/
export default class ImageSizeAttributes extends Plugin {
/**
* @inheritDoc
*/
public static get requires() {
return [ ImageUtils ] as const;
}

/**
* @inheritDoc
*/
public static get pluginName(): 'ImageSizeAttributes' {
return 'ImageSizeAttributes';
}

/**
* @inheritDoc
*/
public afterInit(): void {
this._registerSchema();
this._registerConverters( 'imageBlock' );
this._registerConverters( 'imageInline' );
}

/**
* Registers the `width` and `height` attributes for inline and block images.
*/
private _registerSchema(): void {
if ( this.editor.plugins.has( 'ImageBlockEditing' ) ) {
this.editor.model.schema.extend( 'imageBlock', { allowAttributes: [ 'width', 'height' ] } );
}

if ( this.editor.plugins.has( 'ImageInlineEditing' ) ) {
this.editor.model.schema.extend( 'imageInline', { allowAttributes: [ 'width', 'height' ] } );
}
}

/**
* Registers converters for `width` and `height` attributes.
*/
private _registerConverters( imageType: 'imageBlock' | 'imageInline' ): void {
const editor = this.editor;
const imageUtils = editor.plugins.get( 'ImageUtils' );
const viewElementName = imageType === 'imageBlock' ? 'figure' : 'img';

editor.conversion.for( 'upcast' )
.attributeToAttribute( {
view: {
name: viewElementName,
attributes: {
width: /.+/
}
},
model: {
key: 'width',
value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'width' )
}
} )
.attributeToAttribute( {
view: {
name: viewElementName,
attributes: {
height: /.+/
}
},
model: {
key: 'height',
value: ( viewElement: ViewElement ) => viewElement.getAttribute( 'height' )
}
} );

// Dedicated converter to propagate attributes to the <img> element.
editor.conversion.for( 'downcast' ).add( dispatcher => {
attachDowncastConverter( dispatcher, 'width', 'width' );
attachDowncastConverter( dispatcher, 'height', 'height' );
} );

function attachDowncastConverter( dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string ) {
dispatcher.on<DowncastAttributeEvent>( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}

const viewWriter = conversionApi.writer;
const viewElement = conversionApi.mapper.toViewElement( data.item as Element )!;
const img = imageUtils.findViewImgElement( viewElement )!;

if ( data.attributeNewValue !== null ) {
viewWriter.setAttribute( viewAttributeName, data.attributeNewValue, img );
} else {
viewWriter.removeAttribute( viewAttributeName, img );
}
} );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ export default class ImageUploadEditing extends Plugin {
.join( ', ' );

if ( srcsetAttribute != '' ) {
writer.setAttribute( 'srcset', {
data: srcsetAttribute,
writer.setAttributes( {
srcset: srcsetAttribute,
width: maxWidth
}, image );
}
Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-image/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { default as ImageResize } from './imageresize';
export { default as ImageResizeButtons } from './imageresize/imageresizebuttons';
export { default as ImageResizeEditing } from './imageresize/imageresizeediting';
export { default as ImageResizeHandles } from './imageresize/imageresizehandles';
export { default as ImageSizeAttributes } from './imagesizeattributes';
export { default as ImageStyle } from './imagestyle';
export { default as ImageStyleEditing } from './imagestyle/imagestyleediting';
export { default as ImageStyleUI } from './imagestyle/imagestyleui';
Expand Down
Loading

0 comments on commit 7f84ec2

Please sign in to comment.