ソースを参照

Change the way CKFinder insert images and links.

Maciej Gołaszewski 7 年 前
コミット
3c7b45e77c

+ 66 - 19
packages/ckeditor5-ckfinder/src/ckfindercommand.js

@@ -27,8 +27,26 @@ export default class CKFinderCommand extends Command {
 	/**
 	 * @inheritDoc
 	 */
+	constructor( editor ) {
+		super( editor );
+
+		// Remove default document listener to lower its priority.
+		this.stopListening( this.editor.model.document, 'change' );
+
+		// Lower this command listener priority to be sure that refresh() will be called after link & image refresh.
+		this.listenTo( this.editor.model.document, 'change', () => {
+			this.refresh();
+		}, { priority: 'low' } );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	refresh() {
-		this.isEnabled = true;
+		const imageCommand = this.editor.commands.get( 'imageUpload' );
+		const linkCommand = this.editor.commands.get( 'link' );
+
+		this.isEnabled = ( !imageCommand || !linkCommand ) ? false : ( imageCommand.isEnabled || linkCommand.isEnabled );
 	}
 
 	/**
@@ -50,15 +68,26 @@ export default class CKFinderCommand extends Command {
 		// The onInit method allows to extend CKFinder's behavior. It is used to attach event listeners to file choosing related events.
 		options.onInit = finder => {
 			finder.on( 'files:choose', evt => {
-				for ( const file of evt.data.files.toArray() ) {
-					const url = file.get( 'url' );
-
-					// Use CKFinder file isImage() to insert only image-type files.
-					if ( file.isImage() ) {
-						insertImage( editor.model, url ? url : finder.request( 'file:getProxyUrl', { file } ) );
-					} else {
-						editor.execute( 'link', url );
-					}
+				const files = evt.data.files.toArray();
+
+				// Insert links
+				const links = files.filter( file => !file.isImage() );
+				const images = files.filter( file => file.isImage() );
+
+				for ( const linkFile of links ) {
+					editor.execute( 'link', linkFile.get( 'url' ) );
+				}
+
+				const imagesUrls = [];
+
+				for ( const image of images ) {
+					const url = image.get( 'url' );
+
+					imagesUrls.push( url ? url : finder.request( 'file:getProxyUrl', { file: image } ) );
+				}
+
+				if ( imagesUrls.length ) {
+					insertImages( editor, imagesUrls );
 				}
 			} );
 
@@ -73,10 +102,11 @@ export default class CKFinderCommand extends Command {
 						title: t( 'Selecting resized image failed' ),
 						namespace: 'ckfinder'
 					} );
+
+					return;
 				}
 
-				// Show warning - no resizedUrl returned...
-				insertImage( editor.model, resizedUrl );
+				insertImages( editor, [ resizedUrl ] );
 			} );
 		};
 
@@ -84,17 +114,34 @@ export default class CKFinderCommand extends Command {
 	}
 }
 
-function insertImage( model, url ) {
-	model.change( writer => {
-		const imageElement = writer.createElement( 'image', { src: url } );
+function insertImages( editor, urls ) {
+	const imageCommand = editor.commands.get( 'imageUpload' );
 
-		const insertAtSelection = findOptimalInsertionPosition( model.document.selection, model );
+	if ( !imageCommand.isEnabled ) {
+		const notification = editor.plugins.get( Notification );
+		const t = editor.locale.t;
 
-		model.insertContent( imageElement, insertAtSelection );
+		notification.showWarning( t( 'Could not insert image at current selection.' ), {
+			title: t( 'Inserting image failed' ),
+			namespace: 'ckfinder'
+		} );
 
-		// Inserting an image might've failed due to schema regulations.
-		if ( imageElement.parent ) {
+		return;
+	}
+
+	const model = editor.model;
+	let insertAt = findOptimalInsertionPosition( model.document.selection, model );
+
+	model.change( writer => {
+		for ( const url of urls ) {
+			const imageElement = writer.createElement( 'image', { src: url } );
+
+			// Insert image & update the selection.
+			model.insertContent( imageElement, insertAt );
 			writer.setSelection( imageElement, 'on' );
+
+			// Insert subsequent image after the previous one.
+			insertAt = writer.createPositionAfter( imageElement );
 		}
 	} );
 }

+ 89 - 6
packages/ckeditor5-ckfinder/tests/ckfindercommand.js

@@ -11,6 +11,7 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
+import ImageUploadEditing from '@ckeditor/ckeditor5-image/src/imageupload/imageuploadediting';
 import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting';
 import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
 import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
@@ -25,7 +26,7 @@ describe( 'CKFinderCommand', () => {
 	beforeEach( () => {
 		return VirtualTestEditor
 			.create( {
-				plugins: [ Paragraph, ImageEditing, LinkEditing, Notification ]
+				plugins: [ Paragraph, ImageEditing, ImageUploadEditing, LinkEditing, Notification ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -59,6 +60,53 @@ describe( 'CKFinderCommand', () => {
 
 			expect( command.isEnabled ).to.be.true;
 		} );
+
+		it( 'should be true where only image is allowed', () => {
+			model.schema.register( 'block', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'paragraph', { allowIn: 'block' } );
+			model.schema.extend( 'image', { allowIn: 'block' } );
+
+			// Block link attribute.
+			model.schema.addAttributeCheck( ( ctx, attributeName ) => ( attributeName !== 'linkHref' ) );
+
+			editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) );
+
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be true where only link is allowed', () => {
+			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' } ) );
+
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be false where link & image are not allowed', () => {
+			model.schema.register( 'block', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'paragraph', { allowIn: 'block' } );
+
+			// Block link attribute - image is not allowed in 'block'.
+			model.schema.addAttributeCheck( ( ctx, attributeName ) => ( attributeName !== 'linkHref' ) );
+
+			editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) );
+
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
+			expect( command.isEnabled ).to.be.false;
+		} );
 	} );
 
 	describe( 'execute()', () => {
@@ -112,7 +160,7 @@ describe( 'CKFinderCommand', () => {
 			} ).to.throw( CKEditorError, /ckfinder-unknown-openerMethod/ );
 		} );
 
-		it( 'should insert single chosen image as image widget', () => {
+		it( 'should insert single chosen image', () => {
 			const url = 'foo/bar.jpg';
 
 			command.execute();
@@ -134,11 +182,11 @@ describe( 'CKFinderCommand', () => {
 				.to.equal( `<paragraph>f[<$text linkHref="${ url }">o</$text>]o</paragraph>` );
 		} );
 
-		it( 'should pass CKFinder config options', () => {
+		it( 'should pass CKFinder configuration options', () => {
 			const spy = sinon.spy( window.CKFinder, 'modal' );
 
 			const connectorPath = 'foo/bar.php';
-			editor.config.set( 'ckfinder.config', { connectorPath } );
+			editor.config.set( 'ckfinder.options', { connectorPath } );
 
 			command.execute();
 
@@ -163,7 +211,7 @@ describe( 'CKFinderCommand', () => {
 			);
 		} );
 
-		it( 'should insert only images from chosen files', () => {
+		it( 'should insert images and links to a files from chosen files', () => {
 			const url1 = 'foo/bar1.jpg';
 			const url2 = 'foo/bar2.pdf';
 			const url3 = 'foo/bar3.jpg';
@@ -173,7 +221,9 @@ describe( 'CKFinderCommand', () => {
 			mockFilesChooseEvent( [ mockFinderFile( url1 ), mockFinderFile( url2, false ), mockFinderFile( url3 ) ] );
 
 			expect( getModelData( model ) ).to.equal(
-				`<image src="${ url1 }"></image>[<image src="${ url3 }"></image>]<paragraph>foo</paragraph>`
+				`<image src="${ url1 }"></image>` +
+				`[<image src="${ url3 }"></image>]` +
+				`<paragraph>f<$text linkHref="${ url2 }">o</$text>o</paragraph>`
 			);
 		} );
 
@@ -221,6 +271,39 @@ describe( 'CKFinderCommand', () => {
 				.to.equal( '<paragraph>f[o]o</paragraph>' );
 		} );
 
+		it( 'should show warning notification if image cannot be inserted', done => {
+			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' } ) );
+
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
+			const notification = editor.plugins.get( Notification );
+
+			notification.on( 'show:warning', ( evt, data ) => {
+				expect( data.message ).to.equal( 'Could not insert image at current selection.' );
+				expect( data.title ).to.equal( 'Inserting image failed' );
+				evt.stop();
+
+				done();
+			}, { priority: 'high' } );
+
+			command.execute();
+
+			mockFinderEvent( 'file:choose:resizedImage', { resizedUrl: 'foo/bar.jpg' } );
+
+			expect( getModelData( model ) )
+				.to.equal( '<paragraph>f[o]o</paragraph>' );
+		} );
+
 		it( 'should not insert image nor crash when image could not be inserted', () => {
 			model.schema.register( 'other', {
 				allowIn: '$root',

+ 1 - 0
packages/ckeditor5-ckfinder/tests/ckfinderui.js

@@ -48,6 +48,7 @@ describe( 'CKFinderUI', () => {
 		it( 'should bind #isEnabled to the command', () => {
 			const command = editor.commands.get( 'ckfinder' );
 
+			command.isEnabled = true;
 			expect( button.isEnabled ).to.be.true;
 
 			command.isEnabled = false;