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

Extracted findOptimalInsertionPosition() from the command and made the features use it.

Piotrek Koszuliński 8 лет назад
Родитель
Сommit
d154ca2c2f

+ 4 - 1
packages/ckeditor5-upload/src/imageuploadbutton.js

@@ -11,6 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import ImageUploadEngine from './imageuploadengine';
 import FileDialogButtonView from './ui/filedialogbuttonview';
 import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
+import { findOptimalInsertionPosition } from './utils';
 
 /**
  * Image upload button plugin.
@@ -49,8 +50,10 @@ export default class ImageUploadButton extends Plugin {
 			view.bind( 'isEnabled' ).to( command );
 
 			view.on( 'done', ( evt, files ) => {
+				const insertAt = findOptimalInsertionPosition( editor.document.selection );
+
 				for ( const file of files ) {
-					editor.execute( 'imageUpload', { file } );
+					editor.execute( 'imageUpload', { file, insertAt } );
 				}
 			} );
 

+ 9 - 36
packages/ckeditor5-upload/src/imageuploadcommand.js

@@ -3,10 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
-import ModelDocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment';
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
 import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
-import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
 import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
 import FileRepository from './filerepository';
 import { isImageType } from './utils';
@@ -46,22 +44,19 @@ export default class ImageUploadCommand extends Command {
 		}
 
 		doc.enqueueChanges( () => {
-			const insertAt = options.insertAt || getInsertionPosition( doc );
-
-			// No position to insert.
-			if ( !insertAt ) {
-				return;
-			}
-
 			const imageElement = new ModelElement( 'image', {
 				uploadId: fileRepository.createLoader( file ).id
 			} );
-			const documentFragment = new ModelDocumentFragment( [ imageElement ] );
-			const range = new ModelRange( insertAt );
-			const insertSelection = new ModelSelection();
 
-			insertSelection.setRanges( [ range ] );
-			editor.data.insertContent( documentFragment, insertSelection, batch );
+			let insertAtSelection;
+
+			if ( options.insertAt ) {
+				insertAtSelection = new ModelSelection( [ new ModelRange( options.insertAt ) ] );
+			} else {
+				insertAtSelection = doc.selection;
+			}
+
+			editor.data.insertContent( imageElement, insertAtSelection, batch );
 			selection.setRanges( [ ModelRange.createOn( imageElement ) ] );
 		} );
 	}
@@ -71,26 +66,4 @@ export default class ImageUploadCommand extends Command {
 //
 // @param {module:engine/model/document~Document} doc
 // @returns {module:engine/model/position~Position|undefined}
-function getInsertionPosition( doc ) {
-	const selection = doc.selection;
-	const selectedElement = selection.getSelectedElement();
-
-	// If selected element is placed directly in root - return position after that element.
-	if ( selectedElement && selectedElement.parent.is( 'rootElement' ) ) {
-		return ModelPosition.createAfter( selectedElement );
-	}
-
-	const firstBlock = doc.selection.getSelectedBlocks().next().value;
 
-	if ( firstBlock ) {
-		const positionAfter = ModelPosition.createAfter( firstBlock );
-
-		// If selection is at the end of the block - return position after the block.
-		if ( selection.focus.isTouching( positionAfter ) ) {
-			return positionAfter;
-		}
-
-		// Otherwise return position before the block.
-		return ModelPosition.createBefore( firstBlock );
-	}
-}

+ 9 - 2
packages/ckeditor5-upload/src/imageuploadengine.js

@@ -11,7 +11,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import FileRepository from './filerepository';
 import ImageUploadCommand from './imageuploadcommand';
 import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
-import { isImageType } from './utils';
+import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
+import { isImageType, findOptimalInsertionPosition } from './utils';
 
 /**
  * Image upload engine plugin.
@@ -45,9 +46,15 @@ export default class ImageUploadEngine extends Plugin {
 
 		// Execute imageUpload command when image is dropped or pasted.
 		editor.editing.view.on( 'clipboardInput', ( evt, data ) => {
+			const targetModelSelection = new ModelSelection(
+				data.targetRanges.map( viewRange => editor.editing.mapper.toModelRange( viewRange ) )
+			);
+
+			const insertAt = findOptimalInsertionPosition( targetModelSelection );
+
 			for ( const file of data.dataTransfer.files ) {
 				if ( isImageType( file ) ) {
-					editor.execute( 'imageUpload', { file } );
+					editor.execute( 'imageUpload', { file, insertAt } );
 					evt.stop();
 				}
 			}

+ 46 - 0
packages/ckeditor5-upload/src/utils.js

@@ -7,6 +7,8 @@
  * @module upload/utils
  */
 
