New feature to add mail attachments using drag & drop on HTML5 enabled browsers

pull/13/head
Thomas Bruederli 13 years ago
parent e29515a504
commit ae6d2de17f

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail CHANGELOG Roundcube Webmail
=========================== ===========================
- Add mail attachments using drag & drop on HTML5 enabled browsers
- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585) - Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
- Decode header value in rcube_mime::get() by default (#1488511) - Decode header value in rcube_mime::get() by default (#1488511)
- Fix errors with enabled PHP magic_quotes_sybase option (#1488506) - Fix errors with enabled PHP magic_quotes_sybase option (#1488506)

@ -458,6 +458,14 @@ function rcube_webmail()
if (this.gui_objects.folderlist) if (this.gui_objects.folderlist)
this.gui_containers.foldertray = $(this.gui_objects.folderlist); this.gui_containers.foldertray = $(this.gui_objects.folderlist);
// activate html5 file drop feature (if browser supports it and if configured)
if (this.gui_objects.filedrop && this.env.filedrop && ((XMLHttpRequest && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) {
$(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); });
$(this.gui_objects.filedrop).addClass('droptarget')
.bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); })
.get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false);
}
// trigger init event hook // trigger init event hook
this.triggerEvent('init', { task:this.task, action:this.env.action }); this.triggerEvent('init', { task:this.task, action:this.env.action });
@ -3453,12 +3461,7 @@ function rcube_webmail()
var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>', var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
ts = frame_name.replace(/^rcmupload/, ''); ts = frame_name.replace(/^rcmupload/, '');
if (this.env.loadingicon) this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false });
content = '<img src="'+this.env.loadingicon+'" alt="" class="uploading" />'+content;
content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload" class="cancelupload">'
+ (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + content;
this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false });
// upload progress support // upload progress support
if (this.env.upload_progress_time) { if (this.env.upload_progress_time) {
@ -3478,6 +3481,13 @@ function rcube_webmail()
if (!this.gui_objects.attachmentlist) if (!this.gui_objects.attachmentlist)
return false; return false;
if (!att.complete && ref.env.loadingicon)
att.html = '<img src="'+ref.env.loadingicon+'" alt="" class="uploading" />' + att.html;
if (!att.complete && att.frame)
att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
+ (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html); var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
// replace indicator's li // replace indicator's li
@ -6220,6 +6230,121 @@ function rcube_webmail()
return frame_name; return frame_name;
}; };
// html5 file-drop API
this.document_drag_hover = function(e, over)
{
e.preventDefault();
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active');
};
this.file_drag_hover = function(e, over)
{
e.preventDefault();
e.stopPropagation();
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('hover');
};
// handler when files are dropped to a designated area.
// compose a multipart form data and submit it to the server
this.file_dropped = function(e)
{
// abort event and reset UI
this.file_drag_hover(e, false);
// prepare multipart form data composition
var files = e.target.files || e.dataTransfer.files,
formdata = window.FormData ? new FormData() : null,
fieldname = this.env.filedrop.fieldname || '_file',
boundary = '------multipartformboundary' + (new Date).getTime(),
dashdash = '--', crlf = '\r\n',
multipart = dashdash + boundary + crlf;
if (!file || !files.length)
return;
// inline function to submit the files to the server
var submit_data = function() {
var multiple = files.length > 1,
ts = new Date().getTime(),
content = '<span>' + (multiple ? ref.get_label('uploadingmany') : files[0].name) + '</span>';
// add to attachments list
ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false });
// complete multipart content and post request
multipart += dashdash + boundary + dashdash + crlf;
$.ajax({
type: 'POST',
dataType: 'json',
url: ref.url(ref.env.filedrop.action||'upload', { _id:ref.env.compose_id||'', _uploadid:ts, _remote:1 }),
contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
processData: false,
data: formdata || multipart,
beforeSend: function(xhr, s) { if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; },
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); }
});
};
// get contents of all dropped files
var last = this.env.filedrop.single ? 0 : files.length - 1;
for (var i=0, f; i <= last && (f = files[i]); i++) {
if (!f.name) f.name = f.fileName;
if (!f.size) f.size = f.fileSize;
if (!f.type) f.type = 'application/octet-stream';
// binary encode file name
if (!formdata && /[^\x20-\x7E]/.test(f.name))
f.name_bin = unescape(encodeURIComponent(f.name));
if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
// TODO: show message to user
continue;
}
// the easy way with FormData (FF4+, Chrome, Safari)
if (formdata) {
formdata.append(fieldname + '[]', f);
if (i == last)
return submit_data();
}
// use FileReader supporetd by Firefox 3.6
else if (window.FileReader) {
var reader = new FileReader();
// closure to pass file properties to async callback function
reader.onload = (function(file, i) {
return function(e) {
multipart += 'Content-Disposition: form-data; name="' + fieldname + '[]"';
multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
multipart += 'Content-Length: ' + file.size + crlf;
multipart += 'Content-Type: ' + file.type + crlf + crlf;
multipart += e.target.result + crlf;
multipart += dashdash + boundary + crlf;
if (i == last) // we're done, submit the data
return submit_data();
}
})(f,i);
reader.readAsBinaryString(f);
}
// Firefox 3
else if (f.getAsBinary) {
multipart += 'Content-Disposition: form-data; name="' + fieldname + '[]"';
multipart += '; filename="' + (f.name_bin || f.name) + '"' + crlf;
multipart += 'Content-Length: ' + f.size + crlf;
multipart += 'Content-Type: ' + f.type + crlf + crlf;
multipart += f.getAsBinary() + crlf;
multipart += dashdash + boundary +crlf;
if (i == last)
return submit_data();
}
}
};
// starts interval for keep-alive/check-recent signal // starts interval for keep-alive/check-recent signal
this.start_keepalive = function() this.start_keepalive = function()
{ {

@ -1590,6 +1590,19 @@ function rcmail_contacts_list($attrib = array())
} }
/**
* Register a certain container as active area to drop files onto
*/
function compose_file_drop_area($attrib)
{
global $OUTPUT;
if ($attrib['id']) {
$OUTPUT->add_gui_object('filedrop', $attrib['id']);
$OUTPUT->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
}
}
// register UI objects // register UI objects
$OUTPUT->add_handlers(array( $OUTPUT->add_handlers(array(
@ -1599,6 +1612,7 @@ $OUTPUT->add_handlers(array(
'composeattachmentlist' => 'rcmail_compose_attachment_list', 'composeattachmentlist' => 'rcmail_compose_attachment_list',
'composeattachmentform' => 'rcmail_compose_attachment_form', 'composeattachmentform' => 'rcmail_compose_attachment_form',
'composeattachment' => 'rcmail_compose_attachment_field', 'composeattachment' => 'rcmail_compose_attachment_field',
'filedroparea' => 'compose_file_drop_area',
'priorityselector' => 'rcmail_priority_selector', 'priorityselector' => 'rcmail_priority_selector',
'editorselector' => 'rcmail_editor_selector', 'editorselector' => 'rcmail_editor_selector',
'receiptcheckbox' => 'rcmail_receipt_checkbox', 'receiptcheckbox' => 'rcmail_receipt_checkbox',

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

@ -1168,11 +1168,36 @@ div.message-part blockquote blockquote blockquote {
bottom: 0; bottom: 0;
width: 240px; width: 240px;
background: #f0f0f0; background: #f0f0f0;
border-left: 1px solid #ddd; border-style: solid;
border-color: #f0f0f0 #f0f0f0 #f0f0f0 #ddd;
border-width: 1px;
padding: 8px; padding: 8px;
overflow: auto; overflow: auto;
} }
#compose-attachments.droptarget {
background-image: url(images/filedrop.png);
background-position: center bottom;
background-repeat: no-repeat;
}
#compose-attachments.droptarget.hover,
#compose-attachments.droptarget.active {
border-color: #019bc6;
box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
-o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
}
#compose-attachments.droptarget.hover {
background-color: #d9ecf4;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
}
.defaultSkin table.mceLayout, .defaultSkin table.mceLayout,
.defaultSkin table.mceLayout tr.mceLast td { .defaultSkin table.mceLayout tr.mceLast td {
border: 0 !important; border: 0 !important;

@ -156,6 +156,7 @@
<roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform();return false" tabindex="10" /> <roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform();return false" tabindex="10" />
</div> </div>
<roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" /> <roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" />
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div> </div>
</div> </div>

Loading…
Cancel
Save