From 2c0d3e1dd0a3df01c8adea0de5f2826f0bcb9434 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 25 Jul 2014 11:40:37 -0400
Subject: [PATCH] Fix drag-n-drop after folder move/create (#1489648)

---
 program/js/app.js |  857 +++++++++++++++++++++++++++++++-------------------------
 1 files changed, 476 insertions(+), 381 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index d12dd81..f25b808 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -46,12 +46,12 @@
   this.messages = {};
   this.group2expand = {};
   this.http_request_jobs = {};
-  this.menu_stack = new Array();
+  this.menu_stack = [];
 
   // webmail client settings
   this.dblclick_time = 500;
   this.message_time = 5000;
-  this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
+  this.identifier_expr = /[^0-9a-z_-]/gi;
 
   // environment defaults
   this.env = {
@@ -314,9 +314,9 @@
               });
 
             // avoid textarea loosing focus when hitting the save-response button/link
-            for (var i=0; this.buttons['save-response'] && i < this.buttons['save-response'].length; i++) {
-              $('#'+this.buttons['save-response'][i].id).mousedown(function(e){ return rcube_event.cancel(e); })
-            }
+            $.each(this.buttons['save-response'] || [], function (i, v) {
+              $('#' + v.id).mousedown(function(e){ return rcube_event.cancel(e); })
+            });
           }
 
           // init message compose form
@@ -346,10 +346,10 @@
           this.contact_list
             .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
             .addEventListener('select', function(o) { ref.compose_recipient_select(o); })
-            .addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); })
+            .addEventListener('dblclick', function(o) { ref.compose_add_recipient(); })
             .addEventListener('keypress', function(o) {
               if (o.key_pressed == o.ENTER_KEY) {
-                if (!ref.compose_add_recipient('to')) {
+                if (!ref.compose_add_recipient()) {
                   // execute link action on <enter> if not a recipient entry
                   if (o.last_selected && String(o.last_selected).charAt(0) == 'G') {
                     $(o.rows[o.last_selected].obj).find('a').first().click();
@@ -358,6 +358,9 @@
               }
             })
             .init();
+
+          // remember last focused address field
+          $('#_to,#_cc,#_bcc').focus(function() { ref.env.focused_field = this; });
         }
 
         if (this.gui_objects.addressbookslist) {
@@ -403,15 +406,23 @@
             .addEventListener('dragend', function(e) { ref.drag_end(e); })
             .init();
 
-          if (this.env.cid)
-            this.contact_list.highlight_row(this.env.cid);
-
           this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); };
 
           $(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); });
 
           this.update_group_commands();
           this.command('list');
+        }
+
+        if (this.gui_objects.savedsearchlist) {
+          this.savedsearchlist = new rcube_treelist_widget(this.gui_objects.savedsearchlist, {
+            id_prefix: 'rcmli',
+            id_encode: this.html_identifier_encode,
+            id_decode: this.html_identifier_decode
+          });
+
+          this.savedsearchlist.addEventListener('select', function(node) {
+            ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }); });
         }
 
         this.set_page_buttons();
@@ -471,9 +482,6 @@
             })
             .init()
             .focus();
-
-          if (this.env.iid)
-            this.identity_list.highlight_row(this.env.iid);
         }
         else if (this.gui_objects.sectionslist) {
           this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:true});
@@ -536,7 +544,7 @@
     // select first input field in an edit form
     if (this.gui_objects.editform)
       $("input,select,textarea", this.gui_objects.editform)
-        .not(':hidden').not(':disabled').first().select();
+        .not(':hidden').not(':disabled').first().select().focus();
 
     // unset contentframe variable if preview_pane is enabled
     if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible'))
