From 669747a81c4b2ff823d1f20dc50899163c0a8a4a Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 28 Jun 2012 03:22:31 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 program/js/app.js |  459 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 340 insertions(+), 119 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index f3c91b2..a2307fd 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -17,8 +17,6 @@
  +-----------------------------------------------------------------------+
  | Requires: jquery.js, common.js, list.js                               |
  +-----------------------------------------------------------------------+
-
-  $Id$
 */
 
 function rcube_webmail()
@@ -54,9 +52,10 @@
 
   // set jQuery ajax options
   $.ajaxSetup({
-    cache:false,
-    error:function(request, status, err){ ref.http_error(request, status, err); },
-    beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
+    cache: false,
+    timeout: this.env.request_timeout * 1000,
+    error: function(request, status, err){ ref.http_error(request, status, err); },
+    beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
   });
 
   // set environment variable(s)
@@ -224,7 +223,7 @@
 
         this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', 'forward',
           'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'download',
-          'print', 'load-attachment', 'load-headers', 'forward-attachment'];
+          'print', 'load-attachment', 'show-headers', 'hide-headers', 'forward-attachment'];
 
         if (this.env.action == 'show' || this.env.action == 'preview') {
           this.enable_command(this.env.message_commands, this.env.uid);
@@ -272,7 +271,7 @@
         // show printing dialog
         else if (this.env.action == 'print' && this.env.uid)
           if (bw.safari)
-            window.setTimeout('window.print()', 10);
+            setTimeout('window.print()', 10);
           else
             window.print();
 
@@ -307,6 +306,10 @@
           }
           this.http_post(postact, postdata);
         }
+
+        // detect browser capabilities
+        if (!this.is_framed())
+          this.browser_capabilities_check();
 
         break;
 
@@ -455,6 +458,14 @@
     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 });
 
@@ -482,7 +493,7 @@
   /*********************************************************/
 
   // execute a specific command on the web client
