diff options
author | Thomas Bruederli <thomas@roundcube.net> | 2012-06-06 17:35:21 +0200 |
---|---|---|
committer | Thomas Bruederli <thomas@roundcube.net> | 2012-06-06 17:35:21 +0200 |
commit | ae6d2de17f740915e47c64d210680eb5e9850335 (patch) | |
tree | d385b6a56aaca33cadeda6ece5bf7e2ae2b4c8e7 /program | |
parent | e29515a504e862065684f6f41ab632ab38087c8d (diff) |
New feature to add mail attachments using drag & drop on HTML5 enabled browsers
Diffstat (limited to 'program')
-rw-r--r-- | program/js/app.js | 137 | ||||
-rw-r--r-- | program/steps/mail/compose.inc | 14 |
2 files changed, 145 insertions, 6 deletions
diff --git a/program/js/app.js b/program/js/app.js index 9d6f7e808..b3d3aed5c 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -458,6 +458,14 @@ function rcube_webmail() if (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 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>', ts = frame_name.replace(/^rcmupload/, ''); - if (this.env.loadingicon) - 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 }); + this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false }); // upload progress support if (this.env.upload_progress_time) { @@ -3478,6 +3481,13 @@ function rcube_webmail() if (!this.gui_objects.attachmentlist) 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); // replace indicator's li @@ -6220,6 +6230,121 @@ function rcube_webmail() 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 this.start_keepalive = function() { diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 306de3608..70f657d8d 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -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 $OUTPUT->add_handlers(array( @@ -1599,6 +1612,7 @@ $OUTPUT->add_handlers(array( 'composeattachmentlist' => 'rcmail_compose_attachment_list', 'composeattachmentform' => 'rcmail_compose_attachment_form', 'composeattachment' => 'rcmail_compose_attachment_field', + 'filedroparea' => 'compose_file_drop_area', 'priorityselector' => 'rcmail_priority_selector', 'editorselector' => 'rcmail_editor_selector', 'receiptcheckbox' => 'rcmail_receipt_checkbox', |