8
0
Просмотр исходного кода

Introduce "imageInsert" plugin as wrapper for "imageUpload".

panr 5 лет назад
Родитель
Сommit
898bf016f0

+ 4 - 3
packages/ckeditor5-image/src/imageinsert.js

@@ -8,9 +8,10 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import ImageUpload from './imageupload';
 import ImageInsertUI from './imageinsert/imageinsertui';
-import ImageInsertEditing from './imageinsert/imageinsertediting';
-import ImageUploadProgress from './imageupload/imageuploadprogress';
+// import ImageInsertEditing from './imageinsert/imageinsertediting';
+// import ImageUploadProgress from './imageupload/imageuploadprogress';
 
 // TODO FIX THE DESCR
 /**
@@ -38,7 +39,7 @@ export default class ImageInsert extends Plugin {
 	 * @inheritDoc
 	 */
 	static get requires() {
-		return [ ImageInsertEditing, ImageInsertUI, ImageUploadProgress ];
+		return [ ImageUpload, ImageInsertUI ];
 	}
 }
 

+ 0 - 51
packages/ckeditor5-image/src/imageinsert/imageinsertcommand.js

@@ -1,51 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-import ImageUploadCommand from '@ckeditor/ckeditor5-image/src/imageupload/imageuploadcommand';
-import { isImageAllowed } from '../image/utils';
-
-/**
- * @module image/imageinsert/imageinsertcommand
- */
-
-// TODO?
-/**
- * The image upload command.
- *
- * The command is registered by the {@link module:image/imageinsert/imageinsertediting~ImageUploadEditing} plugin as `'imageUpload'`.
- *
- * In order to upload an image at the current selection position
- * (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm),
- * execute the command and pass the native image file instance:
- *
- *		this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
- *			// Assuming that only images were pasted:
- *			const images = Array.from( data.dataTransfer.files );
- *
- *			// Upload the first image:
- *			editor.execute( 'imageUpload', { file: images[ 0 ] } );
- *		} );
- *
- * It is also possible to insert multiple images at once:
- *
- *		editor.execute( 'imageUpload', {
- *			file: [
- *				file1,
- *				file2
- *			]
- *		} );
- *
- * @extends module:core/command~Command
- */
-export default class ImageInsertCommand extends ImageUploadCommand {
-	/**
-	 * @inheritDoc
-	 */
-	refresh() {
-		const imageElement = this.editor.model.document.selection.getSelectedElement();
-		const isImage = imageElement && imageElement.name === 'image' || false;
-
-		this.isEnabled = isImageAllowed( this.editor.model ) || isImage;
-	}
-}

+ 0 - 345
packages/ckeditor5-image/src/imageinsert/imageinsertediting.js

@@ -1,345 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * @module image/imageinsert/imageinsertediting
- */
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
-import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
-import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
-import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
-import env from '@ckeditor/ckeditor5-utils/src/env';
-
-import ImageInsertCommand from '../../src/imageinsert/imageinsertcommand';
-import { fetchLocalImage, isLocalImage } from '../../src/imageinsert/utils';
-import { createImageTypeRegExp } from './utils';
-import { getViewImgFromWidget } from '../image/utils';
-
-/**
- * The editing part of the image upload feature. It registers the `'imageUpload'` command.
- *
- * @extends module:core/plugin~Plugin
- */
-export default class ImageInsertEditing extends Plugin {
-	/**
-	 * @inheritDoc
-	 */
-	static get requires() {
-		return [ FileRepository, Notification, Clipboard ];
-	}
-
-	static get pluginName() {
-		return 'ImageInsertEditing';
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	constructor( editor ) {
-		super( editor );
-
-		editor.config.define( 'image', {
-			upload: {
-				types: [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff' ]
-			}
-		} );
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	init() {
-		const editor = this.editor;
-		const doc = editor.model.document;
-		const schema = editor.model.schema;
-		const conversion = editor.conversion;
-		const fileRepository = editor.plugins.get( FileRepository );
-
-		const imageTypes = createImageTypeRegExp( editor.config.get( 'image.upload.types' ) );
-
-		// Setup schema to allow uploadId and uploadStatus for images.
-		schema.extend( 'image', {
-			allowAttributes: [ 'uploadId', 'uploadStatus' ]
-		} );
-
-		// Register imageInsert command.
-		editor.commands.add( 'imageUpload', new ImageInsertCommand( editor ) );
-
-		// Register upcast converter for uploadId.
-		conversion.for( 'upcast' )
-			.attributeToAttribute( {
-				view: {
-					name: 'img',
-					key: 'uploadId'
-				},
-				model: 'uploadId'
-			} );
-
-		// Handle pasted images.
-		// For every image file, a new file loader is created and a placeholder image is
-		// inserted into the content. Then, those images are uploaded once they appear in the model
-		// (see Document#change listener below).
-		this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => {
-			// Skip if non empty HTML data is included.
-			// https://github.com/ckeditor/ckeditor5-upload/issues/68
-			if ( isHtmlIncluded( data.dataTransfer ) ) {
-				return;
-			}
-
-			const images = Array.from( data.dataTransfer.files ).filter( file => {
-				// See https://github.com/ckeditor/ckeditor5-image/pull/254.
-				if ( !file ) {
-					return false;
-				}
-
-				return imageTypes.test( file.type );
-			} );
-
-			const ranges = data.targetRanges.map( viewRange => editor.editing.mapper.toModelRange( viewRange ) );
-
-			editor.model.change( writer => {
-				// Set selection to paste target.
-				writer.setSelection( ranges );
-
-				if ( images.length ) {
-					evt.stop();
-
-					// Upload images after the selection has changed in order to ensure the command's state is refreshed.
-					editor.model.enqueueChange( 'default', () => {
-						editor.execute( 'imageUpload', { file: images } );
-					} );
-				}
-			} );
-		} );
-
-		// Handle HTML pasted with images with base64 or blob sources.
-		// For every image file, a new file loader is created and a placeholder image is
-		// inserted into the content. Then, those images are uploaded once they appear in the model
-		// (see Document#change listener below).
-		this.listenTo( editor.plugins.get( Clipboard ), 'inputTransformation', ( evt, data ) => {
-			const fetchableImages = Array.from( editor.editing.view.createRangeIn( data.content ) )
-				.filter( value => isLocalImage( value.item ) && !value.item.getAttribute( 'uploadProcessed' ) )
-				.map( value => { return { promise: fetchLocalImage( value.item ), imageElement: value.item }; } );
-
-			if ( !fetchableImages.length ) {
-				return;
-			}
-
-			const writer = new UpcastWriter( editor.editing.view.document );
-
-			for ( const fetchableImage of fetchableImages ) {
-				// Set attribute marking that the image was processed already.
-				writer.setAttribute( 'uploadProcessed', true, fetchableImage.imageElement );
-
-				const loader = fileRepository.createLoader( fetchableImage.promise );
-
-				if ( loader ) {
-					writer.setAttribute( 'src', '', fetchableImage.imageElement );
-					writer.setAttribute( 'uploadId', loader.id, fetchableImage.imageElement );
-				}
-			}
-		} );
-
-		// Prevents from the browser redirecting to the dropped image.
-		editor.editing.view.document.on( 'dragover', ( evt, data ) => {
-			data.preventDefault();
-		} );
-
-		// Upload placeholder images that appeared in the model.
-		doc.on( 'change', () => {
-			const changes = doc.differ.getChanges( { includeChangesInGraveyard: true } );
-
-			for ( const entry of changes ) {
-				if ( entry.type == 'insert' && entry.name != '$text' ) {
-					const item = entry.position.nodeAfter;
-					const isInGraveyard = entry.position.root.rootName == '$graveyard';
-
-					for ( const image of getImagesFromChangeItem( editor, item ) ) {
-						// Check if the image element still has upload id.
-						const uploadId = image.getAttribute( 'uploadId' );
-
-						if ( !uploadId ) {
-							continue;
-						}
-
-						// Check if the image is loaded on this client.
-						const loader = fileRepository.loaders.get( uploadId );
-
-						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, image );
-						}
-					}
-				}
-			}
-		} );
-	}
-
-	/**
-	 * Reads and uploads an image.
-	 *
-	 * The image is read from the disk and as a Base64-encoded string it is set temporarily to
-	 * `image[src]`. When the image is successfully uploaded, the temporary data is replaced with the target
-	 * image's URL (the URL to the uploaded image on the server).
-	 *
-	 * @protected
-	 * @param {module:upload/filerepository~FileLoader} loader
-	 * @param {module:engine/model/element~Element} imageElement
-	 * @returns {Promise}
-	 */
-	_readAndUpload( loader, imageElement ) {
-		const editor = this.editor;
-		const model = editor.model;
-		const t = editor.locale.t;
-		const fileRepository = editor.plugins.get( FileRepository );
-		const notification = editor.plugins.get( Notification );
-
-		model.enqueueChange( 'transparent', writer => {
-			writer.setAttribute( 'uploadStatus', 'reading', imageElement );
-		} );
-
-		return loader.read()
-			.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 = getViewImgFromWidget( viewFigure );
-
-					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.
-						if ( !viewImg.parent ) {
-							return;
-						}
-
-						const domFigure = editor.editing.view.domConverter.mapViewToDom( viewImg.parent );
-
-						if ( !domFigure ) {
-							return;
-						}
-
-						const originalDisplay = domFigure.style.display;
-
-						domFigure.style.display = 'none';
-
-						// Make sure this line will never be removed during minification for having "no effect".
-						domFigure._ckHack = domFigure.offsetHeight;
-
-						domFigure.style.display = originalDisplay;
-					} );
-				}
-
-				model.enqueueChange( 'transparent', writer => {
-					writer.setAttribute( 'uploadStatus', 'uploading', imageElement );
-				} );
-
-				return promise;
-			} )
-			.then( data => {
-				model.enqueueChange( 'transparent', writer => {
-					writer.setAttributes( { uploadStatus: 'complete', src: data.default }, imageElement );
-					this._parseAndSetSrcsetAttributeOnImage( data, imageElement, writer );
-				} );
-
-				clean();
-			} )
-			.catch( error => {
-				// If status is not 'error' nor 'aborted' - throw error because it means that something else went wrong,
-				// it might be generic error and it would be real pain to find what is going on.
-				if ( loader.status !== 'error' && loader.status !== 'aborted' ) {
-					throw error;
-				}
-
-				// Might be 'aborted'.
-				if ( loader.status == 'error' && error ) {
-					notification.showWarning( error, {
-						title: t( 'Upload failed' ),
-						namespace: 'upload'
-					} );
-				}
-
-				clean();
-
-				// Permanently remove image from insertion batch.
-				model.enqueueChange( 'transparent', writer => {
-					writer.remove( imageElement );
-				} );
-			} );
-
-		function clean() {
-			model.enqueueChange( 'transparent', writer => {
-				writer.removeAttribute( 'uploadId', imageElement );
-				writer.removeAttribute( 'uploadStatus', imageElement );
-			} );
-
-			fileRepository.destroyLoader( loader );
-		}
-	}
-
-	/**
-	 * Creates the `srcset` attribute based on a given file upload response and sets it as an attribute to a specific image element.
-	 *
-	 * @protected
-	 * @param {Object} data Data object from which `srcset` will be created.
-	 * @param {module:engine/model/element~Element} image The image element on which the `srcset` attribute will be set.
-	 * @param {module:engine/model/writer~Writer} writer
-	 */
-	_parseAndSetSrcsetAttributeOnImage( data, image, writer ) {
-		// Srcset attribute for responsive images support.
-		let maxWidth = 0;
-
-		const srcsetAttribute = Object.keys( data )
-		// Filter out keys that are not integers.
-			.filter( key => {
-				const width = parseInt( key, 10 );
-
-				if ( !isNaN( width ) ) {
-					maxWidth = Math.max( maxWidth, width );
-
-					return true;
-				}
-			} )
-
-			// Convert each key to srcset entry.
-			.map( key => `${ data[ key ] } ${ key }w` )
-
-			// Join all entries.
-			.join( ', ' );
-
-		if ( srcsetAttribute != '' ) {
-			writer.setAttribute( 'srcset', {
-				data: srcsetAttribute,
-				width: maxWidth
-			}, image );
-		}
-	}
-}
-
-// Returns `true` if non-empty `text/html` is included in the data transfer.
-//
-// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer
-// @returns {Boolean}
-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( 'element', 'image' ) )
-		.map( value => value.item );
-}