@@ -557,6 +565,7 @@
     // init treelist widget
     if (this.gui_objects.folderlist && window.rcube_treelist_widget) {
       this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
+          selectable: true,
           id_prefix: 'rcmli',
           id_encode: this.html_identifier_encode,
           id_decode: this.html_identifier_decode,
@@ -621,7 +630,7 @@
   {
     var ret, uid, cid, url, flag, aborted = false;
 
-    if (obj && obj.blur && !(event || rcube_event.is_keyboard(event)))
+    if (obj && obj.blur && !(event && rcube_event.is_keyboard(event)))
       obj.blur();
 
     // do nothing if interface is locked by other command (with exception for searching reset)
@@ -649,17 +658,18 @@
 
       // remove copy from local storage if compose screen is left intentionally
       this.remove_compose_data(this.env.compose_id);
+      this.compose_skip_unsavedcheck = true;
     }
 
     this.last_command = command;
 
     // process external commands
     if (typeof this.command_handlers[command] === 'function') {
-      ret = this.command_handlers[command](props, obj);
+      ret = this.command_handlers[command](props, obj, event);
       return ret !== undefined ? ret : (obj ? false : true);
     }
     else if (typeof this.command_handlers[command] === 'string') {
-      ret = window[this.command_handlers[command]](props, obj);
+      ret = window[this.command_handlers[command]](props, obj, event);
       return ret !== undefined ? ret : (obj ? false : true);
     }
 
@@ -710,6 +720,7 @@
 
           if (win) {
             this.save_compose_form_local();
+            this.compose_skip_unsavedcheck = true;
             $("input[name='_action']", form).val('compose');
             form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
             form.target = win.name;
@@ -1344,7 +1355,7 @@
   this.command_enabled = function(cmd)
   {
     return this.commands[cmd];
-  }
+  };
 
   // lock/unlock interface
   this.set_busy = function(a, message, id)
@@ -1386,13 +1397,14 @@
   // switch to another application task
   this.switch_task = function(task)
   {
-    if (this.task===task && task!='mail')
+    if (this.task === task && task != 'mail')
       return;
 
     var url = this.get_task_url(task);
+
     if (task == 'mail')
       url += '&_mbox=INBOX';
-    else if (task == 'logout')
+    else if (task == 'logout' && !this.env.server_error)
       this.clear_compose_data();
 
     this.redirect(url);
@@ -1444,10 +1456,10 @@
 
   this.save_pref = function(prop)
   {
-    var request = {'_name': prop.name, '_value': prop.value};
+    var request = {_name: prop.name, _value: prop.value};
 
     if (prop.session)
-      request['_session'] = prop.session;
+      request._session = prop.session;
     if (prop.env)
       this.env[prop.env] = prop.value;
 
@@ -1587,7 +1599,7 @@
 
       // select the folder if one of its childs is currently selected
       // don't select if it's virtual (#1488346)
-      if (this.env.mailbox && this.env.mailbox.startsWith(name + this.env.delimiter) && !node.virtual)
+      if (!node.virtual && this.env.mailbox && this.env.mailbox.startsWith(name + this.env.delimiter))
         this.command('list', name);
     }
     else {
@@ -1629,7 +1641,7 @@
     }
 
     // reset popup menus; delayed to have updated menu_stack data
-    window.setTimeout(function(e){
+    setTimeout(function(e){
       var obj, skip, config, id, i, parents = $(target).parents();
       for (i = ref.menu_stack.length - 1; i >= 0; i--) {
         id = ref.menu_stack[i];
@@ -1669,6 +1681,9 @@
     var target = e.target || {},
       keyCode = rcube_event.get_keycode(e);
 
+    // save global reference for keyboard detection on click events in IE
+    rcube_event._last_keyboard_event = e;
+
     if (e.keyCode != 27 && (!this.menu_keyboard_active || target.nodeName == 'TEXTAREA' || target.nodeName == 'SELECT')) {
       return true;
     }
@@ -1678,8 +1693,8 @@
       case 40:
       case 63232: // "up", in safari keypress
       case 63233: // "down", in safari keypress
-        focus_menu_item(mod = keyCode == 38 || keyCode == 63232 ? -1 : 1);
-        break;
+        focus_menu_item(keyCode == 38 || keyCode == 63232 ? -1 : 1);
+        return rcube_event.cancel(e);
 
       case 9:   // tab
         if (this.focused_menu) {
@@ -1842,9 +1857,6 @@
         return (this.env.mailboxes[id]
             && !this.env.mailboxes[id].virtual
             && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0;
-
-      case 'settings':
-        return id != this.env.mailbox ? 1 : 0;
 
       case 'addressbook':
         var target;
@@ -2428,6 +2440,9 @@
       url._framed = 1;
     }
 
+    if (this.env.uid)
+      url._uid = this.env.uid;
+
     // load message list to target frame/window
     if (mbox) {
       this.set_busy(true, 'loading');
@@ -2474,7 +2489,7 @@
         selection.push(selected[i]);
 
     this.message_list.selection = selection;
-  }
+  };
 
   // expand all threads with unread children
   this.expand_unread = function()
@@ -2487,8 +2502,10 @@
         this.message_list.expand_all(r);
         this.set_unread_children(r.uid);
       }
+
       new_row = new_row.nextSibling;
     }
+
     return false;
   };
 
@@ -2705,8 +2722,8 @@
     }
 
     // update unread_children for roots
-    for (var i=0; i<roots.length; i++)
-      this.set_unread_children(roots[i].uid);
+    for (r=0; r<roots.length; r++)
+      this.set_unread_children(roots[r].uid);
 
     return count;
   };
@@ -3357,63 +3374,7 @@
     }
 
     // check for locally stored compose data
-    if (window.localStorage) {
-      var key, formdata, index = this.local_storage_get_item('compose.index', []);
-
-      for (i = 0; i < index.length; i++) {
-        key = index[i];
-        formdata = this.local_storage_get_item('compose.' + key, null, true);
-        if (!formdata) {
-          continue;
-        }
-        // restore saved copy of current compose_id
-        if (formdata.changed && key == this.env.compose_id) {
-          this.restore_compose_form(key, html_mode);
-          break;
-        }
-        // skip records from 'other' drafts
-        if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) {
-          continue;
-        }
-        // skip records on reply
-        if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) {
-          continue;
-        }
-        // show dialog asking to restore the message
-        if (formdata.changed && formdata.session != this.env.session_id) {
-          this.show_popup_dialog(
-            this.get_label('restoresavedcomposedata')
-              .replace('$date', new Date(formdata.changed).toLocaleString())
-              .replace('$subject', formdata._subject)
-              .replace(/\n/g, '<br/>'),
-            this.get_label('restoremessage'),
-            [{
-              text: this.get_label('restore'),
-              click: function(){
-                ref.restore_compose_form(key, html_mode);
-                ref.remove_compose_data(key);  // remove old copy
-                ref.save_compose_form_local();  // save under current compose_id
-                $(this).dialog('close');
-              }
-            },
-            {
-              text: this.get_label('delete'),
-              click: function(){
-                ref.remove_compose_data(key);
-                $(this).dialog('close');
-              }
-            },
-            {
-              text: this.get_label('ignore'),
-              click: function(){
-                $(this).dialog('close');
-              }
-            }]
-          );
-          break;
-        }
-      }
-    }
+    this.compose_restore_dialog(0, html_mode)
 
     if (input_to.val() == '')
       input_to.focus();
@@ -3430,6 +3391,72 @@
     // start the auto-save timer
     this.auto_save_start();
   };
