Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #323 from ckeditor/t/ckeditor5/674
Browse files Browse the repository at this point in the history
Feature: Introduced `config.image.upload.types` configuration option for setting allowed image mime-types. Closes #295. Closes ckeditor/ckeditor5#674.

BREAKING CHANGE: Removed `isImageType()` util.
  • Loading branch information
Reinmar authored Sep 2, 2019
2 parents a7169a3 + d7fe0b1 commit 8c36aee
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/imageresize.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export default class ImageResize extends Plugin {
* ClassicEditor
* .create( editorElement, {
* image: {
* resizeUnit: 'px'
* resizeUnit: 'px'
* }
* } )
* .then( ... )
Expand Down
43 changes: 43 additions & 0 deletions src/imageupload.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,46 @@ export default class ImageUpload extends Plugin {
return [ ImageUploadEditing, ImageUploadUI, ImageUploadProgress ];
}
}

/**
* Image upload configuration.
*
* @member {module:image/imageupload~ImageUploadConfig} module:image/image~ImageConfig#upload
*/

/**
* The configuration of the image upload feature. Used by the image upload feature in the `@ckeditor/ckeditor5-image` package.
*
* ClassicEditor
* .create( editorElement, {
* image: {
* upload: ... // Image upload feature options.
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface module:image/imageupload~ImageUploadConfig
*/

/**
* List of accepted image types.
*
* The accepted types of images can be customize to allow only certain types of images:
*
* // Allow only JPEG and PNG images:
* const imageUploadConfig = {
* types: [ 'png', 'jpeg' ]
* };
*
* The type string should match [one of the sub-types](https://www.iana.org/assignments/media-types/media-types.xhtml#image)
* of the image mime-type. E.g. for the `image/jpeg` mime-type add `'jpeg'`.
*
* **Note:** This setting only restricts some image types to be selected and uploaded through the CKEditor UI and commands. Image type
* recognition and filtering should be also implemented on the server which accepts image uploads
*
* @member {Array.<String>} module:image/imageupload~ImageUploadConfig#types
* @default [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff' ]
*/
24 changes: 22 additions & 2 deletions src/imageupload/imageuploadediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
import env from '@ckeditor/ckeditor5-utils/src/env';

import ImageUploadCommand from '../../src/imageupload/imageuploadcommand';
import { fetchLocalImage, isImageType, isLocalImage } from '../../src/imageupload/utils';
import { fetchLocalImage, isLocalImage } from '../../src/imageupload/utils';
import { createImageTypeRegExp } from './utils';