+ 0 - 276
packages/ckeditor5-image/src/imageinsert/imageinsertprogress.js

@@ -1,276 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * @module image/imageinsert/imageinsertprogress
- */
-
-/* globals setTimeout */
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
-import uploadingPlaceholder from '../../theme/icons/image_placeholder.svg';
-import { getViewImgFromWidget } from '../image/utils';
-
-import '../../theme/imageinsertprogress.css';
-import '../../theme/imageinserticon.css';
-import '../../theme/imageinsertloader.css';
-
-/**
- * The image upload progress plugin.
- * It shows a placeholder when the image is read from the disk and a progress bar while the image is uploading.
- *
- * @extends module:core/plugin~Plugin
- */
-export default class ImageUploadProgress extends Plugin {
-	/**
-	 * @inheritDoc
-	 */
-	constructor( editor ) {
-		super( editor );
-
-		/**
-		 * The image placeholder that is displayed before real image data can be accessed.
-		 *
-		 * @protected
-		 * @member {String} #placeholder
-		 */
-		this.placeholder = 'data:image/svg+xml;utf8,' + encodeURIComponent( uploadingPlaceholder );
-	}
-
-	/**
-	 * @inheritDoc
-	 */
-	init() {
-		const editor = this.editor;
-
-		// Upload status change - update image's view according to that status.
-		editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
-	}
-
-	/**
-	 * This method is called each time the image `uploadStatus` attribute is changed.
-	 *
-	 * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
-	 * @param {Object} data Additional information about the change.
-	 * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
-	 */
-	uploadStatusChange( evt, data, conversionApi ) {
-		const editor = this.editor;
-		const modelImage = data.item;
-		const uploadId = modelImage.getAttribute( 'uploadId' );
-
-		if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
-			return;
-		}
-
-		const fileRepository = editor.plugins.get( FileRepository );
-		const status = uploadId ? data.attributeNewValue : null;
-		const placeholder = this.placeholder;
-		const viewFigure = editor.editing.mapper.toViewElement( modelImage );
-		const viewWriter = conversionApi.writer;
-
-		if ( status == 'reading' ) {
-			// Start "appearing" effect and show placeholder with infinite progress bar on the top
-			// while image is read from disk.
-			_startAppearEffect( viewFigure, viewWriter );
-			_showPlaceholder( placeholder, viewFigure, viewWriter );
-
-			return;
-		}
-
-		// Show progress bar on the top of the image when image is uploading.
-		if ( status == 'uploading' ) {
-			const loader = fileRepository.loaders.get( uploadId );
-
-			// Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
-			_startAppearEffect( viewFigure, viewWriter );
-
-			if ( !loader ) {
-				// There is no loader associated with uploadId - this means that image came from external changes.
-				// In such cases we still want to show the placeholder until image is fully uploaded.
-				// Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
-				_showPlaceholder( placeholder, viewFigure, viewWriter );
-			} else {
-				// Hide placeholder and initialize progress bar showing upload progress.
-				_hidePlaceholder( viewFigure, viewWriter );
-				_showProgressBar( viewFigure, viewWriter, loader, editor.editing.view );
-				_displayLocalImage( viewFigure, viewWriter, loader );
-			}
-
-			return;
-		}
-
-		if ( status == 'complete' && fileRepository.loaders.get( uploadId ) ) {
-			_showCompleteIcon( viewFigure, viewWriter, editor.editing.view );
-		}
-
-		// Clean up.
-		_hideProgressBar( viewFigure, viewWriter );
-		_hidePlaceholder( viewFigure, viewWriter );
-		_stopAppearEffect( viewFigure, viewWriter );
-	}
-}
-
-// Adds ck-appear class to the image figure if one is not already applied.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function _startAppearEffect( viewFigure, writer ) {
-	if ( !viewFigure.hasClass( 'ck-appear' ) ) {
-		writer.addClass( 'ck-appear', viewFigure );
-	}
-}
-
-// Removes ck-appear class to the image figure if one is not already removed.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function _stopAppearEffect( viewFigure, writer ) {
-	writer.removeClass( 'ck-appear', viewFigure );
-}
-
-// Shows placeholder together with infinite progress bar on given image figure.
-//
-// @param {String} Data-uri with a svg placeholder.
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function _showPlaceholder( placeholder, viewFigure, writer ) {
-	if ( !viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
-		writer.addClass( 'ck-image-upload-placeholder', viewFigure );
-	}
-
-	const viewImg = getViewImgFromWidget( viewFigure );
-
-	if ( viewImg.getAttribute( 'src' ) !== placeholder ) {
-		writer.setAttribute( 'src', placeholder, viewImg );
-	}
-
-	if ( !_getUIElement( viewFigure, 'placeholder' ) ) {
-		writer.insert( writer.createPositionAfter( viewImg ), _createPlaceholder( writer ) );
-	}
-}
-
-// Removes placeholder together with infinite progress bar on given image figure.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function _hidePlaceholder( viewFigure, writer ) {
-	if ( viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
-		writer.removeClass( 'ck-image-upload-placeholder', viewFigure );
-	}
-
-	_removeUIElement( viewFigure, writer, 'placeholder' );
-}
-
-// Shows progress bar displaying upload progress.
-// Attaches it to the file loader to update when upload percentace is changed.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-// @param {module:upload/filerepository~FileLoader} loader
-// @param {module:engine/view/view~View} view
-function _showProgressBar( viewFigure, writer, loader, view ) {
-	const progressBar = _createProgressBar( writer );
-	writer.insert( writer.createPositionAt( viewFigure, 'end' ), progressBar );
-
-	// Update progress bar width when uploadedPercent is changed.
-	loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
-		view.change( writer => {
-			writer.setStyle( 'width', value + '%', progressBar );
-		} );
-	} );
-}
-
-// Hides upload progress bar.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-function _hideProgressBar( viewFigure, writer ) {
-	_removeUIElement( viewFigure, writer, 'progressBar' );
-}
-
-// Shows complete icon and hides after a certain amount of time.
-//
-// @param {module:engine/view/containerelement~ContainerElement} viewFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-// @param {module:engine/view/view~View} view
-function _showCompleteIcon( viewFigure, writer, view ) {
-	const completeIcon = writer.createUIElement( 'div', { class: 'ck-image-upload-complete-icon' } );
-
-	writer.insert( writer.createPositionAt( viewFigure, 'end' ), completeIcon );
-
-	setTimeout( () => {
-		view.change( writer => writer.remove( writer.createRangeOn( completeIcon ) ) );
-	}, 3000 );
-}
-
-// Create progress bar element using {@link module:engine/view/uielement~UIElement}.
-//
-// @private
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-// @returns {module:engine/view/uielement~UIElement}
-function _createProgressBar( writer ) {
-	const progressBar = writer.createUIElement( 'div', { class: 'ck-progress-bar' } );
-
-	writer.setCustomProperty( 'progressBar', true, progressBar );
-
-	return progressBar;
-}
-
-// Create placeholder element using {@link module:engine/view/uielement~UIElement}.
-//
-// @private
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-// @returns {module:engine/view/uielement~UIElement}
-function _createPlaceholder( writer ) {
-	const placeholder = writer.createUIElement( 'div', { class: 'ck-upload-placeholder-loader' } );
-
-	writer.setCustomProperty( 'placeholder', true, placeholder );
-
-	return placeholder;
-}
-
-// Returns {@link module:engine/view/uielement~UIElement} of given unique property from image figure element.
-// Returns `undefined` if element is not found.
-//
-// @private
-// @param {module:engine/view/element~Element} imageFigure
-// @param {String} uniqueProperty
-// @returns {module:engine/view/uielement~UIElement|undefined}
-function _getUIElement( imageFigure, uniqueProperty ) {
-	for ( const child of imageFigure.getChildren() ) {
-		if ( child.getCustomProperty( uniqueProperty ) ) {
-			return child;
-		}
-	}
-}
-
-// Removes {@link module:engine/view/uielement~UIElement} of given unique property from image figure element.
-//
-// @private
-// @param {module:engine/view/element~Element} imageFigure
-// @param {module:engine/view/downcastwriter~DowncastWriter} writer
-// @param {String} uniqueProperty
-function _removeUIElement( viewFigure, writer, uniqueProperty ) {
-	const element = _getUIElement( viewFigure, uniqueProperty );
-
-	if ( element ) {
-		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 = getViewImgFromWidget( viewFigure );
-
-		writer.setAttribute( 'src', loader.data, viewImg );
-	}
-}

