diff --git a/README.md b/README.md index 3a7249d..00e3be3 100644 --- a/README.md +++ b/README.md @@ -152,11 +152,13 @@ easyMDE.value('New input for **EasyMDE**'); - **uploadImage**: If set to `true`, enables the image upload functionality, which can be triggered by drag&drop, copy-paste and through the browse-file window (opened when the user click on the *upload-image* icon). Defaults to `false`. - **imageMaxSize**: Maximum image size in bytes, checked before upload (note: never trust client, always check image size at server-side). Defaults to `1024*1024*2` (2Mb). - **imageAccept**: A comma-separated list of mime-types used to check image type before upload (note: never trust client, always check file types at server-side). Defaults to `image/png, image/jpeg`. +- **imageUploadFunction**: A custom function for handling the image upload. Using this function will render the options `imageMaxSize`, `imageAccept`, `imageUploadEndpoint` and `imageCSRFToken` ineffective. + - The function gets a file and onSuccess and onError callback functions as parameters. `onSuccess(imageUrl: string)` and `onError(errorMessage: string)` - **imageUploadEndpoint**: The endpoint where the images data will be sent, via an asynchronous *POST* request. The server is supposed to save this image, and return a json response. - if the request was successfully processed (HTTP 200-OK): `{"data": {"filePath": ""}}` where *filePath* is the relative path of the image; - otherwise: `{"error": ""}`, where *errorCode* can be `noFileGiven` (HTTP 400), `typeNotAllowed` (HTTP 415), `fileTooLarge` (HTTP 413) or `importError` (see *errorMessages* below). If *errorCode* is not one of the *errorMessages*, it is alerted unchanged to the user. This allows for server side error messages. No default value. -- **imageCSRFToken**: CSRF token to include with AJAX call to upload image. For instance used with Django backend. +- **imageCSRFToken**: CSRF token to include with AJAX call to upload image. For instance used with Django backend. - **imageTexts**: Texts displayed to the user (mainly on the status bar) for the import image feature, where `#image_name#`, `#image_size#` and `#image_max_size#` will replaced by their respective values, that can be used for customization or internationalization: - **sbInit**: Status message displayed initially if `uploadImage` is set to `true`. Defaults to `Attach files by drag and dropping or pasting from clipboard.`. - **sbOnDragEnter**: Status message displayed when the user drags a file to the text area. Defaults to `Drop image to upload it.`. diff --git a/src/js/easymde.js b/src/js/easymde.js index 8beb664..5e2376c 100644 --- a/src/js/easymde.js +++ b/src/js/easymde.js @@ -1640,11 +1640,19 @@ function EasyMDE(options) { this.codemirror.on('drop', function (cm, event) { event.stopPropagation(); event.preventDefault(); - self.uploadImages(event.dataTransfer.files); + if (options.imageUploadFunction) { + self.uploadImagesUsingCustomFunction(options.imageUploadFunction, event.dataTransfer.files); + } else { + self.uploadImages(event.dataTransfer.files); + } }); this.codemirror.on('paste', function (cm, event) { - self.uploadImages(event.clipboardData.files); + if (options.imageUploadFunction) { + self.uploadImagesUsingCustomFunction(options.imageUploadFunction, event.clipboardData.files); + } else { + self.uploadImages(event.clipboardData.files); + } }); } } @@ -1669,6 +1677,25 @@ EasyMDE.prototype.uploadImages = function (files, onSuccess, onError) { this.updateStatusBar('upload-image', this.options.imageTexts.sbOnDrop.replace('#images_names#', names.join(', '))); }; +/** + * Upload asynchronously a list of images to a server. + * + * Can be triggered by: + * - drag&drop; + * - copy-paste; + * - the browse-file window (opened when the user clicks on the *upload-image* icon). + * @param imageUploadFunction {Function} The custom function to upload the image passed in options. + * @param {FileList} files The files to upload the the server. + */ +EasyMDE.prototype.uploadImagesUsingCustomFunction = function (imageUploadFunction, files) { + var names = []; + for (var i = 0; i < files.length; i++) { + names.push(files[i].name); + this.uploadImageUsingCustomFunction(imageUploadFunction, files[i]); + } + this.updateStatusBar('upload-image', this.options.imageTexts.sbOnDrop.replace('#images_names#', names.join(', '))); +}; + /** * Update an item in the status bar. * @param itemName {string} The name of the item to update (ie. 'upload-image', 'autosave', etc.). @@ -2058,6 +2085,42 @@ EasyMDE.prototype.uploadImage = function (file, onSuccess, onError) { }; +/** + * Upload an image to the server using a custom upload function. + * + * @param imageUploadFunction {Function} The custom function to upload the image passed in options + * @param file {File} The image to upload, as a HTML5 File object (https://developer.mozilla.org/en-US/docs/Web/API/File). + */ +EasyMDE.prototype.uploadImageUsingCustomFunction = function(imageUploadFunction, file) { + var self = this; + function onSuccess(imageUrl) { + afterImageUploaded(self, imageUrl); + } + + function onError(errorMessage) { + var filledErrorMessage = fillErrorMessage(errorMessage); + // show error on status bar and reset after 10000ms + self.updateStatusBar('upload-image', filledErrorMessage); + + setTimeout(function () { + self.updateStatusBar('upload-image', self.options.imageTexts.sbInit); + }, 10000); + + // run error handler from options, this alerts the message. + self.options.errorCallback(filledErrorMessage); + } + + function fillErrorMessage(errorMessage) { + var units = self.options.imageTexts.sizeUnits.split(','); + return errorMessage + .replace('#image_name#', file.name) + .replace('#image_size#', humanFileSize(file.size, units)) + .replace('#image_max_size#', humanFileSize(self.options.imageMaxSize, units)); + } + + imageUploadFunction(file, onSuccess, onError); +}; + EasyMDE.prototype.createSideBySide = function () { var cm = this.codemirror; var wrapper = cm.getWrapperElement(); diff --git a/types/easymde-test.ts b/types/easymde-test.ts index 12b0555..103569b 100644 --- a/types/easymde-test.ts +++ b/types/easymde-test.ts @@ -1,4 +1,6 @@ // Create new instance +import EasyMDE = require('./easymde'); + const editor = new EasyMDE({ autoDownloadFontAwesome: false, element: document.getElementById('mdEditor')!, @@ -40,7 +42,7 @@ const editor2 = new EasyMDE({ title: 'Bold', }, '|', { // Separator name: 'alert', - action: (editor) => { + action: (editor: EasyMDE) => { alert('This is from a custom button action!'); // Custom functions have access to the `editor` instance. }, @@ -84,3 +86,30 @@ const editorImages = new EasyMDE({ console.error(errorMessage); }, }); + +const editorImagesCustom = new EasyMDE({ + uploadImage: true, + imageAccept: 'image/png, image/bmp', + imageCSRFToken: undefined, + imageMaxSize: 10485760, + imageUploadFunction: (file: File, onSuccess: Function, onError: Function) => { + console.log(file) + }, + imageTexts: { + sbInit: 'Drag & drop images!', + sbOnDragEnter: 'Let it go, let it go', + sbOnDrop: 'Uploading...', + sbProgress: 'Uploading... (#progress#)', + sbOnUploaded: 'Upload complete!', + sizeUnits: 'b,Kb,Mb' + }, + errorMessages: { + noFileGiven: 'Please select a file', + typeNotAllowed: 'This file type is not allowed!', + fileTooLarge: 'Image too big', + importError: 'Something went oops!', + }, + errorCallback: (errorMessage) => { + console.error(errorMessage); + }, + }); diff --git a/types/easymde.d.ts b/types/easymde.d.ts index 2a7db40..1fa121c 100644 --- a/types/easymde.d.ts +++ b/types/easymde.d.ts @@ -134,6 +134,7 @@ declare namespace EasyMDE { uploadImage?: boolean; imageMaxSize?: number; imageAccept?: string; + imageUploadFunction?: (file: File, onSuccess: Function, onError: Function) => void; imageUploadEndpoint?: string; imageCSRFToken?: string; imageTexts?: ImageTextsOptions;