+
+  this.compose_restore_dialog = function(j, html_mode)
+  {
+    var i, key, formdata, index = this.local_storage_get_item('compose.index', []);
+
+    var show_next = function(i) {
+      if (++i < index.length)
+        ref.compose_restore_dialog(i, html_mode)
+    }
+
+    for (i = j || 0; i < index.length; i++) {
+      key = index[i];
+      formdata = this.local_storage_get_item('compose.' + key, null, true);
+      if (!formdata) {
+        continue;
+      }
+      // restore saved copy of current compose_id
+      if (formdata.changed && key == this.env.compose_id) {
+        this.restore_compose_form(key, html_mode);
+        break;
+      }
+      // skip records from 'other' drafts
+      if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) {
+        continue;
+      }
+      // skip records on reply
+      if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) {
+        continue;
+      }
+      // show dialog asking to restore the message
+      if (formdata.changed && formdata.session != this.env.session_id) {
+        this.show_popup_dialog(
+          this.get_label('restoresavedcomposedata')
+            .replace('$date', new Date(formdata.changed).toLocaleString())
+            .replace('$subject', formdata._subject)
+            .replace(/\n/g, '<br/>'),
+          this.get_label('restoremessage'),
+          [{
+            text: this.get_label('restore'),
+            click: function(){
+              ref.restore_compose_form(key, html_mode);
+              ref.remove_compose_data(key);  // remove old copy
+              ref.save_compose_form_local();  // save under current compose_id
+              $(this).dialog('close');
+            }
+          },
+          {
+            text: this.get_label('delete'),
+            click: function(){
+              ref.remove_compose_data(key);
+              $(this).dialog('close');
+              show_next(i);
+            }
+          },
+          {
+            text: this.get_label('ignore'),
+            click: function(){
+              $(this).dialog('close');
+              show_next(i);
+            }
+          }]
+        );
+        break;
+      }
+    }
+  }
 
   this.init_address_input_events = function(obj, props)
   {
@@ -3459,6 +3486,7 @@
     form._draft.value = draft ? '1' : '';
     form.action = this.add_url(form.action, '_unlock', msgid);
     form.action = this.add_url(form.action, '_lang', lang);
+    form.action = this.add_url(form.action, '_framed', 1);
 
     // register timer to notify about connection timeout
     this.submit_timer = setTimeout(function(){
@@ -3482,6 +3510,12 @@
 
   this.compose_add_recipient = function(field)
   {
+    // find last focused field name
+    if (!field) {
+      field = $(this.env.focused_field).filter(':visible');
+      field = field.length ? field.attr('id').replace('_', '') : 'to';
+    }
+
     var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter;
 
     if (this.contact_list && this.contact_list.selection.length) {
@@ -3550,26 +3584,32 @@
         myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>')
           .appendTo(document.body),
         prompt_value = $('<input>').attr({type: 'text', size: 30}).val(this.get_label('nosubject'))
-          .appendTo(myprompt);
+          .appendTo(myprompt),
+        save_func = function() {
+          input_subject.val(prompt_value.val());
+          myprompt.dialog('close');
+          ref.command(cmd, { nocheck:true });  // repeat command which triggered this
+        };
 
-      buttons[this.get_label('cancel')] = function(){
+      buttons[this.get_label('sendmessage')] = function() {
+        save_func($(this));
+      };
+      buttons[this.get_label('cancel')] = function() {
         input_subject.focus();
         $(this).dialog('close');
-      };
-      buttons[this.get_label('sendmessage')] = function(){
-        input_subject.val(prompt_value.val());
-        $(this).dialog('close');
-        ref.command(cmd, { nocheck:true });  // repeat command which triggered this
       };
 
       myprompt.dialog({
         modal: true,
         resizable: false,
         buttons: buttons,
-        close: function(event, ui) { $(this).remove() }
+        close: function(event, ui) { $(this).remove(); }
       });
 
-      prompt_value.select();
+      prompt_value.select().keydown(function(e) {
+        if (e.which == 13) save_func();
+      });
+
       return false;
     }
 
@@ -3593,6 +3633,11 @@
     if (!result && e) {
       // fix selector value if operation failed
       $(e.target).filter('select').val(props.html ? 'plain' : 'html');
+    }
+
+    if (result) {
+      // update internal format flag
+      $("input[name='_is_html']").val(props.html ? 1 : 0);
     }
 
     return result;
@@ -3642,7 +3687,7 @@
       $(this).dialog('close');
     };
 
-    this.show_popup_dialog(html, this.gettext('savenewresponse'), buttons);
+    this.show_popup_dialog(html, this.gettext('newresponse'), buttons);
 
     $('#ffresponsetext').val(text);
     $('#ffresponsename').select();
@@ -3690,10 +3735,7 @@
     // submit delete request
     if (key && confirm(this.get_label('deleteresponseconfirm'))) {
       this.http_post('settings/delete-response', { _key: key }, false);
-      return true;
     }
-
-    return false;
   };
 
   // updates spellchecker buttons on state change
@@ -3701,8 +3743,9 @@
   {
     var active = this.editor.spellcheck_state();
 
-    if (this.buttons.spellcheck)
-      $('#'+this.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected');
+    $.each(this.buttons.spellcheck || [], function(i, v) {
+      $('#' + v.id)[active ? 'addClass' : 'removeClass']('selected');
+    });
 
     return active;
   };
@@ -3749,6 +3792,7 @@
 
     // always remove local copy upon saving as draft
     this.remove_compose_data(this.env.compose_id);
+    this.compose_skip_unsavedcheck = false;
   };
 
   this.auto_save_start = function()
@@ -3773,6 +3817,21 @@
           ref.compose_type_activity_last = ref.compose_type_activity;
         }
       }, 5000);
+
+      $(window).unload(function() {
+        // remove copy from local storage if compose screen is left after warning
+        if (!ref.env.server_error)
+          ref.remove_compose_data(ref.env.compose_id);
+      });
+    }
+
+    // check for unsaved changes before leaving the compose page
+    if (!window.onbeforeunload) {
+      window.onbeforeunload = function() {
+        if (!ref.compose_skip_unsavedcheck && ref.cmp_hash != ref.compose_field_hash()) {
+          return ref.get_label('notsentwarning');
+        }
+      };
     }
 
     // Unlock interface now that saving is complete
@@ -3839,15 +3898,16 @@
       }
     });
 
-    if (window.localStorage && !empty) {
+    if (!empty) {
       var index = this.local_storage_get_item('compose.index', []),
         key = this.env.compose_id;
 
-        if ($.inArray(key, index) < 0) {
-          index.push(key);
-        }
-        this.local_storage_set_item('compose.' + key, formdata, true);
-        this.local_storage_set_item('compose.index', index);
+      if ($.inArray(key, index) < 0) {
+        index.push(key);
+      }
+
+      this.local_storage_set_item('compose.' + key, formdata, true);
+      this.local_storage_set_item('compose.index', index);
     }
   };
 
@@ -3879,28 +3939,25 @@
   // remove stored compose data from localStorage
   this.remove_compose_data = function(key)
   {
-    if (window.localStorage) {
-      var index = this.local_storage_get_item('compose.index', []);
+    var index = this.local_storage_get_item('compose.index', []);
 
-      if ($.inArray(key, index) >= 0) {
-        this.local_storage_remove_item('compose.' + key);
-        this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; }));
-      }
+    if ($.inArray(key, index) >= 0) {
+      this.local_storage_remove_item('compose.' + key);
+      this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; }));
     }
   };
 
   // clear all stored compose data of this user
   this.clear_compose_data = function()
   {
-    if (window.localStorage) {
-      var i, index = this.local_storage_get_item('compose.index', []);
+    var i, index = this.local_storage_get_item('compose.index', []);
 
-      for (i=0; i < index.length; i++) {
-        this.local_storage_remove_item('compose.' + index[i]);
-      }
-      this.local_storage_remove_item('compose.index');
+    for (i=0; i < index.length; i++) {
+      this.local_storage_remove_item('compose.' + index[i]);
     }
-  }
+
+    this.local_storage_remove_item('compose.index');
+  };
 
 
   this.change_identity = function(obj, show_sig)