-  this.command = function(command, props, obj)
+  this.command = function(command, props, obj, event)
   {
     var ret, uid, cid, url, flag;
 
@@ -580,10 +591,6 @@
           this.list_contacts(props);
         break;
 
-      case 'load-headers':
-        this.load_headers(obj);
-        break;
-
       case 'sort':
         var sort_order, sort_col = props;
 
@@ -632,7 +639,7 @@
           uid = this.get_single_uid();
           if (uid && (!this.env.uid || uid != this.env.uid)) {
             if (this.env.mailbox == this.env.drafts_mailbox)
-              this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
+              this.goto_url('compose', { _draft_uid: uid, _mbox: this.env.mailbox }, true);
             else
               this.show_message(uid);
           }
@@ -654,13 +661,14 @@
         break;
 
       case 'edit':
-        if (this.task=='addressbook' && (cid = this.get_single_cid()))
+        if (this.task == 'addressbook' && (cid = this.get_single_cid()))
           this.load_contact(cid, 'edit');
-        else if (this.task=='settings' && props)
+        else if (this.task == 'settings' && props)
           this.load_identity(props, 'edit-identity');
-        else if (this.task=='mail' && (cid = this.get_single_uid())) {
-          url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
-          this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
+        else if (this.task == 'mail' && (cid = this.get_single_uid())) {
+          url = { _mbox: this.env.mailbox };
+          url[this.env.mailbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'] = cid;
+          this.goto_url('compose', url, true);
         }
         break;
 
@@ -705,7 +713,7 @@
       case 'delete':
         // mail task
         if (this.task == 'mail')
-          this.delete_messages();
+          this.delete_messages(event);
         // addressbook task
         else if (this.task == 'addressbook')
           this.delete_contacts();
@@ -770,8 +778,8 @@
 
       case 'always-load':
         if (this.env.uid && this.env.sender) {
-          this.add_contact(urlencode(this.env.sender));
-          window.setTimeout(function(){ ref.command('load-images'); }, 300);
+          this.add_contact(this.env.sender);
+          setTimeout(function(){ ref.command('load-images'); }, 300);
           break;
         }
 
@@ -789,7 +797,7 @@
             qstring += '&_safe=1';
           this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment');
           if (this.attachment_win) {
-            window.setTimeout(function(){ ref.attachment_win.focus(); }, 10);
+            setTimeout(function(){ ref.attachment_win.focus(); }, 10);
             break;
           }
         }
@@ -912,19 +920,25 @@
         break;
 
       case 'savedraft':
+        var form = this.gui_objects.messageform, msgid;
+
         // Reset the auto-save timer
-        self.clearTimeout(this.save_timer);
+        clearTimeout(this.save_timer);
 
-        if (!this.gui_objects.messageform)
+        // saving Drafts is disabled
+        if (!form)
           break;
 
-        // if saving Drafts is disabled in main.inc.php
-        // or if compose form did not change
-        if (!this.env.drafts_mailbox || this.cmp_hash == this.compose_field_hash())
+        // compose form did not change
+        if (this.cmp_hash == this.compose_field_hash()) {
+          this.auto_save_start();
           break;
+        }
 
-        var form = this.gui_objects.messageform,
-          msgid = this.set_busy(true, 'savingmessage');
+        // re-set keep-alive timeout
+        this.start_keepalive();
+
+        msgid = this.set_busy(true, 'savingmessage');
 
         form.target = "savetarget";
         form._draft.value = '1';
@@ -940,7 +954,7 @@
           break;
 
         // Reset the auto-save timer
-        self.clearTimeout(this.save_timer);
+        clearTimeout(this.save_timer);
 
         // all checks passed, send message
         var lang = this.spellcheck_lang(),
@@ -953,13 +967,11 @@
         form.action = this.add_url(form.action, '_lang', lang);
         form.submit();
 
-        // clear timeout (sending could take longer)
-        clearTimeout(this.request_timer);
         break;
 
       case 'send-attachment':
         // Reset the auto-save timer
-        self.clearTimeout(this.save_timer);
+        clearTimeout(this.save_timer);
 
         this.upload_file(props || this.gui_objects.uploadform);
         break;
@@ -981,12 +993,12 @@
       case 'reply-list':
       case 'reply':
         if (uid = this.get_single_uid()) {
-          url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
+          url = {_reply_uid: uid, _mbox: this.env.mailbox};
           if (command == 'reply-all')
             // do reply-list, when list is detected and popup menu wasn't used 
-            url += '&_all=' + (!props && this.commands['reply-list'] ? 'list' : 'all');
+            url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
           else if (command == 'reply-list')
-            url += '&_all=list';
+            url._all = list;
 
           this.goto_url('compose', url, true);
         }
@@ -995,9 +1007,9 @@
       case 'forward-attachment':
       case 'forward':
         if (uid = this.get_single_uid()) {
-          url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
+          url = { _forward_uid: uid, _mbox: this.env.mailbox };
           if (command == 'forward-attachment' || (!props && this.env.forward_attachment))
-            url += '&_attachment=1';
+            url._attachment = 1;
           this.goto_url('compose', url, true);
         }
         break;
@@ -1006,7 +1018,7 @@
         if (uid = this.get_single_uid()) {
           ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
           if (this.printwin) {
-            window.setTimeout(function(){ ref.printwin.focus(); }, 20);
+            setTimeout(function(){ ref.printwin.focus(); }, 20);
             if (this.env.action != 'show')
               this.mark_message('read', uid);
           }
@@ -1017,13 +1029,13 @@
         if (uid = this.get_single_uid()) {
           ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
           if (this.sourcewin)
-            window.setTimeout(function(){ ref.sourcewin.focus(); }, 20);
+            setTimeout(function(){ ref.sourcewin.focus(); }, 20);
           }
         break;
 
       case 'download':
         if (uid = this.get_single_uid())
-          this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
+          this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 });
         break;
 
       // quicksearch
@@ -1076,7 +1088,7 @@
 
       case 'export':
         if (this.contact_list.rowcount > 0) {
-          this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
+          this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request });
         }
         break;
 
@@ -1103,7 +1115,7 @@
       default:
         var func = command.replace(/-/g, '_');
         if (this[func] && typeof this[func] === 'function') {
-          ret = this[func](props);
+          ret = this[func](props, obj);
         }
         break;
     }
@@ -1156,14 +1168,6 @@
     if (this.gui_objects.editform)
       this.lock_form(this.gui_objects.editform, a);
 
