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

Merge pull request #13 from ckeditor/t/12

Feature: Added toolbar button for image inserting. Closes #12 .
Piotr Jasiun 8 лет назад
Родитель
Сommit
6ad60cbf56

+ 3 - 0
packages/ckeditor5-upload/lang/contexts.json

@@ -0,0 +1,3 @@
+{
+	"Insert image": "Label for the insert image toolbar button."
+}

+ 55 - 0
packages/ckeditor5-upload/src/imageupload.js

@@ -0,0 +1,55 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module upload/imageupload
+ */
+
+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';
+
+/**
+ * Image upload plugin.
+ * Adds `insertImage` button to UI component factory.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class ImageUpload extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ ImageUploadEngine ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+
+		editor.ui.componentFactory.add( 'insertImage', ( locale ) => {
+			const view = new FileDialogButtonView( locale );
+
+			view.set( {
+				label: t( 'Insert image' ),
+				icon: imageIcon,
+				tooltip: true,
+				acceptedType: 'image/*'
+			} );
+
+			view.on( 'done', ( evt, files ) => {
+				for ( const file of files ) {
+					editor.execute( 'imageUpload', { file: file } );
+				}
+			} );
+
+			return view;
+		} );
+	}
+}

+ 4 - 0
packages/ckeditor5-upload/src/imageuploadcommand.js

@@ -11,6 +11,10 @@ import FileRepository from './filerepository';
 import { isImageType } from './utils';
 import { isImageType } from './utils';
 import Command from '@ckeditor/ckeditor5-core/src/command/command';
 import Command from '@ckeditor/ckeditor5-core/src/command/command';
 
 
+/**
+ * @module upload/imageuploadcommand
+ */
+
 /**
 /**
  * Image upload command.
  * Image upload command.
  *
  *

+ 4 - 0
packages/ckeditor5-upload/src/imageuploadengine.js

@@ -3,6 +3,10 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
+/**
+ * @module upload/imageuploadengine
+ */
+
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
 import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
 import FileRepository from './filerepository';
 import FileRepository from './filerepository';

+ 135 - 0
packages/ckeditor5-upload/src/ui/filedialogbuttonview.js

@@ -0,0 +1,135 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+/**
+ * @module upload/ui/filedialogbuttonview
+ */
+
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import View from '@ckeditor/ckeditor5-ui/src/view';
+import Template from '@ckeditor/ckeditor5-ui/src/template';
+
+/**
+ * File Dialog button view.
+ *
+ * @extends module:ui/button/buttonview~ButtonView
+ */
+export default class FileDialogButtonView extends ButtonView {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( locale ) {
+		super( locale );
+
+		/**
+		 * Hidden input view used to execute file dialog. It will be hidden and added to the end of `document.body`.
+		 *
+		 * @protected
+		 * @member module:upload/ui/filedialogbuttonview~FileDialogButtonView #fileInputView
+		 */
+		this.fileInputView = new FileInputView( locale );
+
+		/**
+		 * Accepted file types. Can be provided in form of file extensions, media type or one of:
+		 * * `audio/*`,
+		 * * `video/*`,
+		 * * `image/*`.
+		 *
+		 * @observable
+		 * @member {String} #acceptedType
+		 */
+		this.fileInputView.bind( 'acceptedType' ).to( this, 'acceptedType' );
+
+		/**
+		 * Fired when file dialog is closed with file selected.
+		 *
+		 *	fileDialogButtonView.on( 'done', ( evt, files ) => {
+		 *		for ( const file of files ) {
+		 *			processFile( file );
+		 *		}
+		 *	}
+		 *
+		 * @event done
+		 * @param {Array.<File>} files Array of selected files.
+		 */
+		this.fileInputView.delegate( 'done' ).to( this );
+
+		this.on( 'execute', () => {
+			this.fileInputView.open();
+		} );
+
+		document.body.appendChild( this.fileInputView.element );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		document.body.removeChild( this.fileInputView.element );
+
+		return super.destroy();
+	}
+}
+
+/**
+ * Hidden file input view class.
+ *
+ * @private
+ * @extends {module:ui/view~View}
+ */
+class FileInputView extends View {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( locale ) {
+		super( locale );
+
+		/**
+		 * Accepted file types. Can be provided in form of file extensions, media type or one of:
+		 * * `audio/*`,
+		 * * `video/*`,
+		 * * `image/*`.
+		 *
+		 * @observable
+		 * @member {String} #acceptedType
+		 */
+		this.set( 'acceptedType' );
+
+		const bind = this.bindTemplate;
+
+		this.template = new Template( {
+			tag: 'input',
+
+			attributes: {
+				class: [
+					'ck-hidden'
+				],
+				type: 'file',
+				tabindex: '-1',
+				accept: bind.to( 'acceptedType' )
+			},
+
+			on: {
+				// Removing from code coverage since we cannot programmatically set input element files.
+				change: bind.to( /* istanbul ignore next */ () => {
+					if ( this.element && this.element.files && this.element.files.length ) {
+						this.fire( 'done', this.element.files );
+					}
+
+					this.element.value = '';
+				} )
+			}
+		} );
+	}
+
+	/**
+	 * Opens file dialog.
+	 */
+	open() {
+		this.element.click();
+	}
+}

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

@@ -3,6 +3,10 @@
  * For licensing, see LICENSE.md.
  * For licensing, see LICENSE.md.
  */
  */
 
 
