From 02e079f9170539a639a9f42248829faeb7c17ae3 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Fri, 01 Jul 2011 16:06:46 -0400
Subject: [PATCH] - Fix adding signatures (#1487965)

---
 program/js/app.js |  824 ++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 511 insertions(+), 313 deletions(-)

diff --git a/program/js/app.js b/program/js/app.js
index d0e408d..c96c3eb 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -70,9 +70,12 @@
   };
 
   // add a localized label to the client environment
-  this.add_label = function(key, value)
+  this.add_label = function(p, value)
   {
-    this.labels[key] = value;
+    if (typeof p == 'string')
+      this.labels[p] = value;
+    else if (typeof p == 'object')
+      $.extend(this.labels, p);
   };
 
   // add a button to the button list
@@ -161,7 +164,7 @@
     }
 
     // enable general commands
-    this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
+    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', true);
 
     if (this.env.permaurl)
       this.enable_command('permaurl', true);
@@ -211,7 +214,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'];
+          'print', 'load-attachment', 'load-headers', 'forward-attachment'];
 
         if (this.env.action=='show' || this.env.action=='preview') {
           this.enable_command(this.env.message_commands, this.env.uid);
@@ -277,7 +280,6 @@
 
         break;
 
-
       case 'addressbook':
         if (this.gui_objects.folderlist)
           this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
@@ -302,14 +304,11 @@
           if (this.gui_objects.qsearchbox) {
             $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
           }
+
+          this.enable_command('group-create', this.env.address_sources[this.env.source].groups);
         }
 
         this.set_page_buttons();
-
-        if (this.env.address_sources && this.env.address_sources[this.env.source] && !this.env.address_sources[this.env.source].readonly) {
-          this.enable_command('add', 'import', true);
-          this.enable_command('group-create', this.env.address_sources[this.env.source].groups);
-        }
 
         if (this.env.cid) {
           this.enable_command('show', 'edit', true);
@@ -324,24 +323,12 @@
           }
         }
 