-    // clear pending timer
-    if (this.request_timer)
-      clearTimeout(this.request_timer);
-
-    // set timer for requests
-    if (a && this.env.request_timeout)
-      this.request_timer = window.setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000);
-
     return id;
   };
 
@@ -1202,19 +1206,12 @@
     return url.replace(/_task=[a-z]+/, '_task='+task);
   };
 
-  // called when a request timed out
-  this.request_timed_out = function()
-  {
-    this.set_busy(false);
-    this.display_message('Request timed out!', 'error');
-  };
-
   this.reload = function(delay)
   {
     if (this.is_framed())
       parent.rcmail.reload(delay);
     else if (delay)
-      window.setTimeout(function(){ rcmail.reload(); }, delay);
+      setTimeout(function(){ rcmail.reload(); }, delay);
     else if (window.location)
       location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
   };
@@ -1347,7 +1344,7 @@
     this.env.last_folder_target = null;
 
     if (this.folder_auto_timer) {
-      window.clearTimeout(this.folder_auto_timer);
+      clearTimeout(this.folder_auto_timer);
       this.folder_auto_timer = null;
       this.folder_auto_expand = null;
     }
@@ -1400,15 +1397,15 @@
             // if the folder is collapsed, expand it after 1sec and restart the drag & drop process.
             if (div.hasClass('collapsed')) {
               if (this.folder_auto_timer)
-                window.clearTimeout(this.folder_auto_timer);
+                clearTimeout(this.folder_auto_timer);
 
               this.folder_auto_expand = this.env.mailboxes[k].id;
-              this.folder_auto_timer = window.setTimeout(function() {
+              this.folder_auto_timer = setTimeout(function() {
                 rcmail.command('collapse-folder', rcmail.folder_auto_expand);
                 rcmail.drag_start(null);
               }, 1000);
             } else if (this.folder_auto_timer) {
-              window.clearTimeout(this.folder_auto_timer);
+              clearTimeout(this.folder_auto_timer);
               this.folder_auto_timer = null;
               this.folder_auto_expand = null;
             }
@@ -1560,7 +1557,7 @@
 
     // start timer for message preview (wait for double click)
     if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select)
-      this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
+      this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
     else if (this.env.contentframe)
       this.show_contentframe(false);
   };
@@ -1577,7 +1574,7 @@
           clearTimeout(this.preview_timer);
         if (this.preview_read_timer)
           clearTimeout(this.preview_read_timer);
-        this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
+        this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
       }
     }
   };
@@ -1592,7 +1589,7 @@
 
     var uid = list.get_single_selection();
     if (uid && this.env.mailbox == this.env.drafts_mailbox)
-      this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
+      this.goto_url('compose', { _draft_uid: uid, _mbox: this.env.mailbox }, true);
     else if (uid)
       this.show_message(uid, false, false);
   };