+import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
+
 /**
  * Checks if given file is an image.
  *
@@ -19,3 +21,47 @@ export function isImageType( file ) {
 	return types.test( file.type );
 }
 
+/**
+ * Returns a model position which is optimal (in terms of UX) for inserting and image.
+ *
+ * For instance, if a selection is in a middle of a paragraph, position before this paragraph
+ * will be returned, so that it's not split. If the selection is at the end of a paragraph,
+ * position after this paragraph will be returned.
+ *
+ * Note: If selection is placed in an empty block, that block will be returned. If that position
+ * is then passed to {@link module:engine/controller/datacontroller~DataController#insertContent}
+ * that block will be fully replaced by the image.
+ *
+ * @param {module:engine/model/selection~Selection} selection Selection based on which the
+ * insertion position should be calculated.
+ * @returns {module:engine/model/position~Position} The optimal position.
+ */
+export function findOptimalInsertionPosition( selection ) {
+	const selectedElement = selection.getSelectedElement();
+
+	if ( selectedElement ) {
+		return ModelPosition.createAfter( selectedElement );
+	}
+
+	const firstBlock = selection.getSelectedBlocks().next().value;
+
+	if ( firstBlock ) {
+		// If inserting into an empty block – return position in that block. It will get
+		// replaced with the image by insertContent(). #42.
+		if ( firstBlock.isEmpty ) {
+			return ModelPosition.createAt( firstBlock );
+		}
+
+		const positionAfter = ModelPosition.createAfter( firstBlock );
+
+		// If selection is at the end of the block - return position after the block.
+		if ( selection.focus.isTouching( positionAfter ) ) {
+			return positionAfter;
+		}
+
+		// Otherwise return position before the block.
+		return ModelPosition.createBefore( firstBlock );
+	}
+
+	return selection.focus;
+}

+ 83 - 3
packages/ckeditor5-upload/tests/utils.js

@@ -3,10 +3,12 @@
  * For licensing, see LICENSE.md.
  */
 
-import { isImageType } from '../src/utils';
+import { isImageType, findOptimalInsertionPosition } from '../src/utils';
+import Document from '@ckeditor/ckeditor5-engine/src/model/document';
+import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
-describe( 'utils', () => {
-	describe( 'isImageType', () => {
+describe( 'upload utils', () => {
+	describe( 'isImageType()', () => {
 		it( 'should return true for png mime type', () => {
 			expect( isImageType( { type: 'image/png' } ) ).to.be.true;
 		} );
@@ -28,4 +30,82 @@ describe( 'utils', () => {
 			expect( isImageType( { type: 'video/mpeg' } ) ).to.be.false;
 		} );
 	} );
+
+	describe( 'findOptimalInsertionPosition()', () => {
+		let doc;
+
+		beforeEach( () => {
+			doc = new Document();
+
+			doc.createRoot();
+
+			doc.schema.registerItem( 'paragraph', '$block' );
+			doc.schema.registerItem( 'image' );
+			doc.schema.registerItem( 'span' );
+
+			doc.schema.allow( { name: 'image', inside: '$root' } );
+			doc.schema.objects.add( 'image' );
+
+			doc.schema.allow( { name: 'span', inside: 'paragraph' } );
+			doc.schema.allow( { name: '$text', inside: 'span' } );
+		} );
+
+		it( 'returns position after selected element', () => {
+			setData( doc, '<paragraph>x</paragraph>[<image></image>]<paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 2 ] );
+		} );
+
+		it( 'returns position inside empty block', () => {
+			setData( doc, '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 1, 0 ] );
+		} );
+
+		it( 'returns position before block if at the beginning of that block', () => {
+			setData( doc, '<paragraph>x</paragraph><paragraph>[]foo</paragraph><paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 1 ] );
+		} );
+
+		it( 'returns position before block if in the middle of that block', () => {
+			setData( doc, '<paragraph>x</paragraph><paragraph>f[]oo</paragraph><paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 1 ] );
+		} );
+
+		it( 'returns position after block if at the end of that block', () => {
+			setData( doc, '<paragraph>x</paragraph><paragraph>foo[]</paragraph><paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 2 ] );
+		} );
+
+		// Checking if isTouching() was used.
+		it( 'returns position after block if at the end of that block (deeply nested)', () => {
+			setData( doc, '<paragraph>x</paragraph><paragraph>foo<span>bar[]</span></paragraph><paragraph>y</paragraph>' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 2 ] );
+		} );
+
+		it( 'returns selection focus if not in a block', () => {
+			doc.schema.allow( { name: '$text', inside: '$root' } );
+			setData( doc, 'foo[]bar' );
+
+			const pos = findOptimalInsertionPosition( doc.selection );
+
+			expect( pos.path ).to.deep.equal( [ 3 ] );
+		} );
+	} );
 } );