8
0
فهرست منبع

Merge pull request #13 from ckeditor/t/12

Feature: Added toolbar button for image inserting. Closes #12 .
Piotr Jasiun 8 سال پیش
والد
کامیت
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 Command from '@ckeditor/ckeditor5-core/src/command/command';
 
+/**
+ * @module upload/imageuploadcommand
+ */
+
 /**
  * Image upload command.
  *

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

@@ -3,6 +3,10 @@
  * For licensing, see LICENSE.md.
  */
 
+/**
+ * @module upload/imageuploadengine
+ */
+
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
 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.
  */
 
+/**
+ * @module upload/utils
+ */
+
 /**
  * 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 Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
 import List from '@ckeditor/ckeditor5-list/src/list';
-import ImageUploadEngine from '../../src/imageuploadengine';
+import ImageUpload from '../../src/imageupload';
 import { AdapterMock } from '../_utils/mocks';
 
 ClassicEditor.create( document.querySelector( '#editor' ), {
 	plugins: [
 		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 => {
 	let adapterMock, progress;

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

@@ -5,3 +5,5 @@
 1. Open console.
 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.
+
+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 );
+	} );
+} );