@@ -1830,7 +1827,7 @@
         html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
       }
       else if (c == 'attachment') {
-        if (/application\/|multipart\/m/.test(flags.ctype))
+        if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
           html = '<span class="attachment">&nbsp;</span>';
         else if (/multipart\/report/.test(flags.ctype))
           html = '<span class="report">&nbsp;</span>';
@@ -1851,8 +1848,11 @@
       else if (c == 'threads')
         html = expando;
       else if (c == 'subject') {
-        if (bw.ie)
+        if (bw.ie) {
           col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
+          if (bw.ie8)
+            tree = '<span></span>' + tree; // #1487821
+        }
         html = tree + cols[c];
       }
       else if (c == 'priority') {
@@ -1956,14 +1956,17 @@
     if (this.env.search_request)
       url += '&_search='+this.env.search_request;
 
-    if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
+    // add browser capabilities, so we can properly handle attachments
+    url += '&_caps='+urlencode(this.browser_capabilities());
+
+    if (preview && String(target.location.href).indexOf(url) >= 0)
       this.show_contentframe(true);
     else {
       this.location_href(this.env.comm_path+url, target, true);
 
       // mark as read and change mbox unread counter
-      if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
-        this.preview_read_timer = window.setTimeout(function() {
+      if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
+        this.preview_read_timer = setTimeout(function() {
           ref.set_message(id, 'unread', false);
           ref.update_thread_root(id, 'read');
           if (ref.env.unread_counts[ref.env.mailbox]) {
@@ -2552,7 +2555,7 @@
   };
 
   // delete selected messages from the current mailbox
-  this.delete_messages = function()
+  this.delete_messages = function(event)
   {
     var uid, i, len, trash = this.env.trash_mailbox,
       list = this.message_list,
@@ -2584,7 +2587,7 @@
     // if there is a trash mailbox defined and we're not currently in it
     else {
       // if shift was pressed delete it immediately
-      if (list && list.modkey == SHIFT_KEY) {
+      if ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
         if (confirm(this.get_label('deletemessagesconfirm')))
           this.permanently_remove_messages();
       }
@@ -2999,7 +3002,7 @@
       this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
       // add signature according to selected identity
       // if we have HTML editor, signature is added in callback
-      if (input_from.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
+      if (input_from.prop('type') == 'select-one') {
         this.change_identity(input_from[0]);
       }
     }
@@ -3152,7 +3155,7 @@
       tinyMCE.execCommand('mceAddControl', false, props.id);
 
       if (this.env.default_font)
-        window.setTimeout(function() {
+        setTimeout(function() {
           $(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font);
         }, 500);
     }
@@ -3251,7 +3254,7 @@
   this.auto_save_start = function()
   {
     if (this.env.draft_autosave)
-      this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
+      this.save_timer = setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
 
     // Unlock interface now that saving is complete
     this.busy = false;
@@ -3260,20 +3263,11 @@
   this.compose_field_hash = function(save)
   {
     // check input fields
-    var ed, str = '',
-      value_to = $("[name='_to']").val(),
-      value_cc = $("[name='_cc']").val(),
-      value_bcc = $("[name='_bcc']").val(),
-      value_subject = $("[name='_subject']").val();
+    var ed, i, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject'];
 
-    if (value_to)
-      str += value_to+':';
-    if (value_cc)
-      str += value_cc+':';
-    if (value_bcc)
-      str += value_bcc+':';
-    if (value_subject)
-      str += value_subject+':';
+    for (i=0; i<hash_fields.length; i++)
+      if (val = $('[name="_' + hash_fields[i] + '"]').val())
+        str += val + ':';
 
     if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
       str += ed.getContent();
@@ -3467,12 +3461,7 @@
       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) {
@@ -3491,6 +3480,13 @@
   {
     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);
 
@@ -3536,7 +3532,7 @@
 
   this.upload_progress_start = function(action, name)
   {
-    window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
+    setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
       this.env.upload_progress_time * 1000);
   };
 
@@ -3641,7 +3637,7 @@
   {
     this.display_message(msg, type);
     // before redirect we need to wait some time for Chrome (#1486177)
-    window.setTimeout(function(){ ref.list_mailbox(); }, 500);
+    setTimeout(function(){ ref.list_mailbox(); }, 500);
   };
 
 
@@ -3699,11 +3695,11 @@
       case 37:  // left
       case 39:  // right
         if (mod != SHIFT_KEY)
-	      return;
+          return;
     }
 
     // start timer
-    this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
+    this.ksearch_timer = setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
     this.ksearch_input = obj;
 
     return true;
@@ -3818,7 +3814,7 @@
       return;
 
     // ...new search value contains old one and previous search was not finished or its result was empty
-    if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || !ac.num) && this.env.contacts && !this.env.contacts.length)
+    if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || ac.num <= 0) && this.env.contacts && !this.env.contacts.length)
       return;
 
     var i, lock, source, xhr, reqid = new Date().getTime(),
@@ -3832,7 +3828,7 @@
 
     for (i=0; i<threads; i++) {
       source = this.ksearch_data.sources.shift();
-      if (threads > 1 && source === null)
+      if (threads > 1 && source === undefined)
         break;
 
       post_data._source = source ? source : '';
@@ -4011,7 +4007,7 @@
       source = this.env.source ? this.env.address_sources[this.env.source] : null;
 
     if (id = list.get_single_selection())
-      this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
+      this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
     else if (this.env.contentframe)
       this.show_contentframe(false);
 
@@ -4719,11 +4715,11 @@
   {
     if (form && form.elements._photo.value) {
       this.async_upload_form(form, 'upload-photo', function(e) {
-        rcmail.set_busy(false, null, rcmail.photo_upload_id);
+        rcmail.set_busy(false, null, rcmail.file_upload_id);
       });
 
       // display upload indicator
-      this.photo_upload_id = this.set_busy(true, 'uploading');
+      this.file_upload_id = this.set_busy(true, 'uploading');
     }
   };
 
@@ -4738,8 +4734,8 @@
 
   this.photo_upload_end = function()
   {
-    this.set_busy(false, null, this.photo_upload_id);
-    delete this.photo_upload_id;
+    this.set_busy(false, null, this.file_upload_id);
+    delete this.file_upload_id;
   };
 
   this.set_photo_actions = function(id)
@@ -4940,7 +4936,7 @@
 
     // submit request with appended token
     if (confirm(this.get_label('deleteidentityconfirm')))
-      this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
+      this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true);
 
     return true;
   };
@@ -5553,7 +5549,7 @@
       }
       // add element and set timeout
       this.messages[key].elements.push(id);
-      window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
+      setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
       return id;
     }
 
@@ -5573,7 +5569,7 @@
     this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj });
 
     if (timeout > 0)
-      window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
+      setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
     return id;
   };
 