@@ -3920,18 +3977,16 @@
         return;
     }
 
-    var i, rx,
-      id = obj.options[obj.selectedIndex].value,
+    var id = obj.options[obj.selectedIndex].value,
       sig = this.env.identity,
       delim = this.env.recipients_separator,
-      rx_delim = RegExp.escape(delim),
-      headers = ['replyto', 'bcc'];
+      rx_delim = RegExp.escape(delim);
 
     // update reply-to/bcc fields with addresses defined in identities
-    for (i in headers) {
-      var key = headers[i],
-        old_val = sig && this.env.identities[sig] ? this.env.identities[sig][key] : '',
-        new_val = id && this.env.identities[id] ? this.env.identities[id][key] : '',
+    $.each(['replyto', 'bcc'], function() {
+      var rx, key = this,
+        old_val = sig && ref.env.identities[sig] ? ref.env.identities[sig][key] : '',
+        new_val = id && ref.env.identities[id] ? ref.env.identities[id][key] : '',
         input = $('[name="_'+key+'"]'), input_val = input.val();
 
       // remove old address(es)
@@ -3958,7 +4013,7 @@
 
       if (old_val || new_val)
         input.val(input_val).change();
-    }
+    });
 
     // enable manual signature insert
     if (this.env.signatures && this.env.signatures[id]) {
@@ -4047,6 +4102,14 @@
     if (upload_id)
       this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id});
 
+    if (!this.env.attachments)
+      this.env.attachments = {};
+
+    if (upload_id && this.env.attachments[upload_id])
+      delete this.env.attachments[upload_id];
+
+    this.env.attachments[name] = att;
+
     if (!this.gui_objects.attachmentlist)
       return false;
 
@@ -4055,7 +4118,7 @@
 
     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;
+        + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="'+this.get_label('cancel')+'" />' : this.get_label('cancel')) + '</a>' + att.html;
 
     var indicator, li = $('<li>');
 
@@ -4072,10 +4135,9 @@
       li.appendTo(this.gui_objects.attachmentlist);
     }
 
-    if (upload_id && this.env.attachments[upload_id])
-      delete this.env.attachments[upload_id];
-
-    this.env.attachments[name] = att;
+    // set tabindex attribute
+    var tabindex = $(this.gui_objects.attachmentlist).attr('data-tabindex') || '0';
+    li.find('a').attr('tabindex', tabindex);
 
     return true;
   };
@@ -4114,7 +4176,7 @@
 
   this.upload_progress_update = function(param)
   {
-    var elem = $('#'+param.name + '> span');
+    var elem = $('#'+param.name + ' > span');
 
     if (!elem.length || !param.text)
       return;
@@ -4269,23 +4331,25 @@
   this.sent_successfully = function(type, msg, folders)
   {
     this.display_message(msg, type);
+    this.compose_skip_unsavedcheck = true;
 
     if (this.env.extwin) {
-      var rc = this.opener();
       this.lock_form(this.gui_objects.messageform);
+
+      var rc = this.opener();
       if (rc) {
         rc.display_message(msg, type);
         // refresh the folder where sent message was saved or replied message comes from
         if (folders && rc.env.task == 'mail' && rc.env.action == '' && $.inArray(rc.env.mailbox, folders) >= 0) {
-          // @TODO: try with 'checkmail' here when #1485186 is fixed. See also #1489249.
-          rc.command('list');
+          rc.command('checkmail');
         }
       }
-      setTimeout(function(){ window.close() }, 1000);
+
+      setTimeout(function() { window.close(); }, 1000);
     }
     else {
       // before redirect we need to wait some time for Chrome (#1486177)
-      setTimeout(function(){ ref.list_mailbox(); }, 500);
+      setTimeout(function() { ref.list_mailbox(); }, 500);
     }
   };
 
@@ -4505,10 +4569,6 @@
       this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').attr('role', 'listbox')
         .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
       this.ksearch_pane.__ul = ul[0];
-
-      // register (delegate) event handlers
-      ul.on('mouseover', 'li', function(e){ ref.ksearch_select(e.target); })
-        .on('onmouseup', 'li', function(e){ ref.ksearch_click(e.target); })
     }
 
     ul = this.ksearch_pane.__ul;
@@ -4531,7 +4591,7 @@
     // add each result line to list
     if (results && (len = results.length)) {
       for (i=0; i < len && maxlen > 0; i++) {
-        text = typeof results[i] === 'object' ? results[i].name : results[i];
+        text = typeof results[i] === 'object' ? (results[i].display || results[i].name) : results[i];
         type = typeof results[i] === 'object' ? results[i].type : '';
         id = i + this.env.contacts.length;
         $('<li>').attr('id', 'rcmkSearchItem' + id)
@@ -4539,6 +4599,8 @@
           .html(this.quote_html(text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>'))
           .addClass(type || '')
           .appendTo(ul)
+          .mouseover(function() { ref.ksearch_select(this); })
+          .mouseup(function() { ref.ksearch_click(this); })
           .get(0)._rcm_id = id;
         maxlen -= 1;
       }
@@ -4731,7 +4793,8 @@
         $(this.gui_objects.addresslist_title).html(this.get_label('contacts'));
     }
 
-    this.select_folder(folder, '', true);
+    if (!this.env.search_id)
+      this.select_folder(folder, '', true);
 
     // load contacts remotely
     if (this.gui_objects.contactslist) {
@@ -4963,7 +5026,7 @@
   {
     var selection = this.contact_list ? this.contact_list.get_selection() : [];
 
-    // exit if no mailbox specified or if selection is empty
+    // exit if no contact specified or if selection is empty
     if (!selection.length && !this.env.cid)
       return;
 
@@ -5155,7 +5218,7 @@
 
       // find list (UL) element
       if (type == 'contactsearch')
-        ul = this.gui_objects.folderlist;
+        ul = this.gui_objects.savedsearchlist;
       else
         ul = $('ul.groups', this.get_folder_li(this.env.source,'',true));
 
@@ -5256,7 +5319,7 @@
         .html(prop.name);
 
     this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
-    this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, true);
+    this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, 'contactgroup');
 
     this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) });
   };
