Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #316 from ckeditor/t/ckeditor5/1985
Browse files Browse the repository at this point in the history
Fix: Image upload should handle images that are deeply nested in other blocks. Closes ckeditor/ckeditor5#1985.
  • Loading branch information
jodator authored Aug 23, 2019
2 parents c3c0e4a + a3768e2 commit 5a729d3
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 28 deletions.
55 changes: 30 additions & 25 deletions src/imageupload/imageuploadediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
import env from '@ckeditor/ckeditor5-utils/src/env';

import ImageUploadCommand from '../../src/imageupload/imageuploadcommand';
import { isImageType, isLocalImage, fetchLocalImage } from '../../src/imageupload/utils';
import { fetchLocalImage, isImageType, isLocalImage } from '../../src/imageupload/utils';

/**
* The editing part of the image upload feature. It registers the `'imageUpload'` command.
Expand Down Expand Up @@ -134,30 +134,32 @@ export default class ImageUploadEditing extends Plugin {
const changes = doc.differ.getChanges( { includeChangesInGraveyard: true } );

for ( const entry of changes ) {
if ( entry.type == 'insert' && entry.name == 'image' ) {
if ( entry.type == 'insert' && entry.name != '$text' ) {
const item = entry.position.nodeAfter;
const isInGraveyard = entry.position.root.rootName == '$graveyard';

// Check if the image element still has upload id.
const uploadId = item.getAttribute( 'uploadId' );
for ( const image of getImagesFromChangeItem( editor, item ) ) {
// Check if the image element still has upload id.
const uploadId = image.getAttribute( 'uploadId' );

if ( !uploadId ) {
continue;
}
if ( !uploadId ) {
continue;
}

// Check if the image is loaded on this client.
const loader = fileRepository.loaders.get( uploadId );
// Check if the image is loaded on this client.
const loader = fileRepository.loaders.get( uploadId );

if ( !loader ) {
continue;
}
if ( !loader ) {
continue;
}

if ( isInGraveyard ) {
// If the image was inserted to the graveyard - abort the loading process.
loader.abort();
} else if ( loader.status == 'idle' ) {
// If the image was inserted into content and has not been loaded yet, start loading it.
this._readAndUpload( loader, item );
if ( isInGraveyard ) {
// If the image was inserted to the graveyard - abort the loading process.
loader.abort();
} else if ( loader.status == 'idle' ) {
// If the image was inserted into content and has not been loaded yet, start loading it.
this._readAndUpload( loader, image );
}
}
}
}
Expand Down Expand Up @@ -188,15 +190,16 @@ export default class ImageUploadEditing extends Plugin {
} );

return loader.read()
.then( data => {
const viewFigure = editor.editing.mapper.toViewElement( imageElement );
const viewImg = viewFigure.getChild( 0 );
.then( () => {
const promise = loader.upload();

// Force re–paint in Safari. Without it, the image will display with a wrong size.
// https://github.com/ckeditor/ckeditor5/issues/1975
/* istanbul ignore next */
if ( env.isSafari ) {
const viewFigure = editor.editing.mapper.toViewElement( imageElement );
const viewImg = viewFigure.getChild( 0 );

editor.editing.view.once( 'render', () => {
// Early returns just to be safe. There might be some code ran
// in between the outer scope and this callback.
Expand All @@ -221,10 +224,6 @@ export default class ImageUploadEditing extends Plugin {
} );
}

editor.editing.view.change( writer => {
writer.setAttribute( 'src', data, viewImg );
} );

model.enqueueChange( 'transparent', writer => {
writer.setAttribute( 'uploadStatus', 'uploading', imageElement );
} );
Expand Down Expand Up @@ -318,3 +317,9 @@ export default class ImageUploadEditing extends Plugin {
export function isHtmlIncluded( dataTransfer ) {
return Array.from( dataTransfer.types ).includes( 'text/html' ) && dataTransfer.getData( 'text/html' ) !== '';
}

function getImagesFromChangeItem( editor, item ) {
return Array.from( editor.model.createRangeOn( item ) )
.filter( value => value.item.is( 'image' ) )
.map( value => value.item );
}
14 changes: 14 additions & 0 deletions src/imageupload/imageuploadprogress.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default class ImageUploadProgress extends Plugin {
// Hide placeholder and initialize progress bar showing upload progress.
_hidePlaceholder( viewFigure, viewWriter );
_showProgressBar( viewFigure, viewWriter, loader, editor.editing.view );
_displayLocalImage( viewFigure, viewWriter, loader );
}

return;
Expand Down Expand Up @@ -261,3 +262,16 @@ function _removeUIElement( viewFigure, writer, uniqueProperty ) {
writer.remove( writer.createRangeOn( element ) );
}
}

// Displays local data from file loader.
//
// @param {module:engine/view/element~Element} imageFigure
// @param {module:engine/view/downcastwriter~DowncastWriter} writer
// @param {module:upload/filerepository~FileLoader} loader
function _displayLocalImage( viewFigure, writer, loader ) {
if ( loader.data ) {
const viewImg = viewFigure.getChild( 0 );

writer.setAttribute( 'src', loader.data, viewImg );
}
}
5 changes: 3 additions & 2 deletions tests/imageupload/imageuploadediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ describe( 'ImageUploadEditing', () => {
'</figure>]' );
} );

