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

Working Base64 upload adapter (tests included).

Kamil Piechaczek 6 лет назад
Родитель
Сommit
094a93dce2

+ 3 - 0
packages/ckeditor5-upload/docs/_snippets/features/base64-upload.html

@@ -0,0 +1,3 @@
+<div id="snippet-image-base64-upload">
+	<p>Paste or drop an image directly into the editor. You can also use the "Insert image" button in the toolbar.</p>
+</div>

+ 56 - 0
packages/ckeditor5-upload/docs/_snippets/features/base64-upload.js

@@ -0,0 +1,56 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import UploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import Image from '@ckeditor/ckeditor5-image/src/image';
+import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
+import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
+import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
+import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice';
+import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/base64uploadadapter';
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-image-base64-upload' ), {
+		plugins: [
+			Essentials,
+			UploadAdapter,
+			Base64UploadAdapter,
+			Bold,
+			Italic,
+			Heading,
+			Image,
+			ImageCaption,
+			ImageStyle,
+			ImageToolbar,
+			ImageUpload,
+			Paragraph,
+			PasteFromOffice
+		],
+		toolbar: {
+			items: [
+				'heading', '|', 'bold', 'italic', 'imageUpload', 'undo', 'redo'
+			],
+			viewportTopOffset: window.getViewportTopOffsetConfig()
+		},
+		image: {
+			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ],
+			styles: [ 'full', 'alignLeft', 'alignRight' ]
+		},
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 99 - 0
packages/ckeditor5-upload/src/base64uploadadapter.js

@@ -0,0 +1,99 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module upload/base64uploadadapter
+ */
+
+/* globals window */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
+
+/**
+ * @extends module:core/plugin~Plugin
+ */
+export default class Base64UploadAdapter extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FileRepository ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'Base64UploadAdapter';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		this.editor.plugins.get( FileRepository ).createUploadAdapter = loader => new Adapter( loader );
+	}
+}
+
+/**
+ * Upload adapter for Base64.
+ *
+ * @private
+ * @implements module:upload/base64uploadadapter~Adapter
+ */
+class Adapter {
+	/**
+	 * Creates a new adapter instance.
+	 *
+	 * @param {module:upload/filerepository~FileLoader} loader
+	 */
+	constructor( loader ) {
+		/**
+		 * FileLoader instance to use during the upload.
+		 *
+		 * @member {module:upload/filerepository~FileLoader} #loader
+		 */
+		this.loader = loader;
+	}
+
+	/**
+	 * Starts the upload process.
+	 *
+	 * @see module:upload/filerepository~UploadAdapter#upload
+	 * @returns {Promise}
+	 */
+	upload() {
+		return new Promise( ( resolve, reject ) => {
+			const reader = this.reader = new window.FileReader();
+
+			reader.addEventListener( 'load', () => {
+				resolve( { default: reader.result } );
+			} );
+
+			reader.addEventListener( 'error', err => {
+				reject( err );
+			} );
+
+			reader.addEventListener( 'abort', () => {
+				reject();
+			} );
+
+			this.loader.file.then( file => {
+				reader.readAsDataURL( file );
+			} );
+		} );
+	}
+
+	/**
+	 * Aborts the upload process.
+	 *
+	 * @see module:upload/filerepository~UploadAdapter#abort
+	 * @returns {Promise}
+	 */
+	abort() {
+		this.reader.abort();
+	}
+}

+ 168 - 0
packages/ckeditor5-upload/tests/base64uploadadapter.js

