diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts index 9ff14cb0fb5..ba692060928 100644 --- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts +++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.ts @@ -17,7 +17,8 @@ import { type DataTransfer, type ViewElement, type NodeAttributes, - type DowncastAttributeEvent + type DowncastAttributeEvent, + type UpcastElementEvent } from 'ckeditor5/src/engine.js'; import { Notification } from 'ckeditor5/src/ui.js'; @@ -116,13 +117,34 @@ export default class ImageUploadEditing extends Plugin { }, model: 'uploadId' } ) - .attributeToAttribute( { - view: { - name: 'img', - key: 'data-ck-upload-id' - }, - model: 'uploadId' - } ); + + // Handle the case when the image is not fully uploaded yet but it's being moved. + // See more: https://github.com/ckeditor/ckeditor5/pull/17327 + .add( dispatcher => dispatcher.on( 'element:img', ( evt, data, conversionApi ) => { + const uploadId = data.viewItem.getAttribute( 'data-ck-upload-id' ); + + if ( !uploadId ) { + return; + } + + if ( !data.modelRange ) { + Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) ); + } + + const [ modelElement ] = Array.from( data.modelRange!.getItems() ); + const loader = fileRepository.loaders.get( uploadId as string ); + + if ( modelElement ) { + // Handle case when `uploadId` is set on the image element but the loader is not present in the registry. + // It may happen when the image was successfully uploaded and the loader was removed from the registry. + // It's still present in the `_uploadedImages` map though. It's why we do not place this line in the condition below. + conversionApi.writer.setAttribute( 'uploadId', uploadId, modelElement ); + + if ( loader && loader.data ) { + conversionApi.writer.setAttribute( 'uploadStatus', loader.status, modelElement ); + } + } + } ) ); // Handle pasted images. // For every image file, a new file loader is created and a placeholder image is diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js index 7542113d687..feff9587dfa 100644 --- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js +++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js @@ -22,6 +22,7 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js'; import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository.js'; import { UploadAdapterMock, createNativeFileMock, NativeFileReaderMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks.js'; +import UpcastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js'; import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js'; import { getData as getViewData, stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view.js'; @@ -1684,6 +1685,29 @@ describe( 'ImageUploadEditing', () => { } ); } ); + describe( 'data upcast of `data-ck-upload-id` attribute', () => { + it( 'should upcast `data-ck-upload-id` attribute', () => { + editor.setData( '

' ); + + expect( getModelData( model, { withoutSelection: true } ) ).to.equal( + '' + ); + } ); + + it( 'should upcast `uploadStatus` if image is present in registry', () => { + sinon.stub( fileRepository.loaders, 'get' ).withArgs( '123' ).returns( { + status: 'uploading', + data: {} + } ); + + editor.setData( '

' ); + + expect( getModelData( model, { withoutSelection: true } ) ).to.equal( + '' + ); + } ); + } ); + // Helper for validating clipboard and model data as a result of a paste operation. This function checks both clipboard // data and model data synchronously (`expectedClipboardData`, `expectedModel`) and then the model data after `loader.file` // promise is resolved (so model state after successful/failed file fetch attempt).