@@ -5853,14 +5849,14 @@
   };
 
   // display all-headers row and fetch raw message headers
-  this.load_headers = function(elem)
+  this.show_headers = function(props, elem)
   {
     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
       return;
 
     $(elem).removeClass('show-headers').addClass('hide-headers');
     $(this.gui_objects.all_headers_row).show();
-    elem.onclick = function() { rcmail.hide_headers(elem); };
+    elem.onclick = function() { rcmail.command('hide-headers', '', elem); };
 
     // fetch headers only once
     if (!this.gui_objects.all_headers_box.innerHTML) {
@@ -5870,14 +5866,14 @@
   };
 
   // hide all-headers row
-  this.hide_headers = function(elem)
+  this.hide_headers = function(props, elem)
   {
     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
       return;
 
     $(elem).removeClass('hide-headers').addClass('show-headers');
     $(this.gui_objects.all_headers_row).hide();
-    elem.onclick = function() { rcmail.load_headers(elem); };
+    elem.onclick = function() { rcmail.command('show-headers', '', elem); };
   };
 
 
@@ -6001,7 +5997,7 @@
     return $.ajax({
       type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
       success: function(data){ ref.http_response(data); },
-      error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
+      error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
     });
   };
 
@@ -6033,7 +6029,7 @@
     return $.ajax({
       type: 'POST', url: url, data: postdata, dataType: 'json',
       success: function(data){ ref.http_response(data); },
-      error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
+      error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
     });
   };
 
@@ -6165,7 +6161,7 @@
   };
 
   // handle HTTP request errors
-  this.http_error = function(request, status, err, lock)
+  this.http_error = function(request, status, err, lock, action)
   {
     var errmsg = request.statusText;
 
@@ -6174,6 +6170,16 @@
 
     if (request.status && errmsg)
       this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
+    else if (status == 'timeout')
+      this.display_message(this.get_label('requesttimedout'), 'error');
+    else if (request.status == 0 && status != 'abort')
+      this.display_message(this.get_label('servererror') + ' (No connection)', 'error');
+
+    // re-send keep-alive requests after 30 seconds
+    if (action == 'keep-alive')
+      setTimeout(function(){ ref.keep_alive(); }, 30000);
+    else if (action == 'check-recent')
+      setTimeout(function(){ ref.check_for_recent(false); }, 30000);
   };
 
   // post the given form to a hidden iframe
@@ -6224,15 +6230,137 @@
     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') + (this.env.filedrop.single ? '' : '[]'),