@@ -0,0 +1,168 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals window, setTimeout */
+
+import Base64UploadAdapter from '../src/base64uploadadapter';
+import FileRepository from '../src/filerepository';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+const createNativeFileMock = () => ( {
+	type: 'image/jpeg',
+	size: 1024
+} );
+
+describe( 'Base64UploadAdapter', () => {
+	let div, stubs;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		div = window.document.createElement( 'div' );
+		window.document.body.appendChild( div );
+
+		stubs = {
+			addEventListener( event, callback ) {
+				stubs[ `on${ event }` ] = callback;
+			},
+			readAsDataURL: testUtils.sinon.spy(),
+			abort: testUtils.sinon.spy(),
+			result: 'data:image/png;base64'
+		};
+
+		testUtils.sinon.stub( window, 'FileReader' ).callsFake( function FileReader() {
+			return stubs;
+		} );
+	} );
+
+	afterEach( () => {
+		window.document.body.removeChild( div );
+	} );
+
+	it( 'should require FileRepository plugin', () => {
+		expect( Base64UploadAdapter.requires ).to.deep.equal( [ FileRepository ] );
+	} );
+
+	it( 'should be named', () => {
+		expect( Base64UploadAdapter.pluginName ).to.equal( 'Base64UploadAdapter' );
+	} );
+
+	describe( 'init()', () => {
+		it( 'should set loader', () => {
+			return ClassicTestEditor
+				.create( div, {
+					plugins: [ Base64UploadAdapter ],
+				} )
+				.then( editor => {
+					expect( editor.plugins.get( FileRepository ).createUploadAdapter ).is.a( 'function' );
+
+					return editor.destroy();
+				} );
+		} );
+	} );
+
+	describe( 'Adapter', () => {
+		let editor, fileRepository;
+
+		beforeEach( () => {
+			return ClassicTestEditor.create( div, {
+				plugins: [ Base64UploadAdapter ],
+			} ).then( _editor => {
+				editor = _editor;
+				fileRepository = editor.plugins.get( FileRepository );
+			} );
+		} );
+
+		afterEach( () => {
+			return editor.destroy();
+		} );
+
+		describe( 'upload()', () => {
+			it( 'returns a promise that resolves an image as base64 string', () => {
+				const loader = fileRepository.createLoader( createNativeFileMock() );
+
+				setTimeout( () => {
+					// FileReader has loaded the file.
+					stubs.onload();
+				} );
+
+				return loader.upload()
+					.then( response => {
+						expect( response.default ).to.equal( 'data:image/png;base64' );
+						expect( stubs.readAsDataURL.calledOnce ).to.equal( true );
+					} );
+			} );
+
+			it( 'returns a promise that rejects if something went wrong', () => {
+				const loader = fileRepository.createLoader( createNativeFileMock() );
+				const uploadError = new Error( 'Something went wrong.' );
+
+				setTimeout( () => {
+					// An error occurred while FileReader was reading the file.
+					stubs.onerror( uploadError );
+				} );
+
+				return loader.upload()
+					.then(
+						() => {
+							return new Error( 'Supposed to be rejected.' );
+						},
+						err => {
+							expect( err ).to.equal( uploadError );
+							expect( stubs.readAsDataURL.calledOnce ).to.equal( true );
+						}
+					);
+			} );
+
+			it( 'returns a promise that rejects if FileReader aborted reading a file', () => {
+				const loader = fileRepository.createLoader( createNativeFileMock() );
+
+				setTimeout( () => {
+					// FileReader aborted reading the file.
+					stubs.onabort();
+				} );
+
+				return loader.upload()
+					.then(
+						() => {
+							return new Error( 'Supposed to be rejected.' );
+						},
+						() => {
+							expect( stubs.readAsDataURL.calledOnce ).to.equal( true );
+						}
+					);
+			} );
+		} );
+
+		describe( 'abort()', () => {
+			it( 'should not call abort on the non-existing FileReader uploader (`loader.file` not resolved)', () => {
+				const loader = fileRepository.createLoader( createNativeFileMock() );
+
+				expect( () => {
+					loader.upload();
+					loader.abort();
+				} ).to.not.throw();
+
+				expect( stubs.abort.called ).to.equal( false );
+			} );
+
+			it( 'should call abort on the FileReader uploader (`loader.file` resolved)', done => {
+				const loader = fileRepository.createLoader( createNativeFileMock() );
+
+				loader.upload();
+
+				// Wait for the `loader.file` promise.
+				setTimeout( () => {
+					loader.abort();
+
+					expect( stubs.abort.called ).to.equal( true );
+
+					done();
+				} );
+			} );
+		} );
+	} );
+} );