@@ -5538,7 +5601,7 @@
         .html(name),
       prop = { name:name, id:id };
 
-    this.treelist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch');
+    this.savedsearchlist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch');
     this.select_folder(key,'',true);
     this.enable_command('search-delete', true);
     this.env.search_id = id;
@@ -5564,7 +5627,7 @@
   this.remove_search_item = function(id)
   {
     var li, key = 'S'+id;
-    if (this.treelist.remove(key)) {
+    if (this.savedsearchlist.remove(key)) {
       this.triggerEvent('search_delete', { id:id, li:li });
     }
 
@@ -5584,7 +5647,13 @@
     }
 
     this.reset_qsearch();
-    this.select_folder('S'+id, '', true);
+
+    if (this.savedsearchlist) {
+      this.treelist.select('');
+      this.savedsearchlist.select('S'+id);
+    }
+    else
+      this.select_folder('S'+id, '', true);
 
     // reset vars
     this.env.current_page = 1;
@@ -5654,10 +5723,8 @@
       id = this.env.iid ? this.env.iid : selection[0];
 
     // submit request with appended token
-    if (confirm(this.get_label('deleteidentityconfirm')))
-      this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true);
-
-    return true;
+    if (id && confirm(this.get_label('deleteidentityconfirm')))
+      this.http_post('settings/delete-identity', { _iid: id }, true);
   };
 
   this.update_identity_row = function(id, name, add)
@@ -5701,6 +5768,23 @@
         frame.location.href = this.env.blankpage;
       }
     }
+
+    this.enable_command('delete', false);
+  };
+
+  this.remove_identity = function(id)
+  {
+    var frame, list = this.identity_list,
+      rid = this.html_identifier(id);
+
+    if (list && id) {
+      list.remove_row(rid);
+      if (this.env.contentframe && (frame = this.get_frame_window(this.env.contentframe))) {
+        frame.location.href = this.env.blankpage;
+      }
+    }
+
+    this.enable_command('delete', false);
   };
 
 
@@ -5714,64 +5798,53 @@
 
     this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$');
 
-    this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
-      {multiselect:false, draggable:true, keyboard:true, toggleselect:true});
+    this.subscription_list = new rcube_treelist_widget(this.gui_objects.subscriptionlist, {
+        selectable: true,
+        id_prefix: 'rcmli',
+        id_encode: this.html_identifier_encode,
+        id_decode: this.html_identifier_decode
+    });
+
     this.subscription_list
-      .addEventListener('select', function(o){ ref.subscription_select(o); })
-      .addEventListener('dragstart', function(o){ ref.drag_active = true; })
-      .addEventListener('dragend', function(o){ ref.subscription_move_folder(o); })
-      .addEventListener('initrow', function (row) {
-        row.obj.onmouseover = function() { ref.focus_subscription(row.id); };
-        row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); };
-      })
-      .init()
-      .focus();
+      .addEventListener('select', function(node) { ref.subscription_select(node.id); })
+      .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
+      .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
+      .draggable({cancel: 'li.mailbox.root'})
+      .droppable({
+        // @todo: find better way, accept callback is executed for every folder
+        // on the list when dragging starts (and stops), this is slow, but
+        // I didn't find a method to check droptarget on over event
+        accept: function(node) {
+          var source_folder = ref.folder_id2name($(node).attr('id')),
+            dest_folder = ref.folder_id2name(this.id),
+            source = ref.env.subscriptionrows[source_folder],
+            dest = ref.env.subscriptionrows[dest_folder];
 
-    $('#mailboxroot')
-      .mouseover(function(){ ref.focus_subscription(this.id); })
-      .mouseout(function(){ ref.unfocus_subscription(this.id); })
-  };
+          return source && !source[2]
+            && dest_folder != source_folder.replace(ref.last_sub_rx, '')
+            && !dest_folder.startsWith(source_folder + ref.env.delimiter);
+        },
+        drop: function(e, ui) {
+          var source = ref.folder_id2name(ui.draggable.attr('id')),
+            dest = ref.folder_id2name(this.id);
 
-  this.focus_subscription = function(id)
-  {
-    var row, folder;
-
-    if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
-      if (this.env.subscriptionrows[id] &&
-          (folder = this.env.subscriptionrows[id][0]) !== null
-      ) {
-        if (this.check_droptarget(folder) &&
-            !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] &&
-            folder != this.env.mailbox.replace(this.last_sub_rx, '') &&
-            !folder.startsWith(this.env.mailbox + this.env.delimiter)
-        ) {
-          this.env.dstfolder = folder;
-          $(row).addClass('droptarget');
+          ref.subscription_move_folder(source, dest);
         }
-      }
+      });
   };
 
-  this.unfocus_subscription = function(id)
+  this.folder_id2name = function(id)
   {
-    var row = $('#'+id);
-
-    this.env.dstfolder = null;
-
-    if (row.length && this.env.subscriptionrows[id])
-      row.removeClass('droptarget');
-    else
-      $(this.subscription_list.frame).removeClass('droptarget');
+    return ref.html_identifier_decode(id.replace(/^rcmli/, ''));
   };
 
