Skip to content

Commit 70d7593

Browse files
authored
Merge pull request #15135 from ckeditor/ck/15093-fix-the-url-editing-of-the-responsive-image
Fix (image): Remove outdated image attributes when an image is replaced by a URL. Closes #15093.
2 parents 6575762 + 0056448 commit 70d7593

File tree

5 files changed

+133
-6
lines changed

5 files changed

+133
-6
lines changed

packages/ckeditor5-ckbox/src/ckboxediting.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ import {
2323
type ViewElement,
2424
type Writer
2525
} from 'ckeditor5/src/engine';
26-
import { CKEditorError, logError } from 'ckeditor5/src/utils';
26+
import { CKEditorError, logError, type DecoratedMethodEvent } from 'ckeditor5/src/utils';
2727

2828
import type { CKBoxAssetDefinition } from './ckboxconfig';
2929

3030
import CKBoxCommand from './ckboxcommand';
3131
import CKBoxUploadAdapter from './ckboxuploadadapter';
32+
import type { ReplaceImageSourceCommand } from '@ckeditor/ckeditor5-image';
3233

3334
const DEFAULT_CKBOX_THEME_NAME = 'lark';
3435

@@ -318,6 +319,18 @@ export default class CKBoxEditing extends Plugin {
318319
}
319320
}
320321
} );
322+
323+
const replaceImageSourceCommand = editor.commands.get( 'replaceImageSource' );
324+
325+
if ( replaceImageSourceCommand ) {
326+
this.listenTo<DecoratedMethodEvent<ReplaceImageSourceCommand, 'cleanupImage'>>(
327+
replaceImageSourceCommand,
328+
'cleanupImage',
329+
( _, [ writer, image ] ) => {
330+
writer.removeAttribute( 'ckboxImageId', image );
331+
}
332+
);
333+
}
321334
}
322335

323336
/**

packages/ckeditor5-ckbox/tests/ckboxediting.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import TokenMock from '@ckeditor/ckeditor5-cloud-services/tests/_utils/tokenmock
3434
const CKBOX_API_URL = 'https://upload.example.com';
3535

3636
describe( 'CKBoxEditing', () => {
37-
let editor, model, view, originalCKBox;
37+
let editor, model, view, originalCKBox, replaceImageSourceCommand;
3838

3939
testUtils.createSinonSandbox();
4040

@@ -50,6 +50,7 @@ describe( 'CKBoxEditing', () => {
5050
}
5151
} );
5252

53+
replaceImageSourceCommand = editor.commands.get( 'replaceImageSource' );
5354
model = editor.model;
5455
view = editor.editing.view;
5556
} );
@@ -2056,6 +2057,23 @@ describe( 'CKBoxEditing', () => {
20562057
} );
20572058
} );
20582059
} );
2060+
2061+
it( 'should remove ckboxImageId attribute on image replace', () => {
2062+
const schema = model.schema;
2063+
schema.extend( 'imageBlock', { allowAttributes: 'ckboxImageId' } );
2064+
2065+
setModelData( model, `[<imageBlock
2066+
ckboxImageId="id"
2067+
></imageBlock>]` );
2068+
2069+
const element = model.document.selection.getSelectedElement();
2070+
2071+
expect( element.getAttribute( 'ckboxImageId' ) ).to.equal( 'id' );
2072+
2073+
replaceImageSourceCommand.execute( { source: 'bar/foo.jpg' } );
2074+
2075+
expect( element.getAttribute( 'ckboxImageId' ) ).to.be.undefined;
2076+
} );
20592077
} );
20602078

20612079
function createTestEditor( config = {} ) {

packages/ckeditor5-ckbox/tests/manual/ckbox.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
99
import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
1010
import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
11+
import ImageInsert from '@ckeditor/ckeditor5-image/src/imageinsert';
1112
import LinkImageEditing from '@ckeditor/ckeditor5-link/src/linkimageediting';
1213
import PictureEditing from '@ckeditor/ckeditor5-image/src/pictureediting';
1314
import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
@@ -16,14 +17,15 @@ import CKBox from '../../src/ckbox';
1617

1718
ClassicEditor
1819
.create( document.querySelector( '#editor' ), {
19-
plugins: [ ArticlePluginSet, PictureEditing, ImageUpload, LinkImageEditing, CloudServices, CKBox ],
20+
plugins: [ ArticlePluginSet, PictureEditing, ImageUpload, LinkImageEditing, ImageInsert, CloudServices, CKBox ],
2021
toolbar: [
2122
'heading',
2223
'|',
2324
'bold',
2425
'italic',
2526
'link',
2627
'insertTable',
28+
'insertImage',
2729
'|',
2830
'undo',
2931
'redo',

packages/ckeditor5-image/src/image/replaceimagesourcecommand.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
44
*/
55

6-
import { Command } from 'ckeditor5/src/core';
6+
import { Command, type Editor } from 'ckeditor5/src/core';
77
import type ImageUtils from '../imageutils';
8+
import type { Writer, Element } from 'ckeditor5/src/engine';
89

