浏览代码

Changed: The ImageUploadCommand not the ImageUploadUI should calculate the optimal position to inserting images.

Maciej Gołaszewski 7 年之前
父节点
当前提交
774c62b217

+ 36 - 10
packages/ckeditor5-image/src/imageupload/imageuploadcommand.js

@@ -7,6 +7,7 @@ import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
 import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
 import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
 import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import Command from '@ckeditor/ckeditor5-core/src/command';
+import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
 
 
 /**
 /**
  * @module image/imageupload/imageuploadcommand
  * @module image/imageupload/imageuploadcommand
@@ -25,14 +26,8 @@ export default class ImageUploadCommand extends Command {
 		const model = this.editor.model;
 		const model = this.editor.model;
 		const selection = model.document.selection;
 		const selection = model.document.selection;
 		const schema = model.schema;
 		const schema = model.schema;
-		const position = selection.getFirstPosition();
-		let parent = position.parent;
 
 
-		if ( parent != parent.root ) {
-			parent = parent.parent;
-		}
-
-		this.isEnabled = schema.checkChild( parent, 'image' );
+		this.isEnabled = isImageAllowedInParent( selection, schema ) && checkSelectionWithImage( selection );
 	}
 	}
 
 
 	/**
 	/**
@@ -52,8 +47,7 @@ export default class ImageUploadCommand extends Command {
 		editor.model.change( writer => {
 		editor.model.change( writer => {
 			const filesToUpload = Array.isArray( options.files ) ? options.files : [ options.files ];
 			const filesToUpload = Array.isArray( options.files ) ? options.files : [ options.files ];
 
 
-			// Reverse the order of items as the editor will place in reverse when using the same position.
-			for ( const file of filesToUpload.reverse() ) {
+			for ( const file of filesToUpload ) {
 				uploadImage( writer, editor, file, options.insertAt );
 				uploadImage( writer, editor, file, options.insertAt );
 			}
 			}
 		} );
 		} );
@@ -86,7 +80,7 @@ function uploadImage( writer, editor, file, insertAt ) {
 	if ( insertAt ) {
 	if ( insertAt ) {
 		insertAtSelection = new ModelSelection( [ new ModelRange( insertAt ) ] );
 		insertAtSelection = new ModelSelection( [ new ModelRange( insertAt ) ] );
 	} else {
 	} else {
-		insertAtSelection = doc.selection;
+		insertAtSelection = findOptimalInsertionPosition( doc.selection );
 	}
 	}
 
 
 	editor.model.insertContent( imageElement, insertAtSelection );
 	editor.model.insertContent( imageElement, insertAtSelection );
@@ -96,3 +90,35 @@ function uploadImage( writer, editor, file, insertAt ) {
 		writer.setSelection( imageElement, 'on' );
 		writer.setSelection( imageElement, 'on' );
 	}
 	}
 }
 }
+
+// Checks if image is allowed by schema in optimal insertion parent.
+function isImageAllowedInParent( selection, schema ) {
+	const parent = getInsertImageParent( selection );
+
+	return schema.checkChild( parent, 'image' );
+}
+
+// Additional check for when the command should be disabled:
+// - selection is on image
+// - selection is inside image (image caption)
+function checkSelectionWithImage( selection ) {
+	const selectedElement = selection.getSelectedElement();
+
+	const isSelectionOnImage = !!selectedElement && selectedElement.is( 'image' );
+	const isSelectionInImage = !![ ...selection.focus.parent.getAncestors() ].find( ancestor => ancestor.name == 'image' );
+
+	return !isSelectionOnImage && !isSelectionInImage;
+}
+
+// Returns a node that will be used to insert image with `model.insertContent` to check if image can be placed there.
+function getInsertImageParent( selection ) {
+	const insertAt = findOptimalInsertionPosition( selection );
+
+	let parent = insertAt.parent;
+
+	if ( !parent.is( '$root' ) ) {
+		parent = parent.parent;
+	}
+
+	return parent;
+}

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

@@ -11,7 +11,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview';
 import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview';
 import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
 import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
 import { isImageType } from './utils';
 import { isImageType } from './utils';
-import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
 
 
 /**
 /**
  * The image upload button plugin.
  * The image upload button plugin.
@@ -52,9 +51,7 @@ export default class ImageUploadUI extends Plugin {
 				const imagesToUpload = Array.from( files ).filter( isImageType );
 				const imagesToUpload = Array.from( files ).filter( isImageType );
 
 
 				if ( imagesToUpload.length ) {
 				if ( imagesToUpload.length ) {
-					const insertAt = findOptimalInsertionPosition( editor.model.document.selection );
-
-					editor.execute( 'imageUpload', { files: imagesToUpload, insertAt } );
+					editor.execute( 'imageUpload', { files: imagesToUpload } );
 				}
 				}
 			} );
 			} );
 
 

+ 39 - 9
packages/ckeditor5-image/tests/imageupload/imageuploadcommand.js

@@ -58,7 +58,17 @@ describe( 'ImageUploadCommand', () => {
 
 
 	describe( 'isEnabled', () => {
 	describe( 'isEnabled', () => {
 		it( 'should be true when the selection directly in the root', () => {
 		it( 'should be true when the selection directly in the root', () => {
-			setModelData( model, '[]' );
+			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;
 			expect( command.isEnabled ).to.be.true;
 		} );
 		} );
 
 
@@ -76,21 +86,41 @@ describe( 'ImageUploadCommand', () => {
 			expect( command.isEnabled ).to.be.true;
 			expect( command.isEnabled ).to.be.true;
 		} );
 		} );
 
 
-		it( 'should be false when the selection in a limit element', () => {
-			model.schema.register( 'block', { inheritAllFrom: '$block' } );
-			model.schema.register( 'limit', { allowIn: 'block', isLimit: true } );
-			model.schema.extend( '$text', { allowIn: 'limit' } );
+		it( 'should be false when the selection is on other image', () => {
+			setModelData( model, '[<image></image>]' );
+			expect( command.isEnabled ).to.be.false;
+		} );
 
 
+		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' ).add( downcastElementToElement( { model: 'caption', view: 'figcaption' } ) );
+			setModelData( model, '<image><caption>[]</caption></image>' );
+			expect( command.isEnabled ).to.be.false;
+		} );
+
+		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' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) );
 			editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) );
-			editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'limit', view: 'limit' } ) );
 
 
-			setModelData( model, '<block><limit>foo[]</limit></block>' );
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
 			expect( command.isEnabled ).to.be.false;
 			expect( command.isEnabled ).to.be.false;
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'execute()', () => {
 	describe( 'execute()', () => {
-		it( 'should insert image at selection position (includes deleting selected content)', () => {
+		it( 'should insert image at selection position as other widgets', () => {
 			const file = createNativeFileMock();
 			const file = createNativeFileMock();
 			setModelData( model, '<paragraph>f[o]o</paragraph>' );
 			setModelData( model, '<paragraph>f[o]o</paragraph>' );
 
 
@@ -98,7 +128,7 @@ describe( 'ImageUploadCommand', () => {
 
 
 			const id = fileRepository.getLoader( file ).id;
 			const id = fileRepository.getLoader( file ).id;
 			expect( getModelData( model ) )
 			expect( getModelData( model ) )
-				.to.equal( `<paragraph>f</paragraph>[<image uploadId="${ id }"></image>]<paragraph>o</paragraph>` );
+				.to.equal( `[<image uploadId="${ id }"></image>]<paragraph>foo</paragraph>` );
 		} );
 		} );
 
 
 		it( 'should insert directly at specified position (options.insertAt)', () => {
 		it( 'should insert directly at specified position (options.insertAt)', () => {

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

@@ -142,8 +142,8 @@ describe( 'ImageUploadUI', () => {
 
 
 		expect( getModelData( model ) ).to.equal(
 		expect( getModelData( model ) ).to.equal(
 			'<paragraph>foo</paragraph>' +
 			'<paragraph>foo</paragraph>' +
-			`[<image uploadId="${ id1 }" uploadStatus="reading"></image>]` +
-			`<image uploadId="${ id2 }" uploadStatus="reading"></image>` +
+			`<image uploadId="${ id1 }" uploadStatus="reading"></image>` +
+			`[<image uploadId="${ id2 }" uploadStatus="reading"></image>]` +
 			'<paragraph>bar</paragraph>'
 			'<paragraph>bar</paragraph>'
 		);
 		);
 	} );
 	} );