-  this.subscription_select = function(list)
+  this.subscription_select = function(id)
   {
-    var id, folder;
+    var folder;
 
-    if (list && (id = list.get_single_selection()) &&
-        (folder = this.env.subscriptionrows['rcmrow'+id])
-    ) {
-      this.env.mailbox = folder[0];
-      this.show_folder(folder[0]);
+    if (id && id != '*' && (folder = this.env.subscriptionrows[id])) {
+      this.env.mailbox = id;
+      this.show_folder(id);
       this.enable_command('delete-folder', !folder[2]);
     }
     else {
@@ -5781,24 +5854,18 @@
     }
   };
 
-  this.subscription_move_folder = function(list)
+  this.subscription_move_folder = function(from, to)
   {
-    if (this.env.mailbox && this.env.dstfolder !== null &&
-        this.env.dstfolder != this.env.mailbox &&
-        this.env.dstfolder != this.env.mailbox.replace(this.last_sub_rx, '')
-    ) {
-      var path = this.env.mailbox.split(this.env.delimiter),
+    if (from && to !== null && from != to && to != from.replace(this.last_sub_rx, '')) {
+      var path = from.split(this.env.delimiter),
         basename = path.pop(),
-        newname = this.env.dstfolder === '' ? basename : this.env.dstfolder + this.env.delimiter + basename;
+        newname = to === '' || to === '*' ? basename : to + this.env.delimiter + basename;
 
-      if (newname != this.env.mailbox) {
-        this.http_post('rename-folder', {_folder_oldname: this.env.mailbox, _folder_newname: newname}, this.set_busy(true, 'foldermoving'));
-        this.subscription_list.draglayer.hide();
+      if (newname != from) {
+        this.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname},
+          this.set_busy(true, 'foldermoving'));
       }
     }
-
-    this.drag_active = false;
-    this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
   };
 
   // tell server to create and subscribe a new mailbox
@@ -5810,51 +5877,54 @@
   // delete a specific mailbox with all its messages
   this.delete_folder = function(name)
   {
-    var id = this.get_folder_row_id(name ? name : this.env.mailbox),
-      folder = this.env.subscriptionrows[id][0];
+    if (!name)
+      name = this.env.mailbox;
 
-    if (folder && confirm(this.get_label('deletefolderconfirm'))) {
-      var lock = this.set_busy(true, 'folderdeleting');
-      this.http_post('delete-folder', {_mbox: folder}, lock);
+    if (name && confirm(this.get_label('deletefolderconfirm'))) {
+      this.http_post('delete-folder', {_mbox: name}, this.set_busy(true, 'folderdeleting'));
     }
   };
 
   // Add folder row to the table and initialize it
-  this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
+  this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders)
   {
     if (!this.gui_objects.subscriptionlist)
       return false;
 
-    var row, n, tmp, tmp_name, rowid, collator,
-      folders = [], list = [], slist = [],
-      tbody = this.gui_objects.subscriptionlist.tBodies[0],
-      refrow = $('tr', tbody).get(1),
-      id = 'rcmrow'+((new Date).getTime());
+    // disable drag-n-drop temporarily
+    this.subscription_list.draggable('destroy').droppable('destroy');
 
-    if (!refrow) {
+    var row, n, tmp, tmp_name, rowid, collator, pos, p, parent = '',
+      folders = [], list = [], slist = [],
+      list_element = $(this.gui_objects.subscriptionlist);
+      row = refrow ? refrow : $($('li', list_element).get(1)).clone(true);
+
+    if (!row.length) {
       // Refresh page if we don't have a table row to clone
       this.goto_url('folders');
       return false;
     }
 
-    // clone a table row if there are existing rows
-    row = $(refrow).clone(true);
-
     // set ID, reset css class
-    row.attr({id: id, 'class': class_name});
+    row.attr({id: 'rcmli' + this.html_identifier_encode(id), 'class': class_name});
+
+    if (!refrow || !refrow.length) {
+      // remove old subfolders and toggle
+      $('ul,div.treetoggle', row).remove();
+    }
 
     // set folder name
-    row.find('td:first').html(display_name);
+    $('a:first', row).text(display_name);
 
     // update subscription checkbox
-    $('input[name="_subscribed[]"]', row).val(name)
+    $('input[name="_subscribed[]"]:first', row).val(id)
       .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
 
     // add to folder/row-ID map
     this.env.subscriptionrows[id] = [name, display_name, false];
 
     // copy folders data to an array for sorting
-    $.each(this.env.subscriptionrows, function(k, v) { folders.push(v); });
+    $.each(this.env.subscriptionrows, function(k, v) { v[3] = k; folders.push(v); });
 
     try {
       // use collator if supported (FF29, IE11, Opera15, Chrome24)
@@ -5866,63 +5936,106 @@
     folders.sort(function(a, b) {
       var i, f1, f2,
         path1 = a[0].split(ref.env.delimiter),
-        path2 = b[0].split(ref.env.delimiter);
+        path2 = b[0].split(ref.env.delimiter),
+        len = path1.length;
 
-      for (i=0; i<path1.length; i++) {
+      for (i=0; i<len; i++) {
         f1 = path1[i];
         f2 = path2[i];
 
         if (f1 !== f2) {
+          if (f2 === undefined)
+            return 1;
           if (collator)
             return collator.compare(f1, f2);
           else
             return f1 < f2 ? -1 : 1;
         }
+        else if (i == len-1) {
+          return -1
+        }
       }
     });
 
     for (n in folders) {
+      p = folders[n][3];
       // protected folder
       if (folders[n][2]) {
-        tmp_name = folders[n][0] + this.env.delimiter;
+        tmp_name = p + this.env.delimiter;
         // prefix namespace cannot have subfolders (#1488349)
         if (tmp_name == this.env.prefix_ns)
           continue;
-        slist.push(folders[n][0]);
+        slist.push(p);
         tmp = tmp_name;
       }
       // protected folder's child
-      else if (tmp && folders[n][0].startsWith(tmp))
-        slist.push(folders[n][0]);
+      else if (tmp && p.startsWith(tmp))
+        slist.push(p);
       // other
       else {
-        list.push(folders[n][0]);
+        list.push(p);
         tmp = null;
       }
     }
 
     // check if subfolder of a protected folder
     for (n=0; n<slist.length; n++) {
-      if (name.startsWith(slist[n] + this.env.delimiter))
-        rowid = this.get_folder_row_id(slist[n]);
+      if (id.startsWith(slist[n] + this.env.delimiter))
+        rowid = slist[n];
     }
 
     // find folder position after sorting
     for (n=0; !rowid && n<list.length; n++) {
-      if (n && list[n] == name)
-        rowid = this.get_folder_row_id(list[n-1]);
+      if (n && list[n] == id)
+        rowid = list[n-1];
     }
 
     // add row to the table
-    if (rowid)
-      $('#'+rowid).after(row);
-    else
-      row.appendTo(tbody);
+    if (rowid && (n = this.subscription_list.get_item(rowid, true))) {
+      // find parent folder
+      if (pos = id.lastIndexOf(this.env.delimiter)) {
+        parent = id.substring(0, pos);
+        parent = this.subscription_list.get_item(parent, true);
+
+        // add required tree elements to the parent if not already there
+        if (!$('div.treetoggle', parent).length) {
+          $('<div>&nbsp;</div>').addClass('treetoggle collapsed').appendTo(parent);
+        }
+        if (!$('ul', parent).length) {
+          $('<ul>').css('display', 'none').appendTo(parent);
+        }
+      }
+
+      if (parent && n == parent) {
+        $('ul:first', parent).append(row);
+      }
+      else {
+        while (p = $(n).parent().parent().get(0)) {
+          if (parent && p == parent)
+            break;
+          if (!$(p).is('li.mailbox'))
+            break;
+          n = p;
+        }
+
+        $(n).after(row);
+      }
+    }
+    else {
+      list_element.append(row);
+    }
+
+    // add subfolders
+    $.extend(this.env.subscriptionrows, subfolders || {});
 
     // update list widget
-    this.subscription_list.clear_selection();
-    if (!skip_init)
-      this.init_subscription_list();
+    this.subscription_list.reset(true);
+    this.subscription_select();
+
+    // expand parent
+    if (parent) {
+      this.subscription_list.expand(this.folder_id2name(parent.id));
+    }
 
     row = row.get(0);
     if (row.scrollIntoView)
@@ -5932,114 +6045,71 @@
   };
 
   // replace an existing table row with a new folder line (with subfolders)
-  this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
+  this.replace_folder_row = function(oldid, id, name, display_name, is_protected, class_name)
   {
     if (!this.gui_objects.subscriptionlist) {
       if (this.is_framed)
-        return parent.rcmail.replace_folder_row(oldfolder, newfolder, display_name, is_protected, class_name);
+        return parent.rcmail.replace_folder_row(oldid, id, name, display_name, is_protected, class_name);
+
       return false;
     }
 
-    var i, n, len, name, dispname, oldrow, tmprow, row, level,
-      tbody = this.gui_objects.subscriptionlist.tBodies[0],
-      folders = this.env.subscriptionrows,
-      id = this.get_folder_row_id(oldfolder),
-      prefix_len = oldfolder.length,
-      subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
-      // find subfolders of renamed folder
-      list = this.get_subfolders(oldfolder);
+    var subfolders = {},
+      row = this.subscription_list.get_item(oldid, true),
+      parent = $(row).parent(),
+      old_folder = this.env.subscriptionrows[oldid],
+      prefix_len_id = oldid.length,
+      prefix_len_name = old_folder[0].length,
+      subscribed = $('input[name="_subscribed[]"]:first', row).prop('checked');
 
     // no renaming, only update class_name
-    if (oldfolder == newfolder) {
-      $('#'+id).attr('class', class_name || '');
-      this.subscription_list.focus();
+    if (oldid == id) {
+      $(row).attr('class', class_name || '');
       return;
     }
 
-    // replace an existing table row
-    this._remove_folder_row(id);
-    row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
+    // update subfolders
+    $('li', row).each(function() {
+      var fname = ref.folder_id2name(this.id),
+        folder = ref.env.subscriptionrows[fname],
+        newid = id + fname.slice(prefix_len_id);
 
-    // detect tree depth change
-    if (len = list.length) {
-      level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
+      this.id = 'rcmli' + ref.html_identifier_encode(newid);
+      $('input[name="_subscribed[]"]:first', this).val(newid);
+      folder[0] = name + folder[0].slice(prefix_len_name);
+
+      subfolders[newid] = folder;
+      delete ref.env.subscriptionrows[fname];
+    });
+
+    // get row off the list
+    row = $(row).detach();
+
+    delete this.env.subscriptionrows[oldid];
+
+    // remove parent list/toggle elements if not needed
+    if (parent.get(0) != this.gui_objects.subscriptionlist && !$('li', parent).length) {
+      $('ul,div.treetoggle', parent.parent()).remove();
     }
 
-    // move subfolders to the new branch
-    for (n=0; n<len; n++) {
-      id = list[n];
-      name = this.env.subscriptionrows[id][0];
-      dispname = this.env.subscriptionrows[id][1];
-      oldrow = $('#'+id);
-      tmprow = oldrow.clone(true);
-      oldrow.remove();
-      row.after(tmprow);
-      row = tmprow;
-      // update folder index
-      name = newfolder + name.slice(prefix_len);
-      $('input[name="_subscribed[]"]', row).val(name);
-      this.env.subscriptionrows[id][0] = name;
-      // update the name if level is changed
-      if (level != 0) {
-        if (level > 0) {
-          for (i=level; i>0; i--)
-            dispname = dispname.replace(/^&nbsp;&nbsp;&nbsp;&nbsp;/, '');
-        }
-        else {
-          for (i=level; i<0; i++)
-            dispname = '&nbsp;&nbsp;&nbsp;&nbsp;' + dispname;
-        }
-        row.find('td:first').html(dispname);
-        this.env.subscriptionrows[id][1] = dispname;
-      }
-    }
-
-    // update list widget
-    this.init_subscription_list();
+    // move the existing table row
+    this.add_folder_row(id, name, display_name, is_protected, subscribed, class_name, row, subfolders);
   };
 
   // remove the table row of a specific mailbox from the table
-  this.remove_folder_row = function(folder, subs)
+  this.remove_folder_row = function(folder)
   {
-    var n, len, list = [], id = this.get_folder_row_id(folder);
+    var list = [], row = this.subscription_list.get_item(folder, true);
 
     // get subfolders if any
-    if (subs)
-      list = this.get_subfolders(folder);
+    $('li', row).each(function() { list.push(ref.folder_id2name(this.id)); });
 
-    // remove old row
-    this._remove_folder_row(id);
+    // remove folder row (and subfolders)
+    this.subscription_list.remove(folder);
 
-    // remove subfolders
-    for (n=0, len=list.length; n<len; n++)
-      this._remove_folder_row(list[n]);
-  };
-
-  this._remove_folder_row = function(id)
-  {
-    this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
-    $('#'+id).remove();
-    delete this.env.subscriptionrows[id];
-  };
-
-  this.get_subfolders = function(folder)
-  {
-    var name, list = [],
-      prefix = folder + this.env.delimiter,
-      row = $('#'+this.get_folder_row_id(folder)).get(0);
-
-    while (row = row.nextSibling) {
-      if (row.id) {
-        name = this.env.subscriptionrows[row.id][0];
-        if (name && name.startsWith(prefix)) {
-          list.push(row.id);
-        }
-        else
-          break;
-      }
-    }
-
-    return list;
+    // update local list variable
+    list.push(folder);
+    $.each(list, function(i, v) { delete ref.env.subscriptionrows[v]; });
   };
 
   this.subscribe = function(folder)
@@ -6056,15 +6126,6 @@
       var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
       this.http_post('unsubscribe', {_mbox: folder}, lock);
     }
-  };
-
-  // helper method to find a specific mailbox row ID
-  this.get_folder_row_id = function(folder)
-  {
-    var id, folders = this.env.subscriptionrows;
-    for (id in folders)
-      if (folders[id] && folders[id][0] == folder)
-        return id;
   };
 
   // when user select a folder in manager
@@ -6090,9 +6151,9 @@
   // disables subscription checkbox (for protected folder)
   this.disable_subscription = function(folder)
   {
-    var id = this.get_folder_row_id(folder);
-    if (id)
-      $('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true);
+    var row = this.subscription_list.get_item(folder, true);
+    if (row)
+      $('input[name="_subscribed[]"]:first', row).prop('disabled', true);
   };
 
   this.folder_size = function(folder)
@@ -6449,6 +6510,10 @@
   // mark a mailbox as selected and set environment variable
   this.select_folder = function(name, prefix, encode)
   {
+    if (this.savedsearchlist) {
+      this.savedsearchlist.select('');
+    }
+
     if (this.treelist) {
       this.treelist.select(name);
     }
@@ -7250,11 +7315,24 @@
           this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount && !is_multifolder);
 
           if ((response.action == 'list' || response.action == 'search') && this.message_list) {
+            var list = this.message_list, uid = this.env.list_uid;
+
+            // highlight message row when we're back from message page
+            if (uid) {
+              if (!list.rows[uid])
+                uid += '-' + this.env.mailbox;
+              if (list.rows[uid]) {
+                list.select(uid);
+              }
+              delete this.env.list_uid;
+            }
+
             this.enable_command('set-listmode', this.env.threads && !is_multifolder);
-            if (this.message_list.rowcount > 0)
-              this.message_list.focus();
-            this.msglist_select(this.message_list);
-            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
+            if (list.rowcount > 0)
+              list.focus();
+            this.msglist_select(list);
+            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:list.rowcount });
+
           }
         }
         else if (this.task == 'addressbook') {
@@ -7333,6 +7411,7 @@
     // save message in local storage and do not redirect
     if (this.env.action == 'compose') {
       this.save_compose_form_local();
+      this.compose_skip_unsavedcheck = true;
     }
     else if (redirect_url) {
       setTimeout(function(){ ref.redirect(redirect_url, true); }, 2000);
@@ -7475,8 +7554,10 @@
   // post the given form to a hidden iframe
   this.async_upload_form = function(form, action, onload)
   {
-    var frame, ts = new Date().getTime(),
-      frame_name = 'rcmupload'+ts;
+    // create hidden iframe
+    var ts = new Date().getTime(),
+      frame_name = 'rcmupload' + ts,
+      frame = this.async_upload_form_frame(frame_name);
 
     // upload progress support
     if (this.env.upload_progress_name) {
@@ -7491,31 +7572,24 @@
       field.val(ts);
     }
 
-    // have to do it this way for IE
-    // otherwise the form will be posted to a new window
-    if (document.all) {
-      document.body.insertAdjacentHTML('BeforeEnd', '<iframe name="'+frame_name+'"'
-        + ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>');
-      frame = $('iframe[name="'+frame_name+'"]');
-    }
-    // for standards-compliant browsers
-    else {
-      frame = $('<iframe>').attr('name', frame_name)
-        .css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
-        .appendTo(document.body);
-    }
-
-    // handle upload errors, parsing iframe content in onload
+    // handle upload errors by parsing iframe content in onload
     frame.bind('load', {ts:ts}, onload);
 
     $(form).attr({
         target: frame_name,
-        action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
+        action: this.url(action, {_id: this.env.compose_id || '', _uploadid: ts, _from: this.env.action}),
         method: 'POST'})
       .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
       .submit();
 
     return frame_name;
+  };
+
+  // create iframe element for files upload
+  this.async_upload_form_frame = function(name)
+  {
+    return $('<iframe>').attr({name: name, style: 'border: none; width: 0; height: 0; visibility: hidden'})
+      .appendTo(document.body);
   };
 
   // html5 file-drop API
@@ -7566,7 +7640,7 @@
       $.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 }),
+        url: ref.url(ref.env.filedrop.action || 'upload', {_id: ref.env.compose_id||ref.env.cid||'', _uploadid: ts, _remote: 1, _from: ref.env.action}),
         contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
         processData: false,
         timeout: 0, // disable default timeout set in ajaxSetup()
@@ -7955,22 +8029,43 @@
   // wrapper for localStorage.getItem(key)
   this.local_storage_get_item = function(key, deflt, encrypted)
   {
+    var item;
+
     // TODO: add encryption
-    var item = localStorage.getItem(this.get_local_storage_prefix() + key);
+    try {
+      item = localStorage.getItem(this.get_local_storage_prefix() + key);
+    }
+    catch (e) { }
+
     return item !== null ? JSON.parse(item) : (deflt || null);
   };
 
   // wrapper for localStorage.setItem(key, data)
   this.local_storage_set_item = function(key, data, encrypted)
   {
-    // TODO: add encryption
-    return localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data));
+    // try/catch to handle no localStorage support, but also error
+    // in Safari-in-private-browsing-mode where localStorage exists
+    // but can't be used (#1489996)
+    try {
+      // TODO: add encryption
+      localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data));
+      return true;
+    }
+    catch (e) {
+      return false;
+    }
   };
 
   // wrapper for localStorage.removeItem(key)
   this.local_storage_remove_item = function(key)
   {
-    return localStorage.removeItem(this.get_local_storage_prefix() + key);
+    try {
+      localStorage.removeItem(this.get_local_storage_prefix() + key);
+      return true;
+    }
+    catch (e) {
+      return false;
+    }
   };
 }  // end object rcube_webmail
 

--
Gitblit v1.9.1