+      boundary = '------multipartformboundary' + (new Date).getTime(),
+      dashdash = '--', crlf = '\r\n',
+      multipart = dashdash + boundary + crlf;
+
+    if (!files || !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
+      if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
+        ref.file_upload_id = ref.set_busy(true, 'uploading');
+
+      // 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||ref.env.cid||'', _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 j=0, i=0, f; j <= 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';
+
+      // file name contains non-ASCII characters, do UTF8-binary string conversion.
+      if (!formdata && /[^\x20-\x7E]/.test(f.name))
+        f.name_bin = unescape(encodeURIComponent(f.name));
+
+      // filter by file type if requested
+      if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
+        // TODO: show message to user
+        continue;
+      }
+
+      // do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
+      if (formdata) {
+        formdata.append(fieldname, f);
+        if (j == 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, j) {
+          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 (j == last)  // we're done, submit the data
+              return submit_data();
+          }
+        })(f,j);
+        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 (j == last)
+          return submit_data();
+      }
+
+      j++;
+    }
+  };
+
+
   // starts interval for keep-alive/check-recent signal
   this.start_keepalive = function()
   {
+    if (!this.env.keep_alive || this.env.framed)
+      return;
+
     if (this._int)
       clearInterval(this._int);
 
-    if (this.env.keep_alive && !this.env.framed && this.task == 'mail' && this.gui_objects.mailboxlist)
+    if (this.task == 'mail' && this.gui_objects.mailboxlist)
       this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
-    else if (this.env.keep_alive && !this.env.framed && this.task != 'login' && this.env.action != 'print')
+    else if (this.task != 'login' && this.env.action != 'print')
       this._int = setInterval(function(){ ref.keep_alive(); }, this.env.keep_alive * 1000);
   };
 
@@ -6293,7 +6421,7 @@
       return obj.selectionEnd;
     else if (document.selection && document.selection.createRange) {
       var range = document.selection.createRange();
-      if (range.parentElement()!=obj)
+      if (range.parentElement() != obj)
         return 0;
 
       var gm = range.duplicate();
@@ -6377,6 +6505,99 @@
       $(elem).click(function() { rcmail.register_protocol_handler(name); return false; });
   };
 
+  // Checks browser capabilities eg. PDF support, TIF support
+  this.browser_capabilities_check = function()
+  {
+    if (!this.env.browser_capabilities)
+      this.env.browser_capabilities = {};
+
+    if (this.env.browser_capabilities.pdf === undefined)
+      this.env.browser_capabilities.pdf = this.pdf_support_check();
+
+    if (this.env.browser_capabilities.flash === undefined)
+      this.env.browser_capabilities.flash = this.flash_support_check();
+
+    if (this.env.browser_capabilities.tif === undefined)
+      this.tif_support_check();
+  };
+
+  // Returns browser capabilities string
+  this.browser_capabilities = function()
+  {
+    if (!this.env.browser_capabilities)
+      return '';
+
+    var n, ret = [];
+
+    for (n in this.env.browser_capabilities)
+      ret.push(n + '=' + this.env.browser_capabilities[n]);
+
+    return ret.join();
+  };
+
+  this.tif_support_check = function()
+  {
+    var img = new Image();
+
+    img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
+    img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
+    img.src = 'program/blank.tif';
+  };
+
+  this.pdf_support_check = function()
+  {
+    var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
+      plugins = navigator.plugins,
+      len = plugins.length,
+      regex = /Adobe Reader|PDF|Acrobat/i;
+
+    if (plugin && plugin.enabledPlugin)
+        return 1;
+
+    if (window.ActiveXObject) {
+      try {
+        if (axObj = new ActiveXObject("AcroPDF.PDF"))
+          return 1;
+      }
+      catch (e) {}
+      try {
+        if (axObj = new ActiveXObject("PDF.PdfCtrl"))
+          return 1;
+      }
+      catch (e) {}
+    }
+
+    for (i=0; i<len; i++) {
+      plugin = plugins[i];
+      if (typeof plugin === 'String') {
+        if (regex.test(plugin))
+          return 1;
+      }
+      else if (plugin.name && regex.test(plugin.name))
+        return 1;
+    }
+
+    return 0;
+  };
+
+  this.flash_support_check = function()
+  {
+    var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
+
+    if (plugin && plugin.enabledPlugin)
+        return 1;
+
+    if (window.ActiveXObject) {
+      try {
+        if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
+          return 1;
+      }
+      catch (e) {}
+    }
+
+    return 0;
+  };
+
 }  // end object rcube_webmail
 
 

--
Gitblit v1.9.1