Upload images functionality update (#101)

Upload images functionality update

Co-authored-by: Nathanaël <njourdane@irap.omp.eu>
Co-authored-by: Jeroen Akkerman <Jeroen_akkerman1@hotmail.com>
pull/104/head
Jeroen Akkerman 5 years ago committed by GitHub
commit ad862a6816
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,7 +4,10 @@ All notable changes to easymde will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!--## [Unreleased]-->
## [Unreleased]
### Added
- Upload images functionality. (Thanks to [@roipoussiere] and [@JeroenvO], [#71], [#101]
## [2.7.0] - 2019-07-13
### Added
- `previewClass` option for overwriting the preview screen class ([#99]).
@ -121,14 +124,17 @@ Project forked from [SimpleMDE](https://github.com/sparksuite/simplemde-markdown
[#9]: https://github.com/Ionaru/easy-markdown-editor/issues/9
<!-- Linked PRs -->
[#101]: https://github.com/Ionaru/easy-markdown-editor/pull/101
[#93]: https://github.com/Ionaru/easy-markdown-editor/pull/93
[#75]: https://github.com/Ionaru/easy-markdown-editor/pull/75
[#71]: https://github.com/Ionaru/easy-markdown-editor/pull/71
[#54]: https://github.com/Ionaru/easy-markdown-editor/pull/54
[#31]: https://github.com/Ionaru/easy-markdown-editor/pull/31
[#27]: https://github.com/Ionaru/easy-markdown-editor/pull/27
[#19]: https://github.com/Ionaru/easy-markdown-editor/pull/19
<!-- Linked users -->
[@JeroenvO]: https://github.com/JeroenvO
[@sn3p]: https://github.com/sn3p
[@roryok]: https://github.com/roryok
[@ysykzheng]: https://github.com/ysykzheng

@ -149,6 +149,27 @@ easyMDE.value('New input for **EasyMDE**');
- **promptTexts**: Customize the text used to prompt for URLs.
- **image**: The text to use when prompting for an image's URL. Defaults to `URL of the image:`.
- **link**: The text to use when prompting for a link's URL. Defaults to `URL for the link:`.
- **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`.
- **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": "<filePath>"}}` where *filePath* is the relative path of the image;
- otherwise: `{"error": "<errorCode>"}`, 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.
- **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.`.
- **sbOnDrop**: Status message displayed when the user drops a file in the text area. Defaults to `Uploading images #images_names#`.
- **sbProgress**: Status message displayed to show uploading progress. Defaults to `Uploading #file_name#: #progress#%`.
- **sbOnUploaded**: Status message displayed when the image has been uploaded. Defaults to `Uploaded #image_name#`.
- **sizeUnits**: A comma-separated list of units used to display messages with human-readable file sizes. Defaults to `b,Kb,Mb`.
- **errorMessages**: Errors displayed to the user, using the `errorCallback` option, where `#image_name#`, `#image_size#` and `#image_max_size#` will replaced by their respective values, that can be used for customization or internationalization:
- **noFileGiven**: The server did not receive any file from the user. Defaults to `You must select a file.`.
- **typeNotAllowed**: The user send a file type which doesn't match the `imageAccept` list, or the server returned this error code. Defaults to `This image type is not allowed.`.
- **fileTooLarge**: The size of the image being imported is bigger than the `imageMaxSize`, or if the server returned this error code. Defaults to `Image #image_name# is too big (#image_size#).\nMaximum file size is #image_max_size#.`.
- **importError**: An unexpected error occurred when uploading the image. Defaults to `Something went wrong when uploading the image #image_name#.`.
- **errorCallback**: A callback function used to define how to display an error message. Defaults to `function(errorMessage) {alert(errorMessage);};`.
- **renderingConfig**: Adjust settings for parsing the Markdown during previewing (not editing).
- **codeSyntaxHighlighting**: If set to `true`, will highlight using [highlight.js](https://github.com/isagalaev/highlight.js). Defaults to `false`. To use this feature you must include highlight.js on your page or pass in using the `hljs` option. For example, include the script and the CSS files like:<br>`<script src="https://cdn.jsdelivr.net/highlight.js/latest/highlight.min.js"></script>`<br>`<link rel="stylesheet" href="https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css">`
- **hljs**: An injectible instance of [highlight.js](https://github.com/isagalaev/highlight.js). If you don't want to rely on the global namespace (`window.hljs`), you can provide an instance here. Defaults to `undefined`.

149
package-lock.json generated

@ -95,9 +95,9 @@
"dev": true
},
"ajv": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz",
"integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
@ -362,23 +362,15 @@
"dev": true
},
"async-done": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.1.tgz",
"integrity": "sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz",
"integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.2",
"process-nextick-args": "^1.0.7",
"process-nextick-args": "^2.0.0",
"stream-exhaust": "^1.0.1"
},
"dependencies": {
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
"dev": true
}
}
},
"async-each": {
@ -1163,12 +1155,13 @@
}
},
"d": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dev": true,
"requires": {
"es5-ext": "^0.10.9"
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"dash-ast": {
@ -1457,14 +1450,14 @@
}
},
"es6-weak-map": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
"integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"dev": true,
"requires": {
"d": "1",
"es5-ext": "^0.10.14",
"es6-iterator": "^2.0.1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
@ -1561,10 +1554,13 @@
}
},
"eslint-utils": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
"dev": true
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz",
"integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
},
"eslint-visitor-keys": {
"version": "1.0.0",
@ -2540,9 +2536,9 @@
"dev": true
},
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -2646,9 +2642,9 @@
}
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
"integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
"dev": true
},
"gulp": {
@ -3476,12 +3472,12 @@
"dev": true
},
"lodash.template": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
"integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
"integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=",
"dev": true,
"requires": {
"lodash._reinterpolate": "^3.0.0",
"lodash._reinterpolate": "~3.0.0",
"lodash.templatesettings": "^4.0.0"
}
},
@ -3655,9 +3651,9 @@
"dev": true
},
"mixin-deep": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
@ -4442,9 +4438,9 @@
"dev": true
},
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
"integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
@ -4556,9 +4552,9 @@
"dev": true
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true
},
"semver-greatest-satisfied-range": {
@ -4577,9 +4573,9 @@
"dev": true
},
"set-value": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
@ -4860,9 +4856,9 @@
}
},
"spdx-license-ids": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"split-string": {
@ -5090,14 +5086,14 @@
}
},
"terser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz",
"integrity": "sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.1.2.tgz",
"integrity": "sha512-jvNoEQSPXJdssFwqPSgWjsOrb+ELoE+ILpHPKXC83tIxOlh2U75F1KuB2luLD/3a6/7K3Vw5pDn+hvu0C4AzSw==",
"dev": true,
"requires": {
"commander": "^2.19.0",
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.10"
"source-map-support": "~0.5.12"
},
"dependencies": {
"source-map": {
@ -5243,6 +5239,12 @@
"integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
"dev": true
},
"type": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz",
"integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==",
"dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@ -5336,38 +5338,15 @@
"dev": true
},
"union-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
"set-value": "^0.4.3"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"set-value": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.1",
"to-object-path": "^0.3.0"
}
}
"set-value": "^2.0.1"
}
},
"unique-stream": {

@ -122,8 +122,8 @@ function createToolbarButton(options, enableTooltips, shortcuts) {
enableTooltips = (enableTooltips == undefined) ? true : enableTooltips;
// Properly hande custom shortcuts
if( options.name && options.name in shortcuts ){
bindings[options.name] = options.action;
if (options.name && options.name in shortcuts) {
bindings[options.name] = options.action;
}
if (options.title && enableTooltips) {
@ -284,7 +284,7 @@ function toggleFullScreen(editor) {
if (/editor-preview-active-side/.test(sidebyside.className))
toggleSideBySide(editor);
if (editor.options.onToggleFullScreen) {
if (editor.options.onToggleFullScreen) {
editor.options.onToggleFullScreen(cm.getOption('fullScreen') || false);
}
}
@ -707,6 +707,33 @@ function drawImage(editor) {
_replaceSelection(cm, stat.image, options.insertTexts.image, url);
}
/**
* Action for opening the browse-file window to upload an image to a server.
* @param editor {EasyMDE} The EasyMDE object
*/
function drawUploadedImage(editor) {
// TODO: Draw the image template with a fake url? ie: '![](importing foo.png...)'
editor.openBrowseFileWindow();
}
/**
* Action executed after an image have been successfully imported on the server.
* @param editor {EasyMDE} The EasyMDE object
* @param url {string} The url of the uploaded image
*/
function afterImageUploaded(editor, url) {
var cm = editor.codemirror;
var stat = getState(cm);
var options = editor.options;
var imageName = url.substr(url.lastIndexOf('/') + 1);
_replaceSelection(cm, stat.image, options.insertTexts.uploadedImage, url);
// show uploaded image filename for 1000ms
editor.updateStatusBar('upload-image', editor.options.imageTexts.sbOnUploaded.replace('#image_name#', imageName));
setTimeout(function () {
editor.updateStatusBar('upload-image', editor.options.imageTexts.sbInit);
}, 1000);
}
/**
* Action for drawing a table.
*/
@ -877,6 +904,7 @@ function _replaceSelection(cm, active, startEnd, url) {
Object.assign(startPoint, cm.getCursor('start'));
Object.assign(endPoint, cm.getCursor('end'));
if (url) {
start = start.replace('#url#', url); // url is in start for upload-image
end = end.replace('#url#', url);
}
if (active) {
@ -1016,7 +1044,7 @@ function _toggleLine(cm, name) {
char = '';
}
text = arr[1] + char + arr[3] + text.replace(whitespacesRegexp, '').replace(repl[name], '$1');
} else if (untoggleOnly == false){
} else if (untoggleOnly == false) {
text = char + ' ' + text;
}
return text;
@ -1141,6 +1169,24 @@ function _cleanBlock(cm) {
}
}
/**
* Convert a number of bytes to a human-readable file size.
* @param bytes {integer} A number of bytes, as integer. Ex: 421137
* @param units {number[]} An array of human-readable units, ie. ['b', 'Kb', 'Mb']
* @returns string A human-readable file size. Ex: '412Kb'
*/
function humanFileSize(bytes, units) {
if (Math.abs(bytes) < 1024) {
return '' + bytes + units[0];
}
var u = 0;
do {
bytes /= 1024;
++u;
} while (Math.abs(bytes) >= 1024 && u < units.length);
return '' + bytes.toFixed(1) + units[u];
}
// Merge the properties of one object into another.
function _mergeProperties(target, source) {
for (var property in source) {
@ -1298,6 +1344,12 @@ var toolbarBuiltInButtons = {
title: 'Insert Image',
default: true,
},
'upload-image': {
name: 'upload-image',
action: drawUploadedImage,
className: 'fa fa-image',
title: 'Import an image',
},
'table': {
name: 'table',
action: drawTable,
@ -1372,6 +1424,8 @@ var toolbarBuiltInButtons = {
var insertTexts = {
link: ['[', '](#url#)'],
image: ['![](', '#url#)'],
uploadedImage: ['![](#url#)', ''],
// uploadedImage: ['![](#url#)\n', ''], // TODO: New line insertion doesn't work here.
table: ['', '\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n'],
horizontalRule: ['', '\n\n-----\n\n'],
};
@ -1387,6 +1441,31 @@ var blockStyles = {
'italic': '*',
};
/**
* Texts displayed to the user (mainly on the status bar) for the import image
* feature. Can be used for customization or internationalization.
*/
var imageTexts = {
sbInit: 'Attach files by drag and dropping or pasting from clipboard.',
sbOnDragEnter: 'Drop image to upload it.',
sbOnDrop: 'Uploading image #images_names#...',
sbProgress: 'Uploading #file_name#: #progress#%',
sbOnUploaded: 'Uploaded #image_name#',
sizeUnits: 'b,Kb,Mb',
};
/**
* Errors displayed to the user, using the `errorCallback` option. Can be used for
* customization or internationalization.
*/
var errorMessages = {
noFileGiven: 'You must select a file.',
typeNotAllowed: 'This image type is not allowed.',
fileTooLarge: 'Image #image_name# is too big (#image_size#).\n' +
'Maximum file size is #image_max_size#.',
importError: 'Something went wrong when uploading the image #image_name#.',
};
/**
* Interface of EasyMDE.
*/
@ -1462,6 +1541,10 @@ function EasyMDE(options) {
// Handle status bar
if (!Object.prototype.hasOwnProperty.call(options, 'status')) {
options.status = ['autosave', 'lines', 'words', 'cursor'];
if (options.uploadImage) {
options.status.unshift('upload-image');
}
}
@ -1497,6 +1580,17 @@ function EasyMDE(options) {
options.minHeight = options.minHeight || '300px';
options.errorCallback = options.errorCallback || function (errorMessage) {
alert(errorMessage);
};
// Import-image default configuration
options.uploadImage = options.uploadImage || false;
options.imageMaxSize = options.imageMaxSize || 2097152; // 1024 * 1024 * 2
options.imageAccept = options.imageAccept || 'image/png, image/jpeg';
options.imageTexts = extend({}, imageTexts, options.imageTexts || {});
options.errorMessages = extend({}, errorMessages, options.errorMessages || {});
// Change unique_id to uniqueId for backwards compatibility
if (options.autosave != undefined && options.autosave.unique_id != undefined && options.autosave.unique_id != '')
@ -1517,8 +1611,80 @@ function EasyMDE(options) {
if (options.initialValue && (!this.options.autosave || this.options.autosave.foundSavedValue !== true)) {
this.value(options.initialValue);
}
if (options.uploadImage) {
var self = this;
this.codemirror.on('dragenter', function (cm, event) {
self.updateStatusBar('upload-image', self.options.imageTexts.sbOnDragEnter);
event.stopPropagation();
event.preventDefault();
});
this.codemirror.on('dragend', function (cm, event) {
self.updateStatusBar('upload-image', self.options.imageTexts.sbInit);
event.stopPropagation();
event.preventDefault();
});
this.codemirror.on('dragleave', function (cm, event) {
self.updateStatusBar('upload-image', self.options.imageTexts.sbInit);
event.stopPropagation();
event.preventDefault();
});
this.codemirror.on('dragover', function (cm, event) {
self.updateStatusBar('upload-image', self.options.imageTexts.sbOnDragEnter);
event.stopPropagation();
event.preventDefault();
});
this.codemirror.on('drop', function (cm, event) {
event.stopPropagation();
event.preventDefault();
self.uploadImages(event.dataTransfer.files);
});
this.codemirror.on('paste', function (cm, event) {
self.uploadImages(event.clipboardData.files);
});
}
}
/**
* 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 {FileList} files The files to upload the the server.
* @param [onSuccess] {function} see EasyMDE.prototype.uploadImage
* @param [onError] {function} see EasyMDE.prototype.uploadImage
*/
EasyMDE.prototype.uploadImages = function (files, onSuccess, onError) {
var names = [];
for (var i = 0; i < files.length; i++) {
names.push(files[i].name);
this.uploadImage(files[i], onSuccess, onError);
}
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.).
* @param content {string} the new content of the item to write in the status bar.
*/
EasyMDE.prototype.updateStatusBar = function (itemName, content) {
var matchingClasses = this.gui.statusbar.getElementsByClassName(itemName);
if (matchingClasses.length === 1) {
this.gui.statusbar.getElementsByClassName(itemName)[0].textContent = content;
} else if (matchingClasses.length === 0) {
console.log('EasyMDE: status bar item ' + itemName + ' was not found.');
} else {
console.log('EasyMDE: Several status bar items named ' + itemName + ' was found.');
}
};
/**
* Default markdown render.
*/
@ -1713,22 +1879,22 @@ EasyMDE.prototype.autosave = function () {
return;
}
if(this.options.autosave.binded !== true) {
if (easyMDE.element.form != null && easyMDE.element.form != undefined) {
easyMDE.element.form.addEventListener('submit', function () {
clearTimeout(easyMDE.autosaveTimeoutId);
easyMDE.autosaveTimeoutId = undefined;
if (this.options.autosave.binded !== true) {
if (easyMDE.element.form != null && easyMDE.element.form != undefined) {
easyMDE.element.form.addEventListener('submit', function () {
clearTimeout(easyMDE.autosaveTimeoutId);
easyMDE.autosaveTimeoutId = undefined;
localStorage.removeItem('smde_' + easyMDE.options.autosave.uniqueId);
localStorage.removeItem('smde_' + easyMDE.options.autosave.uniqueId);
// Restart autosaving in case the submit will be cancelled down the line
setTimeout(function() {
easyMDE.autosave();
}, easyMDE.options.autosave.delay || 10000);
});
}
// Restart autosaving in case the submit will be cancelled down the line
setTimeout(function () {
easyMDE.autosave();
}, easyMDE.options.autosave.delay || 10000);
});
}
this.options.autosave.binded = true;
this.options.autosave.binded = true;
}
if (this.options.autosave.loaded !== true) {
@ -1782,6 +1948,116 @@ EasyMDE.prototype.clearAutosavedValue = function () {
}
};
/**
* Open the browse-file window to upload an image to a server.
* @param [onSuccess] {function} see EasyMDE.prototype.uploadImage
* @param [onError] {function} see EasyMDE.prototype.uploadImage
*/
EasyMDE.prototype.openBrowseFileWindow = function (onSuccess, onError) {
var self = this;
var imageInput = this.gui.toolbar.getElementsByClassName('imageInput')[0];
imageInput.click(); //dispatchEvent(new MouseEvent('click')); // replaced with click() for IE11 compatibility.
function onChange(event) {
self.uploadImages(event.target.files, onSuccess, onError);
imageInput.removeEventListener('change', onChange);
}
imageInput.addEventListener('change', onChange);
};
/**
* Upload an image to the server.
*
* @param file {File} The image to upload, as a HTML5 File object (https://developer.mozilla.org/en-US/docs/Web/API/File)
* @param [onSuccess] {function} A callback function to execute after the image has been successfully uploaded, with one parameter:
* - url (string): The URL of the uploaded image.
* @param [onError] {function} A callback function to execute when the image upload fails, with one parameter:
* - error (string): the detailed error to display to the user (based on messages from options.errorMessages).
*/
EasyMDE.prototype.uploadImage = function (file, onSuccess, onError) {
var self = this;
onSuccess = onSuccess || function onSuccess(imageUrl) {
afterImageUploaded(self, imageUrl);
};
function onErrorSup(errorMessage) {
// show error on status bar and reset after 10000ms
self.updateStatusBar('upload-image', errorMessage);
setTimeout(function () {
self.updateStatusBar('upload-image', self.options.imageTexts.sbInit);
}, 10000);
// run custom error handler
if (onError && typeof onError === 'function') {
onError(errorMessage);
}
// run error handler from options, this alerts the message.
self.options.errorCallback(errorMessage);
}
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));
}
if (file.size > this.options.imageMaxSize) {
onErrorSup(fillErrorMessage(this.options.errorMessages.fileTooLarge));
return;
}
var formData = new FormData();
formData.append('image', file);
// insert CSRF token if provided in config.
if (self.options.imageCSRFToken) {
formData.append('csrfmiddlewaretoken', self.options.imageCSRFToken);
}
var request = new XMLHttpRequest();
request.upload.onprogress = function (event) {
if (event.lengthComputable) {
var progress = '' + Math.round((event.loaded * 100) / event.total);
self.updateStatusBar('upload-image', self.options.imageTexts.sbProgress.replace('#file_name#', file.name).replace('#progress#', progress));
}
};
request.open('POST', this.options.imageUploadEndpoint);
request.onload = function () {
try {
var response = JSON.parse(this.responseText);
} catch (error) {
console.error('EasyMDE: The server did not return a valid json.');
onErrorSup(fillErrorMessage(self.options.errorMessages.importError));
return;
}
if (this.status === 200 && response && !response.error && response.data && response.data.filePath) {
onSuccess(window.location.origin + '/' + response.data.filePath);
} else {
if (response.error && response.error in self.options.errorMessages) { // preformatted error message
onErrorSup(fillErrorMessage(self.options.errorMessages[response.error]));
} else if (response.error) { // server side generated error message
onErrorSup(fillErrorMessage(response.error));
} else { //unknown error
console.error('EasyMDE: Received an unexpected response after uploading the image.'
+ this.status + ' (' + this.statusText + ')');
onErrorSup(fillErrorMessage(self.options.errorMessages.importError));
}
}
};
request.onerror = function (event) {
console.error('EasyMDE: An unexpected error occurred when trying to upload the image.'
+ event.target.status + ' (' + event.target.statusText + ')');
onErrorSup(self.options.errorMessages.importError);
};
request.send(formData);
};
EasyMDE.prototype.createSideBySide = function () {
var cm = this.codemirror;
var wrapper = cm.getWrapperElement();
@ -1912,6 +2188,20 @@ EasyMDE.prototype.createToolbar = function (items) {
toolbarData[item.name || item] = el;
bar.appendChild(el);
// Create the input element (ie. <input type='file'>), used among
// with the 'import-image' icon to open the browse-file window.
if (item.name === 'upload-image') {
var imageInput = document.createElement('input');
imageInput.className = 'imageInput';
imageInput.type = 'file';
imageInput.multiple = true;
imageInput.name = 'image';
imageInput.accept = self.options.imageAccept;
imageInput.style.display = 'none';
imageInput.style.opacity = 0;
bar.appendChild(imageInput);
}
})(items[i]);
}
@ -1944,11 +2234,10 @@ EasyMDE.prototype.createStatusbar = function (status) {
var options = this.options;
var cm = this.codemirror;
// Make sure the status variable is valid
if (!status || status.length === 0)
if (!status || status.length === 0) {
return;
}
// Set up the built-in items
var items = [];
@ -1998,6 +2287,10 @@ EasyMDE.prototype.createStatusbar = function (status) {
el.setAttribute('id', 'autosaved');
}
};
} else if (name === 'upload-image') {
defaultValue = function (el) {
el.innerHTML = options.imageTexts.sbInit;
};
}
items.push({
@ -2176,7 +2469,6 @@ EasyMDE.prototype.isPreviewActive = function () {
return /editor-preview-active/.test(preview.className);
};
EasyMDE.prototype.isSideBySideActive = function () {
var cm = this.codemirror;
var wrapper = cm.getWrapperElement();

@ -59,3 +59,28 @@ const editor2 = new EasyMDE({
});
editor2.clearAutosavedValue();
const editorImages = new EasyMDE({
uploadImage: true,
imageAccept: 'image/png, image/bmp',
imageCSRFToken: undefined,
imageMaxSize: 10485760,
imageUploadEndpoint: 'https://my.domain/image-upload/',
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);
},
});

31
types/easymde.d.ts vendored

@ -1,16 +1,16 @@
// This file is based on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/simplemde/index.d.ts,
// which is written by Scalesoft <https://github.com/Scalesoft> and licensed under the MIT license:
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -86,6 +86,22 @@ declare namespace EasyMDE {
noMobile?: boolean;
}
interface ImageTextsOptions {
sbInit?: string;
sbOnDragEnter?: string;
sbOnDrop?: string;
sbProgress?: string;
sbOnUploaded?: string;
sizeUnits?: string;
}
interface ImageErrorTextsOptions {
noFileGiven?: string;
typeNotAllowed?: string;
fileTooLarge?: string;
importError?: string;
}
interface Options {
autoDownloadFontAwesome?: boolean;
autofocus?: boolean;
@ -114,6 +130,15 @@ declare namespace EasyMDE {
toolbarTips?: boolean;
onToggleFullScreen?: (goingIntoFullScreen: boolean) => void;
theme?: string;
uploadImage?: boolean;
imageMaxSize?: number;
imageAccept?: string;
imageUploadEndpoint?: string;
imageCSRFToken?: string;
imageTexts?: ImageTextsOptions;
errorMessages?: ImageErrorTextsOptions;
errorCallback?: (errorMessage: string) => void;
}
}

Loading…
Cancel
Save