/**
* The editing part of the image upload feature. It registers the `'imageUpload'` command.
Expand All @@ -29,6 +30,23 @@ export default class ImageUploadEditing extends Plugin {
return [ FileRepository, Notification ];
}

static get pluginName() {
return 'ImageUploadEditing';
}

/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );

editor.config.define( 'image', {
upload: {
types: [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff' ]
}
} );
}

/**
* @inheritDoc
*/
Expand All @@ -39,6 +57,8 @@ export default class ImageUploadEditing extends Plugin {
const conversion = editor.conversion;
const fileRepository = editor.plugins.get( FileRepository );

const imageTypes = createImageTypeRegExp( editor.config.get( 'image.upload.types' ) );

// Setup schema to allow uploadId and uploadStatus for images.
schema.extend( 'image', {
allowAttributes: [ 'uploadId', 'uploadStatus' ]
Expand Down Expand Up @@ -74,7 +94,7 @@ export default class ImageUploadEditing extends Plugin {
return false;
}

return isImageType( file );
return imageTypes.test( file.type );
} );

const ranges = data.targetRanges.map( viewRange => editor.editing.mapper.toModelRange( viewRange ) );
Expand Down
8 changes: 5 additions & 3 deletions src/imageupload/imageuploadui.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview';
import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
import { isImageType } from './utils';
import { createImageTypeRegExp } from './utils';

/**
* The image upload button plugin.
Expand All @@ -33,9 +33,11 @@ export default class ImageUploadUI extends Plugin {
editor.ui.componentFactory.add( 'imageUpload', locale => {
const view = new FileDialogButtonView( locale );
const command = editor.commands.get( 'imageUpload' );
const imageTypes = editor.config.get( 'image.upload.types' );
const imageTypesRegExp = createImageTypeRegExp( imageTypes );

view.set( {
acceptedType: 'image/*',
acceptedType: imageTypes.map( type => `image/${ type }` ).join( ',' ),
allowMultipleFiles: true
} );

Expand All @@ -48,7 +50,7 @@ export default class ImageUploadUI extends Plugin {
view.buttonView.bind( 'isEnabled' ).to( command );

view.on( 'done', ( evt, files ) => {
const imagesToUpload = Array.from( files ).filter( isImageType );
const imagesToUpload = Array.from( files ).filter( file => imageTypesRegExp.test( file.type ) );

if ( imagesToUpload.length ) {
editor.execute( 'imageUpload', { file: imagesToUpload } );
Expand Down
17 changes: 11 additions & 6 deletions src/imageupload/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@
/* global fetch, File */

/**
* Checks if a given file is an image.
* Creates a RegExp used to test for image files.
*
* @param {File} file
* @returns {Boolean}
* const imageType = createImageTypeRegExp( [ 'png', 'jpeg', 'svg+xml', 'vnd.microsoft.icon' ] );
*
* console.log( 'is supported image', imageType.test( file.type ) );
*
* @param {Array.<String>} types
* @returns {RegExp}
*/
export function isImageType( file ) {
const types = /^image\/(jpeg|png|gif|bmp)$/;
export function createImageTypeRegExp( types ) {
// Sanitize mime-type name which may include: "+", "-" or ".".
const regExpSafeNames = types.map( type => type.replace( '+', '\\+' ) );

return types.test( file.type );
return new RegExp( `^image\\/(${ regExpSafeNames.join( '|' ) })$` );
}

/**
Expand Down
24 changes: 24 additions & 0 deletions tests/imageupload/imageuploadediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,30 @@ describe( 'ImageUploadEditing', () => {
expect( eventInfo.stop.called ).to.be.undefined;
} );

it( 'should not insert image when file is not an configured image type', () => {
const viewDocument = editor.editing.view.document;
const fileMock = {
type: 'image/svg+xml',
size: 1024
};
const dataTransfer = new DataTransfer( {
files: [ fileMock ],
types: [ 'Files' ],
getData: () => ''
} );

setModelData( model, '<paragraph>foo[]</paragraph>' );

const targetRange = doc.selection.getFirstRange();
const targetViewRange = editor.editing.mapper.toViewRange( targetRange );

const eventInfo = new EventInfo( viewDocument, 'clipboardInput' );
viewDocument.fire( eventInfo, { dataTransfer, targetRanges: [ targetViewRange ] } );

expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
expect( eventInfo.stop.called ).to.be.undefined;
} );

it( 'should not insert image when file is null', () => {
const viewDocument = editor.editing.view.document;
const dataTransfer = new DataTransfer( { files: [ null ], types: [ 'Files' ], getData: () => null } );
Expand Down
8 changes: 8 additions & 0 deletions tests/imageupload/imageuploadui.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ describe( 'ImageUploadUI', () => {
expect( button ).to.be.instanceOf( FileDialogButtonView );
} );

it( 'should set proper accepted mime-types for imageUpload button as defined in configuration', () => {
editor.config.set( 'image.upload.types', [ 'svg+xml', 'jpeg', 'vnd.microsoft.icon', 'x-xbitmap' ] );

const button = editor.ui.componentFactory.create( 'imageUpload' );

expect( button.acceptedType ).to.equal( 'image/svg+xml,image/jpeg,image/vnd.microsoft.icon,image/x-xbitmap' );
} );

it( 'should be disabled while ImageUploadCommand is disabled', () => {
const button = editor.ui.componentFactory.create( 'imageUpload' );
const command = editor.commands.get( 'imageUpload' );
Expand Down
33 changes: 18 additions & 15 deletions tests/imageupload/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,32 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import { isImageType } from '../../src/imageupload/utils';
import { createImageTypeRegExp } from '../../src/imageupload/utils';

describe( 'upload utils', () => {
describe( 'isImageType()', () => {
it( 'should return true for png mime type', () => {
expect( isImageType( { type: 'image/png' } ) ).to.be.true;
describe( 'createImageTypeRegExp()', () => {
it( 'should return RegExp for testing regular mime type', () => {
expect( createImageTypeRegExp( [ 'png' ] ).test( 'image/png' ) ).to.be.true;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'foo/png' ) ).to.be.false;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'png' ) ).to.be.false;
} );

it( 'should return true for jpeg mime type', () => {
expect( isImageType( { type: 'image/jpeg' } ) ).to.be.true;
it( 'should return RegExp for testing mime type with dot', () => {
expect( createImageTypeRegExp( [ 'vnd.microsoft.icon' ] ).test( 'image/vnd.microsoft.icon' ) ).to.be.true;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'foo/vnd.microsoft.icon' ) ).to.be.false;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'vnd.microsoft.icon' ) ).to.be.false;
} );

it( 'should return true for gif mime type', () => {
expect( isImageType( { type: 'image/gif' } ) ).to.be.true;
it( 'should return RegExp for testing mime type with dash', () => {
expect( createImageTypeRegExp( [ 'x-xbitmap' ] ).test( 'image/x-xbitmap' ) ).to.be.true;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'foo/x-xbitmap' ) ).to.be.false;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'x-xbitmap' ) ).to.be.false;
} );

it( 'should return true for bmp mime type', () => {
expect( isImageType( { type: 'image/bmp' } ) ).to.be.true;
} );

it( 'should return false for other mime types', () => {
expect( isImageType( { type: 'audio/mp3' } ) ).to.be.false;
expect( isImageType( { type: 'video/mpeg' } ) ).to.be.false;
it( 'should return RegExp for testing mime type with plus', () => {
expect( createImageTypeRegExp( [ 'svg+xml' ] ).test( 'image/svg+xml' ) ).to.be.true;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'foo/svg+xml' ) ).to.be.false;
expect( createImageTypeRegExp( [ 'png' ] ).test( 'svg+xml' ) ).to.be.false;
} );
} );
} );

0 comments on commit 8c36aee

Please sign in to comment.