-        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) {
+        if (this.gui_objects.editform) {
           this.enable_command('save', true);
-          this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
-          this.enable_command('delete-photo', this.env.coltypes.photo && this.env.action == 'edit');
-
-          for (var col in this.env.coltypes)
-            this.init_edit_field(col, null);
-
-          $('.contactfieldgroup .row a.deletebutton').click(function(){ ref.delete_edit_field(this); return false });
-
-          $('select.addfieldmenu').change(function(e){
-            ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
-            this.selectedIndex = 0;
-          });
-
-          $("input[type='text']:visible").first().focus();
+          if (this.env.action == 'add' || this.env.action == 'edit')
+              this.init_contact_form();
         }
-        else if (this.gui_objects.qsearchbox) {
+        if (this.gui_objects.qsearchbox) {
           this.enable_command('search', 'reset-search', 'moveto', true);
           $(this.gui_objects.qsearchbox).select();
         }
@@ -349,21 +336,22 @@
         if (this.contact_list && this.contact_list.rowcount > 0)
           this.enable_command('export', true);
 
-        this.enable_command('list', 'listgroup', true);
+        this.enable_command('add', 'import', this.env.writable_source);
+        this.enable_command('list', 'listgroup', 'advanced-search', true);
         break;
 
 
       case 'settings':
         this.enable_command('preferences', 'identities', 'save', 'folders', true);
 
-        if (this.env.action=='identities') {
+        if (this.env.action == 'identities') {
           this.enable_command('add', this.env.identities_level < 2);
         }
-        else if (this.env.action=='edit-identity' || this.env.action=='add-identity') {
+        else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
           this.enable_command('add', this.env.identities_level < 2);
           this.enable_command('save', 'delete', 'edit', 'toggle-editor', true);
         }
-        else if (this.env.action=='folders') {
+        else if (this.env.action == 'folders') {
           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
         }
         else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
@@ -407,7 +395,7 @@
 
         // display 'loading' message on form submit, lock submit button
         $('form').submit(function () {
-          $('input[type=submit]', this).attr('disabled', true);
+          $('input[type=submit]', this).prop('disabled', true);
           rcmail.display_message('', 'loading');
         });
 
@@ -486,13 +474,13 @@
 
     // trigger plugin hooks
     this.triggerEvent('actionbefore', {props:props, action:command});
-    var event_ret = this.triggerEvent('before'+command, props);
-    if (event_ret !== undefined) {
+    var ret = this.triggerEvent('before'+command, props);
+    if (ret !== undefined) {
       // abort if one the handlers returned false
-      if (event_ret === false)
+      if (ret === false)
         return false;
       else
-        props = event_ret;
+        props = ret;
     }
 
     // process internal command
@@ -541,12 +529,12 @@
           if (this.env.trash_mailbox)
             this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
         }
-        else if (this.task=='addressbook') {
+        else if (this.task == 'addressbook') {
           if (!this.env.search_request || (props != this.env.source))
             this.reset_qsearch();
 
           this.list_contacts(props);
-          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
+          this.enable_command('add', 'import', this.env.writable_source);
         }
         break;
 
@@ -598,7 +586,7 @@
 
       // common commands used in multiple tasks
       case 'show':
-        if (this.task=='mail') {
+        if (this.task == 'mail') {
           var uid = this.get_single_uid();
           if (uid && (!this.env.uid || uid != this.env.uid)) {
             if (this.env.mailbox == this.env.drafts_mailbox)
@@ -607,17 +595,17 @@
               this.show_message(uid);
           }
         }
-        else if (this.task=='addressbook') {
+        else if (this.task == 'addressbook') {
           var cid = props ? props : this.get_single_cid();
-          if (cid && !(this.env.action=='show' && cid==this.env.cid))
+          if (cid && !(this.env.action == 'show' && cid == this.env.cid))
             this.load_contact(cid, 'show');
         }
         break;
 
       case 'add':
-        if (this.task=='addressbook')
+        if (this.task == 'addressbook')
           this.load_contact(0, 'add');
-        else if (this.task=='settings') {
+        else if (this.task == 'settings') {
           this.identity_list.clear_selection();
           this.load_identity(0, 'add-identity');
         }
@@ -636,27 +624,29 @@
         break;
 
       case 'save':
-        if (this.gui_objects.editform) {
-          var input_pagesize = $("input[name='_pagesize']");
-          var input_name  = $("input[name='_name']");
-          var input_email = $("input[name='_email']");
-
+        var input, form = this.gui_objects.editform;
+        if (form) {
+          // adv. search
+          if (this.env.action == 'search') {
+          }
           // user prefs
-          if (input_pagesize.length && isNaN(parseInt(input_pagesize.val()))) {
+          else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
             alert(this.get_label('nopagesizewarning'));
-            input_pagesize.focus();
+            input.focus();
             break;
           }
           // contacts/identities
           else {
-            if (input_name.length && input_name.val() == '') {
+            if ((input = $("input[name='_name']", form)) &&input.length && input.val() == '') {
               alert(this.get_label('nonamewarning'));
-              input_name.focus();
+              input.focus();
               break;
             }
-            else if (this.task == 'settings' && input_email.length && (this.env.identities_level % 2) == 0 && !rcube_check_email(input_email.val())) {
+            else if (this.task == 'settings' && (this.env.identities_level % 2) == 0  &&
+              (input = $("input[name='_email']", form)) && input.length&& !rcube_check_email(input.val())
+            ) {
               alert(this.get_label('noemailwarning'));
-              input_email.focus();
+              input.focus();
               break;
             }
 
@@ -664,19 +654,19 @@
             $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
           }
 
-          this.gui_objects.editform.submit();
+          form.submit();
         }
         break;
 
       case 'delete':
         // mail task
-        if (this.task=='mail')
+        if (this.task == 'mail')
           this.delete_messages();
         // addressbook task
-        else if (this.task=='addressbook')
+        else if (this.task == 'addressbook')
           this.delete_contacts();
         // user settings task
-        else if (this.task=='settings')
+        else if (this.task == 'settings')
           this.delete_identity();
         break;
 
@@ -821,10 +811,10 @@
       case 'compose':
         var url = this.env.comm_path+'&_action=compose';
 
-        if (this.task=='mail') {
+        if (this.task == 'mail') {
           url += '&_mbox='+urlencode(this.env.mailbox);
 
-          if (this.env.mailbox==this.env.drafts_mailbox) {
+          if (this.env.mailbox == this.env.drafts_mailbox) {
             var uid;
             if (uid = this.get_single_uid())
               url += '&_draft_uid='+uid;
@@ -833,7 +823,7 @@
              url += '&_to='+urlencode(props);
         }
         // modify url if we're in addressbook
-        else if (this.task=='addressbook') {
+        else if (this.task == 'addressbook') {
           // switch to mail compose step directly
           if (props && props.indexOf('@') > 0) {
             url = this.get_task_url('mail', url);
@@ -842,24 +832,21 @@
           }
 
           // use contact_id passed as command parameter
-          var a_cids = [];
+          var n, len, a_cids = [];
           if (props)
             a_cids.push(props);
           // get selected contacts
           else if (this.contact_list) {
             var selection = this.contact_list.get_selection();
-            for (var n=0; n<selection.length; n++)
+            for (n=0, len=selection.length; n<len; n++)
               a_cids.push(selection[n]);
           }
 
           if (a_cids.length)
-            this.http_request('mailto', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source), true);
+            this.http_post('mailto', {_cid: a_cids.join(','), _source: this.env.source}, true);
 
           break;
         }
-
-        // don't know if this is necessary...
-        url = url.replace(/&_framed=1/, '');
 
         this.redirect(url);
         break;
@@ -906,12 +893,14 @@
         self.clearTimeout(this.save_timer);
 
         // all checks passed, send message
-        var form = this.gui_objects.messageform,
+        var lang = this.spellcheck_lang(),
+          form = this.gui_objects.messageform,
           msgid = this.set_busy(true, 'sendingmessage');
 
         form.target = 'savetarget';
         form._draft.value = '';
         form.action = this.add_url(form.action, '_unlock', msgid);
+        form.action = this.add_url(form.action, '_lang', lang);
         form.submit();
 
         // clear timeout (sending could take longer)
@@ -945,10 +934,15 @@
         }
         break;
 
+      case 'forward-attachment':
       case 'forward':
-        var uid;
-        if (uid = this.get_single_uid())
-          this.goto_url('compose', '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
+        var uid, url;
+        if (uid = this.get_single_uid()) {
+          url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
+          if (command == 'forward-attachment' || (!props && this.env.forward_attachment))
+            url += '&_attachment=1';
+          this.goto_url('compose', url, true);
+        }
         break;
 
       case 'print':
@@ -994,8 +988,14 @@
 
         if (s && this.env.mailbox)
           this.list_mailbox(this.env.mailbox);
-        else if (s && this.task == 'addressbook')
+        else if (s && this.task == 'addressbook') {
+          if (this.env.source == '') {
+            for (var n in this.env.address_sources) break;
+            this.env.source = n;
+            this.env.group = '';
+          }
           this.list_contacts(this.env.source, this.env.group);
+        }
         break;
 
       case 'listgroup':
@@ -1180,6 +1180,18 @@
   this.is_framed = function()
   {
     return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
+  };
+
+  this.save_pref = function(prop)
+  {
+    var request = {'_name': prop.name, '_value': prop.value};
+
+    if (prop.session)
+      request['_session'] = prop.session;
+    if (prop.env)
+      this.env[prop.env] = prop.value;
+
+    this.http_post('save-pref', request);
   };
 
 
@@ -1374,28 +1386,26 @@
       }
     }
 
-    this.http_post('save-pref', '_name=collapsed_folders&_value='+urlencode(this.env.collapsed_folders));
+    this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders });
     this.set_unread_count_display(id, false);
   };
 
   this.doc_mouse_up = function(e)
   {
-    var model, list, li;
+    var model, list, li, id;
 
-    if (this.message_list) {
-      if (!rcube_mouse_is_over(e, this.message_list.list.parentNode))
-        this.message_list.blur();
+    if (list = this.message_list) {
+      if (!rcube_mouse_is_over(e, list.list.parentNode))
+        list.blur();
       else
-        this.message_list.focus();
-      list = this.message_list;
+        list.focus();
       model = this.env.mailboxes;
     }
-    else if (this.contact_list) {
-      if (!rcube_mouse_is_over(e, this.contact_list.list.parentNode))
-        this.contact_list.blur();
+    else if (list = this.contact_list) {
+      if (!rcube_mouse_is_over(e, list.list.parentNode))
+        list.blur();
       else
-        this.contact_list.focus();
-      list = this.contact_list;
+        list.focus();
       model = this.env.contactfolders;
     }
     else if (this.ksearch_value) {
@@ -1416,7 +1426,7 @@
 
     // reset 'pressed' buttons
     if (this.buttons_sel) {
-      for (var id in this.buttons_sel)
+      for (id in this.buttons_sel)
         if (typeof id !== 'function')
           this.button_out(this.buttons_sel[id], id);
       this.buttons_sel = {};
@@ -1449,7 +1459,7 @@
     if (selected) {
       // Hide certain command buttons when Drafts folder is selected
       if (this.env.mailbox == this.env.drafts_mailbox)
-        this.enable_command('reply', 'reply-all', 'reply-list', 'forward', false);
+        this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', false);
       // Disable reply-list when List-Post header is not set
       else {
         var msg = this.env.messages[list.get_single_selection()];
@@ -1515,8 +1525,6 @@
       this.command('previouspage');
     else if (list.key_pressed == 34)
       this.command('nextpage');
-    else
-      list.shiftkey = false;
   };
 
   this.msglist_get_preview = function()
@@ -1552,7 +1560,7 @@
     if ((found = $.inArray('subject', this.env.coltypes)) >= 0)
       this.set_env('subject_col', found);
 
-    this.http_post('save-pref', { '_name':'list_cols', '_value':this.env.coltypes, '_session':'list_attrib/columns' });
+    this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' });
   };
 
   this.check_droptarget = function(id)
@@ -2442,17 +2450,19 @@
   // delete selected messages from the current mailbox
   this.delete_messages = function()
   {
-    var selection = this.message_list ? $.merge([], this.message_list.get_selection()) : [];
+    var uid, i, len, trash = this.env.trash_mailbox,
+      list = this.message_list,
+      selection = list ? $.merge([], list.get_selection()) : [];
 
     // exit if no mailbox specified or if selection is empty
     if (!this.env.uid && !selection.length)
       return;
 
     // also select childs of collapsed rows
-    for (var uid, i=0, len=selection.length; i<len; i++) {
+    for (i=0, len=selection.length; i<len; i++) {
       uid = selection[i];
-      if (this.message_list.rows[uid].has_children && !this.message_list.rows[uid].expanded)
-        this.message_list.select_childs(uid);
+      if (list.rows[uid].has_children && !list.rows[uid].expanded)
+        list.select_childs(uid);
     }
 
     // if config is set to flag for deletion
@@ -2461,17 +2471,18 @@
       return false;
     }
     // if there isn't a defined trash mailbox or we are in it
-    else if (!this.env.trash_mailbox || this.env.mailbox == this.env.trash_mailbox)
+    // @TODO: we should check if defined trash mailbox exists
+    else if (!trash || this.env.mailbox == trash)
       this.permanently_remove_messages();
     // if there is a trash mailbox defined and we're not currently in it
     else {
       // if shift was pressed delete it immediately
-      if (this.message_list && this.message_list.shiftkey) {
+      if (list && list.shiftkey) {
         if (confirm(this.get_label('deletemessagesconfirm')))
           this.permanently_remove_messages();
       }
       else
-        this.move_messages(this.env.trash_mailbox);
+        this.move_messages(trash);
     }
 
     return true;
@@ -2601,12 +2612,13 @@
   // set class to read/unread
   this.toggle_read_status = function(flag, a_uids)
   {
-    // mark all message rows as read/unread
-    for (var i=0; i<a_uids.length; i++)
-      this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
-
-    var url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
+    var i, len = a_uids.length,
+      url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
       lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+    // mark all message rows as read/unread
+    for (i=0; i<len; i++)
+      this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
 
     // also send search request to get the right messages
     if (this.env.search_request)
@@ -2614,19 +2626,20 @@
 
     this.http_post('mark', url, lock);
 
-    for (var i=0; i<a_uids.length; i++)
+    for (i=0; i<len; i++)
       this.update_thread_root(a_uids[i], flag);
   };
 
   // set image to flagged or unflagged
   this.toggle_flagged_status = function(flag, a_uids)
   {
-    // mark all message rows as flagged/unflagged
-    for (var i=0; i<a_uids.length; i++)
-      this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
-
-    var url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
+    var i, len = a_uids.length,
+      url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
       lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+    // mark all message rows as flagged/unflagged
+    for (i=0; i<len; i++)
+      this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
 
     // also send search request to get the right messages
     if (this.env.search_request)
@@ -2638,9 +2651,11 @@
   // mark all message rows as deleted/undeleted
   this.toggle_delete_status = function(a_uids)
   {
-    var rows = this.message_list ? this.message_list.rows : [];
+    var len = a_uids.length,
+      i, uid, all_deleted = true,
+      rows = this.message_list ? this.message_list.rows : [];
 
-    if (a_uids.length==1) {
+    if (len == 1) {
       if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
         this.flag_as_deleted(a_uids);
       else
@@ -2649,8 +2664,7 @@
       return true;
     }
 
-    var uid, all_deleted = true;
-    for (var i=0, len=a_uids.length; i<len; i++) {
+    for (i=0; i<len; i++) {
       uid = a_uids[i];
       if (rows[uid] && !rows[uid].deleted) {
         all_deleted = false;
@@ -2668,11 +2682,12 @@
 
   this.flag_as_undeleted = function(a_uids)
   {
-    for (var i=0, len=a_uids.length; i<len; i++)
-      this.set_message(a_uids[i], 'deleted', false);
-
-    var url = '_uid='+this.uids_to_list(a_uids)+'&_flag=undelete',
+    var i, len=a_uids.length,
+      url = '_uid='+this.uids_to_list(a_uids)+'&_flag=undelete',
       lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+    for (i=0; i<len; i++)
+      this.set_message(a_uids[i], 'deleted', false);
 
     // also send search request to get the right messages
     if (this.env.search_request)
@@ -2739,13 +2754,13 @@
   // argument should be a coma-separated list of uids
   this.flag_deleted_as_read = function(uids)
   {
-    var icn_src, uid,
-      rows = this.message_list ? this.message_list.rows : [],
-      str = String(uids),
-      a_uids = str.split(',');
+    var icn_src, uid, i, len,
+      rows = this.message_list ? this.message_list.rows : [];
 
-    for (var i=0; i<a_uids.length; i++) {
-      uid = a_uids[i];
+    uids = String(uids).split(',');
+
+    for (i=0, len=uids.length; i<len; i++) {
+      uid = uids[i];
       if (rows[uid])
         this.set_message(uid, 'unread', false);
     }
@@ -2852,7 +2867,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.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
+      if (input_from.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
         this.change_identity(input_from[0]);
       }
     }
@@ -2891,7 +2906,7 @@
       input_message = $("[name='_message']");
 
     // check sender (if have no identities)
-    if (input_from.attr('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
+    if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
       alert(this.get_label('nosenderwarning'));
       input_from.focus();
       return false;
@@ -3003,6 +3018,36 @@
     this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
     this.enable_command('spellcheck', this.spellcheck_ready);
   };
+
+  // get selected language
+  this.spellcheck_lang = function()
+  {
+    var ed;
+    if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins.spellchecker) {
+      return ed.plugins.spellchecker.selectedLang;
+    }
+    else if (this.env.spellcheck) {
+      return GOOGIE_CUR_LANG;
+    }
+  };
+
+  // resume spellchecking, highlight provided mispellings without new ajax request
+  this.spellcheck_resume = function(ishtml, data)
+  {
+    if (ishtml) {
+      var ed = tinyMCE.get(this.env.composebody);
+        sp = ed.plugins.spellchecker;
+
+      sp.active = 1;
+      sp._markWords(data);
+      ed.nodeChanged();
+    }
+    else {
+      var sp = this.env.spellcheck;
+      sp.prepare(false, true);
+      sp.processData(data);
+    }
+  }
 
   this.set_draft_id = function(id)
   {
@@ -3187,16 +3232,12 @@
     if (!form)
       return false;
 
-    // get file input fields
-    var send = false;
-    for (var n=0; n<form.elements.length; n++)
-      if (form.elements[n].type=='file' && form.elements[n].value) {
-        send = true;
-        break;
-      }
+    // get file input field, count files on capable browser
+    var field = $('input[type=file]', form).get(0),
+      files = field.files ? field.files.length : field.value ? 1 : 0;
 
     // create hidden iframe and post upload form
-    if (send) {
+    if (files) {
       var frame_name = this.async_upload_form(form, 'upload', function(e) {
         var d, content = '';
         try {
@@ -3219,7 +3260,7 @@
       });
 
       // display upload indicator and cancel button
-      var content = this.get_label('uploading'),
+      var content = this.get_label('uploading' + (files > 1 ? 'many' : '')),
         ts = frame_name.replace(/^rcmupload/, '');
 
       if (this.env.loadingicon)
@@ -3241,8 +3282,7 @@
     if (!this.gui_objects.attachmentlist)
       return false;
 
-    var li = $('<li>').attr('id', name).html(att.html);
-    var indicator;
+    var indicator, li = $('<li>').attr('id', name).html(att.html);
 
     // replace indicator's li
     if (upload_id && (indicator = document.getElementById(upload_id))) {
@@ -3269,7 +3309,7 @@
       return false;
 
     var list = this.gui_objects.attachmentlist.getElementsByTagName("li");
-    for (i=0;i<list.length;i++)
+    for (i=0; i<list.length; i++)
       if (list[i].id == name)
         this.gui_objects.attachmentlist.removeChild(list[i]);
   };
@@ -3305,21 +3345,23 @@
   this.qsearch = function(value)
   {
     if (value != '') {
-      var addurl = '';
+      var n, addurl = '', mods_arr = [],
+        mods = this.env.search_mods,
+        mbox = this.env.mailbox,
+        lock = this.set_busy(true, 'searching');
+
       if (this.message_list) {
         this.clear_message_list();
-        if (this.env.search_mods) {
-          var mods = this.env.search_mods[this.env.mailbox] ? this.env.search_mods[this.env.mailbox] : this.env.search_mods['*'];
-          if (mods) {
-            var head_arr = [];
-            for (var n in mods)
-              head_arr.push(n);
-            addurl += '&_headers='+head_arr.join(',');
-          }
-        }
+        if (mods)
+          mods = mods[mbox] ? mods[mbox] : mods['*'];
       } else if (this.contact_list) {
-        this.contact_list.clear(true);
-        this.show_contentframe(false);
+        this.list_contacts_clear();
+      }
+
+      if (mods) {
+        for (n in mods)
+          mods_arr.push(n);
+        addurl += '&_headers='+mods_arr.join(',');
       }
 
       if (this.gui_objects.search_filter)
@@ -3327,9 +3369,8 @@
 
       // reset vars
       this.env.current_page = 1;
-      var lock = this.set_busy(true, 'searching');
       this.http_request('search', '_q='+urlencode(value)
-        + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : '')
+        + (mbox ? '&_mbox='+urlencode(mbox) : '')
         + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
         + (this.env.group ? '&_gid='+urlencode(this.env.group) : '')
         + (addurl ? addurl : ''), lock);
@@ -3598,7 +3639,7 @@
 
     if (this.ksearch_pane)
       this.ksearch_pane.hide();
-  };
+   };
 
 
   /*********************************************************/
@@ -3616,15 +3657,34 @@
     if (this.preview_timer)
       clearTimeout(this.preview_timer);
 
-    var id, frame, ref = this;
+    var n, id, sid, ref = this, writable = false,
+      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);
     else if (this.env.contentframe)
       this.show_contentframe(false);
 
+    // no source = search result, we'll need to detect if any of
+    // selected contacts are in writable addressbook to enable edit/delete
+    if (list.selection.length) {
+      if (!source) {
+        for (n in list.selection) {
+          sid = String(list.selection[n]).replace(/^[^-]+-/, '');
+          if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
+            writable = true;
+            break;
+          }
+        }
+      }
+      else {
+        writable = !source.readonly;
+      }
+    }
+
     this.enable_command('compose', list.selection.length > 0);
-    this.enable_command('edit', (id && this.env.address_sources && !this.env.address_sources[this.env.source].readonly) ? true : false);
-    this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
+    this.enable_command('edit', id && writable);
+    this.enable_command('delete', list.selection.length && writable);
 
     return false;
   };
@@ -3680,9 +3740,7 @@
   this.list_contacts_remote = function(src, group, page)
   {
     // clear message list first
-    this.contact_list.clear(true);
-    this.show_contentframe(false);
-    this.enable_command('delete', 'compose', false);
+    this.list_contacts_clear();
 
     // send request to server
     var url = (src ? '_source='+urlencode(src) : '') + (page ? (src?'&':'') + '_page='+page : ''),
@@ -3699,6 +3757,13 @@
       url += '&_search='+this.env.search_request;
 
     this.http_request('list', url, lock);
+  };
+
+  this.list_contacts_clear = function()
+  {
+    this.contact_list.clear(true);
+    this.show_contentframe(false);
+    this.enable_command('delete', 'compose', false);
   };
 
   // load contact record
@@ -3757,12 +3822,12 @@
     if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
       return;
 
-    var id, a_cids = [], qs = '';
+    var id, n, a_cids = [], qs = '';
 
     if (this.env.cid)
       a_cids.push(this.env.cid);
     else {
-      for (var n=0; n<selection.length; n++) {
+      for (n=0; n<selection.length; n++) {
         id = selection[n];
         a_cids.push(id);
         this.contact_list.remove_row(id, (n == selection.length-1));
@@ -3777,7 +3842,7 @@
       qs += '&_gid='+urlencode(this.env.group);
 
     // also send search request to get the right records from the next page
-    if (this.env.search_request) 
+    if (this.env.search_request)
       qs += '&_search='+this.env.search_request;
 
     // send request to server
@@ -3838,6 +3903,28 @@
     this.contact_list.insert_row(row);
 
     this.enable_command('export', (this.contact_list.rowcount > 0));
+  };
+
+  this.init_contact_form = function()
+  {
+    var ref = this, col;
+
+    this.set_photo_actions($('#ff_photo').val());
+
+    for (col in this.env.coltypes)
+      this.init_edit_field(col, null);
+
+    $('.contactfieldgroup .row a.deletebutton').click(function() {
+      ref.delete_edit_field(this);
+      return false;
+    });
+
+    $('select.addfieldmenu').change(function(e) {
+      ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
+      this.selectedIndex = 0;
+    });
+
+    $("input[type='text']:visible").first().focus();
   };
 
   this.group_create = function()
@@ -3999,7 +4086,6 @@
     this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
   };
 
-
   this.init_edit_field = function(col, elem)
   {
     if (!elem)
@@ -4016,12 +4102,12 @@
     var elem = $('#ff_'+col);
     if (elem.length) {
       elem.show().focus();
-      $(menu).children('option[value="'+col+'"]').attr('disabled', true);
+      $(menu).children('option[value="'+col+'"]').prop('disabled', true);
     }
     else {
       var lastelem = $('.ff_'+col),
         appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
-      
+
       if (!appendcontainer.length)
         appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last());
 
@@ -4030,7 +4116,7 @@
           row = $('<div>').addClass('row'),
           cell = $('<div>').addClass('contactfieldcontent data'),
           label = $('<div>').addClass('contactfieldlabel label');
-          
+
         if (colprop.subtypes_select)
           label.html(colprop.subtypes_select);
         else
@@ -4040,24 +4126,33 @@
         if (colprop.type == 'text' || colprop.type == 'date') {
           input = $('<input>')
             .addClass('ff_'+col)
-            .attr('type', 'text')
-            .attr('name', '_'+col+name_suffix)
-            .attr('size', colprop.size)
+            .attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size})
             .appendTo(cell);
 
           this.init_edit_field(col, input);
         }
         else if (colprop.type == 'composite') {
-          var childcol, cp, first;
-          for (var childcol in colprop.childs) {
+          var childcol, cp, first, templ, cols = [], suffices = [];
+          // read template for composite field order
+          if ((templ = this.env[col+'_template'])) {
+            for (var j=0; j < templ.length; j++) {
+              cols.push(templ[j][1]);
+              suffices.push(templ[j][2]);
+            }
+          }
+          else {  // list fields according to appearance in colprop
+            for (childcol in colprop.childs)
+              cols.push(childcol);
+          }
+
+          for (var i=0; i < cols.length; i++) {
+            childcol = cols[i];
             cp = colprop.childs[childcol];
             input = $('<input>')
               .addClass('ff_'+childcol)
-              .attr('type', 'text')
-              .attr('name', '_'+childcol+name_suffix)
-              .attr('size', cp.size)
+              .attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
               .appendTo(cell);
-            cell.append(" ");
+            cell.append(suffices[i] || " ");
             this.init_edit_field(childcol, input);
             if (!first) first = input;
           }
@@ -4068,7 +4163,7 @@
             .addClass('ff_'+col)
             .attr('name', '_'+col+name_suffix)
             .appendTo(cell);
-          
+
           var options = input.attr('options');
           options[options.length] = new Option('---', '');
           if (colprop.options)
@@ -4078,19 +4173,18 @@
         if (input) {
           var delbutton = $('<a href="#del"></a>')
             .addClass('contactfieldbutton deletebutton')
-            .attr('title', this.get_label('delete'))
-            .attr('rel', col)
+            .attr({title: this.get_label('delete'), rel: col})
             .html(this.env.delbutton)
             .click(function(){ ref.delete_edit_field(this); return false })
             .appendTo(cell);
-          
+
           row.append(label).append(cell).appendTo(appendcontainer.show());
           input.first().focus();
-          
+
           // disable option if limit reached
           if (!colprop.count) colprop.count = 0;
           if (++colprop.count == colprop.limit && colprop.limit)
-            $(menu).children('option[value="'+col+'"]').attr('disabled', true);
+            $(menu).children('option[value="'+col+'"]').prop('disabled', true);
         }
       }
     }
@@ -4102,7 +4196,7 @@
       colprop = this.env.coltypes[col],
       fieldset = $(elem).parents('fieldset.contactfieldgroup'),
       addmenu = fieldset.parent().find('select.addfieldmenu');
-    
+
     // just clear input but don't hide the last field
     if (--colprop.count <= 0 && colprop.visible)
       $(elem).parent().children('input').val('').blur();
@@ -4112,18 +4206,17 @@
       if (!fieldset.children('div.row').length)
         fieldset.hide();
     }
-    
+
     // enable option in add-field selector or insert it if necessary
     if (addmenu.length) {
       var option = addmenu.children('option[value="'+col+'"]');
       if (option.length)
-        option.attr('disabled', false);
+        option.prop('disabled', false);
       else
         option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
       addmenu.show();
     }
   };
-
 
   this.upload_contact_photo = function(form)
   {
@@ -4136,47 +4229,63 @@
       this.photo_upload_id = this.set_busy(true, 'uploading');
     }
   };
-  
+
   this.replace_contact_photo = function(id)
   {
-    $('#ff_photo').val(id);
-    
-    var buttons = this.buttons['upload-photo'];
-    for (var n=0; n < buttons.length; n++)
-      $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
-    
     var img_src = id == '-del-' ? this.env.photo_placeholder :
       this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id;
+
+    this.set_photo_actions(id);
     $(this.gui_objects.contactphoto).children('img').attr('src', img_src);
-    
-    this.enable_command('delete-photo', id != '-del-');
   };
-  
+
   this.photo_upload_end = function()
   {
     this.set_busy(false, null, this.photo_upload_id);
     delete this.photo_upload_id;
   };
 
+  this.set_photo_actions = function(id)
+  {
+    var n, buttons = this.buttons['upload-photo'];
+    for (n=0; n < buttons.length; n++)
+      $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
+
+    $('#ff_photo').val(id);
+    this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
+    this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-');
+  };
+
+  // load advanced search page
+  this.advanced_search = function()
+  {
+    var add_url = '&_form=1', target = window;
+
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+      add_url += '&_framed=1';
+      target = window.frames[this.env.contentframe];
+      this.contact_list.clear_selection();
+    }
+
+    this.location_href(this.env.comm_path+'&_action=search'+add_url, target);
+
+    return true;
+  };
+
+  // unselect directory/group
+  this.unselect_directory = function()
+  {
+    if (this.env.address_sources.length > 1 || this.env.group != '') {
+      this.select_folder('', (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
+      this.env.group = '';
+      this.env.source = '';
+    }
+  };
+
 
   /*********************************************************/
   /*********        user settings methods          *********/
   /*********************************************************/
-
-  this.init_subscription_list = function()
-  {
-    var p = this;
-    this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
-      {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
-    this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
-    this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
-    this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
-    this.subscription_list.row_init = function (row) {
-      row.obj.onmouseover = function() { p.focus_subscription(row.id); };
-      row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
-    };
-    this.subscription_list.init();
-  };
 
   // preferences section select and load options frame
   this.section_select = function(list)
@@ -4240,6 +4349,26 @@
     this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
 
     return true;
+  };
+
+
+  /*********************************************************/
+  /*********        folder manager methods         *********/
+  /*********************************************************/
+
+  this.init_subscription_list = function()
+  {
+    var p = this;
+    this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
+      {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
+    this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
+    this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
+    this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
+    this.subscription_list.row_init = function (row) {
+      row.obj.onmouseover = function() { p.focus_subscription(row.id); };
+      row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
+    };
+    this.subscription_list.init();
   };
 
   this.focus_subscription = function(id)
@@ -4331,90 +4460,185 @@
     }
   };
 
-  // add a new folder to the subscription list by cloning a folder row
-  this.add_folder_row = function(name, display_name, replace, before)
+  // Add folder row to the table and initialize it
+  this.add_folder_row = function (name, display_name, protected, subscribed, skip_init, class_name)
   {
     if (!this.gui_objects.subscriptionlist)
       return false;
 
-    // find not protected folder
-    var refid;
-    for (var rid in this.env.subscriptionrows) {
-      if (this.env.subscriptionrows[rid]!=null && !this.env.subscriptionrows[rid][2]) {
-        refid = rid;
-        break;
-      }
-    }
-
-    var refrow, form,
+    var row, n, i, tmp, folders, len, list = [], slist = [],
       tbody = this.gui_objects.subscriptionlist.tBodies[0],
-      id = 'rcmrow'+(tbody.childNodes.length+1),
-      selection = this.subscription_list.get_single_selection();
+      refrow = $('tr', tbody).get(0),
+      id = 'rcmrow'+((new Date).getTime());
 
-    if (replace && replace.id) {
-      id = replace.id;
-      refid = replace.id;
-    }
-
-    if (!id || !refid || !(refrow = document.getElementById(refid))) {
+    if (!refrow) {
       // 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
-    var row = this.clone_table_row(refrow);
-    row.id = id;
+    row = $(refrow).clone(true);
 
-    if (before && (before = this.get_folder_row_id(before)))
-      tbody.insertBefore(row, document.getElementById(before));
-    else
-      tbody.appendChild(row);
-
-    if (replace)
-      tbody.removeChild(replace);
-
-    // add to folder/row-ID map
-    this.env.subscriptionrows[row.id] = [name, display_name, 0];
+    // set ID, reset css class
+    row.attr('id', id);
+    row.attr('class', class_name);
 
     // set folder name
-    row.cells[0].innerHTML = display_name;
+    row.find('td:first').html(display_name);
 
-    if (!replace) {
-      // set messages count to zero
-      row.cells[1].innerHTML = '*';
+    // update subscription checkbox
+    $('input[name="_subscribed[]"]', row).val(name)
+      .prop({checked: subscribed ? true : false, disabled: protected ? true : false});
 
-      // update subscription checkbox
-      $('input[name="_subscribed[]"]', row).val(name).attr('checked', true);
+    // add to folder/row-ID map
+    this.env.subscriptionrows[id] = [name, display_name, 0];
+
+    // sort folders, to find a place where to insert the row
+    folders = this.env.subscriptionrows;
+    for (n in folders) {
+      // protected folder
+      if (folders[n][2]) {
+        slist.push(folders[n][0]);
+        tmp = folders[n][0]+this.env.delimiter;
+      }
+      // protected folder's child
+      else if (tmp && folders[n][0].indexOf(tmp) == 0)
+        slist.push(folders[n][0]);
+      // other
+      else {
+        list.push(folders[n][0]);
+        tmp = null;
+      }
+    }
+    list.sort();
+    // make sure protected folders (and their subs) are on top
+    list = slist.concat(list);
+
+    // find folder position after sorting
+    for (n=0, len=list.length; n<len; n++) {
+      if (list[n] == name)
+        break;
     }
 
-    this.init_subscription_list();
-    if (selection && document.getElementById('rcmrow'+selection))
-      this.subscription_list.select_row(selection);
+    // add row to the table
+    if (n && n < len)
+      $('#'+this.get_folder_row_id(list[n-1])).after(row);
+    else
+      row.appendTo(tbody);
 
-    if (document.getElementById(id).scrollIntoView)
-      document.getElementById(id).scrollIntoView();
+    // update list widget
+    this.subscription_list.clear_selection();
+    if (!skip_init)
+      this.init_subscription_list();
+
+    row = row.get(0);
+    if (row.scrollIntoView)
+      row.scrollIntoView();
+
+    return row;
   };
 
-  // replace an existing table row with a new folder line
-  this.replace_folder_row = function(oldfolder, newfolder, display_name, before)
+  // replace an existing table row with a new folder line (with subfolders)
+  this.replace_folder_row = function(oldfolder, newfolder, display_name, protected, class_name)
   {
-    var id = this.get_folder_row_id(oldfolder),
-      row = document.getElementById(id);
+    if (!this.gui_objects.subscriptionlist)
+      return false;
 
-    // replace an existing table row (if found)
-    this.add_folder_row(newfolder, display_name, row, before);
+    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),
+      regex = new RegExp('^'+RegExp.escape(oldfolder)),
+      subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
+      // find subfolders of renamed folder
+      list = this.get_subfolders(oldfolder);
+
+    // replace an existing table row
+    this._remove_folder_row(id);
+    row = $(this.add_folder_row(newfolder, display_name, protected, subscribed, true, class_name));
+
+    // detect tree depth change
+    if (len = list.length) {
+      level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
+    }
+
+    // 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 = name.replace(regex, newfolder);
+      $('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();
   };
 
   // remove the table row of a specific mailbox from the table
-  // (the row will not be removed, just hidden)
-  this.remove_folder_row = function(folder)
+  this.remove_folder_row = function(folder, subs)
   {
-    var row, id = this.get_folder_row_id(folder);
+    var n, len, list = [], id = this.get_folder_row_id(folder);
 
-    if (id && (row = document.getElementById(id)))
-      row.style.display = 'none';
+    // get subfolders if any
+    if (subs)
+      list = this.get_subfolders(folder);
+
+    // remove old row
+    this._remove_folder_row(id);
+
+    // 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 = [],
+      regex = new RegExp('^'+RegExp.escape(folder)+RegExp.escape(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 (regex.test(name)) {
+          list.push(row.id);
+        }
+        else
+          break;
+      }
+    }
+
+    return list;
+  }
 
   this.subscribe = function(folder)
   {
@@ -4435,33 +4659,12 @@
   // helper method to find a specific mailbox row ID
   this.get_folder_row_id = function(folder)
   {
-    for (var id in this.env.subscriptionrows)
-      if (this.env.subscriptionrows[id] && this.env.subscriptionrows[id][0] == folder)
+    var id, folders = this.env.subscriptionrows;
+    for (id in folders)
+      if (folders[id] && folders[id][0] == folder)
         break;
 
     return id;
-  };
-
-  // duplicate a specific table row
-  this.clone_table_row = function(row)
-  {
-    var cell, td,
-      new_row = document.createElement('tr');
-
-    for (var n=0; n<row.cells.length; n++) {
-      cell = row.cells[n];
-      td = document.createElement('td');
-
-      if (cell.className)
-        td.className = cell.className;
-      if (cell.align)
-        td.setAttribute('align', cell.align);
-
-      td.innerHTML = cell.innerHTML;
-      new_row.appendChild(td);
-    }
-
-    return new_row;
   };
 
   // when user select a folder in manager
@@ -4494,7 +4697,7 @@
   {
     var id = this.get_folder_row_id(folder);
     if (id)
-      $('input[name="_subscribed[]"]', $('#'+id)).attr('disabled', true);
+      $('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true);
   };
 
   this.folder_size = function(folder)
@@ -5001,16 +5204,6 @@
     }
   };
 
-  // notifies that a new message(s) has hit the mailbox
-  this.new_message_focus = function()
-  {
-    // focus main window
-    if (this.env.framed && window.parent)
-      window.parent.focus();
-    else
-      window.focus();
-  };
-
   this.toggle_prefer_html = function(checkbox)
   {
     var elem;
@@ -5074,8 +5267,9 @@
       quota_width = parseInt(quota / 100 * width),
       pos = $(obj).position();
 
-    // Opera bug?
+    // workarounds for Opera and Webkit bugs
     pos.top = Math.max(0, pos.top);
+    pos.left = Math.max(0, pos.left);
 
     this.env.indicator_width = width;
     this.env.indicator_height = height;
@@ -5113,7 +5307,7 @@
       bar1.addClass('quota_mid');
     }
     else {
-      main.addClass(' quota_text_normal');
+      main.addClass(' quota_text_low');
       bar1.addClass('quota_low');
     }
 
@@ -5308,9 +5502,20 @@
     switch (response.action) {
       case 'delete':
         if (this.task == 'addressbook') {
-          var uid = this.contact_list.get_selection();
+          var sid, uid = this.contact_list.get_selection(), writable = false;
+
+          if (uid && this.contact_list.rows[uid]) {
+            // search results, get source ID from record ID
+            if (this.env.source == '') {
+              sid = String(uid).replace(/^[^-]+-/, '');
+              writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
+            }
+            else {
+              writable = !this.env.address_sources[this.env.source].readonly;
+            }
+          }
           this.enable_command('compose', (uid && this.contact_list.rows[uid]));
-          this.enable_command('delete', 'edit', (uid && this.contact_list.rows[uid] && this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
+          this.enable_command('delete', 'edit', writable);
           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
         }
 
@@ -5359,10 +5564,9 @@
           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
 
           if (response.action == 'list' || response.action == 'search') {
-            this.enable_command('group-create',
-              (this.env.address_sources[this.env.source].groups && !this.env.address_sources[this.env.source].readonly));
-            this.enable_command('group-rename', 'group-delete',
-              (this.env.address_sources[this.env.source].groups && this.env.group && !this.env.address_sources[this.env.source].readonly));
+            var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
+            this.enable_command('group-create', (source && source.groups && !source.readonly));
+            this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
             this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
           }
         }
@@ -5415,12 +5619,13 @@
 
     form.target = frame_name;
     form.action = this.url(action, { _id:this.env.compose_id||'', _uploadid:ts });
+    form.setAttribute('method', 'POST');
     form.setAttribute('enctype', 'multipart/form-data');
     form.submit();
 
     return frame_name;
   };
-  
+
   // starts interval for keep-alive/check-recent signal
   this.start_keepalive = function()
   {
@@ -5430,14 +5635,7 @@
     if (this.env.keep_alive && !this.env.framed && 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')
-      this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000);
-  };
-
-  // sends keep-alive signal to the server
-  this.send_keep_alive = function()
-  {
-    var d = new Date();
-    this.http_request('keep-alive', '_t='+d.getTime());
+      this._int = setInterval(function(){ ref.http_request('keep-alive'); }, this.env.keep_alive * 1000);
   };
 
   // sends request to check for recent messages
@@ -5446,7 +5644,7 @@
     if (this.busy)
       return;
 
-    var lock, addurl = '_t=' + (new Date().getTime()) + '&_mbox=' + urlencode(this.env.mailbox);
+    var lock, addurl = '_mbox=' + urlencode(this.env.mailbox);
 
     if (refresh) {
       lock = this.set_busy(true, 'checkingmail');

--
Gitblit v1.9.1