mirror of
https://github.com/moodle/moodle.git
synced 2025-03-21 08:00:37 +01:00
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.
This commit is contained in:
parent
d87bcfb325
commit
1461aee88a
@ -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';
|
@ -49,6 +49,7 @@ function atto_image_strings_for_js() {
|
||||
'presentationoraltrequired',
|
||||
'size',
|
||||
'width',
|
||||
'uploading',
|
||||
);
|
||||
|
||||
$PAGE->requires->strings_for_js($strings, 'atto_image');
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
|
File diff suppressed because one or more lines are too long
@ -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;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user