it( 'should use read data once it is present', done => {
it( 'should not use read data once it is present', done => {
const file = createNativeFileMock();
setModelData( model, '<paragraph>{}foo bar</paragraph>' );
editor.execute( 'imageUpload', { file } );
Expand All @@ -343,7 +343,8 @@ describe( 'ImageUploadEditing', () => {
tryExpect( done, () => {
expect( getViewData( view ) ).to.equal(
'[<figure class="ck-widget image" contenteditable="false">' +
`<img src="${ base64Sample }"></img>` +
// Rendering the image data is left to a upload progress converter.
'<img></img>' +
'</figure>]' +
'<p>foo bar</p>'
);
Expand Down
51 changes: 50 additions & 1 deletion tests/imageupload/imageuploadprogress.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';

import { UploadAdapterMock, createNativeFileMock, NativeFileReaderMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks';
import { createNativeFileMock, NativeFileReaderMock, UploadAdapterMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks';
import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
Expand Down Expand Up @@ -107,6 +107,55 @@ describe( 'ImageUploadProgress', () => {
loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
} );

// See https://github.com/ckeditor/ckeditor5/issues/1985.
it( 'should work if image parent is refreshed by the differ', function( done ) {
model.schema.register( 'outerBlock', {
allowWhere: '$block',
isBlock: true
} );

model.schema.register( 'innerBlock', {
allowIn: 'outerBlock',
isLimit: true
} );

model.schema.extend( '$block', { allowIn: 'innerBlock' } );
editor.conversion.elementToElement( { model: 'outerBlock', view: 'outerBlock' } );
editor.conversion.elementToElement( { model: 'innerBlock', view: 'innerBlock' } );

model.document.registerPostFixer( () => {
for ( const change of doc.differ.getChanges() ) {
// The differ.refreshItem() simulates remove and insert of and image parent thus preventing image from proper work.
if ( change.type == 'insert' && change.name == 'image' ) {
doc.differ.refreshItem( change.position.parent );

return true;
}
}
} );

setModelData( model, '<outerBlock><innerBlock><paragraph>[]</paragraph></innerBlock></outerBlock>' );

editor.execute( 'imageUpload', { file: createNativeFileMock() } );

model.document.once( 'change', () => {
try {
expect( getViewData( view ) ).to.equal(
'<outerBlock><innerBlock>[<figure class="ck-appear ck-widget image" contenteditable="false">' +
`<img src="${ base64Sample }"></img>` +
'<div class="ck-progress-bar"></div>' +
'</figure>]</innerBlock></outerBlock>'
);

done();
} catch ( err ) {
done( err );
}
}, { priority: 'lowest' } );

loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
} );

it( 'should work correctly when there is no "reading" status and go straight to "uploading"', () => {
const fileRepository = editor.plugins.get( FileRepository );
const file = createNativeFileMock();
Expand Down

0 comments on commit 5a729d3

Please sign in to comment.