diff --git a/README.md b/README.md index 2e28b57..3655348 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Inline Attachment 2.0.1 [![Master Branch Build Status](https://api.travis-ci.org/Rovak/InlineAttachment.png?branch=master)](http://travis-ci.org/Rovak/InlineAttachment) ![project status](http://stillmaintained.com/Rovak/InlineAttachment.png) +# Inline Attachment 2.0.2 [![Master Branch Build Status](https://api.travis-ci.org/Rovak/InlineAttachment.png?branch=master)](http://travis-ci.org/Rovak/InlineAttachment) ![project status](http://stillmaintained.com/Rovak/InlineAttachment.png) [![Master Branch Build Status](https://api.travis-ci.org/Rovak/InlineAttachment.png?branch=master)](http://travis-ci.org/Rovak/InlineAttachment) ![project status](http://stillmaintained.com/Rovak/InlineAttachment.png) diff --git a/dist/angularjs.inline-attachment.js b/dist/angularjs.inline-attachment.js new file mode 100644 index 0000000..9fe8a8c --- /dev/null +++ b/dist/angularjs.inline-attachment.js @@ -0,0 +1,497 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +/*jslint newcap: true */ +/*global XMLHttpRequest: false, FormData: false */ +/* + * Inline Text Attachment + * + * Author: Roy van Kaathoven + * Contact: ik@royvankaathoven.nl + */ +(function(document, window) { + 'use strict'; + + var inlineAttachment = function(options, instance) { + this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults); + this.editor = instance; + this.filenameTag = '{filename}'; + this.lastValue = null; + }; + + /** + * Will holds the available editors + * + * @type {Object} + */ + inlineAttachment.editors = {}; + + /** + * Utility functions + */ + inlineAttachment.util = { + + /** + * Simple function to merge the given objects + * + * @param {Object[]} object Multiple object parameters + * @returns {Object} + */ + merge: function() { + var result = {}; + for (var i = arguments.length - 1; i >= 0; i--) { + var obj = arguments[i]; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + result[k] = obj[k]; + } + } + } + return result; + }, + + /** + * Append a line of text at the bottom, ensuring there aren't unnecessary newlines + * + * @param {String} appended Current content + * @param {String} previous Value which should be appended after the current content + */ + appendInItsOwnLine: function(previous, appended) { + return (previous + "\n\n[[D]]" + appended) + .replace(/(\n{2,})\[\[D\]\]/, "\n\n") + .replace(/^(\n*)/, ""); + }, + + /** + * Inserts the given value at the current cursor position of the textarea element + * + * @param {HtmlElement} el + * @param {String} value Text which will be inserted at the cursor position + */ + insertTextAtCursor: function(el, text) { + var scrollPos = el.scrollTop, + strPos = 0, + browser = false, + range; + + if ((el.selectionStart || el.selectionStart === '0')) { + browser = "ff"; + } else if (document.selection) { + browser = "ie"; + } + + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + strPos = range.text.length; + } else if (browser === "ff") { + strPos = el.selectionStart; + } + + var front = (el.value).substring(0, strPos); + var back = (el.value).substring(strPos, el.value.length); + el.value = front + text + back; + strPos = strPos + text.length; + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + range.moveStart('character', strPos); + range.moveEnd('character', 0); + range.select(); + } else if (browser === "ff") { + el.selectionStart = strPos; + el.selectionEnd = strPos; + el.focus(); + } + el.scrollTop = scrollPos; + } + }; + + /** + * Default configuration options + * + * @type {Object} + */ + inlineAttachment.defaults = { + /** + * URL where the file will be send + */ + uploadUrl: 'upload_attachment.php', + + /** + * Which method will be used to send the file to the upload URL + */ + uploadMethod: 'POST', + + /** + * Name in which the file will be placed + */ + uploadFieldName: 'file', + + /** + * Extension which will be used when a file extension could not + * be detected + */ + defaultExtension: 'png', + + /** + * JSON field which refers to the uploaded file URL + */ + jsonFieldName: 'filename', + + /** + * Allowed MIME types + */ + allowedTypes: [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ], + + /** + * Text which will be inserted when dropping or pasting a file. + * Acts as a placeholder which will be replaced when the file is done with uploading + */ + progressText: '![Uploading file...]()', + + /** + * When a file has successfully been uploaded the progressText + * will be replaced by the urlText, the {filename} tag will be replaced + * by the filename that has been returned by the server + */ + urlText: "![file]({filename})", + + /** + * Text which will be used when uploading has failed + */ + errorText: "Error uploading file", + + /** + * Extra parameters which will be send when uploading a file + */ + extraParams: {}, + + /** + * Extra headers which will be send when uploading a file + */ + extraHeaders: {}, + + /** + * Before the file is send + */ + beforeFileUpload: function() { + return true; + }, + + /** + * Triggers when a file is dropped or pasted + */ + onFileReceived: function() {}, + + /** + * Custom upload handler + * + * @return {Boolean} when false is returned it will prevent default upload behavior + */ + onFileUploadResponse: function() { + return true; + }, + + /** + * Custom error handler. Runs after removing the placeholder text and before the alert(). + * Return false from this function to prevent the alert dialog. + * + * @return {Boolean} when false is returned it will prevent default error behavior + */ + onFileUploadError: function() { + return true; + }, + + /** + * When a file has succesfully been uploaded + */ + onFileUploaded: function() {} + }; + + /** + * Uploads the blob + * + * @param {Blob} file blob data received from event.dataTransfer object + * @return {XMLHttpRequest} request object which sends the file + */ + inlineAttachment.prototype.uploadFile = function(file) { + var me = this, + formData = new FormData(), + xhr = new XMLHttpRequest(), + settings = this.settings, + extension = settings.defaultExtension || settings.defualtExtension; + + if (typeof settings.setupFormData === 'function') { + settings.setupFormData(formData, file); + } + + // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now) + // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name + if (file.name) { + var fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches) { + extension = fileNameMatches[1]; + } + } + + var remoteFilename = "image-" + Date.now() + "." + extension; + if (typeof settings.remoteFilename === 'function') { + remoteFilename = settings.remoteFilename(file); + } + + formData.append(settings.uploadFieldName, file, remoteFilename); + + // Append the extra parameters to the formdata + if (typeof settings.extraParams === "object") { + for (var key in settings.extraParams) { + if (settings.extraParams.hasOwnProperty(key)) { + formData.append(key, settings.extraParams[key]); + } + } + } + + xhr.open('POST', settings.uploadUrl); + + // Add any available extra headers + if (typeof settings.extraHeaders === "object") { + for (var header in settings.extraHeaders) { + if (settings.extraHeaders.hasOwnProperty(header)) { + xhr.setRequestHeader(header, settings.extraHeaders[header]); + } + } + } + + xhr.onload = function() { + // If HTTP status is OK or Created + if (xhr.status === 200 || xhr.status === 201) { + me.onFileUploadResponse(xhr); + } else { + me.onFileUploadError(xhr); + } + }; + if (settings.beforeFileUpload(xhr) !== false) { + xhr.send(formData); + } + return xhr; + }; + + /** + * Returns if the given file is allowed to handle + * + * @param {File} clipboard data file + */ + inlineAttachment.prototype.isFileAllowed = function(file) { + if (this.settings.allowedTypes.indexOf('*') === 0){ + return true; + } else { + return this.settings.allowedTypes.indexOf(file.type) >= 0; + } + }; + + /** + * Handles upload response + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadResponse = function(xhr) { + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { + var result = JSON.parse(xhr.responseText), + filename = result[this.settings.jsonFieldName]; + + if (result && filename) { + var newValue = this.settings.urlText.replace(this.filenameTag, filename); + var text = this.editor.getValue().replace(this.lastValue, newValue); + this.editor.setValue(text); + this.settings.onFileUploaded.call(this, filename); + } + } + }; + + + /** + * Called when a file has failed to upload + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadError = function(xhr) { + if (this.settings.onFileUploadError.call(this, xhr) !== false) { + var text = this.editor.getValue().replace(this.lastValue, ""); + this.editor.setValue(text); + } + }; + + /** + * Called when a file has been inserted, either by drop or paste + * + * @param {File} file + * @return {Void} + */ + inlineAttachment.prototype.onFileInserted = function(file) { + if (this.settings.onFileReceived.call(this, file) !== false) { + this.lastValue = this.settings.progressText; + this.editor.insertValue(this.lastValue); + } + }; + + + /** + * Called when a paste event occured + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onPaste = function(e) { + var result = false, + clipboardData = e.clipboardData, + items; + + if (typeof clipboardData === "object") { + items = clipboardData.items || clipboardData.files || []; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (this.isFileAllowed(item)) { + result = true; + this.onFileInserted(item.getAsFile()); + this.uploadFile(item.getAsFile()); + } + } + } + + if (result) { e.preventDefault(); } + + return result; + }; + + /** + * Called when a drop event occures + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onDrop = function(e) { + var result = false; + for (var i = 0; i < e.dataTransfer.files.length; i++) { + var file = e.dataTransfer.files[i]; + if (this.isFileAllowed(file)) { + result = true; + this.onFileInserted(file); + this.uploadFile(file); + } + } + + return result; + }; + + window.inlineAttachment = inlineAttachment; + +})(document, window); + +/*jslint newcap: true */ +/*global inlineAttachment: false */ +(function() { + 'use strict'; + + inlineAttachment.editors.input = { + Editor: function(instance) { + + var input = instance; + + return { + getValue: function() { + return input.value; + }, + insertValue: function(val) { + inlineAttachment.util.insertTextAtCursor(input, val); + }, + setValue: function(val) { + input.value = val; + } + }; + }, + attachToInput: function(input, options) { + options = options || {}; + + var editor = new inlineAttachment.editors.input.Editor(input), + inlineattach = new inlineAttachment(options, editor); + + input.addEventListener('paste', function(e) { + inlineattach.onPaste(e); + }, false); + input.addEventListener('drop', function(e) { + e.stopPropagation(); + e.preventDefault(); + inlineattach.onDrop(e); + }, false); + input.addEventListener('dragenter', function(e) { + e.stopPropagation(); + e.preventDefault(); + }, false); + input.addEventListener('dragover', function(e) { + e.stopPropagation(); + e.preventDefault(); + }, false); + } + }; + +})(); +/*jslint newcap: true */ +/*global inlineAttachment: false, angular: false */ +/** + * AngularJS plugin for inline attachment + * + * @param {document} document + * @param {window} window + * @param {Object} ng + */ +(function(document, window, ng) { + 'use strict'; + + inlineAttachment.editors.angular = {}; + + var module = ng.module('inlineattachment', []), + attrName = 'inlineattachment'; + + function lcfirst(str) { + return str.charAt(0).toLowerCase() + str.substr(1); + } + + /** + * Read all parameters from the given attributes object + * + * @param {Object} obj attributes + * @return {Object} + */ + function readParameters(obj, scope) { + var result = {}, + attrs = obj.$attr, + option, value; + + for (var key in attrs) { + option = lcfirst(key.substr(attrName.length)); + value = obj[key]; + // Check if the given key is a valid string type, not empty and starts with the attribute name + if ((option.length > 0) && (key.substring(0, attrName.length) === attrName)) { + result[option] = value; + if (typeof scope[value] === 'function') { + result[option] = scope[value]; + } + } + } + + return result; + } + + module.directive(attrName, function() { + return function(scope, element, attrs) { + var options = readParameters(attrs, scope); + inlineAttachment.editors.input.attachToInput(element.context, options); + }; + }); +})(document, window, angular); diff --git a/dist/angularjs.inline-attachment.min.js b/dist/angularjs.inline-attachment.min.js new file mode 100644 index 0000000..b61cd42 --- /dev/null +++ b/dist/angularjs.inline-attachment.min.js @@ -0,0 +1,2 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +!function(a,b){"use strict";var c=function(a,b){this.settings=c.util.merge(a,c.defaults),this.editor=b,this.filenameTag="{filename}",this.lastValue=null};c.editors={},c.util={merge:function(){for(var a={},b=arguments.length-1;b>=0;b--){var c=arguments[b];for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},appendInItsOwnLine:function(a,b){return(a+"\n\n[[D]]"+b).replace(/(\n{2,})\[\[D\]\]/,"\n\n").replace(/^(\n*)/,"")},insertTextAtCursor:function(b,c){var d,e=b.scrollTop,f=0,g=!1;b.selectionStart||"0"===b.selectionStart?g="ff":a.selection&&(g="ie"),"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),f=d.text.length):"ff"===g&&(f=b.selectionStart);var h=b.value.substring(0,f),i=b.value.substring(f,b.value.length);b.value=h+c+i,f+=c.length,"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),d.moveStart("character",f),d.moveEnd("character",0),d.select()):"ff"===g&&(b.selectionStart=f,b.selectionEnd=f,b.focus()),b.scrollTop=e}},c.defaults={uploadUrl:"upload_attachment.php",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",jsonFieldName:"filename",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![file]({filename})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload:function(){return!0},onFileReceived:function(){},onFileUploadResponse:function(){return!0},onFileUploadError:function(){return!0},onFileUploaded:function(){}},c.prototype.uploadFile=function(a){var b=this,c=new FormData,d=new XMLHttpRequest,e=this.settings,f=e.defaultExtension||e.defualtExtension;if("function"==typeof e.setupFormData&&e.setupFormData(c,a),a.name){var g=a.name.match(/\.(.+)$/);g&&(f=g[1])}var h="image-"+Date.now()+"."+f;if("function"==typeof e.remoteFilename&&(h=e.remoteFilename(a)),c.append(e.uploadFieldName,a,h),"object"==typeof e.extraParams)for(var i in e.extraParams)e.extraParams.hasOwnProperty(i)&&c.append(i,e.extraParams[i]);if(d.open("POST",e.uploadUrl),"object"==typeof e.extraHeaders)for(var j in e.extraHeaders)e.extraHeaders.hasOwnProperty(j)&&d.setRequestHeader(j,e.extraHeaders[j]);return d.onload=function(){200===d.status||201===d.status?b.onFileUploadResponse(d):b.onFileUploadError(d)},e.beforeFileUpload(d)!==!1&&d.send(c),d},c.prototype.isFileAllowed=function(a){return 0===this.settings.allowedTypes.indexOf("*")?!0:this.settings.allowedTypes.indexOf(a.type)>=0},c.prototype.onFileUploadResponse=function(a){if(this.settings.onFileUploadResponse.call(this,a)!==!1){var b=JSON.parse(a.responseText),c=b[this.settings.jsonFieldName];if(b&&c){var d=this.settings.urlText.replace(this.filenameTag,c),e=this.editor.getValue().replace(this.lastValue,d);this.editor.setValue(e),this.settings.onFileUploaded.call(this,c)}}},c.prototype.onFileUploadError=function(a){if(this.settings.onFileUploadError.call(this,a)!==!1){var b=this.editor.getValue().replace(this.lastValue,"");this.editor.setValue(b)}},c.prototype.onFileInserted=function(a){this.settings.onFileReceived.call(this,a)!==!1&&(this.lastValue=this.settings.progressText,this.editor.insertValue(this.lastValue))},c.prototype.onPaste=function(a){var b,c=!1,d=a.clipboardData;if("object"==typeof d){b=d.items||d.files||[];for(var e=0;e0&&i.substring(0,g.length)===g&&(f[c]=e,"function"==typeof b[e]&&(f[c]=b[e]));return f}inlineAttachment.editors.angular={};var f=c.module("inlineattachment",[]),g="inlineattachment";f.directive(g,function(){return function(a,b,c){var d=e(c,a);inlineAttachment.editors.input.attachToInput(b.context,d)}})}(document,window,angular); \ No newline at end of file diff --git a/dist/codemirror-3.inline-attachment.js b/dist/codemirror-3.inline-attachment.js new file mode 100644 index 0000000..2820eb5 --- /dev/null +++ b/dist/codemirror-3.inline-attachment.js @@ -0,0 +1,394 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +/*jslint newcap: true */ +/*global XMLHttpRequest: false, FormData: false */ +/* + * Inline Text Attachment + * + * Author: Roy van Kaathoven + * Contact: ik@royvankaathoven.nl + */ +(function(document, window) { + 'use strict'; + + var inlineAttachment = function(options, instance) { + this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults); + this.editor = instance; + this.filenameTag = '{filename}'; + this.lastValue = null; + }; + + /** + * Will holds the available editors + * + * @type {Object} + */ + inlineAttachment.editors = {}; + + /** + * Utility functions + */ + inlineAttachment.util = { + + /** + * Simple function to merge the given objects + * + * @param {Object[]} object Multiple object parameters + * @returns {Object} + */ + merge: function() { + var result = {}; + for (var i = arguments.length - 1; i >= 0; i--) { + var obj = arguments[i]; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + result[k] = obj[k]; + } + } + } + return result; + }, + + /** + * Append a line of text at the bottom, ensuring there aren't unnecessary newlines + * + * @param {String} appended Current content + * @param {String} previous Value which should be appended after the current content + */ + appendInItsOwnLine: function(previous, appended) { + return (previous + "\n\n[[D]]" + appended) + .replace(/(\n{2,})\[\[D\]\]/, "\n\n") + .replace(/^(\n*)/, ""); + }, + + /** + * Inserts the given value at the current cursor position of the textarea element + * + * @param {HtmlElement} el + * @param {String} value Text which will be inserted at the cursor position + */ + insertTextAtCursor: function(el, text) { + var scrollPos = el.scrollTop, + strPos = 0, + browser = false, + range; + + if ((el.selectionStart || el.selectionStart === '0')) { + browser = "ff"; + } else if (document.selection) { + browser = "ie"; + } + + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + strPos = range.text.length; + } else if (browser === "ff") { + strPos = el.selectionStart; + } + + var front = (el.value).substring(0, strPos); + var back = (el.value).substring(strPos, el.value.length); + el.value = front + text + back; + strPos = strPos + text.length; + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + range.moveStart('character', strPos); + range.moveEnd('character', 0); + range.select(); + } else if (browser === "ff") { + el.selectionStart = strPos; + el.selectionEnd = strPos; + el.focus(); + } + el.scrollTop = scrollPos; + } + }; + + /** + * Default configuration options + * + * @type {Object} + */ + inlineAttachment.defaults = { + /** + * URL where the file will be send + */ + uploadUrl: 'upload_attachment.php', + + /** + * Which method will be used to send the file to the upload URL + */ + uploadMethod: 'POST', + + /** + * Name in which the file will be placed + */ + uploadFieldName: 'file', + + /** + * Extension which will be used when a file extension could not + * be detected + */ + defaultExtension: 'png', + + /** + * JSON field which refers to the uploaded file URL + */ + jsonFieldName: 'filename', + + /** + * Allowed MIME types + */ + allowedTypes: [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ], + + /** + * Text which will be inserted when dropping or pasting a file. + * Acts as a placeholder which will be replaced when the file is done with uploading + */ + progressText: '![Uploading file...]()', + + /** + * When a file has successfully been uploaded the progressText + * will be replaced by the urlText, the {filename} tag will be replaced + * by the filename that has been returned by the server + */ + urlText: "![file]({filename})", + + /** + * Text which will be used when uploading has failed + */ + errorText: "Error uploading file", + + /** + * Extra parameters which will be send when uploading a file + */ + extraParams: {}, + + /** + * Extra headers which will be send when uploading a file + */ + extraHeaders: {}, + + /** + * Before the file is send + */ + beforeFileUpload: function() { + return true; + }, + + /** + * Triggers when a file is dropped or pasted + */ + onFileReceived: function() {}, + + /** + * Custom upload handler + * + * @return {Boolean} when false is returned it will prevent default upload behavior + */ + onFileUploadResponse: function() { + return true; + }, + + /** + * Custom error handler. Runs after removing the placeholder text and before the alert(). + * Return false from this function to prevent the alert dialog. + * + * @return {Boolean} when false is returned it will prevent default error behavior + */ + onFileUploadError: function() { + return true; + }, + + /** + * When a file has succesfully been uploaded + */ + onFileUploaded: function() {} + }; + + /** + * Uploads the blob + * + * @param {Blob} file blob data received from event.dataTransfer object + * @return {XMLHttpRequest} request object which sends the file + */ + inlineAttachment.prototype.uploadFile = function(file) { + var me = this, + formData = new FormData(), + xhr = new XMLHttpRequest(), + settings = this.settings, + extension = settings.defaultExtension || settings.defualtExtension; + + if (typeof settings.setupFormData === 'function') { + settings.setupFormData(formData, file); + } + + // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now) + // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name + if (file.name) { + var fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches) { + extension = fileNameMatches[1]; + } + } + + var remoteFilename = "image-" + Date.now() + "." + extension; + if (typeof settings.remoteFilename === 'function') { + remoteFilename = settings.remoteFilename(file); + } + + formData.append(settings.uploadFieldName, file, remoteFilename); + + // Append the extra parameters to the formdata + if (typeof settings.extraParams === "object") { + for (var key in settings.extraParams) { + if (settings.extraParams.hasOwnProperty(key)) { + formData.append(key, settings.extraParams[key]); + } + } + } + + xhr.open('POST', settings.uploadUrl); + + // Add any available extra headers + if (typeof settings.extraHeaders === "object") { + for (var header in settings.extraHeaders) { + if (settings.extraHeaders.hasOwnProperty(header)) { + xhr.setRequestHeader(header, settings.extraHeaders[header]); + } + } + } + + xhr.onload = function() { + // If HTTP status is OK or Created + if (xhr.status === 200 || xhr.status === 201) { + me.onFileUploadResponse(xhr); + } else { + me.onFileUploadError(xhr); + } + }; + if (settings.beforeFileUpload(xhr) !== false) { + xhr.send(formData); + } + return xhr; + }; + + /** + * Returns if the given file is allowed to handle + * + * @param {File} clipboard data file + */ + inlineAttachment.prototype.isFileAllowed = function(file) { + if (this.settings.allowedTypes.indexOf('*') === 0){ + return true; + } else { + return this.settings.allowedTypes.indexOf(file.type) >= 0; + } + }; + + /** + * Handles upload response + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadResponse = function(xhr) { + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { + var result = JSON.parse(xhr.responseText), + filename = result[this.settings.jsonFieldName]; + + if (result && filename) { + var newValue = this.settings.urlText.replace(this.filenameTag, filename); + var text = this.editor.getValue().replace(this.lastValue, newValue); + this.editor.setValue(text); + this.settings.onFileUploaded.call(this, filename); + } + } + }; + + + /** + * Called when a file has failed to upload + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadError = function(xhr) { + if (this.settings.onFileUploadError.call(this, xhr) !== false) { + var text = this.editor.getValue().replace(this.lastValue, ""); + this.editor.setValue(text); + } + }; + + /** + * Called when a file has been inserted, either by drop or paste + * + * @param {File} file + * @return {Void} + */ + inlineAttachment.prototype.onFileInserted = function(file) { + if (this.settings.onFileReceived.call(this, file) !== false) { + this.lastValue = this.settings.progressText; + this.editor.insertValue(this.lastValue); + } + }; + + + /** + * Called when a paste event occured + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onPaste = function(e) { + var result = false, + clipboardData = e.clipboardData, + items; + + if (typeof clipboardData === "object") { + items = clipboardData.items || clipboardData.files || []; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (this.isFileAllowed(item)) { + result = true; + this.onFileInserted(item.getAsFile()); + this.uploadFile(item.getAsFile()); + } + } + } + + if (result) { e.preventDefault(); } + + return result; + }; + + /** + * Called when a drop event occures + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onDrop = function(e) { + var result = false; + for (var i = 0; i < e.dataTransfer.files.length; i++) { + var file = e.dataTransfer.files[i]; + if (this.isFileAllowed(file)) { + result = true; + this.onFileInserted(file); + this.uploadFile(file); + } + } + + return result; + }; + + window.inlineAttachment = inlineAttachment; + +})(document, window); diff --git a/dist/codemirror-3.inline-attachment.min.js b/dist/codemirror-3.inline-attachment.min.js new file mode 100644 index 0000000..5bc46b3 --- /dev/null +++ b/dist/codemirror-3.inline-attachment.min.js @@ -0,0 +1,2 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +!function(a,b){"use strict";var c=function(a,b){this.settings=c.util.merge(a,c.defaults),this.editor=b,this.filenameTag="{filename}",this.lastValue=null};c.editors={},c.util={merge:function(){for(var a={},b=arguments.length-1;b>=0;b--){var c=arguments[b];for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},appendInItsOwnLine:function(a,b){return(a+"\n\n[[D]]"+b).replace(/(\n{2,})\[\[D\]\]/,"\n\n").replace(/^(\n*)/,"")},insertTextAtCursor:function(b,c){var d,e=b.scrollTop,f=0,g=!1;b.selectionStart||"0"===b.selectionStart?g="ff":a.selection&&(g="ie"),"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),f=d.text.length):"ff"===g&&(f=b.selectionStart);var h=b.value.substring(0,f),i=b.value.substring(f,b.value.length);b.value=h+c+i,f+=c.length,"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),d.moveStart("character",f),d.moveEnd("character",0),d.select()):"ff"===g&&(b.selectionStart=f,b.selectionEnd=f,b.focus()),b.scrollTop=e}},c.defaults={uploadUrl:"upload_attachment.php",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",jsonFieldName:"filename",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![file]({filename})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload:function(){return!0},onFileReceived:function(){},onFileUploadResponse:function(){return!0},onFileUploadError:function(){return!0},onFileUploaded:function(){}},c.prototype.uploadFile=function(a){var b=this,c=new FormData,d=new XMLHttpRequest,e=this.settings,f=e.defaultExtension||e.defualtExtension;if("function"==typeof e.setupFormData&&e.setupFormData(c,a),a.name){var g=a.name.match(/\.(.+)$/);g&&(f=g[1])}var h="image-"+Date.now()+"."+f;if("function"==typeof e.remoteFilename&&(h=e.remoteFilename(a)),c.append(e.uploadFieldName,a,h),"object"==typeof e.extraParams)for(var i in e.extraParams)e.extraParams.hasOwnProperty(i)&&c.append(i,e.extraParams[i]);if(d.open("POST",e.uploadUrl),"object"==typeof e.extraHeaders)for(var j in e.extraHeaders)e.extraHeaders.hasOwnProperty(j)&&d.setRequestHeader(j,e.extraHeaders[j]);return d.onload=function(){200===d.status||201===d.status?b.onFileUploadResponse(d):b.onFileUploadError(d)},e.beforeFileUpload(d)!==!1&&d.send(c),d},c.prototype.isFileAllowed=function(a){return 0===this.settings.allowedTypes.indexOf("*")?!0:this.settings.allowedTypes.indexOf(a.type)>=0},c.prototype.onFileUploadResponse=function(a){if(this.settings.onFileUploadResponse.call(this,a)!==!1){var b=JSON.parse(a.responseText),c=b[this.settings.jsonFieldName];if(b&&c){var d=this.settings.urlText.replace(this.filenameTag,c),e=this.editor.getValue().replace(this.lastValue,d);this.editor.setValue(e),this.settings.onFileUploaded.call(this,c)}}},c.prototype.onFileUploadError=function(a){if(this.settings.onFileUploadError.call(this,a)!==!1){var b=this.editor.getValue().replace(this.lastValue,"");this.editor.setValue(b)}},c.prototype.onFileInserted=function(a){this.settings.onFileReceived.call(this,a)!==!1&&(this.lastValue=this.settings.progressText,this.editor.insertValue(this.lastValue))},c.prototype.onPaste=function(a){var b,c=!1,d=a.clipboardData;if("object"==typeof d){b=d.items||d.files||[];for(var e=0;e= 0; i--) { + var obj = arguments[i]; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + result[k] = obj[k]; + } + } + } + return result; + }, + + /** + * Append a line of text at the bottom, ensuring there aren't unnecessary newlines + * + * @param {String} appended Current content + * @param {String} previous Value which should be appended after the current content + */ + appendInItsOwnLine: function(previous, appended) { + return (previous + "\n\n[[D]]" + appended) + .replace(/(\n{2,})\[\[D\]\]/, "\n\n") + .replace(/^(\n*)/, ""); + }, + + /** + * Inserts the given value at the current cursor position of the textarea element + * + * @param {HtmlElement} el + * @param {String} value Text which will be inserted at the cursor position + */ + insertTextAtCursor: function(el, text) { + var scrollPos = el.scrollTop, + strPos = 0, + browser = false, + range; + + if ((el.selectionStart || el.selectionStart === '0')) { + browser = "ff"; + } else if (document.selection) { + browser = "ie"; + } + + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + strPos = range.text.length; + } else if (browser === "ff") { + strPos = el.selectionStart; + } + + var front = (el.value).substring(0, strPos); + var back = (el.value).substring(strPos, el.value.length); + el.value = front + text + back; + strPos = strPos + text.length; + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + range.moveStart('character', strPos); + range.moveEnd('character', 0); + range.select(); + } else if (browser === "ff") { + el.selectionStart = strPos; + el.selectionEnd = strPos; + el.focus(); + } + el.scrollTop = scrollPos; + } + }; + + /** + * Default configuration options + * + * @type {Object} + */ + inlineAttachment.defaults = { + /** + * URL where the file will be send + */ + uploadUrl: 'upload_attachment.php', + + /** + * Which method will be used to send the file to the upload URL + */ + uploadMethod: 'POST', + + /** + * Name in which the file will be placed + */ + uploadFieldName: 'file', + + /** + * Extension which will be used when a file extension could not + * be detected + */ + defaultExtension: 'png', + + /** + * JSON field which refers to the uploaded file URL + */ + jsonFieldName: 'filename', + + /** + * Allowed MIME types + */ + allowedTypes: [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ], + + /** + * Text which will be inserted when dropping or pasting a file. + * Acts as a placeholder which will be replaced when the file is done with uploading + */ + progressText: '![Uploading file...]()', + + /** + * When a file has successfully been uploaded the progressText + * will be replaced by the urlText, the {filename} tag will be replaced + * by the filename that has been returned by the server + */ + urlText: "![file]({filename})", + + /** + * Text which will be used when uploading has failed + */ + errorText: "Error uploading file", + + /** + * Extra parameters which will be send when uploading a file + */ + extraParams: {}, + + /** + * Extra headers which will be send when uploading a file + */ + extraHeaders: {}, + + /** + * Before the file is send + */ + beforeFileUpload: function() { + return true; + }, + + /** + * Triggers when a file is dropped or pasted + */ + onFileReceived: function() {}, + + /** + * Custom upload handler + * + * @return {Boolean} when false is returned it will prevent default upload behavior + */ + onFileUploadResponse: function() { + return true; + }, + + /** + * Custom error handler. Runs after removing the placeholder text and before the alert(). + * Return false from this function to prevent the alert dialog. + * + * @return {Boolean} when false is returned it will prevent default error behavior + */ + onFileUploadError: function() { + return true; + }, + + /** + * When a file has succesfully been uploaded + */ + onFileUploaded: function() {} + }; + + /** + * Uploads the blob + * + * @param {Blob} file blob data received from event.dataTransfer object + * @return {XMLHttpRequest} request object which sends the file + */ + inlineAttachment.prototype.uploadFile = function(file) { + var me = this, + formData = new FormData(), + xhr = new XMLHttpRequest(), + settings = this.settings, + extension = settings.defaultExtension || settings.defualtExtension; + + if (typeof settings.setupFormData === 'function') { + settings.setupFormData(formData, file); + } + + // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now) + // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name + if (file.name) { + var fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches) { + extension = fileNameMatches[1]; + } + } + + var remoteFilename = "image-" + Date.now() + "." + extension; + if (typeof settings.remoteFilename === 'function') { + remoteFilename = settings.remoteFilename(file); + } + + formData.append(settings.uploadFieldName, file, remoteFilename); + + // Append the extra parameters to the formdata + if (typeof settings.extraParams === "object") { + for (var key in settings.extraParams) { + if (settings.extraParams.hasOwnProperty(key)) { + formData.append(key, settings.extraParams[key]); + } + } + } + + xhr.open('POST', settings.uploadUrl); + + // Add any available extra headers + if (typeof settings.extraHeaders === "object") { + for (var header in settings.extraHeaders) { + if (settings.extraHeaders.hasOwnProperty(header)) { + xhr.setRequestHeader(header, settings.extraHeaders[header]); + } + } + } + + xhr.onload = function() { + // If HTTP status is OK or Created + if (xhr.status === 200 || xhr.status === 201) { + me.onFileUploadResponse(xhr); + } else { + me.onFileUploadError(xhr); + } + }; + if (settings.beforeFileUpload(xhr) !== false) { + xhr.send(formData); + } + return xhr; + }; + + /** + * Returns if the given file is allowed to handle + * + * @param {File} clipboard data file + */ + inlineAttachment.prototype.isFileAllowed = function(file) { + if (this.settings.allowedTypes.indexOf('*') === 0){ + return true; + } else { + return this.settings.allowedTypes.indexOf(file.type) >= 0; + } + }; + + /** + * Handles upload response + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadResponse = function(xhr) { + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { + var result = JSON.parse(xhr.responseText), + filename = result[this.settings.jsonFieldName]; + + if (result && filename) { + var newValue = this.settings.urlText.replace(this.filenameTag, filename); + var text = this.editor.getValue().replace(this.lastValue, newValue); + this.editor.setValue(text); + this.settings.onFileUploaded.call(this, filename); + } + } + }; + + + /** + * Called when a file has failed to upload + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadError = function(xhr) { + if (this.settings.onFileUploadError.call(this, xhr) !== false) { + var text = this.editor.getValue().replace(this.lastValue, ""); + this.editor.setValue(text); + } + }; + + /** + * Called when a file has been inserted, either by drop or paste + * + * @param {File} file + * @return {Void} + */ + inlineAttachment.prototype.onFileInserted = function(file) { + if (this.settings.onFileReceived.call(this, file) !== false) { + this.lastValue = this.settings.progressText; + this.editor.insertValue(this.lastValue); + } + }; + + + /** + * Called when a paste event occured + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onPaste = function(e) { + var result = false, + clipboardData = e.clipboardData, + items; + + if (typeof clipboardData === "object") { + items = clipboardData.items || clipboardData.files || []; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (this.isFileAllowed(item)) { + result = true; + this.onFileInserted(item.getAsFile()); + this.uploadFile(item.getAsFile()); + } + } + } + + if (result) { e.preventDefault(); } + + return result; + }; + + /** + * Called when a drop event occures + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onDrop = function(e) { + var result = false; + for (var i = 0; i < e.dataTransfer.files.length; i++) { + var file = e.dataTransfer.files[i]; + if (this.isFileAllowed(file)) { + result = true; + this.onFileInserted(file); + this.uploadFile(file); + } + } + + return result; + }; + + window.inlineAttachment = inlineAttachment; + +})(document, window); diff --git a/dist/codemirror-4.inline-attachment.min.js b/dist/codemirror-4.inline-attachment.min.js new file mode 100644 index 0000000..5bc46b3 --- /dev/null +++ b/dist/codemirror-4.inline-attachment.min.js @@ -0,0 +1,2 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +!function(a,b){"use strict";var c=function(a,b){this.settings=c.util.merge(a,c.defaults),this.editor=b,this.filenameTag="{filename}",this.lastValue=null};c.editors={},c.util={merge:function(){for(var a={},b=arguments.length-1;b>=0;b--){var c=arguments[b];for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},appendInItsOwnLine:function(a,b){return(a+"\n\n[[D]]"+b).replace(/(\n{2,})\[\[D\]\]/,"\n\n").replace(/^(\n*)/,"")},insertTextAtCursor:function(b,c){var d,e=b.scrollTop,f=0,g=!1;b.selectionStart||"0"===b.selectionStart?g="ff":a.selection&&(g="ie"),"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),f=d.text.length):"ff"===g&&(f=b.selectionStart);var h=b.value.substring(0,f),i=b.value.substring(f,b.value.length);b.value=h+c+i,f+=c.length,"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),d.moveStart("character",f),d.moveEnd("character",0),d.select()):"ff"===g&&(b.selectionStart=f,b.selectionEnd=f,b.focus()),b.scrollTop=e}},c.defaults={uploadUrl:"upload_attachment.php",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",jsonFieldName:"filename",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![file]({filename})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload:function(){return!0},onFileReceived:function(){},onFileUploadResponse:function(){return!0},onFileUploadError:function(){return!0},onFileUploaded:function(){}},c.prototype.uploadFile=function(a){var b=this,c=new FormData,d=new XMLHttpRequest,e=this.settings,f=e.defaultExtension||e.defualtExtension;if("function"==typeof e.setupFormData&&e.setupFormData(c,a),a.name){var g=a.name.match(/\.(.+)$/);g&&(f=g[1])}var h="image-"+Date.now()+"."+f;if("function"==typeof e.remoteFilename&&(h=e.remoteFilename(a)),c.append(e.uploadFieldName,a,h),"object"==typeof e.extraParams)for(var i in e.extraParams)e.extraParams.hasOwnProperty(i)&&c.append(i,e.extraParams[i]);if(d.open("POST",e.uploadUrl),"object"==typeof e.extraHeaders)for(var j in e.extraHeaders)e.extraHeaders.hasOwnProperty(j)&&d.setRequestHeader(j,e.extraHeaders[j]);return d.onload=function(){200===d.status||201===d.status?b.onFileUploadResponse(d):b.onFileUploadError(d)},e.beforeFileUpload(d)!==!1&&d.send(c),d},c.prototype.isFileAllowed=function(a){return 0===this.settings.allowedTypes.indexOf("*")?!0:this.settings.allowedTypes.indexOf(a.type)>=0},c.prototype.onFileUploadResponse=function(a){if(this.settings.onFileUploadResponse.call(this,a)!==!1){var b=JSON.parse(a.responseText),c=b[this.settings.jsonFieldName];if(b&&c){var d=this.settings.urlText.replace(this.filenameTag,c),e=this.editor.getValue().replace(this.lastValue,d);this.editor.setValue(e),this.settings.onFileUploaded.call(this,c)}}},c.prototype.onFileUploadError=function(a){if(this.settings.onFileUploadError.call(this,a)!==!1){var b=this.editor.getValue().replace(this.lastValue,"");this.editor.setValue(b)}},c.prototype.onFileInserted=function(a){this.settings.onFileReceived.call(this,a)!==!1&&(this.lastValue=this.settings.progressText,this.editor.insertValue(this.lastValue))},c.prototype.onPaste=function(a){var b,c=!1,d=a.clipboardData;if("object"==typeof d){b=d.items||d.files||[];for(var e=0;e= 0; i--) { + var obj = arguments[i]; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + result[k] = obj[k]; + } + } + } + return result; + }, + + /** + * Append a line of text at the bottom, ensuring there aren't unnecessary newlines + * + * @param {String} appended Current content + * @param {String} previous Value which should be appended after the current content + */ + appendInItsOwnLine: function(previous, appended) { + return (previous + "\n\n[[D]]" + appended) + .replace(/(\n{2,})\[\[D\]\]/, "\n\n") + .replace(/^(\n*)/, ""); + }, + + /** + * Inserts the given value at the current cursor position of the textarea element + * + * @param {HtmlElement} el + * @param {String} value Text which will be inserted at the cursor position + */ + insertTextAtCursor: function(el, text) { + var scrollPos = el.scrollTop, + strPos = 0, + browser = false, + range; + + if ((el.selectionStart || el.selectionStart === '0')) { + browser = "ff"; + } else if (document.selection) { + browser = "ie"; + } + + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + strPos = range.text.length; + } else if (browser === "ff") { + strPos = el.selectionStart; + } + + var front = (el.value).substring(0, strPos); + var back = (el.value).substring(strPos, el.value.length); + el.value = front + text + back; + strPos = strPos + text.length; + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + range.moveStart('character', strPos); + range.moveEnd('character', 0); + range.select(); + } else if (browser === "ff") { + el.selectionStart = strPos; + el.selectionEnd = strPos; + el.focus(); + } + el.scrollTop = scrollPos; + } + }; + + /** + * Default configuration options + * + * @type {Object} + */ + inlineAttachment.defaults = { + /** + * URL where the file will be send + */ + uploadUrl: 'upload_attachment.php', + + /** + * Which method will be used to send the file to the upload URL + */ + uploadMethod: 'POST', + + /** + * Name in which the file will be placed + */ + uploadFieldName: 'file', + + /** + * Extension which will be used when a file extension could not + * be detected + */ + defaultExtension: 'png', + + /** + * JSON field which refers to the uploaded file URL + */ + jsonFieldName: 'filename', + + /** + * Allowed MIME types + */ + allowedTypes: [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ], + + /** + * Text which will be inserted when dropping or pasting a file. + * Acts as a placeholder which will be replaced when the file is done with uploading + */ + progressText: '![Uploading file...]()', + + /** + * When a file has successfully been uploaded the progressText + * will be replaced by the urlText, the {filename} tag will be replaced + * by the filename that has been returned by the server + */ + urlText: "![file]({filename})", + + /** + * Text which will be used when uploading has failed + */ + errorText: "Error uploading file", + + /** + * Extra parameters which will be send when uploading a file + */ + extraParams: {}, + + /** + * Extra headers which will be send when uploading a file + */ + extraHeaders: {}, + + /** + * Before the file is send + */ + beforeFileUpload: function() { + return true; + }, + + /** + * Triggers when a file is dropped or pasted + */ + onFileReceived: function() {}, + + /** + * Custom upload handler + * + * @return {Boolean} when false is returned it will prevent default upload behavior + */ + onFileUploadResponse: function() { + return true; + }, + + /** + * Custom error handler. Runs after removing the placeholder text and before the alert(). + * Return false from this function to prevent the alert dialog. + * + * @return {Boolean} when false is returned it will prevent default error behavior + */ + onFileUploadError: function() { + return true; + }, + + /** + * When a file has succesfully been uploaded + */ + onFileUploaded: function() {} + }; + + /** + * Uploads the blob + * + * @param {Blob} file blob data received from event.dataTransfer object + * @return {XMLHttpRequest} request object which sends the file + */ + inlineAttachment.prototype.uploadFile = function(file) { + var me = this, + formData = new FormData(), + xhr = new XMLHttpRequest(), + settings = this.settings, + extension = settings.defaultExtension || settings.defualtExtension; + + if (typeof settings.setupFormData === 'function') { + settings.setupFormData(formData, file); + } + + // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now) + // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name + if (file.name) { + var fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches) { + extension = fileNameMatches[1]; + } + } + + var remoteFilename = "image-" + Date.now() + "." + extension; + if (typeof settings.remoteFilename === 'function') { + remoteFilename = settings.remoteFilename(file); + } + + formData.append(settings.uploadFieldName, file, remoteFilename); + + // Append the extra parameters to the formdata + if (typeof settings.extraParams === "object") { + for (var key in settings.extraParams) { + if (settings.extraParams.hasOwnProperty(key)) { + formData.append(key, settings.extraParams[key]); + } + } + } + + xhr.open('POST', settings.uploadUrl); + + // Add any available extra headers + if (typeof settings.extraHeaders === "object") { + for (var header in settings.extraHeaders) { + if (settings.extraHeaders.hasOwnProperty(header)) { + xhr.setRequestHeader(header, settings.extraHeaders[header]); + } + } + } + + xhr.onload = function() { + // If HTTP status is OK or Created + if (xhr.status === 200 || xhr.status === 201) { + me.onFileUploadResponse(xhr); + } else { + me.onFileUploadError(xhr); + } + }; + if (settings.beforeFileUpload(xhr) !== false) { + xhr.send(formData); + } + return xhr; + }; + + /** + * Returns if the given file is allowed to handle + * + * @param {File} clipboard data file + */ + inlineAttachment.prototype.isFileAllowed = function(file) { + if (this.settings.allowedTypes.indexOf('*') === 0){ + return true; + } else { + return this.settings.allowedTypes.indexOf(file.type) >= 0; + } + }; + + /** + * Handles upload response + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadResponse = function(xhr) { + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { + var result = JSON.parse(xhr.responseText), + filename = result[this.settings.jsonFieldName]; + + if (result && filename) { + var newValue = this.settings.urlText.replace(this.filenameTag, filename); + var text = this.editor.getValue().replace(this.lastValue, newValue); + this.editor.setValue(text); + this.settings.onFileUploaded.call(this, filename); + } + } + }; + + + /** + * Called when a file has failed to upload + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadError = function(xhr) { + if (this.settings.onFileUploadError.call(this, xhr) !== false) { + var text = this.editor.getValue().replace(this.lastValue, ""); + this.editor.setValue(text); + } + }; + + /** + * Called when a file has been inserted, either by drop or paste + * + * @param {File} file + * @return {Void} + */ + inlineAttachment.prototype.onFileInserted = function(file) { + if (this.settings.onFileReceived.call(this, file) !== false) { + this.lastValue = this.settings.progressText; + this.editor.insertValue(this.lastValue); + } + }; + + + /** + * Called when a paste event occured + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onPaste = function(e) { + var result = false, + clipboardData = e.clipboardData, + items; + + if (typeof clipboardData === "object") { + items = clipboardData.items || clipboardData.files || []; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (this.isFileAllowed(item)) { + result = true; + this.onFileInserted(item.getAsFile()); + this.uploadFile(item.getAsFile()); + } + } + } + + if (result) { e.preventDefault(); } + + return result; + }; + + /** + * Called when a drop event occures + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onDrop = function(e) { + var result = false; + for (var i = 0; i < e.dataTransfer.files.length; i++) { + var file = e.dataTransfer.files[i]; + if (this.isFileAllowed(file)) { + result = true; + this.onFileInserted(file); + this.uploadFile(file); + } + } + + return result; + }; + + window.inlineAttachment = inlineAttachment; + +})(document, window); + +/*jslint newcap: true */ +/*global inlineAttachment: false */ +(function() { + 'use strict'; + + inlineAttachment.editors.input = { + Editor: function(instance) { + + var input = instance; + + return { + getValue: function() { + return input.value; + }, + insertValue: function(val) { + inlineAttachment.util.insertTextAtCursor(input, val); + }, + setValue: function(val) { + input.value = val; + } + }; + }, + attachToInput: function(input, options) { + options = options || {}; + + var editor = new inlineAttachment.editors.input.Editor(input), + inlineattach = new inlineAttachment(options, editor); + + input.addEventListener('paste', function(e) { + inlineattach.onPaste(e); + }, false); + input.addEventListener('drop', function(e) { + e.stopPropagation(); + e.preventDefault(); + inlineattach.onDrop(e); + }, false); + input.addEventListener('dragenter', function(e) { + e.stopPropagation(); + e.preventDefault(); + }, false); + input.addEventListener('dragover', function(e) { + e.stopPropagation(); + e.preventDefault(); + }, false); + } + }; + +})(); \ No newline at end of file diff --git a/dist/inline-attachment.min.js b/dist/inline-attachment.min.js new file mode 100644 index 0000000..5d0e221 --- /dev/null +++ b/dist/inline-attachment.min.js @@ -0,0 +1,2 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +!function(a,b){"use strict";var c=function(a,b){this.settings=c.util.merge(a,c.defaults),this.editor=b,this.filenameTag="{filename}",this.lastValue=null};c.editors={},c.util={merge:function(){for(var a={},b=arguments.length-1;b>=0;b--){var c=arguments[b];for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},appendInItsOwnLine:function(a,b){return(a+"\n\n[[D]]"+b).replace(/(\n{2,})\[\[D\]\]/,"\n\n").replace(/^(\n*)/,"")},insertTextAtCursor:function(b,c){var d,e=b.scrollTop,f=0,g=!1;b.selectionStart||"0"===b.selectionStart?g="ff":a.selection&&(g="ie"),"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),f=d.text.length):"ff"===g&&(f=b.selectionStart);var h=b.value.substring(0,f),i=b.value.substring(f,b.value.length);b.value=h+c+i,f+=c.length,"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),d.moveStart("character",f),d.moveEnd("character",0),d.select()):"ff"===g&&(b.selectionStart=f,b.selectionEnd=f,b.focus()),b.scrollTop=e}},c.defaults={uploadUrl:"upload_attachment.php",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",jsonFieldName:"filename",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![file]({filename})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload:function(){return!0},onFileReceived:function(){},onFileUploadResponse:function(){return!0},onFileUploadError:function(){return!0},onFileUploaded:function(){}},c.prototype.uploadFile=function(a){var b=this,c=new FormData,d=new XMLHttpRequest,e=this.settings,f=e.defaultExtension||e.defualtExtension;if("function"==typeof e.setupFormData&&e.setupFormData(c,a),a.name){var g=a.name.match(/\.(.+)$/);g&&(f=g[1])}var h="image-"+Date.now()+"."+f;if("function"==typeof e.remoteFilename&&(h=e.remoteFilename(a)),c.append(e.uploadFieldName,a,h),"object"==typeof e.extraParams)for(var i in e.extraParams)e.extraParams.hasOwnProperty(i)&&c.append(i,e.extraParams[i]);if(d.open("POST",e.uploadUrl),"object"==typeof e.extraHeaders)for(var j in e.extraHeaders)e.extraHeaders.hasOwnProperty(j)&&d.setRequestHeader(j,e.extraHeaders[j]);return d.onload=function(){200===d.status||201===d.status?b.onFileUploadResponse(d):b.onFileUploadError(d)},e.beforeFileUpload(d)!==!1&&d.send(c),d},c.prototype.isFileAllowed=function(a){return 0===this.settings.allowedTypes.indexOf("*")?!0:this.settings.allowedTypes.indexOf(a.type)>=0},c.prototype.onFileUploadResponse=function(a){if(this.settings.onFileUploadResponse.call(this,a)!==!1){var b=JSON.parse(a.responseText),c=b[this.settings.jsonFieldName];if(b&&c){var d=this.settings.urlText.replace(this.filenameTag,c),e=this.editor.getValue().replace(this.lastValue,d);this.editor.setValue(e),this.settings.onFileUploaded.call(this,c)}}},c.prototype.onFileUploadError=function(a){if(this.settings.onFileUploadError.call(this,a)!==!1){var b=this.editor.getValue().replace(this.lastValue,"");this.editor.setValue(b)}},c.prototype.onFileInserted=function(a){this.settings.onFileReceived.call(this,a)!==!1&&(this.lastValue=this.settings.progressText,this.editor.insertValue(this.lastValue))},c.prototype.onPaste=function(a){var b,c=!1,d=a.clipboardData;if("object"==typeof d){b=d.items||d.files||[];for(var e=0;e= 0; i--) { + var obj = arguments[i]; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + result[k] = obj[k]; + } + } + } + return result; + }, + + /** + * Append a line of text at the bottom, ensuring there aren't unnecessary newlines + * + * @param {String} appended Current content + * @param {String} previous Value which should be appended after the current content + */ + appendInItsOwnLine: function(previous, appended) { + return (previous + "\n\n[[D]]" + appended) + .replace(/(\n{2,})\[\[D\]\]/, "\n\n") + .replace(/^(\n*)/, ""); + }, + + /** + * Inserts the given value at the current cursor position of the textarea element + * + * @param {HtmlElement} el + * @param {String} value Text which will be inserted at the cursor position + */ + insertTextAtCursor: function(el, text) { + var scrollPos = el.scrollTop, + strPos = 0, + browser = false, + range; + + if ((el.selectionStart || el.selectionStart === '0')) { + browser = "ff"; + } else if (document.selection) { + browser = "ie"; + } + + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + strPos = range.text.length; + } else if (browser === "ff") { + strPos = el.selectionStart; + } + + var front = (el.value).substring(0, strPos); + var back = (el.value).substring(strPos, el.value.length); + el.value = front + text + back; + strPos = strPos + text.length; + if (browser === "ie") { + el.focus(); + range = document.selection.createRange(); + range.moveStart('character', -el.value.length); + range.moveStart('character', strPos); + range.moveEnd('character', 0); + range.select(); + } else if (browser === "ff") { + el.selectionStart = strPos; + el.selectionEnd = strPos; + el.focus(); + } + el.scrollTop = scrollPos; + } + }; + + /** + * Default configuration options + * + * @type {Object} + */ + inlineAttachment.defaults = { + /** + * URL where the file will be send + */ + uploadUrl: 'upload_attachment.php', + + /** + * Which method will be used to send the file to the upload URL + */ + uploadMethod: 'POST', + + /** + * Name in which the file will be placed + */ + uploadFieldName: 'file', + + /** + * Extension which will be used when a file extension could not + * be detected + */ + defaultExtension: 'png', + + /** + * JSON field which refers to the uploaded file URL + */ + jsonFieldName: 'filename', + + /** + * Allowed MIME types + */ + allowedTypes: [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ], + + /** + * Text which will be inserted when dropping or pasting a file. + * Acts as a placeholder which will be replaced when the file is done with uploading + */ + progressText: '![Uploading file...]()', + + /** + * When a file has successfully been uploaded the progressText + * will be replaced by the urlText, the {filename} tag will be replaced + * by the filename that has been returned by the server + */ + urlText: "![file]({filename})", + + /** + * Text which will be used when uploading has failed + */ + errorText: "Error uploading file", + + /** + * Extra parameters which will be send when uploading a file + */ + extraParams: {}, + + /** + * Extra headers which will be send when uploading a file + */ + extraHeaders: {}, + + /** + * Before the file is send + */ + beforeFileUpload: function() { + return true; + }, + + /** + * Triggers when a file is dropped or pasted + */ + onFileReceived: function() {}, + + /** + * Custom upload handler + * + * @return {Boolean} when false is returned it will prevent default upload behavior + */ + onFileUploadResponse: function() { + return true; + }, + + /** + * Custom error handler. Runs after removing the placeholder text and before the alert(). + * Return false from this function to prevent the alert dialog. + * + * @return {Boolean} when false is returned it will prevent default error behavior + */ + onFileUploadError: function() { + return true; + }, + + /** + * When a file has succesfully been uploaded + */ + onFileUploaded: function() {} + }; + + /** + * Uploads the blob + * + * @param {Blob} file blob data received from event.dataTransfer object + * @return {XMLHttpRequest} request object which sends the file + */ + inlineAttachment.prototype.uploadFile = function(file) { + var me = this, + formData = new FormData(), + xhr = new XMLHttpRequest(), + settings = this.settings, + extension = settings.defaultExtension || settings.defualtExtension; + + if (typeof settings.setupFormData === 'function') { + settings.setupFormData(formData, file); + } + + // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now) + // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name + if (file.name) { + var fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches) { + extension = fileNameMatches[1]; + } + } + + var remoteFilename = "image-" + Date.now() + "." + extension; + if (typeof settings.remoteFilename === 'function') { + remoteFilename = settings.remoteFilename(file); + } + + formData.append(settings.uploadFieldName, file, remoteFilename); + + // Append the extra parameters to the formdata + if (typeof settings.extraParams === "object") { + for (var key in settings.extraParams) { + if (settings.extraParams.hasOwnProperty(key)) { + formData.append(key, settings.extraParams[key]); + } + } + } + + xhr.open('POST', settings.uploadUrl); + + // Add any available extra headers + if (typeof settings.extraHeaders === "object") { + for (var header in settings.extraHeaders) { + if (settings.extraHeaders.hasOwnProperty(header)) { + xhr.setRequestHeader(header, settings.extraHeaders[header]); + } + } + } + + xhr.onload = function() { + // If HTTP status is OK or Created + if (xhr.status === 200 || xhr.status === 201) { + me.onFileUploadResponse(xhr); + } else { + me.onFileUploadError(xhr); + } + }; + if (settings.beforeFileUpload(xhr) !== false) { + xhr.send(formData); + } + return xhr; + }; + + /** + * Returns if the given file is allowed to handle + * + * @param {File} clipboard data file + */ + inlineAttachment.prototype.isFileAllowed = function(file) { + if (this.settings.allowedTypes.indexOf('*') === 0){ + return true; + } else { + return this.settings.allowedTypes.indexOf(file.type) >= 0; + } + }; + + /** + * Handles upload response + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadResponse = function(xhr) { + if (this.settings.onFileUploadResponse.call(this, xhr) !== false) { + var result = JSON.parse(xhr.responseText), + filename = result[this.settings.jsonFieldName]; + + if (result && filename) { + var newValue = this.settings.urlText.replace(this.filenameTag, filename); + var text = this.editor.getValue().replace(this.lastValue, newValue); + this.editor.setValue(text); + this.settings.onFileUploaded.call(this, filename); + } + } + }; + + + /** + * Called when a file has failed to upload + * + * @param {XMLHttpRequest} xhr + * @return {Void} + */ + inlineAttachment.prototype.onFileUploadError = function(xhr) { + if (this.settings.onFileUploadError.call(this, xhr) !== false) { + var text = this.editor.getValue().replace(this.lastValue, ""); + this.editor.setValue(text); + } + }; + + /** + * Called when a file has been inserted, either by drop or paste + * + * @param {File} file + * @return {Void} + */ + inlineAttachment.prototype.onFileInserted = function(file) { + if (this.settings.onFileReceived.call(this, file) !== false) { + this.lastValue = this.settings.progressText; + this.editor.insertValue(this.lastValue); + } + }; + + + /** + * Called when a paste event occured + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onPaste = function(e) { + var result = false, + clipboardData = e.clipboardData, + items; + + if (typeof clipboardData === "object") { + items = clipboardData.items || clipboardData.files || []; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (this.isFileAllowed(item)) { + result = true; + this.onFileInserted(item.getAsFile()); + this.uploadFile(item.getAsFile()); + } + } + } + + if (result) { e.preventDefault(); } + + return result; + }; + + /** + * Called when a drop event occures + * @param {Event} e + * @return {Boolean} if the event was handled + */ + inlineAttachment.prototype.onDrop = function(e) { + var result = false; + for (var i = 0; i < e.dataTransfer.files.length; i++) { + var file = e.dataTransfer.files[i]; + if (this.isFileAllowed(file)) { + result = true; + this.onFileInserted(file); + this.uploadFile(file); + } + } + + return result; + }; + + window.inlineAttachment = inlineAttachment; + +})(document, window); + +/*jslint newcap: true */ +/*global inlineAttachment: false, jQuery: false */ +/** + * jQuery plugin for inline attach + * + * @param {document} document + * @param {window} window + * @param {jQuery} $ + */ +(function(document, window, $) { + 'use strict'; + + inlineAttachment.editors.jquery = {}; + + /** + * Creates a new editor using jQuery + */ + var editor = function(instance) { + + var $this = $(instance); + + return { + getValue: function() { + return $this.val(); + }, + insertValue: function(val) { + inlineAttachment.util.insertTextAtCursor($this[0], val); + }, + setValue: function(val) { + $this.val(val); + } + }; + }; + + $.fn.inlineattachment = function(options) { + + var set = $(this); + + set.each(function() { + + var $this = $(this), + ed = new editor($this), + inlineattach = new inlineAttachment(options, ed); + + $this.bind({ + 'paste': function(e) { + inlineattach.onPaste(e.originalEvent); + }, + 'drop': function(e) { + e.stopPropagation(); + e.preventDefault(); + inlineattach.onDrop(e.originalEvent); + }, + 'dragenter dragover': function(e) { + e.stopPropagation(); + e.preventDefault(); + } + }); + }); + + return this; + }; + + inlineAttachment.editors.jquery.Editor = editor; + +})(document, window, jQuery); \ No newline at end of file diff --git a/dist/jquery.inline-attachment.min.js b/dist/jquery.inline-attachment.min.js new file mode 100644 index 0000000..e7eb902 --- /dev/null +++ b/dist/jquery.inline-attachment.min.js @@ -0,0 +1,2 @@ +/*! inline-attachment - v2.0.2 - 2015-11-03 */ +!function(a,b){"use strict";var c=function(a,b){this.settings=c.util.merge(a,c.defaults),this.editor=b,this.filenameTag="{filename}",this.lastValue=null};c.editors={},c.util={merge:function(){for(var a={},b=arguments.length-1;b>=0;b--){var c=arguments[b];for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},appendInItsOwnLine:function(a,b){return(a+"\n\n[[D]]"+b).replace(/(\n{2,})\[\[D\]\]/,"\n\n").replace(/^(\n*)/,"")},insertTextAtCursor:function(b,c){var d,e=b.scrollTop,f=0,g=!1;b.selectionStart||"0"===b.selectionStart?g="ff":a.selection&&(g="ie"),"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),f=d.text.length):"ff"===g&&(f=b.selectionStart);var h=b.value.substring(0,f),i=b.value.substring(f,b.value.length);b.value=h+c+i,f+=c.length,"ie"===g?(b.focus(),d=a.selection.createRange(),d.moveStart("character",-b.value.length),d.moveStart("character",f),d.moveEnd("character",0),d.select()):"ff"===g&&(b.selectionStart=f,b.selectionEnd=f,b.focus()),b.scrollTop=e}},c.defaults={uploadUrl:"upload_attachment.php",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",jsonFieldName:"filename",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![file]({filename})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload:function(){return!0},onFileReceived:function(){},onFileUploadResponse:function(){return!0},onFileUploadError:function(){return!0},onFileUploaded:function(){}},c.prototype.uploadFile=function(a){var b=this,c=new FormData,d=new XMLHttpRequest,e=this.settings,f=e.defaultExtension||e.defualtExtension;if("function"==typeof e.setupFormData&&e.setupFormData(c,a),a.name){var g=a.name.match(/\.(.+)$/);g&&(f=g[1])}var h="image-"+Date.now()+"."+f;if("function"==typeof e.remoteFilename&&(h=e.remoteFilename(a)),c.append(e.uploadFieldName,a,h),"object"==typeof e.extraParams)for(var i in e.extraParams)e.extraParams.hasOwnProperty(i)&&c.append(i,e.extraParams[i]);if(d.open("POST",e.uploadUrl),"object"==typeof e.extraHeaders)for(var j in e.extraHeaders)e.extraHeaders.hasOwnProperty(j)&&d.setRequestHeader(j,e.extraHeaders[j]);return d.onload=function(){200===d.status||201===d.status?b.onFileUploadResponse(d):b.onFileUploadError(d)},e.beforeFileUpload(d)!==!1&&d.send(c),d},c.prototype.isFileAllowed=function(a){return 0===this.settings.allowedTypes.indexOf("*")?!0:this.settings.allowedTypes.indexOf(a.type)>=0},c.prototype.onFileUploadResponse=function(a){if(this.settings.onFileUploadResponse.call(this,a)!==!1){var b=JSON.parse(a.responseText),c=b[this.settings.jsonFieldName];if(b&&c){var d=this.settings.urlText.replace(this.filenameTag,c),e=this.editor.getValue().replace(this.lastValue,d);this.editor.setValue(e),this.settings.onFileUploaded.call(this,c)}}},c.prototype.onFileUploadError=function(a){if(this.settings.onFileUploadError.call(this,a)!==!1){var b=this.editor.getValue().replace(this.lastValue,"");this.editor.setValue(b)}},c.prototype.onFileInserted=function(a){this.settings.onFileReceived.call(this,a)!==!1&&(this.lastValue=this.settings.progressText,this.editor.insertValue(this.lastValue))},c.prototype.onPaste=function(a){var b,c=!1,d=a.clipboardData;if("object"==typeof d){b=d.items||d.files||[];for(var e=0;e (https://github.com/rovak)" ], - "version": "2.0.1", + "version": "2.0.2", "dependencies": {}, "devDependencies": { "grunt": "~0.4.0",