+ 2 - 2
packages/ckeditor5-image/src/imageinsert/imageinsertui.js

@@ -40,7 +40,7 @@ export default class ImageInsertUI extends Plugin {
 	init() {
 		const editor = this.editor;
 
-		editor.ui.componentFactory.add( 'imageUpload', locale => {
+		editor.ui.componentFactory.add( 'imageInsert', locale => {
 			return this._createDropdownView( locale );
 		} );
 	}
@@ -132,7 +132,7 @@ export default class ImageInsertUI extends Plugin {
 		const panelView = dropdownView.panelView;
 		const splitButtonView = dropdownView.buttonView;
 
-		splitButtonView.actionView = this._createFileDialogButtonView( locale );
+		splitButtonView.actionView = editor.ui.componentFactory.create( 'imageUpload' );
 
 		panelView.children.add( imageInsertView );
 

+ 0 - 59
packages/ckeditor5-image/src/imageinsert/utils.js

@@ -7,8 +7,6 @@
  * @module image/imageinsert/utils
  */
 
-/* global fetch, File */
-
 import LabeledFieldView from '@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview';
 import { createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledfield/utils';
 
@@ -29,63 +27,6 @@ export function createImageTypeRegExp( types ) {
 	return new RegExp( `^image\\/(${ regExpSafeNames.join( '|' ) })$` );
 }
 
-/**
- * Creates a promise that fetches the image local source (Base64 or blob) and resolves with a `File` object.
- *
- * @param {module:engine/view/element~Element} image Image whose source to fetch.
- * @returns {Promise.<File>} A promise which resolves when an image source is fetched and converted to a `File` instance.
- * It resolves with a `File` object. If there were any errors during file processing, the promise will be rejected.
- */
-export function fetchLocalImage( image ) {
-	return new Promise( ( resolve, reject ) => {
-		const imageSrc = image.getAttribute( 'src' );
-
-		// Fetch works asynchronously and so does not block browser UI when processing data.
-		fetch( imageSrc )
-			.then( resource => resource.blob() )
-			.then( blob => {
-				const mimeType = getImageMimeType( blob, imageSrc );
-				const ext = mimeType.replace( 'image/', '' );
-				const filename = `image.${ ext }`;
-				const file = new File( [ blob ], filename, { type: mimeType } );
-
-				resolve( file );
-			} )
-			.catch( reject );
-	} );
-}
-
-/**
- * Checks whether a given node is an image element with a local source (Base64 or blob).
- *
- * @param {module:engine/view/node~Node} node The node to check.
- * @returns {Boolean}
- */
-export function isLocalImage( node ) {
-	if ( !node.is( 'element', 'img' ) || !node.getAttribute( 'src' ) ) {
-		return false;
-	}
-
-	return node.getAttribute( 'src' ).match( /^data:image\/\w+;base64,/g ) ||
-		node.getAttribute( 'src' ).match( /^blob:/g );
-}
-
-// Extracts an image type based on its blob representation or its source.
-//
-// @param {String} src Image `src` attribute value.
-// @param {Blob} blob Image blob representation.
-// @returns {String}
-function getImageMimeType( blob, src ) {
-	if ( blob.type ) {
-		return blob.type;
-	} else if ( src.match( /data:(image\/\w+);base64/ ) ) {
-		return src.match( /data:(image\/\w+);base64/ )[ 1 ].toLowerCase();
-	} else {
-		// Fallback to 'jpeg' as common extension.
-		return 'image/jpeg';
-	}
-}
-
 /**
  * Creates integrations object that will be passed to the
  * {@link module:image/imageinsert/ui/imageinsertpanelview~ImageUploadPanelView}.

+ 4 - 1
packages/ckeditor5-image/src/imageupload/imageuploadcommand.js

@@ -44,7 +44,10 @@ export default class ImageUploadCommand extends Command {
 	 * @inheritDoc
 	 */
 	refresh() {
-		this.isEnabled = isImageAllowed( this.editor.model );
+		const imageElement = this.editor.model.document.selection.getSelectedElement();
+		const isImage = imageElement && imageElement.name === 'image' || false;
+
+		this.isEnabled = isImageAllowed( this.editor.model ) || isImage;
 	}
 
 	/**

+ 0 - 199
packages/ckeditor5-image/tests/imageinsert/imageinsertcommand.js

@@ -1,199 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/* globals console */
-
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-
-import ImageUploadCommand from '../../src/imageinsert/imageinsertcommand';
-import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
-
-import { createNativeFileMock, UploadAdapterMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks';
-import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
-import Image from '../../src/image/imageediting';
-import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-
-describe( 'ImageUploadCommand', () => {
-	let editor, command, model, fileRepository;
-
-	class UploadAdapterPluginMock extends Plugin {
-		init() {
-			fileRepository = this.editor.plugins.get( FileRepository );
-			fileRepository.createUploadAdapter = loader => {
-				return new UploadAdapterMock( loader );
-			};
-		}
-	}
-
-	beforeEach( () => {
-		return VirtualTestEditor
-			.create( {
-				plugins: [ FileRepository, Image, Paragraph, UploadAdapterPluginMock ]
-			} )
-			.then( newEditor => {
-				editor = newEditor;
-				model = editor.model;
-
-				command = new ImageUploadCommand( editor );
-
-				const schema = model.schema;
-				schema.extend( 'image', { allowAttributes: 'uploadId' } );
-			} );
-	} );
-
-	afterEach( () => {
-		sinon.restore();
-
-		return editor.destroy();
-	} );
-
-	describe( 'isEnabled', () => {
-		it( 'should be true when the selection directly in the root', () => {
-			model.enqueueChange( 'transparent', () => {
-				setModelData( model, '[]' );
-
-				command.refresh();
-				expect( command.isEnabled ).to.be.true;
-			} );
-		} );
-
-		it( 'should be true when the selection is in empty block', () => {
-			setModelData( model, '<paragraph>[]</paragraph>' );
-
-			expect( command.isEnabled ).to.be.true;
-		} );
-
-		it( 'should be true when the selection directly in a paragraph', () => {
-			setModelData( model, '<paragraph>foo[]</paragraph>' );
-			expect( command.isEnabled ).to.be.true;
-		} );
-
-		it( 'should be true when the selection directly in a block', () => {
-			model.schema.register( 'block', { inheritAllFrom: '$block' } );
-			model.schema.extend( '$text', { allowIn: 'block' } );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } );
-
-			setModelData( model, '<block>foo[]</block>' );
-			expect( command.isEnabled ).to.be.true;
-		} );
-
-		it( 'should be true when the selection is on other image', () => {
-			setModelData( model, '[<image></image>]' );
-			expect( command.isEnabled ).to.be.true;
-		} );
-
-		it( 'should be false when the selection is inside other image', () => {
-			model.schema.register( 'caption', {
-				allowIn: 'image',
-				allowContentOf: '$block',
-				isLimit: true
-			} );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'caption', view: 'figcaption' } );
-			setModelData( model, '<image><caption>[]</caption></image>' );
-
-			expect( command.isEnabled ).to.be.false;
-		} );
-
-		it( 'should be false when the selection is on other object', () => {
-			model.schema.register( 'object', { isObject: true, allowIn: '$root' } );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'object', view: 'object' } );
-			setModelData( model, '[<object></object>]' );
-
-			expect( command.isEnabled ).to.be.false;
-		} );
-
-		it( 'should be true when the selection is inside block element inside isLimit element which allows image', () => {
-			model.schema.register( 'table', { allowWhere: '$block', isLimit: true, isObject: true, isBlock: true } );
-			model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } );
-			model.schema.register( 'tableCell', { allowIn: 'tableRow', isLimit: true, isSelectable: true } );
-			model.schema.extend( '$block', { allowIn: 'tableCell' } );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'table', view: 'table' } );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableRow', view: 'tableRow' } );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableCell', view: 'tableCell' } );
-
-			setModelData( model, '<table><tableRow><tableCell><paragraph>foo[]</paragraph></tableCell></tableRow></table>' );
-		} );
-
-		it( 'should be false when schema disallows image', () => {
-			model.schema.register( 'block', { inheritAllFrom: '$block' } );
-			model.schema.extend( 'paragraph', { allowIn: 'block' } );
-			// Block image in block.
-			model.schema.addChildCheck( ( context, childDefinition ) => {
-				if ( childDefinition.name === 'image' && context.last.name === 'block' ) {
-					return false;
-				}
-			} );
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } );
-
-			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
-
-			expect( command.isEnabled ).to.be.false;
-		} );
-	} );
-
-	describe( 'execute()', () => {
-		it( 'should insert image at selection position as other widgets', () => {
-			const file = createNativeFileMock();
-			setModelData( model, '<paragraph>f[o]o</paragraph>' );
-
-			command.execute( { file } );
-
-			const id = fileRepository.getLoader( file ).id;
-			expect( getModelData( model ) )
-				.to.equal( `[<image uploadId="${ id }"></image>]<paragraph>foo</paragraph>` );
-		} );
-
-		it( 'should use parent batch', () => {
-			const file = createNativeFileMock();
-
-			setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-			model.change( writer => {
-				expect( writer.batch.operations ).to.length( 0 );
-
-				command.execute( { file } );
-
-				expect( writer.batch.operations ).to.length.above( 0 );
-			} );
-		} );
-
-		it( 'should not insert image nor crash when image could not be inserted', () => {
-			const file = createNativeFileMock();
-
-			model.schema.register( 'other', {
-				allowIn: '$root',
-				isLimit: true
-			} );
-			model.schema.extend( '$text', { allowIn: 'other' } );
-
-			editor.conversion.for( 'downcast' ).elementToElement( { model: 'other', view: 'p' } );
-
-			setModelData( model, '<other>[]</other>' );
-
-			command.execute( { file } );
-
-			expect( getModelData( model ) ).to.equal( '<other>[]</other>' );
-		} );
-
-		it( 'should not throw when upload adapter is not set (FileRepository will log an warn anyway)', () => {
-			const file = createNativeFileMock();
-
-			fileRepository.createUploadAdapter = undefined;
-
-			const consoleWarnStub = sinon.stub( console, 'warn' );
-
-			setModelData( model, '<paragraph>fo[]o</paragraph>' );
-
-			expect( () => {
-				command.execute( { file } );
-			} ).to.not.throw();
-
-			expect( getModelData( model ) ).to.equal( '<paragraph>fo[]o</paragraph>' );
-			sinon.assert.calledOnce( consoleWarnStub );
-		} );
-	} );
-} );

