Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure existing picture element is handled correctly on 'sources' downcast #17193

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions packages/ckeditor5-image/src/image/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,6 @@ export function downcastSourcesAttribute( imageUtils: ImageUtils ): ( dispatcher
const attributeNewValue = data.attributeNewValue as null | Array<ViewElementAttributes>;

if ( attributeNewValue && attributeNewValue.length ) {
// Make sure <picture> does not break attribute elements, for instance <a> in linked images.
const pictureElement = viewWriter.createContainerElement( 'picture', null,
attributeNewValue.map( sourceAttributes => {
return viewWriter.createEmptyElement( 'source', sourceAttributes );
} )
);

// Collect all wrapping attribute elements.
const attributeElements = [];
let viewElement = imgElement.parent;
Expand All @@ -249,8 +242,23 @@ export function downcastSourcesAttribute( imageUtils: ImageUtils ): ( dispatcher
viewElement = parentElement;
}

// Insert the picture and move img into it.
viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
const hasPictureElement = imgElement.parent!.is( 'element', 'picture' );

// Reuse existing <picture> element (ckeditor5#17192) or create a new one.
const pictureElement = hasPictureElement ? imgElement.parent : viewWriter.createContainerElement( 'picture', null );

if ( !hasPictureElement ) {
viewWriter.insert( viewWriter.createPositionBefore( imgElement ), pictureElement );
}

viewWriter.remove( viewWriter.createRangeIn( pictureElement ) );

viewWriter.insert( viewWriter.createPositionAt( pictureElement, 'end' ),
attributeNewValue.map( sourceAttributes => {
return viewWriter.createEmptyElement( 'source', sourceAttributes );
} )
);

viewWriter.move( viewWriter.createRangeOn( imgElement ), viewWriter.createPositionAt( pictureElement, 'end' ) );

// Apply collected attribute elements over the new picture element.
Expand Down
152 changes: 152 additions & 0 deletions packages/ckeditor5-image/tests/pictureediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,158 @@ describe( 'PictureEditing', () => {

expect( editor.getData() ).to.equal( '<p>foo<img src="/assets/sample.png">bar</p>' );
} );

it( 'should downcast changed "sources" attribute on an existing picture element', () => {
editor.setData(
'<figure class="image">' +
'<picture>' +
'<source srcset="">' +
'<img src="/assets/sample.png">' +
'</picture>' +
'<figcaption>Caption</figcaption>' +
'</figure>'
);

model.change( writer => {
writer.setAttribute(
'sources',
[
{
srcset: '/assets/sample2.png'
}
],
modelDocument.getRoot().getChild( 0 )
);
} );

expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<figure class="ck-widget image" contenteditable="false">' +
'<picture>' +
'<source srcset="/assets/sample2.png"></source>' +
'<img src="/assets/sample.png"></img>' +
'</picture>' +
'<figcaption ' +
'aria-label="Caption for the image" ' +
'class="ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" ' +
'data-placeholder="Enter image caption" ' +
'role="textbox" ' +
'tabindex="-1">' +
'Caption' +
'</figcaption>' +
'</figure>'
);
} );

it( 'should downcast changed "sources" attribute on an existing linked picture element', () => {
editor.setData(
'<figure class="image">' +
'<a href="https://ckeditor.com">' +
'<picture>' +
'<source srcset="/assets/sample.png">' +
'<img src="/assets/sample.png">' +
'</picture>' +
'</a>' +
'<figcaption>Caption</figcaption>' +
'</figure>'
);

model.change( writer => {
writer.setAttribute(
'sources',
[
{
srcset: '/assets/sample2.png'
}
],
modelDocument.getRoot().getChild( 0 )
);
} );

expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<figure class="ck-widget image" contenteditable="false">' +
'<a href="https://ckeditor.com">' +
'<picture>' +
'<source srcset="/assets/sample2.png"></source>' +
'<img src="/assets/sample.png"></img>' +
'</picture>' +
'</a>' +
'<figcaption ' +
'aria-label="Caption for the image" ' +
'class="ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" ' +
'data-placeholder="Enter image caption" ' +
'role="textbox" ' +
'tabindex="-1">' +
'Caption' +
'</figcaption>' +
'</figure>'
);
} );

it( 'should keep existing picture element attributes when downcasting "sources" attribute', () => {
editor.model.schema.extend( 'imageBlock', {
allowAttributes: [ 'pictureClass' ]
} );

editor.conversion.for( 'upcast' ).add( dispatcher => {
dispatcher.on( 'element:picture', ( _evt, data, conversionApi ) => {
const viewItem = data.viewItem;
const modelElement = data.modelCursor.parent;

conversionApi.writer.setAttribute( 'pictureClass', viewItem.getAttribute( 'class' ), modelElement );
} );
} );

editor.conversion.for( 'downcast' ).add( dispatcher => {
dispatcher.on( 'attribute:pictureClass:imageBlock', ( evt, data, conversionApi ) => {
const element = conversionApi.mapper.toViewElement( data.item );
const pictureElement = element.getChild( 0 );

conversionApi.writer.setAttribute( 'class', data.attributeNewValue, pictureElement );
} );
} );

editor.setData(
'<figure class="image">' +
'<picture class="test-class">' +
'<source srcset="">' +
'<img src="/assets/sample.png">' +
'</picture>' +
'<figcaption>Caption</figcaption>' +
'</figure>'
);

model.change( writer => {
writer.setAttribute(
'sources',
[
{
srcset: '/assets/sample2.png'
}
],
modelDocument.getRoot().getChild( 0 )
);
} );

expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<figure class="ck-widget image" contenteditable="false">' +
'<picture class="test-class">' +
'<source srcset="/assets/sample2.png"></source>' +
'<img src="/assets/sample.png"></img>' +
'</picture>' +
'<figcaption ' +
'aria-label="Caption for the image" ' +
'class="ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" ' +
'data-placeholder="Enter image caption" ' +
'role="textbox" ' +
'tabindex="-1">' +
'Caption' +
'</figcaption>' +
'</figure>'
);
} );
} );
} );
} );
Expand Down