910
/**
1011
* @module image/image/replaceimagesourcecommand
@@ -22,6 +23,12 @@ import type ImageUtils from '../imageutils';
2223
export default class ReplaceImageSourceCommand extends Command {
2324
declare public value: string | null;
2425

26+
constructor( editor: Editor ) {
27+
super( editor );
28+
29+
this.decorate( 'cleanupImage' );
30+
}
31+
2532
/**
2633
* @inheritDoc
2734
*/
@@ -43,10 +50,42 @@ export default class ReplaceImageSourceCommand extends Command {
4350
*/
4451
public override execute( options: { source: string } ): void {
4552
const image = this.editor.model.document.selection.getSelectedElement()!;
53+
const imageUtils: ImageUtils = this.editor.plugins.get( 'ImageUtils' );
54+
4655
this.editor.model.change( writer => {
4756
writer.setAttribute( 'src', options.source, image );
48-
writer.removeAttribute( 'srcset', image );
49-
writer.removeAttribute( 'sizes', image );
57+
58+
this.cleanupImage( writer, image );
59+
60+
imageUtils.setImageNaturalSizeAttributes( image );
5061
} );
5162
}
63+
64+
/**
65+
* Cleanup image attributes that are not relevant to the new source.
66+
*
67+
* Removed attributes are: 'srcset', 'sizes', 'sources', 'width', 'height', 'alt'.
68+
*
69+
* This method is decorated, to allow custom cleanup logic.
70+
* For example, to remove 'myImageId' attribute after 'src' has changed:
71+
*
72+
* ```ts
73+
* replaceImageSourceCommand.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
74+
* writer.removeAttribute( 'myImageId', image );
75+
* } );
76+
* ```
77+
*/
78+
public cleanupImage( writer: Writer, image: Element ): void {
79+
writer.removeAttribute( 'srcset', image );
80+
writer.removeAttribute( 'sizes', image );
81+
82+
/**
83+
* In case responsive images some attributes should be cleaned up.
84+
* Check: https://github.com/ckeditor/ckeditor5/issues/15093
85+
*/
86+
writer.removeAttribute( 'sources', image );
87+
writer.removeAttribute( 'width', image );
88+
writer.removeAttribute( 'height', image );
89+
writer.removeAttribute( 'alt', image );
90+
}
5291
}

packages/ckeditor5-image/tests/image/replaceimagesourcecommand.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
44
*/
55

6+
/* global setTimeout */
7+
68
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
79
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
810
import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
@@ -44,6 +46,59 @@ describe( 'ReplaceImageSourceCommand', () => {
4446

4547
expect( element.getAttribute( 'src' ) ).to.equal( 'bar/foo.jpg' );
4648
} );
49+
50+
it( 'should clean up some attributes in responsive image', () => {
51+
setModelData( model, `[<imageBlock
52+
src="foo/bar.jpg"
53+
width="100"
54+
height="200"
55+
myCustomId="id"
56+
alt="Example image"
57+
sources="[{srcset:'url', sizes:'100vw, 1920px', type: 'image/webp'}]"
58+
></imageBlock>]` );
59+
60+
const element = model.document.selection.getSelectedElement();
61+
62+
expect( element.getAttribute( 'src' ) ).to.equal( 'foo/bar.jpg' );
63+
expect( element.getAttribute( 'sources' ) ).to.equal( '[{srcset:\'url\', sizes:\'100vw, 1920px\', type: \'image/webp\'}]' );
64+
expect( element.getAttribute( 'width' ) ).to.equal( 100 );
65+
expect( element.getAttribute( 'height' ) ).to.equal( 200 );
66+
expect( element.getAttribute( 'alt' ) ).to.equal( 'Example image' );
67+
expect( element.getAttribute( 'myCustomId' ) ).to.equal( 'id' );
68+
69+
command.on( 'cleanupImage', ( eventInfo, [ writer, image ] ) => {
70+
writer.removeAttribute( 'myCustomId', image );
71+
} );
72+
command.execute( { source: 'bar/foo.jpg' } );
73+
74+
expect( element.getAttribute( 'src' ) ).to.equal( 'bar/foo.jpg' );
75+
expect( element.getAttribute( 'sources' ) ).to.be.undefined;
76+
expect( element.getAttribute( 'width' ) ).to.be.undefined;
77+
expect( element.getAttribute( 'height' ) ).to.be.undefined;
78+
expect( element.getAttribute( 'alt' ) ).to.be.undefined;
79+
expect( element.getAttribute( 'myCustomId' ) ).to.be.undefined;
80+
} );
81+
82+
it( 'should set width and height on replaced image', done => {
83+
setModelData( model, `[<imageBlock
84+
src="foo/bar.jpg"
85+
width="100"
86+
height="200"
87+
myCustomId="id"
88+
alt="Example image"
89+
sources="[{srcset:'url', sizes:'100vw, 1920px', type: 'image/webp'}]"
90+
></imageBlock>]` );
91+
92+
const element = model.document.selection.getSelectedElement();
93+
94+
command.execute( { source: '/assets/sample.png' } );
95+
96+
setTimeout( () => {
97+
expect( element.getAttribute( 'width' ) ).to.equal( 96 );
98+
expect( element.getAttribute( 'height' ) ).to.equal( 96 );
99+
done();
100+
}, 100 );
101+
} );
47102
} );
48103

49104
describe( 'refresh()', () => {

0 commit comments

Comments
 (0)