+ 0 - 1054
packages/ckeditor5-image/tests/imageinsert/imageinsertediting.js

@@ -1,1054 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/* globals window, setTimeout, atob, URL, Blob, console */
-
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
-import ImageEditing from '../../src/image/imageediting';
-import ImageUploadEditing from '../../src/imageinsert/imageinsertediting';
-import ImageUploadCommand from '../../src/imageinsert/imageinsertcommand';
-import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
-import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
-import DataTransfer from '@ckeditor/ckeditor5-clipboard/src/datatransfer';
-import EventInfo from '@ckeditor/ckeditor5-utils/src/eventinfo';
-
-import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
-import { UploadAdapterMock, createNativeFileMock, NativeFileReaderMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks';
-
-import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
-import { getData as getViewData, stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
-
-import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
-
-describe( 'ImageUploadEditing', () => {
-	// eslint-disable-next-line max-len
-	const base64Sample = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
-
-	let adapterMocks = [];
-	let editor, model, view, doc, fileRepository, viewDocument, nativeReaderMock, loader;
-
-	class UploadAdapterPluginMock extends Plugin {
-		init() {
-			fileRepository = this.editor.plugins.get( FileRepository );
-			fileRepository.createUploadAdapter = newLoader => {
-				loader = newLoader;
-				const adapterMock = new UploadAdapterMock( loader );
-
-				adapterMocks.push( adapterMock );
-
-				return adapterMock;
-			};
-		}
-	}
-
-	beforeEach( () => {
-		sinon.stub( window, 'FileReader' ).callsFake( () => {
-			nativeReaderMock = new NativeFileReaderMock();
-
-			return nativeReaderMock;
-		} );
-
-		return VirtualTestEditor
-			.create( {
-				plugins: [ ImageEditing, ImageUploadEditing, Paragraph, UndoEditing, UploadAdapterPluginMock, Clipboard ]
-			} )
-			.then( newEditor => {
-				editor = newEditor;
-				model = editor.model;
-				doc = model.document;
-				view = editor.editing.view;
-				viewDocument = view.document;
-
-				// Stub `view.scrollToTheSelection` as it will fail on VirtualTestEditor without DOM.
-				sinon.stub( view, 'scrollToTheSelection' ).callsFake( () => {} );
-			} );
-	} );
-
-	afterEach( () => {
-		sinon.restore();
-		adapterMocks = [];
-
-		return editor.destroy();
-	} );
-
-	it( 'should register proper schema rules', () => {
-		expect( model.schema.checkAttribute( [ '$root', 'image' ], 'uploadId' ) ).to.be.true;
-	} );
-
-	it( 'should register imageUpload command', () => {
-		expect( editor.commands.get( 'imageUpload' ) ).to.be.instanceOf( ImageUploadCommand );
-	} );
-
-	it( 'should load Clipboard plugin', () => {
-		return VirtualTestEditor
-			.create( {
-				plugins: [ ImageEditing, ImageUploadEditing, Paragraph, UndoEditing, UploadAdapterPluginMock ]
-			} )
-			.then( editor => {
-				expect( editor.plugins.get( Clipboard ) ).to.be.instanceOf( Clipboard );
-			} );
-	} );
-
-	it( 'should insert image when is pasted', () => {
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		const eventInfo = new EventInfo( viewDocument, 'clipboardInput' );
-		viewDocument.fire( eventInfo, { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id = fileRepository.getLoader( fileMock ).id;
-		expect( getModelData( model ) ).to.equal(
-			`<paragraph>foo</paragraph>[<image uploadId="${ id }" uploadStatus="reading"></image>]`
-		);
-		expect( eventInfo.stop.called ).to.be.true;
-	} );
-
-	it( 'should insert image at optimized position when is pasted', () => {
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const paragraph = doc.getRoot().getChild( 0 );
-		const targetRange = model.createRange( model.createPositionAt( paragraph, 1 ), model.createPositionAt( paragraph, 1 ) ); // f[]oo
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id = fileRepository.getLoader( fileMock ).id;
-		expect( getModelData( model ) ).to.equal(
-			`[<image uploadId="${ id }" uploadStatus="reading"></image>]<paragraph>foo</paragraph>`
-		);
-	} );
-
-	it( 'should insert multiple image files when are pasted', () => {
-		const files = [ createNativeFileMock(), createNativeFileMock() ];
-		const dataTransfer = new DataTransfer( { files, types: [ 'Files' ] } );
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id1 = fileRepository.getLoader( files[ 0 ] ).id;
-		const id2 = fileRepository.getLoader( files[ 1 ] ).id;
-
-		expect( getModelData( model ) ).to.equal(
-			'<paragraph>foo</paragraph>' +
-			`<image uploadId="${ id1 }" uploadStatus="reading"></image>` +
-			`[<image uploadId="${ id2 }" uploadStatus="reading"></image>]`
-		);
-	} );
-
-	it( 'should insert image when is pasted on allowed position when ImageUploadCommand is disabled', () => {
-		setModelData( model, '<paragraph>foo</paragraph>[<image></image>]' );
-
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-
-		const command = editor.commands.get( 'imageUpload' );
-
-		expect( command.isEnabled ).to.be.true;
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 0 ), model.createPositionAt( doc.getRoot(), 0 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id = fileRepository.getLoader( fileMock ).id;
-		expect( getModelData( model ) ).to.equal(
-			`[<image uploadId="${ id }" uploadStatus="reading"></image>]<paragraph>foo</paragraph><image></image>`
-		);
-	} );
-
-	it( 'should not insert image when editor is in read-only mode', () => {
-		// Clipboard plugin is required for this test.
-		return VirtualTestEditor
-			.create( {
-				plugins: [ ImageEditing, ImageUploadEditing, Paragraph, UploadAdapterPluginMock, Clipboard ]
-			} )
-			.then( editor => {
-				const fileMock = createNativeFileMock();
-				const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-				setModelData( editor.model, '<paragraph>[]foo</paragraph>' );
-
-				const targetRange = editor.model.document.selection.getFirstRange();
-				const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-				editor.isReadOnly = true;
-
-				editor.editing.view.document.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-				expect( getModelData( editor.model ) ).to.equal( '<paragraph>[]foo</paragraph>' );
-
-				return editor.destroy();
-			} );
-	} );
-
-	it( 'should not insert image when file is not an image', () => {
-		const viewDocument = editor.editing.view.document;
-		const fileMock = {
-			type: 'media/mp3',
-			size: 1024
-		};
-		const dataTransfer = new DataTransfer( {
-			files: [ fileMock ],
-			types: [ 'Files' ],
-			getData: () => ''
-		} );
-
-		setModelData( model, '<paragraph>foo[]</paragraph>' );
-
-		const targetRange = doc.selection.getFirstRange();
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		const eventInfo = new EventInfo( viewDocument, 'clipboardInput' );
-		viewDocument.fire( eventInfo, { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
-		expect( eventInfo.stop.called ).to.be.undefined;
-	} );
-
-	it( 'should not insert image when file is not an configured image type', () => {
-		const viewDocument = editor.editing.view.document;
-		const fileMock = {
-			type: 'image/svg+xml',
-			size: 1024
-		};
-		const dataTransfer = new DataTransfer( {
-			files: [ fileMock ],
-			types: [ 'Files' ],
-			getData: () => ''
-		} );
-
-		setModelData( model, '<paragraph>foo[]</paragraph>' );
-
-		const targetRange = doc.selection.getFirstRange();
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		const eventInfo = new EventInfo( viewDocument, 'clipboardInput' );
-		viewDocument.fire( eventInfo, { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
-		expect( eventInfo.stop.called ).to.be.undefined;
-	} );
-
-	it( 'should not insert image when file is null', () => {
-		const viewDocument = editor.editing.view.document;
-		const dataTransfer = new DataTransfer( { files: [ null ], types: [ 'Files' ], getData: () => null } );
-
-		setModelData( model, '<paragraph>foo[]</paragraph>' );
-
-		const targetRange = doc.selection.getFirstRange();
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
-	} );
-
-	it( 'should not insert image when there is non-empty HTML content pasted', () => {
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( {
-			files: [ fileMock ],
-			types: [ 'Files', 'text/html' ],
-			getData: type => type === 'text/html' ? '<p>SomeData</p>' : ''
-		} );
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expect( getModelData( model ) ).to.equal( '<paragraph>SomeData[]foo</paragraph>' );
-	} );
-
-	it( 'should not insert image nor crash when pasted image could not be inserted', () => {
-		model.schema.register( 'other', {
-			allowIn: '$root',
-			isLimit: true
-		} );
-		model.schema.extend( '$text', { allowIn: 'other' } );
-
-		editor.conversion.elementToElement( { model: 'other', view: 'p' } );
-
-		setModelData( model, '<other>[]</other>' );
-
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-
-		const targetRange = doc.selection.getFirstRange();
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expect( getModelData( model ) ).to.equal( '<other>[]</other>' );
-	} );
-
-	it( 'should not throw when upload adapter is not set (FileRepository will log an warn anyway) when image is pasted', () => {
-		const fileMock = createNativeFileMock();
-		const dataTransfer = new DataTransfer( { files: [ fileMock ], types: [ 'Files' ] } );
-		const consoleWarnStub = sinon.stub( console, 'warn' );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		fileRepository.createUploadAdapter = undefined;
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		expect( () => {
-			viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-		} ).to.not.throw();
-
-		expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
-		sinon.assert.calledOnce( consoleWarnStub );
-	} );
-
-	// https://github.com/ckeditor/ckeditor5-upload/issues/70
-	it( 'should not crash on browsers which do not implement DOMStringList as a child class of an Array', () => {
-		const typesDomStringListMock = {
-			length: 2,
-			'0': 'text/html',
-			'1': 'text/plain'
-		};
-		const dataTransfer = new DataTransfer( {
-			types: typesDomStringListMock,
-			getData: type => type === 'text/html' ? '<p>SomeData</p>' : 'SomeData'
-		} );
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const targetRange = doc.selection.getFirstRange();
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		// Well, there's no clipboard plugin, so nothing happens.
-		expect( getModelData( model ) ).to.equal( '<paragraph>SomeData[]foo</paragraph>' );
-	} );
-
-	it( 'should not convert image\'s uploadId attribute if is consumed already', () => {
-		editor.editing.downcastDispatcher.on( 'attribute:uploadId:image', ( evt, data, conversionApi ) => {
-			conversionApi.consumable.consume( data.item, evt.name );
-		}, { priority: 'high' } );
-
-		setModelData( model, '<image uploadId="1234"></image>' );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-widget image" contenteditable="false">' +
-			'<img></img>' +
-			'</figure>]' );
-	} );
-
-	it( 'should not use read data once it is present', done => {
-		const file = createNativeFileMock();
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		model.document.once( 'change', () => {
-			tryExpect( done, () => {
-				expect( getViewData( view ) ).to.equal(
-					'[<figure class="ck-widget image" contenteditable="false">' +
-						// Rendering the image data is left to a upload progress converter.
-						'<img></img>' +
-						'</figure>]' +
-					'<p>foo bar</p>'
-				);
-
-				expect( loader.status ).to.equal( 'uploading' );
-			} );
-		} );
-
-		expect( loader.status ).to.equal( 'reading' );
-
-		loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
-	} );
-
-	it( 'should replace read data with server response once it is present', done => {
-		const file = createNativeFileMock();
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		model.document.once( 'change', () => {
-			model.document.once( 'change', () => {
-				tryExpect( done, () => {
-					expect( getViewData( view ) ).to.equal(
-						'[<figure class="ck-widget image" contenteditable="false"><img src="image.png"></img></figure>]<p>foo bar</p>'
-					);
-					expect( loader.status ).to.equal( 'idle' );
-				} );
-			}, { priority: 'lowest' } );
-
-			loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: 'image.png' } ) );
-		} );
-
-		loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
-	} );
-
-	it( 'should fire notification event in case of error', done => {
-		const notification = editor.plugins.get( Notification );
-		const file = createNativeFileMock();
-
-		notification.on( 'show:warning', ( evt, data ) => {
-			tryExpect( done, () => {
-				expect( data.message ).to.equal( 'Reading error.' );
-				expect( data.title ).to.equal( 'Upload failed' );
-				evt.stop();
-			} );
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		loader.file.then( () => nativeReaderMock.mockError( 'Reading error.' ) );
-	} );
-
-	it( 'should not fire notification on abort', done => {
-		const notification = editor.plugins.get( Notification );
-		const file = createNativeFileMock();
-		const spy = sinon.spy();
-
-		notification.on( 'show:warning', evt => {
-			spy();
-			evt.stop();
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		loader.file.then( () => {
-			nativeReaderMock.abort();
-
-			setTimeout( () => {
-				sinon.assert.notCalled( spy );
-				done();
-			}, 0 );
-		} );
-	} );
-
-	it( 'should throw when other error happens during upload', done => {
-		const file = createNativeFileMock();
-		const error = new Error( 'Foo bar baz' );
-		const uploadEditing = editor.plugins.get( ImageUploadEditing );
-		const loadSpy = sinon.spy( uploadEditing, '_readAndUpload' );
-		const catchSpy = sinon.spy();
-
-		// Throw an error when async attribute change occur.
-		editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:image', ( evt, data ) => {
-			if ( data.attributeNewValue == 'uploading' ) {
-				throw error;
-			}
-		} );
-
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		sinon.assert.calledOnce( loadSpy );
-
-		const promise = loadSpy.returnValues[ 0 ];
-
-		// Check if error can be caught.
-		promise.catch( catchSpy );
-
-		loader.file.then( () => {
-			nativeReaderMock.mockSuccess();
-
-			setTimeout( () => {
-				sinon.assert.calledOnce( catchSpy );
-				const error = catchSpy.getCall( 0 ).args[ 0 ];
-
-				expect( error ).to.be.instanceOf( Error );
-				expect( error ).to.haveOwnProperty( 'message', 'Foo bar baz' );
-
-				done();
-			}, 0 );
-		} );
-	} );
-
-	it( 'should do nothing if image does not have uploadId', () => {
-		setModelData( model, '<image src="image.png"></image>' );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-widget image" contenteditable="false"><img src="image.png"></img></figure>]'
-		);
-	} );
-
-	it( 'should remove image in case of upload error', done => {
-		const file = createNativeFileMock();
-		const spy = sinon.spy();
-		const notification = editor.plugins.get( Notification );
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-
-		notification.on( 'show:warning', evt => {
-			spy();
-			evt.stop();
-		}, { priority: 'high' } );
-
-		editor.execute( 'imageUpload', { file } );
-
-		model.document.once( 'change', () => {
-			model.document.once( 'change', () => {
-				tryExpect( done, () => {
-					expect( getModelData( model ) ).to.equal( '<paragraph>[]foo bar</paragraph>' );
-					sinon.assert.calledOnce( spy );
-				} );
-			} );
-		} );
-
-		loader.file.then( () => nativeReaderMock.mockError( 'Upload error.' ) );
-	} );
-
-	it( 'should abort upload if image is removed', () => {
-		const file = createNativeFileMock();
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		const abortSpy = sinon.spy( loader, 'abort' );
-
-		expect( loader.status ).to.equal( 'reading' );
-
-		return loader.file.then( () => {
-			nativeReaderMock.mockSuccess( base64Sample );
-
-			const image = doc.getRoot().getChild( 0 );
-			model.change( writer => {
-				writer.remove( image );
-			} );
-
-			expect( loader.status ).to.equal( 'aborted' );
-			sinon.assert.calledOnce( abortSpy );
-		} );
-	} );
-
-	it( 'should not abort and not restart upload when image is moved', () => {
-		const file = createNativeFileMock();
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		const abortSpy = sinon.spy( loader, 'abort' );
-		const loadSpy = sinon.spy( loader, 'read' );
-
-		const image = doc.getRoot().getChild( 0 );
-
-		model.change( writer => {
-			writer.move( writer.createRangeOn( image ), writer.createPositionAt( doc.getRoot(), 2 ) );
-		} );
-
-		expect( abortSpy.called ).to.be.false;
-		expect( loadSpy.called ).to.be.false;
-	} );
-
-	it( 'image should be permanently removed if it is removed by user during upload', done => {
-		const file = createNativeFileMock();
-		const notification = editor.plugins.get( Notification );
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			evt.stop();
-		}, { priority: 'high' } );
-
-		editor.execute( 'imageUpload', { file } );
-
-		const stub = sinon.stub();
-		model.document.on( 'change', stub );
-
-		// The first `change` event is fired after the "manual" remove.
-		// The second `change` event is fired after cleaning attributes.
-		stub.onSecondCall().callsFake( () => {
-			expect( getModelData( model ) ).to.equal( '<paragraph>[]foo bar</paragraph>' );
-
-			editor.execute( 'undo' );
-
-			// Expect that the image has not been brought back.
-			expect( getModelData( model ) ).to.equal( '<paragraph>[]foo bar</paragraph>' );
-
-			done();
-		} );
-
-		const image = doc.getRoot().getChild( 0 );
-
-		model.change( writer => {
-			writer.remove( image );
-		} );
-	} );
-
-	it( 'should create responsive image if server return multiple images', done => {
-		const file = createNativeFileMock();
-		setModelData( model, '<paragraph>{}foo bar</paragraph>' );
-		editor.execute( 'imageUpload', { file } );
-
-		model.document.once( 'change', () => {
-			model.document.once( 'change', () => {
-				tryExpect( done, () => {
-					expect( getViewData( view ) ).to.equal(
-						'[<figure class="ck-widget image" contenteditable="false">' +
-						'<img sizes="100vw" src="image.png" srcset="image-500.png 500w, image-800.png 800w" width="800"></img>' +
-						'</figure>]<p>foo bar</p>'
-					);
-					expect( loader.status ).to.equal( 'idle' );
-				} );
-			}, { priority: 'lowest' } );
-
-			loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: 'image.png', 500: 'image-500.png', 800: 'image-800.png' } ) );
-		} );
-
-		loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
-	} );
-
-	it( 'should prevent from browser redirecting when an image is dropped on another image', () => {
-		const spy = sinon.spy();
-
-		editor.editing.view.document.fire( 'dragover', {
-			preventDefault: spy
-		} );
-
-		expect( spy.calledOnce ).to.equal( true );
-	} );
-
-	it( 'should upload image with base64 src', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<p>bar</p><img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id = adapterMocks[ 0 ].loader.id;
-		const expected = '<paragraph>bar</paragraph>' +
-			`[<image src="" uploadId="${ id }" uploadStatus="reading"></image>]` +
-			'<paragraph>foo</paragraph>';
-
-		expectModel( done, getModelData( model ), expected );
-	} );
-
-	it( 'should upload image with blob src', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64ToBlobUrl( base64Sample ) } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const id = adapterMocks[ 0 ].loader.id;
-		const expected = `[<image src="" uploadId="${ id }" uploadStatus="reading"></image>]` +
-			'<paragraph>foo</paragraph>';
-
-		expectModel( done, getModelData( model ), expected );
-	} );
-
-	it( 'should not upload image if no loader available', done => {
-		sinon.stub( fileRepository, 'createLoader' ).callsFake( () => null );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		const expected = `[<image src="${ base64Sample }"></image>]<paragraph>foo</paragraph>`;
-
-		expectModel( done, getModelData( model ), expected );
-	} );
-
-	it( 'should not upload and remove image if fetch failed', done => {
-		const notification = editor.plugins.get( Notification );
-
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			evt.stop();
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		// Stub `fetch` so it can be rejected.
-		sinon.stub( window, 'fetch' ).callsFake( () => {
-			return new Promise( ( res, rej ) => rej( 'could not fetch' ) );
-		} );
-
-		let content = null;
-		editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => {
-			content = data.content;
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expectData(
-			'<img src="" uploadId="#loader1_id" uploadProcessed="true"></img>',
-			'[<image src="" uploadId="#loader1_id" uploadStatus="reading"></image>]<paragraph>foo</paragraph>',
-			'<paragraph>[]foo</paragraph>',
-			content,
-			done,
-			false
-		);
-	} );
-
-	it( 'should upload only images which were successfully fetched and remove failed ones', done => {
-		const notification = editor.plugins.get( Notification );
-
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			evt.stop();
-		}, { priority: 'high' } );
-
-		const expectedModel = '<paragraph>bar</paragraph>' +
-			'<image src="" uploadId="#loader1_id" uploadStatus="reading"></image>' +
-			'<image src="" uploadId="#loader2_id" uploadStatus="reading"></image>' +
-			'[<image src="" uploadId="#loader3_id" uploadStatus="reading"></image>]' +
-			'<paragraph>foo</paragraph>';
-		const expectedFinalModel = '<paragraph>bar</paragraph>' +
-			'<image src="" uploadId="#loader1_id" uploadStatus="reading"></image>' +
-			'[<image src="" uploadId="#loader2_id" uploadStatus="reading"></image>]' +
-			'<paragraph>foo</paragraph>';
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<p>bar</p><img src=${ base64Sample } />` +
-			`<img src=${ base64ToBlobUrl( base64Sample ) } /><img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		// Stub `fetch` in a way that 2 first calls are successful and 3rd fails.
-		let counter = 0;
-		const fetch = window.fetch;
-		sinon.stub( window, 'fetch' ).callsFake( src => {
-			counter++;
-			if ( counter < 3 ) {
-				return fetch( src );
-			} else {
-				return Promise.reject();
-			}
-		} );
-
-		let content = null;
-		editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => {
-			content = data.content;
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expectData(
-			'',
-			expectedModel,
-			expectedFinalModel,
-			content,
-			done
-		);
-	} );
-
-	it( 'should not upload and remove image when `File` constructor is not present', done => {
-		const fileFn = window.File;
-
-		window.File = undefined;
-
-		const notification = editor.plugins.get( Notification );
-
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			evt.stop();
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64ToBlobUrl( base64Sample ) } /><p>baz</p>`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		let content = null;
-		editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => {
-			content = data.content;
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expectData(
-			'<img src="" uploadId="#loader1_id" uploadProcessed="true"></img><p>baz</p>',
-			'<image src="" uploadId="#loader1_id" uploadStatus="reading"></image><paragraph>baz[]foo</paragraph>',
-			'<paragraph>baz[]foo</paragraph>',
-			content,
-			err => {
-				window.File = fileFn;
-				done( err );
-			},
-			false
-		);
-	} );
-
-	it( 'should not upload and remove image when `File` constructor is not supported', done => {
-		sinon.stub( window, 'File' ).throws( 'Function expected.' );
-
-		const notification = editor.plugins.get( Notification );
-
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			evt.stop();
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<p>baz</p><img src=${ base64ToBlobUrl( base64Sample ) } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		let content = null;
-		editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => {
-			content = data.content;
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		expectData(
-			'<p>baz</p><img src="" uploadId="#loader1_id" uploadProcessed="true"></img>',
-			'<paragraph>baz</paragraph>[<image src="" uploadId="#loader1_id" uploadStatus="reading"></image>]<paragraph>foo</paragraph>',
-			'<paragraph>baz[]</paragraph><paragraph>foo</paragraph>',
-			content,
-			done,
-			false
-		);
-	} );
-
-	it( 'should get file extension from base64 string', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		// Stub `fetch` to return custom blob without type.
-		sinon.stub( window, 'fetch' ).callsFake( () => {
-			return new Promise( res => res( {
-				blob() {
-					return new Promise( res => res( new Blob( [ 'foo', 'bar' ] ) ) );
-				}
-			} ) );
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		tryExpect( done, () => {
-			loader.file.then( file => expect( file.name.split( '.' ).pop() ).to.equal( 'png' ) );
-		} );
-	} );
-
-	it( 'should use fallback file extension', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64ToBlobUrl( base64Sample ) } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		// Stub `fetch` to return custom blob without type.
-		sinon.stub( window, 'fetch' ).callsFake( () => {
-			return new Promise( res => res( {
-				blob() {
-					return new Promise( res => res( new Blob( [ 'foo', 'bar' ] ) ) );
-				}
-			} ) );
-		} );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		tryExpect( done, () => {
-			loader.file.then( file => expect( file.name.split( '.' ).pop() ).to.equal( 'jpeg' ) );
-		} );
-	} );
-
-	it( 'should not show notification when file loader failed with no error', done => {
-		const notification = editor.plugins.get( Notification );
-
-		let notificationsCount = 0;
-		// Prevent popping up alert window.
-		notification.on( 'show:warning', evt => {
-			notificationsCount++;
-			evt.stop();
-		}, { priority: 'high' } );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-
-		const clipboardHtml = `<img src=${ base64Sample } />`;
-		const dataTransfer = mockDataTransfer( clipboardHtml );
-
-		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 1 ), model.createPositionAt( doc.getRoot(), 1 ) );
-		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );
-
-		// Stub `fetch` in a way that it always fails.
-		sinon.stub( window, 'fetch' ).callsFake( () => Promise.reject() );
-
-		viewDocument.fire( 'clipboardInput', { dataTransfer, targetRanges: [ targetViewRange ] } );
-
-		adapterMocks[ 0 ].loader.file.then( () => {
-			expect.fail( 'Promise should be rejected.' );
-		} ).catch( () => {
-			// Deffer so the promise could be resolved.
-			setTimeout( () => {
-				expect( notificationsCount ).to.equal( 0 );
-				done();
-			} );
-		} );
-	} );
-
-	// 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).
-	//
-	// @param {String} expectedClipboardData Expected clipboard data on `inputTransformation` event.
-	// @param {String} expectedModel Expected model data on `inputTransformation` event.
-	// @param {String} expectedModelOnFile Expected model data after all `file.loader` promises are fetched.
-	// @param {DocumentFragment} content Content processed in inputTransformation
-	// @param {Function} doneFn Callback function to be called when all assertions are done or error occures.
-	// @param {Boolean} [onSuccess=true] If `expectedModelOnFile` data should be validated
-	// on `loader.file` a promise successful resolution or promise rejection.
-	function expectData( expectedClipboardData, expectedModel, expectedModelOnFile, content, doneFn, onSuccess ) {
-		const clipboardData = injectLoaderId( expectedClipboardData || '', adapterMocks );
-		const modelData = injectLoaderId( expectedModel, adapterMocks );
-		const finalModelData = injectLoaderId( expectedModelOnFile, adapterMocks );
-
-		if ( clipboardData.length ) {
-			expect( stringifyView( content ) ).to.equal( clipboardData );
-		}
-		expect( getModelData( model ) ).to.equal( modelData );
-
-		if ( onSuccess !== false ) {
-			adapterMocks[ 0 ].loader.file.then( () => {
-				// Deffer so the promise could be resolved.
-				setTimeout( () => {
-					expectModel( doneFn, getModelData( model ), finalModelData );
-				} );
-			} );
-		} else {
-			adapterMocks[ 0 ].loader.file.then( () => {
-				expect.fail( 'The `loader.file` should be rejected.' );
-			} ).catch( () => {
-				// Deffer so the promise could be resolved.
-				setTimeout( () => {
-					expectModel( doneFn, getModelData( model ), finalModelData );
-				} );
-			} );
-		}
-	}
-} );
-
-// Replaces '#loaderX_id' parameter in the given string with a loader id. It is used
-// so data string could be created before loader is initialized.
-//
-// @param {String} data String which have 'loader params' replaced.
-// @param {Array.<UploadAdapterMock>} adapters Adapters list. Each adapter holds a reference to a loader which id is used.
-// @returns {String} Data string with 'loader params' replaced.
-function injectLoaderId( data, adapters ) {
-	let newData = data;
-
-	if ( newData.includes( '#loader1_id' ) ) {
-		newData = newData.replace( '#loader1_id', adapters[ 0 ].loader.id );
-	}
-	if ( newData.includes( '#loader2_id' ) ) {
-		newData = newData.replace( '#loader2_id', adapters[ 1 ].loader.id );
-	}
-	if ( newData.includes( '#loader3_id' ) ) {
-		newData = newData.replace( '#loader3_id', adapters[ 2 ].loader.id );
-	}
-
-	return newData;
-}
-
-// Asserts actual and expected model data.
-//
-// @param {function} done Callback function to be called when assertion is done.
-// @param {String} actual Actual model data.
-// @param {String} expected Expected model data.
-function expectModel( done, actual, expected ) {
-	tryExpect( done, () => {
-		expect( actual ).to.equal( expected );
-	} );
-}
-
-// Runs given expect function in a try-catch. It should be used only when `expect` is called as a result of a `Promise`
-// resolution as all errors may be caught by tested code and needs to be rethrow to be correctly processed by a testing framework.
-//
-// @param {Function} doneFn Function to run when assertion is done.
-// @param {Function} expectFn Function containing all assertions.
-function tryExpect( doneFn, expectFn ) {
-	try {
-		expectFn();
-		doneFn();
-	} catch ( err ) {
-		doneFn( err );
-	}
-}
-
-// Creates data transfer object with predefined data.
-//
-// @param {String} content The content returned as `text/html` when queried.
-// @returns {module:clipboard/datatransfer~DataTransfer} DataTransfer object.
-function mockDataTransfer( content ) {
-	return new DataTransfer( {
-		types: [ 'text/html' ],
-		getData: type => type === 'text/html' ? content : ''
-	} );
-}
-
-// Creates blob url from the given base64 data.
-//
-// @param {String} base64 The base64 string from which blob url will be generated.
-// @returns {String} Blob url.
-function base64ToBlobUrl( base64 ) {
-	return URL.createObjectURL( base64ToBlob( base64.trim() ) );
-}
-
-// Transforms base64 data into a blob object.
-//
-// @param {String} The base64 data to be transformed.
-// @returns {Blob} Blob object representing given base64 data.
-function base64ToBlob( base64Data ) {
-	const [ type, data ] = base64Data.split( ',' );
-	const byteCharacters = atob( data );
-	const byteArrays = [];
-
-	for ( let offset = 0; offset < byteCharacters.length; offset += 512 ) {
-		const slice = byteCharacters.slice( offset, offset + 512 );
-		const byteNumbers = new Array( slice.length );
-
-		for ( let i = 0; i < slice.length; i++ ) {
-			byteNumbers[ i ] = slice.charCodeAt( i );
-		}
-
-		byteArrays.push( new Uint8Array( byteNumbers ) );
-	}
-
-	return new Blob( byteArrays, { type } );
-}

+ 0 - 332
packages/ckeditor5-image/tests/imageinsert/imageinsertprogress.js

@@ -1,332 +0,0 @@
-/**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/* globals window */
-
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import ImageEditing from '../../src/image/imageediting';
-import ImageUploadEditing from '../../src/imageinsert/imageinsertediting';
-import ImageUploadProgress from '../../src/imageinsert/imageinsertprogress';
-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 { 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';
-import svgPlaceholder from '../../theme/icons/image_placeholder.svg';
-
-describe( 'ImageUploadProgress', () => {
-	const imagePlaceholder = encodeURIComponent( svgPlaceholder );
-
-	// eslint-disable-next-line max-len
-	const base64Sample = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
-	let editor, model, doc, fileRepository, view, nativeReaderMock, loader, adapterMock;
-
-	class UploadAdapterPluginMock extends Plugin {
-		init() {
-			fileRepository = this.editor.plugins.get( FileRepository );
-			fileRepository.createUploadAdapter = newLoader => {
-				loader = newLoader;
-				adapterMock = new UploadAdapterMock( loader );
-
-				return adapterMock;
-			};
-		}
-	}
-
-	testUtils.createSinonSandbox();
-
-	beforeEach( () => {
-		testUtils.sinon.stub( window, 'FileReader' ).callsFake( () => {
-			nativeReaderMock = new NativeFileReaderMock();
-
-			return nativeReaderMock;
-		} );
-
-		return VirtualTestEditor
-			.create( {
-				plugins: [ ImageEditing, Paragraph, ImageUploadEditing, ImageUploadProgress, UploadAdapterPluginMock, Clipboard ]
-			} )
-			.then( newEditor => {
-				editor = newEditor;
-				model = editor.model;
-				doc = model.document;
-				view = editor.editing.view;
-
-				fileRepository = editor.plugins.get( FileRepository );
-				fileRepository.createUploadAdapter = newLoader => {
-					loader = newLoader;
-					adapterMock = new UploadAdapterMock( loader );
-
-					return adapterMock;
-				};
-			} );
-	} );
-
-	it( 'should convert image\'s "reading" uploadStatus attribute', () => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-				'<div class="ck-upload-placeholder-loader"></div>' +
-			'</figure>]<p>foo</p>'
-		);
-	} );
-
-	it( 'should convert image\'s "uploading" uploadStatus attribute', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		model.document.once( 'change', () => {
-			try {
-				expect( getViewData( view ) ).to.equal(
-					'[<figure class="ck-appear ck-widget image" contenteditable="false">' +
-					`<img src="${ base64Sample }"></img>` +
-					'<div class="ck-progress-bar"></div>' +
-					'</figure>]<p>foo</p>'
-				);
-
-				done();
-			} catch ( err ) {
-				done( err );
-			}
-		}, { priority: 'lowest' } );
-
-		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();
-		const loader = fileRepository.createLoader( file );
-
-		setModelData( model, '<image></image>' );
-		const image = doc.getRoot().getChild( 0 );
-
-		// Set attributes directly on image to simulate instant "uploading" status.
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', 'uploading', image );
-			writer.setAttribute( 'uploadId', loader.id, image );
-			writer.setAttribute( 'src', 'image.png', image );
-		} );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-widget image" contenteditable="false">' +
-				'<img src="image.png"></img>' +
-				'<div class="ck-progress-bar"></div>' +
-			'</figure>]'
-		);
-	} );
-
-	it( 'should work correctly when there is no "reading" status and go straight to "uploading" - external changes', () => {
-		setModelData( model, '<image></image>' );
-		const image = doc.getRoot().getChild( 0 );
-
-		// Set attributes directly on image to simulate instant "uploading" status.
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', 'uploading', image );
-			writer.setAttribute( 'uploadId', '12345', image );
-		} );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-				'<div class="ck-upload-placeholder-loader"></div>' +
-			'</figure>]'
-		);
-	} );
-
-	it( 'should "clear" image when uploadId changes to null', () => {
-		setModelData( model, '<image></image>' );
-		const image = doc.getRoot().getChild( 0 );
-
-		// Set attributes directly on image to simulate instant "uploading" status.
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', 'uploading', image );
-			writer.setAttribute( 'uploadId', '12345', image );
-		} );
-
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', null, image );
-			writer.setAttribute( 'uploadId', null, image );
-		} );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-widget image" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-			'</figure>]'
-		);
-	} );
-
-	it( 'should update progressbar width on progress', done => {
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		model.document.once( 'change', () => {
-			adapterMock.mockProgress( 40, 100 );
-
-			try {
-				expect( getViewData( view ) ).to.equal(
-					'[<figure class="ck-appear ck-widget image" contenteditable="false">' +
-					`<img src="${ base64Sample }"></img>` +
-					'<div class="ck-progress-bar" style="width:40%"></div>' +
-					'</figure>]<p>foo</p>'
-				);
-
-				done();
-			} catch ( err ) {
-				done( err );
-			}
-		}, { priority: 'lowest' } );
-
-		loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
-	} );
-
-	it( 'should convert image\'s "complete" uploadStatus attribute and display temporary icon', done => {
-		const clock = testUtils.sinon.useFakeTimers();
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		model.document.once( 'change', () => {
-			model.document.once( 'change', () => {
-				try {
-					expect( getViewData( view ) ).to.equal(
-						'[<figure class="ck-widget image" contenteditable="false">' +
-						'<img src="image.png"></img>' +
-						'<div class="ck-image-upload-complete-icon"></div>' +
-						'</figure>]<p>foo</p>'
-					);
-
-					clock.tick( 3000 );
-
-					expect( getViewData( view ) ).to.equal(
-						'[<figure class="ck-widget image" contenteditable="false">' +
-						'<img src="image.png"></img>' +
-						'</figure>]<p>foo</p>'
-					);
-
-					done();
-				} catch ( err ) {
-					done( err );
-				}
-			}, { priority: 'lowest' } );
-
-			loader.file.then( () => adapterMock.mockSuccess( { default: 'image.png' } ) );
-		} );
-
-		loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
-	} );
-
-	it( 'should allow to customize placeholder image', () => {
-		const uploadProgress = editor.plugins.get( ImageUploadProgress );
-		uploadProgress.placeholder = base64Sample;
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
-				`<img src="${ base64Sample }"></img>` +
-				'<div class="ck-upload-placeholder-loader"></div>' +
-			'</figure>]<p>foo</p>'
-		);
-	} );
-
-	it( 'should not process attribute change if it is already consumed', () => {
-		editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:image', ( evt, data, conversionApi ) => {
-			conversionApi.consumable.consume( data.item, evt.name );
-		}, { priority: 'highest' } );
-
-		setModelData( model, '<paragraph>[]foo</paragraph>' );
-		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-widget image" contenteditable="false"><img></img></figure>]<p>foo</p>'
-		);
-	} );
-
-	it( 'should not show progress bar and complete icon if there is no loader with given uploadId', () => {
-		setModelData( model, '<image uploadId="123" uploadStatus="reading"></image>' );
-
-		const image = doc.getRoot().getChild( 0 );
-
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', 'uploading', image );
-		} );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-				'<div class="ck-upload-placeholder-loader"></div>' +
-			'</figure>]'
-		);
-
-		model.change( writer => {
-			writer.setAttribute( 'uploadStatus', 'complete', image );
-		} );
-
-		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-widget image" contenteditable="false">' +
-				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
-			'</figure>]'
-		);
-	} );
-} );

+ 15 - 15
packages/ckeditor5-image/tests/imageinsert/imageinsertui.js

@@ -12,8 +12,8 @@ import Image from '../../src/image';
 import DropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/dropdownview';
 import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview';
 import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
+import ImageInsert from '../../src/imageinsert';
 import ImageInsertUI from '../../src/imageinsert/imageinsertui';
-import ImageUploadEditing from '../../src/imageinsert/imageinsertediting';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
 import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
@@ -45,7 +45,7 @@ describe( 'ImageInsertUI', () => {
 
 			return ClassicEditor
 				.create( editorElement, {
-					plugins: [ Paragraph, Image, ImageUploadEditing, ImageInsertUI, FileRepository, UploadAdapterPluginMock, Clipboard ],
+					plugins: [ Paragraph, Image, ImageInsert, ImageInsertUI, FileRepository, UploadAdapterPluginMock, Clipboard ],
 					image: {
 						upload: {
 							panel: {
@@ -72,7 +72,7 @@ describe( 'ImageInsertUI', () => {
 			return editor.destroy();
 		} );
 		it( 'should register imageUpload dropdown', () => {
-			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const button = editor.ui.componentFactory.create( 'imageInsert' );
 
 			expect( button ).to.be.instanceOf( DropdownView );
 		} );
@@ -87,7 +87,7 @@ describe( 'ImageInsertUI', () => {
 		} );
 
 		it( 'should be disabled while ImageUploadCommand is disabled', () => {
-			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const button = editor.ui.componentFactory.create( 'imageInsert' );
 			const command = editor.commands.get( 'imageUpload' );
 
 			command.isEnabled = true;
@@ -101,7 +101,7 @@ describe( 'ImageInsertUI', () => {
 
 		// ckeditor5-upload/#77
 		it( 'should be properly bound with ImageUploadCommand', () => {
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+			const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 			const command = editor.commands.get( 'imageUpload' );
 			const spy = sinon.spy();
 
@@ -207,7 +207,7 @@ describe( 'ImageInsertUI', () => {
 
 		describe( 'dropdown action button', () => {
 			it( 'should be an instance of FileDialogButtonView', () => {
-				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 
 				expect( dropdown.buttonView.actionView ).to.be.instanceOf( FileDialogButtonView );
 			} );
@@ -215,7 +215,7 @@ describe( 'ImageInsertUI', () => {
 
 		describe( 'dropdown panel buttons', () => {
 			it( 'should have "Update" label on submit button when URL input is already filled', () => {
-				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 				const viewDocument = editor.editing.view.document;
 
 				editor.setData( '<figure><img src="/assets/sample.png" /></figure>' );
@@ -242,7 +242,7 @@ describe( 'ImageInsertUI', () => {
 			} );
 
 			it( 'should have "Insert" label on submit button on uploading a new image', () => {
-				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 				const viewDocument = editor.editing.view.document;
 
 				editor.setData( '<p>test</p>' );
@@ -272,7 +272,7 @@ describe( 'ImageInsertUI', () => {
 		describe( 'dropdown panel integrations', () => {
 			describe( 'insert image via URL form', () => {
 				it( 'should have "Insert image via URL" label on inserting new image', () => {
-					const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+					const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 					const viewDocument = editor.editing.view.document;
 
 					editor.setData( '<p>test</p>' );
@@ -301,7 +301,7 @@ describe( 'ImageInsertUI', () => {
 				} );
 
 				it( 'should have "Update image URL" label on updating the image source URL', () => {
-					const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+					const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 					const viewDocument = editor.editing.view.document;
 
 					editor.setData( '<figure><img src="/assets/sample.png" /></figure>' );
@@ -332,7 +332,7 @@ describe( 'ImageInsertUI', () => {
 
 		it( 'should remove all attributes from model except "src" when updating the image source URL', () => {
 			const viewDocument = editor.editing.view.document;
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+			const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 			const insertButtonView = dropdown.panelView.children.first.insertButtonView;
 			const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
 			const submitSpy = sinon.spy();
@@ -369,7 +369,7 @@ describe( 'ImageInsertUI', () => {
 
 		describe( 'events', () => {
 			it( 'should emit "submit" event when clicking on submit button', () => {
-				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 				const insertButtonView = dropdown.panelView.children.first.insertButtonView;
 				const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
 				const submitSpy = sinon.spy();
@@ -386,7 +386,7 @@ describe( 'ImageInsertUI', () => {
 			} );
 
 			it( 'should emit "cancel" event when clicking on cancel button', () => {
-				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 				const cancelButtonView = dropdown.panelView.children.first.cancelButtonView;
 				const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
 				const cancelSpy = sinon.spy();
@@ -410,7 +410,7 @@ describe( 'ImageInsertUI', () => {
 						CKFinder,
 						Paragraph,
 						Image,
-						ImageUploadEditing,
+						ImageInsert,
 						ImageInsertUI,
 						FileRepository,
 						UploadAdapterPluginMock,
@@ -428,7 +428,7 @@ describe( 'ImageInsertUI', () => {
 					}
 				} );
 
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+			const dropdown = editor.ui.componentFactory.create( 'imageInsert' );
 
 			expect( dropdown.panelView.children.first._integrations.length ).to.equal( 2 );
 			expect( dropdown.panelView.children.first._integrations.first ).to.be.instanceOf( LabeledFieldView );

+ 1 - 6
packages/ckeditor5-image/tests/imageinsert/utils.js

@@ -10,7 +10,6 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import Image from '../../src/image';
 import ImageUploadUI from '../../src/imageinsert/imageinsertui';
-import ImageUploadEditing from '../../src/imageinsert/imageinsertediting';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Link from '@ckeditor/ckeditor5-link/src/link';
 import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
@@ -54,7 +53,6 @@ describe( 'Upload utils', () => {
 						CKFinder,
 						Paragraph,
 						Image,
-						ImageUploadEditing,
 						ImageUploadUI
 					],
 					image: {
@@ -79,7 +77,7 @@ describe( 'Upload utils', () => {
 			editorElement.remove();
 		} );
 
-		it( 'should return only "insertImageViaUrl" integration and throw warning' +
+		it( 'should return only "insertImageViaUrl" integration and throw warning ' +
 			'for "image-upload-integrations-invalid-view" error', async () => {
 			const editorElement = document.createElement( 'div' );
 			document.body.appendChild( editorElement );
@@ -89,7 +87,6 @@ describe( 'Upload utils', () => {
 					plugins: [
 						Paragraph,
 						Image,
-						ImageUploadEditing,
 						ImageUploadUI
 					],
 					image: {
@@ -120,7 +117,6 @@ describe( 'Upload utils', () => {
 						Paragraph,
 						Link,
 						Image,
-						ImageUploadEditing,
 						ImageUploadUI
 					],
 					image: {
@@ -151,7 +147,6 @@ describe( 'Upload utils', () => {
 					plugins: [
 						Paragraph,
 						Image,
-						ImageUploadEditing,
 						ImageUploadUI
 					]
 				} );

+ 2 - 2
packages/ckeditor5-image/tests/imageupload/imageuploadcommand.js

@@ -81,9 +81,9 @@ describe( 'ImageUploadCommand', () => {
 			expect( command.isEnabled ).to.be.true;
 		} );
 
-		it( 'should be false when the selection is on other image', () => {
+		it( 'should be true when the selection is on other image', () => {
 			setModelData( model, '[<image></image>]' );
-			expect( command.isEnabled ).to.be.false;
+			expect( command.isEnabled ).to.be.true;
 		} );
 
 		it( 'should be false when the selection is inside other image', () => {

+ 1 - 1
packages/ckeditor5-image/tests/imageupload/imageuploadediting.js

@@ -157,7 +157,7 @@ describe( 'ImageUploadEditing', () => {
 
 		const command = editor.commands.get( 'imageUpload' );
 
-		expect( command.isEnabled ).to.be.false;
+		expect( command.isEnabled ).to.be.true;
 
 		const targetRange = model.createRange( model.createPositionAt( doc.getRoot(), 0 ), model.createPositionAt( doc.getRoot(), 0 ) );
 		const targetViewRange = editor.editing.mapper.toViewRange( targetRange );

+ 2 - 3
packages/ckeditor5-image/tests/manual/imageinsertviaurl.js

@@ -7,13 +7,12 @@
 
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
-import ImageUpload from '../../src/imageupload';
 import ImageInsert from '../../src/imageinsert';
 import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ ArticlePluginSet, ImageUpload, ImageInsert, CKFinder ],
+		plugins: [ ArticlePluginSet, ImageInsert, CKFinder ],
 		toolbar: [
 			'heading',
 			'|',
@@ -23,7 +22,7 @@ ClassicEditor
 			'bulletedList',
 			'numberedList',
 			'blockQuote',
-			'imageUpload',
+			'imageInsert',
 			'insertTable',
 			'mediaEmbed',
 			'undo',