+/**
+ * @module upload/utils
+ */
+
 /**
 /**
  * Checks if given file is an image.
  * Checks if given file is an image.
  *
  *

+ 51 - 0
packages/ckeditor5-upload/tests/imageupload.js

@@ -0,0 +1,51 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classic';
+import Image from '@ckeditor/ckeditor5-image/src/image';
+import FileDialogButtonView from '../src/ui/filedialogbuttonview';
+import ImageUpload from '../src/imageupload';
+import ImageUploadEngine from '../src/imageuploadengine';
+import { createNativeFileMock } from './_utils/mocks';
+
+describe( 'ImageUpload', () => {
+	let editor;
+
+	beforeEach( () => {
+		const editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicEditor.create( editorElement, {
+			plugins: [ Image, ImageUpload ]
+		} )
+		.then( newEditor => {
+			editor = newEditor;
+		} );
+	} );
+
+	it( 'should load ImageUploadEngine', () => {
+		expect( editor.plugins.get( ImageUploadEngine ) ).to.be.instanceOf( ImageUploadEngine );
+	} );
+
+	it( 'should register insertImage button', () => {
+		const button = editor.ui.componentFactory.create( 'insertImage' );
+
+		expect( button ).to.be.instanceOf( FileDialogButtonView );
+	} );
+
+	it( 'should execute imageUpload command', () => {
+		const executeStub = sinon.stub( editor, 'execute' );
+		const button = editor.ui.componentFactory.create( 'insertImage' );
+		const files = [ createNativeFileMock() ];
+
+		button.fire( 'done', files );
+		sinon.assert.calledOnce( executeStub );
+		expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+		expect( executeStub.firstCall.args[ 1 ].file ).to.equal( files[ 0 ] );
+	} );
+} );
+

+ 3 - 3
packages/ckeditor5-upload/tests/manual/imageupload.js

@@ -19,15 +19,15 @@ import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 import List from '@ckeditor/ckeditor5-list/src/list';
 import List from '@ckeditor/ckeditor5-list/src/list';
-import ImageUploadEngine from '../../src/imageuploadengine';
+import ImageUpload from '../../src/imageupload';
 import { AdapterMock } from '../_utils/mocks';
 import { AdapterMock } from '../_utils/mocks';
 
 
 ClassicEditor.create( document.querySelector( '#editor' ), {
 ClassicEditor.create( document.querySelector( '#editor' ), {
 	plugins: [
 	plugins: [
 		Enter, Typing, Paragraph, Heading, Undo, Bold, Italic, Heading, List, Image, ImageToolbar, Clipboard,
 		Enter, Typing, Paragraph, Heading, Undo, Bold, Italic, Heading, List, Image, ImageToolbar, Clipboard,
-		ImageCaption, ImageStyle, ImageUploadEngine
+		ImageCaption, ImageStyle, ImageUpload
 	],
 	],
-	toolbar: [ 'headings', 'undo', 'redo', 'bold', 'italic', 'bulletedList', 'numberedList' ]
+	toolbar: [ 'headings', 'undo', 'redo', 'bold', 'italic', 'bulletedList', 'numberedList', 'insertImage' ]
 } )
 } )
 .then( editor => {
 .then( editor => {
 	let adapterMock, progress;
 	let adapterMock, progress;

+ 2 - 0
packages/ckeditor5-upload/tests/manual/imageupload.md

@@ -5,3 +5,5 @@
 1. Open console.
 1. Open console.
 1. Press "Upload progress" button couple times to simulate upload process.
 1. Press "Upload progress" button couple times to simulate upload process.
 1. After uploading is complete your image should be replaced with sample image from server.
 1. After uploading is complete your image should be replaced with sample image from server.
+
+Repeat all the steps but this time use toolbar button to add image.

+ 59 - 0
packages/ckeditor5-upload/tests/ui/filedialogbuttonview.js

@@ -0,0 +1,59 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classic';
+import FileDialogButtonView from '../../src/ui/filedialogbuttonview';
+
+describe( 'FileDialogButtonView', () => {
+	let view, editor;
+
+	beforeEach( () => {
+		const editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicEditor.create( editorElement )
+		.then( newEditor => {
+			editor = newEditor;
+
+			view = new FileDialogButtonView( editor.locale );
+		} );
+	} );
+
+	it( 'should append input view to document body', () => {
+		expect( view.fileInputView.element.parentNode ).to.equal( document.body );
+	} );
+
+	it( 'should remove input view from body after destroy', () => {
+		return view.destroy().then( () => {
+			expect( view.fileInputView.element.parentNode ).to.be.null;
+		} );
+	} );
+
+	it( 'should open file dialog on execute', () => {
+		const spy = sinon.spy( view.fileInputView, 'open' );
+		view.fire( 'execute' );
+
+		sinon.assert.calledOnce( spy );
+	} );
+
+	it( 'should pass acceptedType to input view', () => {
+		view.set( { acceptedType: 'audio/*' } );
+
+		expect( view.fileInputView.acceptedType ).to.equal( 'audio/*' );
+	} );
+
+	it( 'should delegate input view done event', ( done ) => {
+		const files = [];
+
+		view.on( 'done', ( evt, data ) => {
+			expect( data ).to.equal( files );
+			done();
+		} );
+
+		view.fileInputView.fire( 'done', files );
+	} );
+} );