Skip to content

Commit

Permalink
MDL-43996 editor_atto: add image drag and drop capability
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pauln authored and Jetha Chan committed Dec 5, 2014
1 parent d87bcfb commit 1461aee
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 4 deletions.
3 changes: 2 additions & 1 deletion lib/editor/atto/plugins/image/lang/en/atto_image.php
Expand Up @@ -42,4 +42,5 @@
$string['preview'] = 'Preview';
$string['saveimage'] = 'Save image';
$string['size'] = 'Size';
$string['width'] = 'Width';
$string['uploading'] = 'Uploading, please wait...';
$string['width'] = 'Width';
1 change: 1 addition & 0 deletions lib/editor/atto/plugins/image/lib.php
Expand Up @@ -49,6 +49,7 @@ function atto_image_strings_for_js() {
'presentationoraltrequired',
'size',
'width',
'uploading',
);

$PAGE->requires->strings_for_js($strings, 'atto_image');
Expand Down
2 changes: 1 addition & 1 deletion lib/editor/atto/plugins/image/version.php
Expand Up @@ -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).
Expand Up @@ -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, [], {
Expand Down Expand Up @@ -206,13 +207,129 @@ 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,
tags: 'img',
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;

},

/**
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -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, [], {
Expand Down Expand Up @@ -206,13 +207,129 @@ 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,
tags: 'img',
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;

},

/**
Expand Down

0 comments on commit 1461aee

Please sign in to comment.