From 1461aee88ab8899f690571349fee28beecce3cfa Mon Sep 17 00:00:00 2001 From: Paul Nicholls Date: Fri, 28 Nov 2014 15:37:38 +0800 Subject: [PATCH] MDL-43996 editor_atto: add image drag and drop capability Adds the ability to drag and drop images directly into Atto, uploading the image and embedding it correctly. Contains fixes from 1.0.2, as well as a policy change - images dragged and dropped into Atto now have role=presentation by default. --- .../atto/plugins/image/lang/en/atto_image.php | 3 +- lib/editor/atto/plugins/image/lib.php | 1 + lib/editor/atto/plugins/image/version.php | 2 +- .../moodle-atto_image-button-debug.js | 117 ++++++++++++++++++ .../moodle-atto_image-button-min.js | 5 +- .../moodle-atto_image-button.js | 117 ++++++++++++++++++ .../plugins/image/yui/src/button/js/button.js | 117 ++++++++++++++++++ 7 files changed, 358 insertions(+), 4 deletions(-) diff --git a/lib/editor/atto/plugins/image/lang/en/atto_image.php b/lib/editor/atto/plugins/image/lang/en/atto_image.php index 63b58df9086..1c68f5227d4 100644 --- a/lib/editor/atto/plugins/image/lang/en/atto_image.php +++ b/lib/editor/atto/plugins/image/lang/en/atto_image.php @@ -42,4 +42,5 @@ $string['presentationoraltrequired'] = 'Images must have a description, except i $string['preview'] = 'Preview'; $string['saveimage'] = 'Save image'; $string['size'] = 'Size'; -$string['width'] = 'Width'; +$string['uploading'] = 'Uploading, please wait...'; +$string['width'] = 'Width'; \ No newline at end of file diff --git a/lib/editor/atto/plugins/image/lib.php b/lib/editor/atto/plugins/image/lib.php index 32b87452418..00b3915b2b2 100644 --- a/lib/editor/atto/plugins/image/lib.php +++ b/lib/editor/atto/plugins/image/lib.php @@ -49,6 +49,7 @@ function atto_image_strings_for_js() { 'presentationoraltrequired', 'size', 'width', + 'uploading', ); $PAGE->requires->strings_for_js($strings, 'atto_image'); diff --git a/lib/editor/atto/plugins/image/version.php b/lib/editor/atto/plugins/image/version.php index 7aca6066de5..e0a95e9df7c 100644 --- a/lib/editor/atto/plugins/image/version.php +++ b/lib/editor/atto/plugins/image/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2014111000; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2014112800; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2014110400; // Requires this Moodle version. $plugin->component = 'atto_image'; // Full name of the plugin (used for diagnostics). diff --git a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js index 48a917009c6..511c6a8b7b7 100644 --- a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js +++ b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js @@ -165,6 +165,7 @@ var CSS = { '{{#if presentation}}role="presentation" {{/if}}' + 'style="{{alignment}}{{margin}}{{customstyle}}"' + '{{#if classlist}}class="{{classlist}}" {{/if}}' + + '{{#if id}}id="{{id}}" {{/if}}' + '/>'; Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -206,6 +207,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi _rawImageDimensions: null, initializer: function() { + this.addButton({ icon: 'e/insert_edit_image', callback: this._displayDialogue, @@ -213,6 +215,121 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi tagMatchRequiresAll: false }); this.editor.delegate('dblclick', this._handleDoubleClick, 'img', this); + this.editor.on('drop', this._handleDragDrop, this); + }, + + /** + * Handle a drag and drop event with an image. + * + * @method _handleDragDrop + * @param {EventFacade} e + * @private + */ + _handleDragDrop: function(e) { + + var self = this, + host = this.get('host'), + template = Y.Handlebars.compile(IMAGETEMPLATE); + + host.saveSelection(); + e = e._event; + + // Only handle the event if an image file was dropped in. + if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length && /^image\//.test(e.dataTransfer.files[0].type)) { + + var options = host.get('filepickeroptions').image, + savepath = (options.savepath === undefined) ? '/' : options.savepath, + formData = new FormData(), + timestamp = 0, + uploadid = "", + xhr = new XMLHttpRequest(), + imagehtml = "", + keys = Object.keys(options.repositories); + + e.preventDefault(); + e.stopPropagation(); + formData.append('repo_upload_file', e.dataTransfer.files[0]); + formData.append('itemid', options.itemid); + + // List of repositories is an object rather than an array. This makes iteration more awkward. + for (var i = 0; i < keys.length; i++) { + if (options.repositories[keys[i]].type === 'upload') { + formData.append('repo_id', options.repositories[keys[i]].id); + break; + } + } + formData.append('env', options.env); + formData.append('sesskey', M.cfg.sesskey); + formData.append('client_id', options.client_id); + formData.append('savepath', savepath); + formData.append('ctx_id', options.context.id); + + // Insert spinner as a placeholder. + timestamp = new Date().getTime(); + uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp; + host.focus(); + host.restoreSelection(); + imagehtml = template({ + url: M.util.image_url("i/loading_small", 'moodle'), + alt: M.util.get_string('uploading', COMPONENTNAME), + id: uploadid + }); + host.insertContentAtFocusPoint(imagehtml); + self.markUpdated(); + + // Kick off a XMLHttpRequest. + xhr.onreadystatechange = function() { + var placeholder = self.editor.one('#' + uploadid), + result, + file, + newhtml, + newimage; + + if (xhr.readyState === 4) { + if (xhr.status === 200) { + result = JSON.parse(xhr.responseText); + if (result) { + if (result.error) { + if (placeholder) { + placeholder.remove(true); + } + return new M.core.ajaxException(result); + } + + file = result; + if (result.event && result.event === 'fileexists') { + // A file with this name is already in use here - rename to avoid conflict. + // Chances are, it's a different image (stored in a different folder on the user's computer). + // If the user wants to reuse an existing image, they can copy/paste it within the editor. + file = result.newfile; + } + + // Replace placeholder with actual image. + newhtml = template({ + url: file.url, + presentation: true + }); + newimage = Y.Node.create(newhtml); + if (placeholder) { + placeholder.replace(newimage); + } else { + self.editor.appendChild(newimage); + } + self.markUpdated(); + } + } else { + alert(M.util.get_string('servererror', 'moodle')); + if (placeholder) { + placeholder.remove(true); + } + } + } + }; + xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true); + xhr.send(formData); + } + return false; + }, /** diff --git a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js index d346265ff8a..8cf00acb8de 100644 --- a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js +++ b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js @@ -1,2 +1,3 @@ -YUI.add("moodle-atto_image-button",function(e,t){var n={RESPONSIVE:"img-responsive",INPUTALIGNMENT:"atto_image_alignment",INPUTALT:"atto_image_altentry",INPUTHEIGHT:"atto_image_heightentry",INPUTSUBMIT:"atto_image_urlentrysubmit",INPUTURL:"atto_image_urlentry",INPUTSIZE:"atto_image_size",INPUTWIDTH:"atto_image_widthentry",IMAGEALTWARNING:"atto_image_altwarning",IMAGEBROWSER:"openimagebrowser",IMAGEPRESENTATION:"atto_image_presentation",INPUTCONSTRAIN:"atto_image_constrain",INPUTCUSTOMSTYLE:"atto_image_customstyle",IMAGEPREVIEW:"atto_image_preview",IMAGEPREVIEWBOX:"atto_image_preview_box"},r={INPUTURL:"."+n.INPUTURL},i=[{name:"text-top",str:"alignment_top",value:"vertical-align",margin:"0 .5em"},{name:"middle",str:"alignment_middle",value:"vertical-align",margin:"0 .5em"},{name:"text-bottom",str:"alignment_bottom",value:"vertical-align",margin:"0 .5em",isDefault:!0},{name:"left",str:"alignment_left",value:"float",margin:"0 .5em 0 0"},{name:"right",str:"alignment_right",value:"float",margin:"0 0 0 .5em"},{name:"customstyle",str:"customstyle",value:"style"}],s={ISPERCENT:/\d+%/},o="atto_image",u='

{{#if showFilepicker}}{{/if}}

x

',a='{{alt}}';e.namespace("M.atto_image").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_selectedImage:null,_form:null,_rawImageDimensions:null,initializer:function(){this.addButton({icon:"e/insert_edit_image",callback:this._displayDialogue,tags:"img",tagMatchRequiresAll:!1}),this.editor.delegate("dblclick",this._handleDoubleClick,"img",this)},_handleDoubleClick:function(e){var t=e.target,n=this.get("host").getSelectionFromNode(t);this.get("host").setSelection(n),this._displayDialogue()},_displayDialogue:function(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;this._rawImageDimensions=null;var e=this.getDialogue({headerContent:M.util.get_string("imageproperties",o),width:"480px",focusAfterHide:!0,focusOnShowSelector:r.INPUTURL});e.set("bodyContent",this._getDialogueContent()).show()},_loadPreviewImage:function(e){var t=new Image,r=this;t.onerror=function(){var e=r._form.one("."+n.IMAGEPREVIEW);e.setStyles({display:"none"}),r.getDialogue().centerDialogue()},t.onload=function(){var e,t,i,o,u;r._rawImageDimensions={width:this.width,height:this.height},e=r._form.one("."+n.INPUTWIDTH),t=e.get("value"),t===""&&(e.set("value",this.width),t=""+this.width),e=r._form.one("."+n.INPUTHEIGHT),i=e.get("value"),i===""&&(e.set("value",this.height),i=""+this.height),e=r._form.one("."+n.IMAGEPREVIEW),e.setAttribute("src",this.src),e.setStyles({display:"inline"}),e=r._form.one("."+n.INPUTCONSTRAIN),t.match(s.ISPERCENT)&&i.match(s.ISPERCENT)?e.set("checked",t===i):(this.width===0&&(this.width=1),this.height===0&&(this.height=1),o=Math.round(1e3*parseInt(t,10)/this.width),u=Math.round(1e3*parseInt(i,10)/this.height),e.set("checked",o===u)),r._autoAdjustSize(r),r.getDialogue().centerDialogue()},t.src=e},_getDialogueContent:function(){var t=e.Handlebars.compile(u),r=this.get("host").canShowFilepicker("image"),s=e.Node.create(t({elementid:this.get("host").get("elementid"),CSS:n,component:o,showFilepicker:r,alignments:i}));return this._form=s,this._applyImageProperties(this._form),this._form.one("."+n.INPUTURL).on("blur",this._urlChanged,this),this._form.one("."+n.IMAGEPRESENTATION).on("change",this._updateWarning,this),this._form.one("."+n.INPUTALT).on("change",this._updateWarning,this),this._form.one("."+n.INPUTWIDTH -).on("blur",this._autoAdjustSize,this),this._form.one("."+n.INPUTHEIGHT).on("blur",this._autoAdjustSize,this,!0),this._form.one("."+n.INPUTCONSTRAIN).on("change",function(e){e.target.get("checked")&&this._autoAdjustSize(e)},this),this._form.one("."+n.INPUTURL).on("blur",this._urlChanged,this),this._form.one("."+n.INPUTSUBMIT).on("click",this._setImage,this),r&&this._form.one("."+n.IMAGEBROWSER).on("click",function(){this.get("host").showFilepicker("image",this._filepickerCallback,this)},this),s},_autoAdjustSize:function(e,t){t=t||!1;var r=this._form.one("."+n.INPUTWIDTH),i="width",o=this._form.one("."+n.INPUTHEIGHT),u="height",a=this._form.one("."+n.INPUTCONSTRAIN),f=r.get("value"),l=o.get("value"),c=this._form.one("."+n.IMAGEPREVIEW),h,p;if(!this._rawImageDimensions)return;f===""&&(f=this._rawImageDimensions[i],r.set("value",f),f=r.get("value")),c.setStyles({width:null,height:null});if(!a.get("checked"))f.match(s.ISPERCENT)?(h=parseInt(f,10),p=this._rawImageDimensions.width/100*h,c.setStyle("width",p+"px")):c.setStyle("width",f+"px"),l.match(s.ISPERCENT)?(h=parseInt(l,10),p=this._rawImageDimensions.height/100*h,c.setStyle("height",p+"px")):c.setStyle("height",l+"px");else{if(t){var d;d=r,r=o,o=d,d=i,i=u,u=d,d=f,f=l,l=d}f.match(s.ISPERCENT)?(l=f,h=parseInt(f,10),p=this._rawImageDimensions.width/100*h,c.setStyle("width",p),p=this._rawImageDimensions.height/100*h,c.setStyle("height",p)):(l=Math.round(f/this._rawImageDimensions[i]*this._rawImageDimensions[u]),t?c.setStyles({width:l,height:f}):c.setStyles({width:f,height:l})),o.set("value",l)}},_filepickerCallback:function(e){if(e.url!==""){var t=this._form.one("."+n.INPUTURL);t.set("value",e.url),this._form.one("."+n.INPUTWIDTH).set("value",""),this._form.one("."+n.INPUTHEIGHT).set("value",""),this._loadPreviewImage(e.url)}},_applyImageProperties:function(e){var t=this._getSelectedImageProperties(),r=e.one("."+n.IMAGEPREVIEW),s;if(t===!1){r.setStyle("display","none");for(s in i)i[s].isDefault===!0&&(css=i[s].value+":"+i[s].name+";",e.one("."+n.INPUTALIGNMENT).set("value",css));e.one("."+n.INPUTALIGNMENT).getDOMNode().options.remove(i.length-1);return}t.align?(e.one("."+n.INPUTALIGNMENT).set("value",t.align),e.one("."+n.INPUTALIGNMENT).getDOMNode().options.remove(i.length-1)):e.one("."+n.INPUTALIGNMENT).set("value","style:customstyle;"),t.customstyle&&e.one("."+n.INPUTCUSTOMSTYLE).set("value",t.customstyle),t.width&&e.one("."+n.INPUTWIDTH).set("value",t.width),t.height&&e.one("."+n.INPUTHEIGHT).set("value",t.height),t.alt&&e.one("."+n.INPUTALT).set("value",t.alt),t.src&&(e.one("."+n.INPUTURL).set("value",t.src),this._loadPreviewImage(t.src)),t.presentation&&e.one("."+n.IMAGEPRESENTATION).set("checked","checked"),this._autoAdjustSize()},_getSelectedImageProperties:function(){var e={src:null,alt:null,width:null,height:null,align:"",presentation:!1},t=this.get("host").getSelectedNodes(),n,r,o,u,a;t&&(t=t.filter("img"));if(t&&t.size()){image=t.item(0),this._selectedImage=image,u=image.getAttribute("style"),e.customstyle=u,u=u.replace(/ /g,""),r=image.getAttribute("width"),r.match(s.ISPERCENT)||(r=parseInt(r,10)),o=image.getAttribute("height"),o.match(s.ISPERCENT)||(o=parseInt(o,10)),r!==0&&(e.width=r),o!==0&&(e.height=o);for(n in i){a=i[n].value+":"+i[n].name+";";if(u.indexOf(a)!==-1){margin="margin:"+i[n].margin+";",margin=margin.replace(/ /g,"");if(u.indexOf(margin)!==-1){e.align=a;break}}}return e.src=image.getAttribute("src"),e.alt=image.getAttribute("alt")||"",e.presentation=image.get("role")==="presentation",e}return this._selectedImage=null,!1},_urlChanged:function(){var e=this._form.one("."+n.INPUTURL);e.get("value")!==""&&this._loadPreviewImage(e.get("value"))},_setImage:function(t){var r=this._form,o=r.one("."+n.INPUTURL).get("value"),u=r.one("."+n.INPUTALT).get("value"),f=r.one("."+n.INPUTWIDTH).get("value"),l=r.one("."+n.INPUTHEIGHT).get("value"),c=r.one("."+n.INPUTALIGNMENT).get("value"),h="",p=r.one("."+n.IMAGEPRESENTATION).get("checked"),d=r.one("."+n.INPUTCONSTRAIN).get("checked"),v,m="",g,y=[],b=this.get("host");t.preventDefault();if(this._updateWarning())return;b.focus();if(o!==""){this._selectedImage?b.setSelection(b.getSelectionFromNode(this._selectedImage)):b.setSelection(this._currentSelection);if(c==="style:customstyle;")c="",m=r.one("."+n.INPUTCUSTOMSTYLE).get("value");else for(g in i)css=i[g].value+":"+i[g].name+";",c===css&&(h=" margin: "+i[g].margin+";");d&&y.push(n.RESPONSIVE);if(!f.match(s.ISPERCENT)&&isNaN(parseInt(f,10))){r.one("."+n.INPUTWIDTH).focus();return}if(!l.match(s.ISPERCENT)&&isNaN(parseInt(l,10))){r.one("."+n.INPUTHEIGHT).focus();return}template=e.Handlebars.compile(a),v=template({url:o,alt:u,width:f,height:l,presentation:p,alignment:c,margin:h,customstyle:m,classlist:y.join(" ")}),this.get("host").insertContentAtFocusPoint(v),this.markUpdated()}this.getDialogue({focusAfterHide:null}).hide()},_updateWarning:function(){var e=this._form,t=!0,r=e.one("."+n.INPUTALT).get("value"),i=e.one("."+n.IMAGEPRESENTATION).get("checked");return r===""&&!i?(e.one("."+n.IMAGEALTWARNING).setStyle("display","block"),e.one("."+n.INPUTALT).setAttribute("aria-invalid",!0),e.one("."+n.IMAGEPRESENTATION).setAttribute("aria-invalid",!0),t=!0):(e.one("."+n.IMAGEALTWARNING).setStyle("display","none"),e.one("."+n.INPUTALT).setAttribute("aria-invalid",!1),e.one("."+n.IMAGEPRESENTATION).setAttribute("aria-invalid",!1),t=!1),this.getDialogue().centerDialogue(),t}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); +YUI.add("moodle-atto_image-button",function(e,t){var n={RESPONSIVE:"img-responsive",INPUTALIGNMENT:"atto_image_alignment",INPUTALT:"atto_image_altentry",INPUTHEIGHT:"atto_image_heightentry",INPUTSUBMIT:"atto_image_urlentrysubmit",INPUTURL:"atto_image_urlentry",INPUTSIZE:"atto_image_size",INPUTWIDTH:"atto_image_widthentry",IMAGEALTWARNING:"atto_image_altwarning",IMAGEBROWSER:"openimagebrowser",IMAGEPRESENTATION:"atto_image_presentation",INPUTCONSTRAIN:"atto_image_constrain",INPUTCUSTOMSTYLE:"atto_image_customstyle",IMAGEPREVIEW:"atto_image_preview",IMAGEPREVIEWBOX:"atto_image_preview_box"},r={INPUTURL:"."+n.INPUTURL},i=[{name:"text-top",str:"alignment_top",value:"vertical-align",margin:"0 .5em"},{name:"middle",str:"alignment_middle",value:"vertical-align",margin:"0 .5em"},{name:"text-bottom",str:"alignment_bottom",value:"vertical-align",margin:"0 .5em",isDefault:!0},{name:"left",str:"alignment_left",value:"float",margin:"0 .5em 0 0"},{name:"right",str:"alignment_right",value:"float",margin:"0 0 0 .5em"},{name:"customstyle",str:"customstyle",value:"style"}],s={ISPERCENT:/\d+%/},o="atto_image",u='

{{#if showFilepicker}}{{/if}}

x

',a='{{alt}}';e.namespace("M.atto_image").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_selectedImage:null,_form:null,_rawImageDimensions:null,initializer:function(){this.addButton({icon:"e/insert_edit_image",callback:this._displayDialogue,tags:"img",tagMatchRequiresAll:!1}),this.editor.delegate("dblclick",this._handleDoubleClick,"img",this),this.editor.on("drop",this._handleDragDrop,this)},_handleDragDrop:function(t){var n=this,r=this.get("host"),i=e.Handlebars.compile(a);r.saveSelection(),t=t._event;if(t.dataTransfer&&t.dataTransfer.files&&t.dataTransfer.files.length&&/^image\//.test(t.dataTransfer.files[0].type)){var s=r.get("filepickeroptions").image,u=s.savepath===undefined?"/":s.savepath,f=new FormData,l=0,c="",h=new XMLHttpRequest,p="",d=Object.keys(s.repositories);t.preventDefault(),t.stopPropagation(),f.append("repo_upload_file",t.dataTransfer.files[0]),f.append("itemid",s.itemid);for(var v=0;v'; Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -206,6 +207,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi _rawImageDimensions: null, initializer: function() { + this.addButton({ icon: 'e/insert_edit_image', callback: this._displayDialogue, @@ -213,6 +215,121 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi tagMatchRequiresAll: false }); this.editor.delegate('dblclick', this._handleDoubleClick, 'img', this); + this.editor.on('drop', this._handleDragDrop, this); + }, + + /** + * Handle a drag and drop event with an image. + * + * @method _handleDragDrop + * @param {EventFacade} e + * @private + */ + _handleDragDrop: function(e) { + + var self = this, + host = this.get('host'), + template = Y.Handlebars.compile(IMAGETEMPLATE); + + host.saveSelection(); + e = e._event; + + // Only handle the event if an image file was dropped in. + if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length && /^image\//.test(e.dataTransfer.files[0].type)) { + + var options = host.get('filepickeroptions').image, + savepath = (options.savepath === undefined) ? '/' : options.savepath, + formData = new FormData(), + timestamp = 0, + uploadid = "", + xhr = new XMLHttpRequest(), + imagehtml = "", + keys = Object.keys(options.repositories); + + e.preventDefault(); + e.stopPropagation(); + formData.append('repo_upload_file', e.dataTransfer.files[0]); + formData.append('itemid', options.itemid); + + // List of repositories is an object rather than an array. This makes iteration more awkward. + for (var i = 0; i < keys.length; i++) { + if (options.repositories[keys[i]].type === 'upload') { + formData.append('repo_id', options.repositories[keys[i]].id); + break; + } + } + formData.append('env', options.env); + formData.append('sesskey', M.cfg.sesskey); + formData.append('client_id', options.client_id); + formData.append('savepath', savepath); + formData.append('ctx_id', options.context.id); + + // Insert spinner as a placeholder. + timestamp = new Date().getTime(); + uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp; + host.focus(); + host.restoreSelection(); + imagehtml = template({ + url: M.util.image_url("i/loading_small", 'moodle'), + alt: M.util.get_string('uploading', COMPONENTNAME), + id: uploadid + }); + host.insertContentAtFocusPoint(imagehtml); + self.markUpdated(); + + // Kick off a XMLHttpRequest. + xhr.onreadystatechange = function() { + var placeholder = self.editor.one('#' + uploadid), + result, + file, + newhtml, + newimage; + + if (xhr.readyState === 4) { + if (xhr.status === 200) { + result = JSON.parse(xhr.responseText); + if (result) { + if (result.error) { + if (placeholder) { + placeholder.remove(true); + } + return new M.core.ajaxException(result); + } + + file = result; + if (result.event && result.event === 'fileexists') { + // A file with this name is already in use here - rename to avoid conflict. + // Chances are, it's a different image (stored in a different folder on the user's computer). + // If the user wants to reuse an existing image, they can copy/paste it within the editor. + file = result.newfile; + } + + // Replace placeholder with actual image. + newhtml = template({ + url: file.url, + presentation: true + }); + newimage = Y.Node.create(newhtml); + if (placeholder) { + placeholder.replace(newimage); + } else { + self.editor.appendChild(newimage); + } + self.markUpdated(); + } + } else { + alert(M.util.get_string('servererror', 'moodle')); + if (placeholder) { + placeholder.remove(true); + } + } + } + }; + xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true); + xhr.send(formData); + } + return false; + }, /** diff --git a/lib/editor/atto/plugins/image/yui/src/button/js/button.js b/lib/editor/atto/plugins/image/yui/src/button/js/button.js index b8f286b005b..30711122c3f 100644 --- a/lib/editor/atto/plugins/image/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/image/yui/src/button/js/button.js @@ -163,6 +163,7 @@ var CSS = { '{{#if presentation}}role="presentation" {{/if}}' + 'style="{{alignment}}{{margin}}{{customstyle}}"' + '{{#if classlist}}class="{{classlist}}" {{/if}}' + + '{{#if id}}id="{{id}}" {{/if}}' + '/>'; Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { @@ -204,6 +205,7 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi _rawImageDimensions: null, initializer: function() { + this.addButton({ icon: 'e/insert_edit_image', callback: this._displayDialogue, @@ -211,6 +213,121 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi tagMatchRequiresAll: false }); this.editor.delegate('dblclick', this._handleDoubleClick, 'img', this); + this.editor.on('drop', this._handleDragDrop, this); + }, + + /** + * Handle a drag and drop event with an image. + * + * @method _handleDragDrop + * @param {EventFacade} e + * @private + */ + _handleDragDrop: function(e) { + + var self = this, + host = this.get('host'), + template = Y.Handlebars.compile(IMAGETEMPLATE); + + host.saveSelection(); + e = e._event; + + // Only handle the event if an image file was dropped in. + if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length && /^image\//.test(e.dataTransfer.files[0].type)) { + + var options = host.get('filepickeroptions').image, + savepath = (options.savepath === undefined) ? '/' : options.savepath, + formData = new FormData(), + timestamp = 0, + uploadid = "", + xhr = new XMLHttpRequest(), + imagehtml = "", + keys = Object.keys(options.repositories); + + e.preventDefault(); + e.stopPropagation(); + formData.append('repo_upload_file', e.dataTransfer.files[0]); + formData.append('itemid', options.itemid); + + // List of repositories is an object rather than an array. This makes iteration more awkward. + for (var i = 0; i < keys.length; i++) { + if (options.repositories[keys[i]].type === 'upload') { + formData.append('repo_id', options.repositories[keys[i]].id); + break; + } + } + formData.append('env', options.env); + formData.append('sesskey', M.cfg.sesskey); + formData.append('client_id', options.client_id); + formData.append('savepath', savepath); + formData.append('ctx_id', options.context.id); + + // Insert spinner as a placeholder. + timestamp = new Date().getTime(); + uploadid = 'moodleimage_' + Math.round(Math.random() * 100000) + '-' + timestamp; + host.focus(); + host.restoreSelection(); + imagehtml = template({ + url: M.util.image_url("i/loading_small", 'moodle'), + alt: M.util.get_string('uploading', COMPONENTNAME), + id: uploadid + }); + host.insertContentAtFocusPoint(imagehtml); + self.markUpdated(); + + // Kick off a XMLHttpRequest. + xhr.onreadystatechange = function() { + var placeholder = self.editor.one('#' + uploadid), + result, + file, + newhtml, + newimage; + + if (xhr.readyState === 4) { + if (xhr.status === 200) { + result = JSON.parse(xhr.responseText); + if (result) { + if (result.error) { + if (placeholder) { + placeholder.remove(true); + } + return new M.core.ajaxException(result); + } + + file = result; + if (result.event && result.event === 'fileexists') { + // A file with this name is already in use here - rename to avoid conflict. + // Chances are, it's a different image (stored in a different folder on the user's computer). + // If the user wants to reuse an existing image, they can copy/paste it within the editor. + file = result.newfile; + } + + // Replace placeholder with actual image. + newhtml = template({ + url: file.url, + presentation: true + }); + newimage = Y.Node.create(newhtml); + if (placeholder) { + placeholder.replace(newimage); + } else { + self.editor.appendChild(newimage); + } + self.markUpdated(); + } + } else { + alert(M.util.get_string('servererror', 'moodle')); + if (placeholder) { + placeholder.remove(true); + } + } + } + }; + xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true); + xhr.send(formData); + } + return false; + }, /**