thomascube
2011-10-30 2429cfde7887d1c92369cba972305a7f9ddddb18
program/js/app.js
@@ -3,7 +3,8 @@
 | Roundcube Webmail Client Script                                       |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2011, Kolab Systems AG                                  |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
@@ -40,11 +41,6 @@
  this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
  // mimetypes supported by the browser (default settings)
  this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
    'image/jpeg', 'image/gif', 'image/png',
    'application/x-javascript', 'application/pdf', 'application/x-shockwave-flash');
  // default environment vars
  this.env.keep_alive = 60;        // seconds
  this.env.request_timeout = 180;  // seconds
@@ -70,9 +66,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
@@ -87,12 +86,15 @@
    if (over) button_prop.over = over;
    this.buttons[command].push(button_prop);
    if (this.loaded)
      init_button(command, button_prop);
  };
  // register a specific gui object
  this.gui_object = function(name, id)
  {
    this.gui_objects[name] = id;
    this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
  };
  // register a container object
@@ -135,14 +137,6 @@
      return;
    }
    // Enable debug console
    if (!window.console || !window.console.log) {
      window.console = new rcube_console();
    }
    else {
      $('#console').hide();
    }
    // find all registered gui containers
    for (var n in this.gui_containers)
      this.gui_containers[n] = $('#'+this.gui_containers[n]);
@@ -161,7 +155,7 @@
    }
    // enable general commands
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', true);
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', true);
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
@@ -170,7 +164,7 @@
      case 'mail':
        // enable mail commands
        this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
        this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
        if (this.gui_objects.messagelist) {
@@ -206,7 +200,7 @@
          $(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list.blur(); });
        }
        if (this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox)
        if (!this.env.flag_for_deletion && this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox)
          this.set_alttext('delete', 'movemessagetotrash');
        this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', 'forward',
@@ -257,7 +251,10 @@
        }
        // show printing dialog
        else if (this.env.action == 'print' && this.env.uid)
          window.print();
          if (bw.safari)
            window.setTimeout('window.print()', 10);
          else
            window.print();
        // get unread count for each mailbox
        if (this.gui_objects.mailboxlist) {
@@ -277,10 +274,12 @@
        break;
      case 'addressbook':
        if (this.gui_objects.folderlist)
          this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
        this.enable_command('add', 'import', this.env.writable_source);
        this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
        if (this.gui_objects.contactslist) {
@@ -302,43 +301,34 @@
          if (this.gui_objects.qsearchbox) {
            $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
          }
          this.update_group_commands();
          this.command('list');
        }
        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);
          // register handlers for group assignment via checkboxes
          if (this.gui_objects.editform) {
            $('input.groupmember').change(function(){
              var cmd = this.checked ? 'group-addmembers' : 'group-delmembers';
              ref.http_post(cmd, '_cid='+urlencode(ref.env.cid)
                + '&_source='+urlencode(ref.env.source)
                + '&_gid='+urlencode(this.value));
            $('input.groupmember').change(function() {
              ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
            });
          }
        }
        if ((this.env.action == 'add' || this.env.action == 'edit') && this.gui_objects.editform) {
        if (this.gui_objects.editform) {
          this.enable_command('save', true);
          this.init_contact_form();
          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();
        }
        if (this.contact_list && this.contact_list.rowcount > 0)
          this.enable_command('export', true);
        this.enable_command('list', 'listgroup', true);
        break;
      case 'settings':
        this.enable_command('preferences', 'identities', 'save', 'folders', true);
@@ -390,7 +380,10 @@
          $('#rcmloginpwd').focus();
        // detect client timezone
        $('#rcmlogintz').val(new Date().getTimezoneOffset() / -60);
        var tz = new Date().getTimezoneOffset() / -60;
        var stdtz = new Date().getStdTimezoneOffset() / -60;
        $('#rcmlogintz').val(stdtz);
        $('#rcmlogindst').val(tz > stdtz ? 1 : 0);
        // display 'loading' message on form submit, lock submit button
        $('form').submit(function () {
@@ -405,12 +398,16 @@
        break;
      }
    // prevent from form submit with Enter key in file input fields
    if (bw.ie)
      $('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
    // flag object as complete
    this.loaded = true;
    // show message
    if (this.pending_message)
      this.display_message(this.pending_message[0], this.pending_message[1]);
      this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
    // map implicit containers
    if (this.gui_objects.folderlist)
@@ -432,6 +429,11 @@
    this.start_keepalive();
  };
  this.log = function(msg)
  {
    if (window.console && console.log)
      console.log(msg);
  };
  /*********************************************************/
  /*********       client command interface        *********/
@@ -440,6 +442,8 @@
  // execute a specific command on the web client
  this.command = function(command, props, obj)
  {
    var ret;
    if (obj && obj.blur)
      obj.blur();
@@ -463,24 +467,26 @@
    // process external commands
    if (typeof this.command_handlers[command] === 'function') {
      var ret = this.command_handlers[command](props, obj);
      ret = this.command_handlers[command](props, obj);
      return ret !== undefined ? ret : (obj ? false : true);
    }
    else if (typeof this.command_handlers[command] === 'string') {
      var ret = window[this.command_handlers[command]](props, obj);
      ret = window[this.command_handlers[command]](props, obj);
      return ret !== undefined ? ret : (obj ? false : true);
    }
    // trigger plugin hooks
    this.triggerEvent('actionbefore', {props:props, action:command});
    var ret = this.triggerEvent('before'+command, props);
    ret = this.triggerEvent('before'+command, props);
    if (ret !== undefined) {
      // abort if one the handlers returned false
      // abort if one of the handlers returned false
      if (ret === false)
        return false;
      else
        props = ret;
    }
    ret = undefined;
    // process internal command
    switch (command) {
@@ -519,21 +525,15 @@
        break;
      case 'list':
        if (this.task=='mail') {
          if (!this.env.search_request || (props && props != this.env.mailbox))
            this.reset_qsearch();
        this.reset_qsearch();
        if (this.task == 'mail') {
          this.list_mailbox(props);
          if (this.env.trash_mailbox)
          if (this.env.trash_mailbox && !this.env.flag_for_deletion)
            this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
        }
        else if (this.task=='addressbook') {
          if (!this.env.search_request || (props != this.env.source))
            this.reset_qsearch();
        else if (this.task == 'addressbook') {
          this.list_contacts(props);
          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
        }
        break;
@@ -585,7 +585,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)
@@ -594,17 +594,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');
        }
@@ -623,27 +623,28 @@
        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() == '') {
              alert(this.get_label('nonamewarning'));
              input_name.focus();
              break;
            // reload form
            if (props == 'reload') {
              form.action += '?_reload=1';
            }
            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;
            }
@@ -651,7 +652,11 @@
            $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
          }
          this.gui_objects.editform.submit();
          // add selected source (on the list)
          if (parent.rcmail && parent.rcmail.env.source)
            form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
          form.submit();
        }
        break;
@@ -737,7 +742,7 @@
        var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
        // open attachment in frame if it's of a supported mimetype
        if (this.env.uid && props.mimetype && $.inArray(props.mimetype, this.mimetypes)>=0) {
        if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes)>=0) {
          if (props.mimetype == 'text/html')
            qstring += '&_safe=1';
          this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment');
@@ -806,7 +811,7 @@
        break;
      case 'compose':
        var url = this.env.comm_path+'&_action=compose';
        var url = this.url('mail/compose');
        if (this.task == 'mail') {
          url += '&_mbox='+urlencode(this.env.mailbox);
@@ -840,10 +845,14 @@
          }
          if (a_cids.length)
            this.http_post('mailto', {_cid: a_cids.join(','), _source: this.env.source}, true);
            this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source}, true);
          else if (this.env.group)
            this.http_post('mailto', { _gid: this.env.group, _source: this.env.source}, true);
          break;
        }
        else if (props)
          url += '&_to='+urlencode(props);
        this.redirect(url);
        break;
@@ -890,12 +899,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)
@@ -978,16 +989,25 @@
      // reset quicksearch
      case 'reset-search':
        var s = this.env.search_request;
        var n, s = this.env.search_request || this.env.qsearch;
        this.reset_qsearch();
        this.select_all_mode = false;
        if (s && this.env.mailbox)
          this.list_mailbox(this.env.mailbox);
        else if (s && this.task == 'addressbook')
          this.list_contacts(this.env.source, this.env.group);
          this.list_mailbox(this.env.mailbox, 1);
        else if (s && this.task == 'addressbook') {
          if (this.env.source == '') {
            for (n in this.env.address_sources) break;
            this.env.source = n;
            this.env.group = '';
          }
          this.list_contacts(this.env.source, this.env.group, 1);
        }
        break;
      case 'listgroup':
        this.reset_qsearch();
        this.list_contacts(props.source, props.id);
        break;
@@ -1027,18 +1047,24 @@
        this.goto_url('settings/' + command);
        break;
      case 'undo':
        this.http_request('undo', '', this.display_message('', 'loading'));
        break;
      // unified command call (command name == function name)
      default:
        var func = command.replace(/-/g, '_');
        if (this[func] && typeof this[func] === 'function')
          this[func](props);
        if (this[func] && typeof this[func] === 'function') {
          ret = this[func](props);
        }
        break;
    }
    this.triggerEvent('after'+command, props);
    if (this.triggerEvent('after'+command, props) === false)
      ret = false;
    this.triggerEvent('actionafter', {props:props, action:command});
    return obj ? false : true;
    return ret === false ? false : obj ? false : true;
  };
  // set command(s) enabled or disabled
@@ -1173,10 +1199,10 @@
  this.save_pref = function(prop)
  {
    var request = {'_name': prop.name, '_value': urlencode(prop.value)};
    var request = {'_name': prop.name, '_value': prop.value};
    if (prop.session)
      request['_session'] = urlencode(prop.session);
      request['_session'] = prop.session;
    if (prop.env)
      this.env[prop.env] = prop.value;
@@ -1191,12 +1217,12 @@
  this.drag_menu = function(e, target)
  {
    var modkey = rcube_event.get_modifier(e),
      menu = $('#'+this.gui_objects.message_dragmenu);
      menu = this.gui_objects.message_dragmenu;
    if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
      var pos = rcube_event.get_mouse_pos(e);
      this.env.drag_target = target;
      menu.css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
      $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
      return true;
    }
@@ -1205,9 +1231,9 @@
  this.drag_menu_action = function(action)
  {
    var menu = $('#'+this.gui_objects.message_dragmenu);
    var menu = this.gui_objects.message_dragmenu;
    if (menu) {
      menu.hide();
      $(menu).hide();
    }
    this.command(action, this.env.drag_target);
    this.env.drag_target = null;
@@ -1277,7 +1303,7 @@
      var toffset = -moffset-boffset;
      var li, div, pos, mouse, check, oldclass,
        layerclass = 'draglayernormal';
      if (this.contact_list && this.contact_list.draglayer)
        oldclass = this.contact_list.draglayer.attr('class');
@@ -1354,12 +1380,12 @@
      ul.show();
      div.removeClass('collapsed').addClass('expanded');
      var reg = new RegExp('&'+urlencode(id)+'&');
      this.set_env('collapsed_folders', this.env.collapsed_folders.replace(reg, ''));
      this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, '');
    }
    else {
      ul.hide();
      div.removeClass('expanded').addClass('collapsed');
      this.set_env('collapsed_folders', this.env.collapsed_folders+'&'+urlencode(id)+'&');
      this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(id)+'&';
      // select parent folder if one of its childs is currently selected
      if (this.env.mailbox.indexOf(id + this.env.delimiter) == 0)
@@ -1382,6 +1408,10 @@
  this.doc_mouse_up = function(e)
  {
    var model, list, li, id;
    // ignore event if jquery UI dialog is open
    if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
      return;
    if (list = this.message_list) {
      if (!rcube_mouse_is_over(e, list.list.parentNode))
@@ -1504,11 +1534,12 @@
  this.msglist_keypress = function(list)
  {
    if (list.modkey == CONTROL_KEY)
      return;
    if (list.key_pressed == list.ENTER_KEY)
      this.command('show');
    else if (list.key_pressed == list.DELETE_KEY)
      this.command('delete');
    else if (list.key_pressed == list.BACKSPACE_KEY)
    else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
      this.command('delete');
    else if (list.key_pressed == 33)
      this.command('previouspage');
@@ -1544,10 +1575,10 @@
      }
    if ((found = $.inArray('flag', this.env.coltypes)) >= 0)
      this.set_env('flagged_col', found);
      this.env.flagged_col = found;
    if ((found = $.inArray('subject', this.env.coltypes)) >= 0)
      this.set_env('subject_col', found);
      this.env.subject_col = found;
    this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' });
  };
@@ -1622,14 +1653,18 @@
    if (!this.gui_objects.messagelist || !this.message_list)
      return false;
    // Prevent from adding messages from different folder (#1487752)
    if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
      return false;
    if (!this.env.messages[uid])
      this.env.messages[uid] = {};
    // merge flags over local message object
    $.extend(this.env.messages[uid], {
      deleted: flags.deleted?1:0,
      replied: flags.replied?1:0,
      unread: flags.unread?1:0,
      replied: flags.answered?1:0,
      unread: !flags.seen?1:0,
      forwarded: flags.forwarded?1:0,
      flagged: flags.flagged?1:0,
      has_children: flags.has_children?1:0,
@@ -1643,23 +1678,18 @@
      flags: flags.extra_flags
    });
    var c, html, tree = expando = '',
    var c, n, col, html, tree = '', expando = '',
      list = this.message_list,
      rows = list.rows,
      tbody = this.gui_objects.messagelist.tBodies[0],
      rowcount = tbody.rows.length,
      even = rowcount%2,
      message = this.env.messages[uid],
      css_class = 'message'
        + (even ? ' even' : ' odd')
        + (flags.unread ? ' unread' : '')
        + (!flags.seen ? ' unread' : '')
        + (flags.deleted ? ' deleted' : '')
        + (flags.flagged ? ' flagged' : '')
        + (flags.unread_children && !flags.unread && !this.env.autoexpand_threads ? ' unroot' : '')
        + (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
        + (message.selected ? ' selected' : ''),
      // for performance use DOM instead of jQuery here
      row = document.createElement('tr'),
      col = document.createElement('td');
      row = document.createElement('tr');
    row.id = 'rcmrow'+uid;
    row.className = css_class;
@@ -1670,12 +1700,12 @@
      css_class += ' status';
      if (flags.deleted)
        css_class += ' deleted';
      else if (flags.unread)
      else if (!flags.seen)
        css_class += ' unread';
      else if (flags.unread_children > 0)
        css_class += ' unreadchildren';
    }
    if (flags.replied)
    if (flags.answered)
      css_class += ' replied';
    if (flags.forwarded)
      css_class += ' forwarded';
@@ -1686,9 +1716,10 @@
    // threads
    if (this.env.threading) {
      // This assumes that div width is hardcoded to 15px,
      var width = message.depth * 15;
      if (message.depth) {
        // This assumes that div width is hardcoded to 15px,
        tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
        if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
          || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
            (!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
@@ -1703,13 +1734,9 @@
        if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
          message.expanded = true;
        }
      }
      if (width)
        tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + width + 'px;">&nbsp;&nbsp;</span>';
      if (message.has_children && !message.depth)
        expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
      }
    }
    tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
@@ -1723,7 +1750,7 @@
    }
    // add each submitted col
    for (var n in this.env.coltypes) {
    for (n in this.env.coltypes) {
      c = this.env.coltypes[n];
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
@@ -1743,7 +1770,7 @@
      else if (c == 'status') {
        if (flags.deleted)
          css_class = 'deleted';
        else if (flags.unread)
        else if (!flags.seen)
          css_class = 'unread';
        else if (flags.unread_children > 0)
          css_class = 'unreadchildren';
@@ -1753,8 +1780,17 @@
      }
      else if (c == 'threads')
        html = expando;
      else if (c == 'subject')
      else if (c == 'subject') {
        if (bw.ie)
          col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
        html = tree + cols[c];
      }
      else if (c == 'priority') {
        if (flags.prio > 0 && flags.prio < 6)
          html = '<span class="prio'+flags.prio+'">&nbsp;</span>';
        else
          html = '&nbsp;';
      }
      else
        html = cols[c];
@@ -1853,10 +1889,7 @@
    if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
      this.show_contentframe(true);
    else {
      if (!this.env.frame_lock) {
        (this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
      }
      this.location_href(this.env.comm_path+url, target);
      this.location_href(this.env.comm_path+url, target, true);
      // mark as read and change mbox unread counter
      if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
@@ -1890,6 +1923,12 @@
      this.set_busy(false, null, this.env.frame_lock);
  };
  this.lock_frame = function()
  {
    if (!this.env.frame_lock)
      (this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
  };
  // list a specific page
  this.list_page = function(page)
  {
@@ -1915,18 +1954,13 @@
  // list messages of a specific mailbox using filter
  this.filter_mailbox = function(filter)
  {
    var search, lock = this.set_busy(true, 'searching');
    if (this.gui_objects.qsearchbox)
      search = this.gui_objects.qsearchbox.value;
    var lock = this.set_busy(true, 'searching');
    this.clear_message_list();
    // reset vars
    this.env.current_page = 1;
    this.http_request('search', '_filter='+filter
        + (search ? '&_q='+urlencode(search) : '')
        + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), lock);
    this.http_request('search', this.search_params(false, filter), lock);
  };
  // list messages of a specific mailbox
@@ -1961,7 +1995,7 @@
    if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
      url += '&_refresh=1';
    this.select_folder(mbox, this.env.mailbox);
    this.select_folder(mbox);
    this.env.mailbox = mbox;
    // load message list remotely
@@ -2025,8 +2059,7 @@
      new_row = tbody.firstChild;
    while (new_row) {
      if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid])
       && r.unread_children) {
      if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
       this.message_list.expand_all(r);
       this.set_unread_children(r.uid);
      }
@@ -2061,8 +2094,12 @@
  };
  // Initializes threads indicators/expanders after list update
  this.init_threads = function(roots)
  this.init_threads = function(roots, mbox)
  {
    // #1487752
    if (mbox && mbox != this.env.mailbox)
      return false;
    for (var n=0, len=roots.length; n<len; n++)
      this.add_tree_icons(roots[n]);
    this.expand_threads();
@@ -2466,7 +2503,7 @@
    // if there is a trash mailbox defined and we're not currently in it
    else {
      // if shift was pressed delete it immediately
      if (list && list.shiftkey) {
      if (list && list.modkey == SHIFT_KEY) {
        if (confirm(this.get_label('deletemessagesconfirm')))
          this.permanently_remove_messages();
      }
@@ -2601,12 +2638,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 +2652,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 +2677,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 +2690,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 +2708,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 +2780,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);
    }
@@ -2765,14 +2806,15 @@
  this.expunge_mailbox = function(mbox)
  {
    var lock = false,
      url = '_mbox='+urlencode(mbox);
    var lock, url = '_mbox='+urlencode(mbox);
    // lock interface if it's the active mailbox
    if (mbox == this.env.mailbox) {
       lock = this.set_busy(true, 'loading');
       url += '&_reload=1';
     }
      lock = this.set_busy(true, 'loading');
      url += '&_reload=1';
      if (this.env.search_request)
        url += '&_search='+this.env.search_request;
    }
    // send request to server
    this.http_post('expunge', url, lock);
@@ -2840,19 +2882,28 @@
      input_subject = $("input[name='_subject']"),
      input_message = $("[name='_message']").get(0),
      html_mode = $("input[name='_is_html']").val() == '1',
      ac_fields = ['cc', 'bcc', 'replyto', 'followupto'];
      ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
      ac_props;
    // configure parallel autocompletion
    if (this.env.autocomplete_threads > 0) {
      ac_props = {
        threads: this.env.autocomplete_threads,
        sources: this.env.autocomplete_sources
      };
    }
    // init live search events
    this.init_address_input_events(input_to);
    this.init_address_input_events(input_to, ac_props);
    for (var i in ac_fields) {
      this.init_address_input_events($("[name='_"+ac_fields[i]+"']"));
      this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
    }
    if (!html_mode) {
      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]);
      }
    }
@@ -2873,9 +2924,9 @@
    this.auto_save_start();
  };
  this.init_address_input_events = function(obj)
  this.init_address_input_events = function(obj, props)
  {
    obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e){ return ref.ksearch_keydown(e, this); })
    obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
      .attr('autocomplete', 'off');
  };
@@ -2891,7 +2942,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;
@@ -3004,6 +3055,36 @@
    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)
  {
    $("input[name='_draft_saveid']").val(id);
@@ -3082,7 +3163,7 @@
        sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/))
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
@@ -3094,7 +3175,7 @@
        sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/))
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        if (this.env.sig_above) {
@@ -3163,12 +3244,12 @@
      if (this.env.signatures[id]) {
        if (this.env.signatures[id].is_html) {
          sig = this.env.signatures[id].text;
          if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/))
          if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '<br />' + sig;
        }
        else {
          sig = this.env.signatures[id].text;
          if (!sig.match(/^--[ -]\r?\n/))
          if (!sig.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '\n' + sig;
          sig = '<pre>' + sig + '</pre>';
        }
@@ -3188,11 +3269,21 @@
      return false;
    // get file input field, count files on capable browser
    var field = $('input[type=file]', form).get(0),
    var i, size = 0, 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 (files) {
      // check file size
      if (field.files && this.env.max_filesize && this.env.filesizeerror) {
        for (i=0; i<files; i++)
          size += field.files[i].size;
        if (size && size > this.env.max_filesize) {
          this.display_message(this.env.filesizeerror, 'error');
          return;
        }
      }
      var frame_name = this.async_upload_form(form, 'upload', function(e) {
        var d, content = '';
        try {
@@ -3215,7 +3306,7 @@
      });
      // display upload indicator and cancel button
      var content = this.get_label('uploading' + (files > 1 ? 'many' : '')),
      var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
        ts = frame_name.replace(/^rcmupload/, '');
      if (this.env.loadingicon)
@@ -3223,6 +3314,11 @@
      if (this.env.cancelicon)
        content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content;
      this.add2attachment_list(ts, { name:'', html:content, complete:false });
      // upload progress support
      if (this.env.upload_progress_time) {
        this.upload_progress_start('upload', ts);
      }
    }
    // set reference to the form object
@@ -3287,6 +3383,25 @@
    return false;
  };
  this.upload_progress_start = function(action, name)
  {
    window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
      this.env.upload_progress_time * 1000);
  };
  this.upload_progress_update = function(param)
  {
    var elem = $('#'+param.name + '> span');
    if (!elem.length || !param.text)
      return;
    elem.text(param.text);
    if (!param.done)
      this.upload_progress_start(param.action, param.name);
  };
  // send remote request to add a new contact
  this.add_contact = function(value)
  {
@@ -3300,36 +3415,56 @@
  this.qsearch = function(value)
  {
    if (value != '') {
      var addurl = '';
      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(',');
          }
        }
      } else if (this.contact_list) {
        this.contact_list.clear(true);
        this.show_contentframe(false);
      }
      var n, lock = this.set_busy(true, 'searching');
      if (this.gui_objects.search_filter)
        addurl += '&_filter=' + this.gui_objects.search_filter.value;
      if (this.message_list)
        this.clear_message_list();
      else if (this.contact_list)
        this.list_contacts_clear();
      // 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) : '')
      r = this.http_request('search', this.search_params(value)
        + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : '')
        + (addurl ? addurl : ''), lock);
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
      this.env.qsearch = {lock: lock, request: r};
    }
    return true;
  };
  // build URL params for search
  this.search_params = function(search, filter)
  {
    var n, url = [], mods_arr = [],
      mods = this.env.search_mods,
      mbox = this.env.mailbox;
    if (!filter && this.gui_objects.search_filter)
      filter = this.gui_objects.search_filter.value;
    if (!search && this.gui_objects.qsearchbox)
      search = this.gui_objects.qsearchbox.value;
    if (filter)
      url.push('_filter=' + urlencode(filter));
    if (search) {
      url.push('_q='+urlencode(search));
      if (mods && this.message_list)
        mods = mods[mbox] ? mods[mbox] : mods['*'];
      if (mods) {
        for (n in mods)
          mods_arr.push(n);
        url.push('_headers='+mods_arr.join(','));
      }
    }
    if (mbox)
      url.push('_mbox='+urlencode(mbox));
    return url.join('&');
  };
  // reset quick-search form
@@ -3338,8 +3473,12 @@
    if (this.gui_objects.qsearchbox)
      this.gui_objects.qsearchbox.value = '';
    if (this.env.qsearch)
      this.abort_request(this.env.qsearch);
    this.env.qsearch = null;
    this.env.search_request = null;
    return true;
    this.env.search_id = null;
  };
  this.sent_successfully = function(type, msg)
@@ -3355,14 +3494,14 @@
  /*********************************************************/
  // handler for keyboard events on address-fields
  this.ksearch_keydown = function(e, obj)
  this.ksearch_keydown = function(e, obj, props)
  {
    if (this.ksearch_timer)
      clearTimeout(this.ksearch_timer);
    var highlight;
    var key = rcube_event.get_keycode(e);
    var mod = rcube_event.get_modifier(e);
    var highlight,
      key = rcube_event.get_keycode(e),
      mod = rcube_event.get_modifier(e);
    switch (key) {
      case 38:  // key up
@@ -3381,13 +3520,15 @@
        return rcube_event.cancel(e);
      case 9:  // tab
        if (mod == SHIFT_KEY)
          break;
      case 9:   // tab
        if (mod == SHIFT_KEY || !this.ksearch_visible()) {
          this.ksearch_hide();
          return;
        }
     case 13:  // enter
        if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
          break;
      case 13:  // enter
        if (!this.ksearch_visible())
          return false;
        // insert selected address and hide ksearch pane
        this.insert_recipient(this.ksearch_selected);
@@ -3397,7 +3538,7 @@
      case 27:  // escape
        this.ksearch_hide();
        break;
        return;
      case 37:  // left
      case 39:  // right
@@ -3406,10 +3547,15 @@
    }
    // start timer
    this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(); }, 200);
    this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
    this.ksearch_input = obj;
    return true;
  };
  this.ksearch_visible = function()
  {
    return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
  };
  this.ksearch_select = function(node)
@@ -3427,28 +3573,32 @@
  this.insert_recipient = function(id)
  {
    if (!this.env.contacts[id] || !this.ksearch_input)
    if (id === null || !this.env.contacts[id] || !this.ksearch_input)
      return;
    // get cursor pos
    var inp_value = this.ksearch_input.value,
      cpos = this.get_caret_pos(this.ksearch_input),
      p = inp_value.lastIndexOf(this.ksearch_value, cpos),
      trigger = false,
      insert = '',
      // replace search string with full address
      pre = inp_value.substring(0, p),
      end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
    this.ksearch_destroy();
    // insert all members of a group
    if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) {
      insert += this.env.contacts[id].name + ', ';
      this.group2expand = $.extend({}, this.env.contacts[id]);
      this.group2expand.input = this.ksearch_input;
      this.http_request('group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false);
      this.http_request('mail/group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false);
    }
    else if (typeof this.env.contacts[id] === 'string')
    else if (typeof this.env.contacts[id] === 'string') {
      insert = this.env.contacts[id] + ', ';
      trigger = true;
    }
    this.ksearch_input.value = pre + insert + end;
@@ -3456,18 +3606,22 @@
    cpos = p+insert.length;
    if (this.ksearch_input.setSelectionRange)
      this.ksearch_input.setSelectionRange(cpos, cpos);
    if (trigger)
      this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert });
  };
  this.replace_group_recipients = function(id, recipients)
  {
    if (this.group2expand && this.group2expand.id == id) {
      this.group2expand.input.value = this.group2expand.input.value.replace(this.group2expand.name, recipients);
      this.triggerEvent('autocomplete_insert', { field:this.group2expand.input, insert:recipients });
      this.group2expand = null;
    }
  };
  // address search processor
  this.ksearch_get_results = function()
  this.ksearch_get_results = function(props)
  {
    var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
@@ -3481,7 +3635,8 @@
    var cpos = this.get_caret_pos(this.ksearch_input),
      p = inp_value.lastIndexOf(',', cpos-1),
      q = inp_value.substring(p+1, cpos),
      min = this.env.autocomplete_min_length;
      min = this.env.autocomplete_min_length,
      ac = this.ksearch_data;
    // trim query string
    q = $.trim(q);
@@ -3490,15 +3645,14 @@
    if (q == this.ksearch_value)
      return;
    this.ksearch_destroy();
    if (q.length && q.length < min) {
      if (!this.env.acinfo) {
        this.env.acinfo = this.display_message(
      if (!this.ksearch_info) {
        this.ksearch_info = this.display_message(
          this.get_label('autocompletechars').replace('$min', min));
      }
      return;
    }
    else if (this.env.acinfo) {
      this.hide_message(this.env.acinfo);
    }
    var old_value = this.ksearch_value;
@@ -3508,63 +3662,120 @@
    if (!q.length)
      return;
    // ...new search value contains old one and previous search result was empty
    if (old_value && old_value.length && this.env.contacts && !this.env.contacts.length && q.indexOf(old_value) == 0)
    // ...new search value contains old one and previous search was not finished or its result was empty
    if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || !ac.num) && this.env.contacts && !this.env.contacts.length)
      return;
    var lock = this.display_message(this.get_label('searching'), 'loading');
    this.http_post('autocomplete', '_search='+urlencode(q), lock);
    var i, lock, source, xhr, reqid = new Date().getTime(),
      threads = props && props.threads ? props.threads : 1,
      sources = props && props.sources ? props.sources : [],
      action = props && props.action ? props.action : 'mail/autocomplete';
    this.ksearch_data = {id: reqid, sources: sources.slice(), action: action,
      locks: [], requests: [], num: sources.length};
    for (i=0; i<threads; i++) {
      source = this.ksearch_data.sources.shift();
      if (threads > 1 && source === null)
        break;
      lock = this.display_message(this.get_label('searching'), 'loading');
      xhr = this.http_post(action, '_search='+urlencode(q)+'&_id='+reqid
        + (source ? '&_source='+urlencode(source) : ''), lock);
      this.ksearch_data.locks.push(lock);
      this.ksearch_data.requests.push(xhr);
    }
  };
  this.ksearch_query_results = function(results, search)
  this.ksearch_query_results = function(results, search, reqid)
  {
    // search stopped in meantime?
    if (!this.ksearch_value)
      return;
    // ignore this outdated search response
    if (this.ksearch_value && search != this.ksearch_value)
    if (this.ksearch_input && search != this.ksearch_value)
      return;
    this.env.contacts = results ? results : [];
    this.ksearch_display_results(this.env.contacts);
  };
  this.ksearch_display_results = function (a_results)
  {
    // display search results
    if (a_results.length && this.ksearch_input && this.ksearch_value) {
      var p, ul, li, text, s_val = this.ksearch_value;
    var ul, li, text, init,
      value = this.ksearch_value,
      data = this.ksearch_data,
      maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
      // create results pane if not present
      if (!this.ksearch_pane) {
        ul = $('<ul>');
        this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
        this.ksearch_pane.__ul = ul[0];
      }
    // create results pane if not present
    if (!this.ksearch_pane) {
      ul = $('<ul>');
      this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
        .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
      this.ksearch_pane.__ul = ul[0];
    }
      // remove all search results
      ul = this.ksearch_pane.__ul;
    ul = this.ksearch_pane.__ul;
    // remove all search results or add to existing list if parallel search
    if (reqid && this.ksearch_pane.data('reqid') == reqid) {
      maxlen -= ul.childNodes.length;
    }
    else {
      this.ksearch_pane.data('reqid', reqid);
      init = 1;
      // reset content
      ul.innerHTML = '';
      this.env.contacts = [];
      // move the results pane right under the input box
      var pos = $(this.ksearch_input).offset();
      this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
    }
      // add each result line to list
      for (i=0; i < a_results.length; i++) {
        text = typeof a_results[i] === 'object' ? a_results[i].name : a_results[i];
    // add each result line to list
    if (results && results.length) {
      for (i=0; i < results.length && maxlen > 0; i++) {
        text = typeof results[i] === 'object' ? results[i].name : results[i];
        li = document.createElement('LI');
        li.innerHTML = text.replace(new RegExp('('+RegExp.escape(s_val)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
        li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
        li.onmouseover = function(){ ref.ksearch_select(this); };
        li.onmouseup = function(){ ref.ksearch_click(this) };
        li._rcm_id = i;
        li._rcm_id = this.env.contacts.length + i;
        ul.appendChild(li);
        maxlen -= 1;
      }
      // select the first
      $(ul.firstChild).attr('id', 'rcmksearchSelected').addClass('selected');
      this.ksearch_selected = 0;
      // move the results pane right under the input box and make it visible
      var pos = $(this.ksearch_input).offset();
      this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px' }).show();
    }
    // hide results pane
    else
      this.ksearch_hide();
    if (ul.childNodes.length) {
      this.ksearch_pane.show();
      // select the first
      if (!this.env.contacts.length) {
        $('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
        this.ksearch_selected = 0;
      }
    }
    if (results && results.length)
      this.env.contacts = this.env.contacts.concat(results);
    // run next parallel search
    if (data.id == reqid) {
      data.num--;
      if (maxlen > 0 && data.sources.length) {
        var lock, xhr, source = data.sources.shift();
        if (source) {
          lock = this.display_message(this.get_label('searching'), 'loading');
          xhr = this.http_post(data.action, '_search='+urlencode(value)+'&_id='+reqid
            +'&_source='+urlencode(source), lock);
          this.ksearch_data.locks.push(lock);
          this.ksearch_data.requests.push(xhr);
        }
      }
      else if (!maxlen) {
        if (!this.ksearch_msg)
          this.ksearch_msg = this.display_message(this.get_label('autocompletemore'));
        // abort pending searches
        this.ksearch_abort();
      }
    }
  };
  this.ksearch_click = function(node)
@@ -3581,19 +3792,48 @@
    if (this.ksearch_timer)
      clearTimeout(this.ksearch_timer);
    this.ksearch_value = '';
    this.ksearch_input = null;
    this.ksearch_hide();
  };
  this.ksearch_hide = function()
  {
    this.ksearch_selected = null;
    this.ksearch_value = '';
    if (this.ksearch_pane)
      this.ksearch_pane.hide();
   };
    this.ksearch_destroy();
  };
  // Clears autocomplete data/requests
  this.ksearch_destroy = function()
  {
    this.ksearch_abort();
    if (this.ksearch_info)
      this.hide_message(this.ksearch_info);
    if (this.ksearch_msg)
      this.hide_message(this.ksearch_msg);
    this.ksearch_data = null;
    this.ksearch_info = null;
    this.ksearch_msg = null;
  }
  // Aborts pending autocomplete requests
  this.ksearch_abort = function()
  {
    var i, len, ac = this.ksearch_data;
    if (!ac)
      return;
    for (i=0, len=ac.locks.length; i<len; i++)
      this.abort_request({request: ac.requests[i], lock: ac.locks[i]});
  };
  /*********************************************************/
@@ -3611,22 +3851,41 @@
    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);
    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);
    // 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', this.env.group || list.selection.length > 0);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
    return false;
  };
  this.list_contacts = function(src, group, page)
  {
    var add_url = '',
    var folder, add_url = '',
      target = window;
    if (!src)
@@ -3642,7 +3901,12 @@
    else if (group != this.env.group)
      page = this.env.current_page = 1;
    this.select_folder((group ? 'G'+src+group : src), (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
    if (this.env.search_id)
      folder = 'S'+this.env.search_id;
    else
      folder = group ? 'G'+src+group : src;
    this.select_folder(folder);
    this.env.source = src;
    this.env.group = group;
@@ -3675,9 +3939,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 : ''),
@@ -3689,11 +3951,19 @@
    if (group)
      url += '&_gid='+group;
    // also send search request to get the right messages
    if (this.env.search_request)
    // also send search request to get the right messages
    if (this.env.search_request)
      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', false);
    this.enable_command('compose', this.env.group ? true : false);
  };
  // load contact record
@@ -3705,6 +3975,13 @@
      add_url = '&_framed=1';
      target = window.frames[this.env.contentframe];
      this.show_contentframe(true);
      // load dummy content
      if (!cid) {
        // unselect selected row(s)
        this.contact_list.clear_selection();
        this.enable_command('delete', 'compose', false);
      }
    }
    else if (framed)
      return false;
@@ -3713,10 +3990,22 @@
      if (this.env.group)
        add_url += '&_gid='+urlencode(this.env.group);
      this.set_busy(true);
      this.location_href(this.env.comm_path+'&_action='+action+'&_source='+urlencode(this.env.source)+'&_cid='+urlencode(cid) + add_url, target);
      this.location_href(this.env.comm_path+'&_action='+action
        +'&_source='+urlencode(this.env.source)
        +'&_cid='+urlencode(cid) + add_url, target, true);
    }
    return true;
  };
  // add/delete member to/from the group
  this.group_member_change = function(what, cid, source, gid)
  {
    what = what == 'add' ? 'add' : 'del';
    var lock = this.display_message(this.get_label(what == 'add' ? 'addingmember' : 'removingmember'), 'loading');
    this.http_post('group-'+what+'members', '_cid='+urlencode(cid)
      + '&_source='+urlencode(source)
      + '&_gid='+urlencode(gid), lock);
  };
  // copy a contact to the specified target (group or directory)
@@ -3725,39 +4014,40 @@
    if (!cid)
      cid = this.contact_list.get_selection().join(',');
    if (to.type == 'group' && to.source == this.env.source) {
      this.http_post('group-addmembers', '_cid='+urlencode(cid)
        + '&_source='+urlencode(this.env.source)
        + '&_gid='+urlencode(to.id));
    }
    if (to.type == 'group' && to.source == this.env.source)
      this.group_member_change('add', cid, to.source, to.id);
    else if (to.type == 'group' && !this.env.address_sources[to.source].readonly) {
      var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
      this.http_post('copy', '_cid='+urlencode(cid)
        + '&_source='+urlencode(this.env.source)
        + '&_to='+urlencode(to.source)
        + '&_togid='+urlencode(to.id)
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''));
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
    }
    else if (to.id != this.env.source && cid && this.env.address_sources[to.id] && !this.env.address_sources[to.id].readonly) {
      var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
      this.http_post('copy', '_cid='+urlencode(cid)
        + '&_source='+urlencode(this.env.source)
        + '&_to='+urlencode(to.id)
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''));
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
    }
  };
  this.delete_contacts = function()
  {
    // exit if no mailbox specified or if selection is empty
    var selection = this.contact_list.get_selection();
    if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
    var selection = this.contact_list.get_selection(),
      undelete = this.env.address_sources[this.env.source].undelete;
    if (!(selection.length || this.env.cid) || (!undelete && !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));
@@ -3772,21 +4062,31 @@
      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
    this.http_post('delete', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source)+'&_from='+(this.env.action ? this.env.action : '')+qs);
    this.http_post('delete', '_cid='+urlencode(a_cids.join(','))
      +'&_source='+urlencode(this.env.source)
      +'&_from='+(this.env.action ? this.env.action : '')+qs,
      this.display_message(this.get_label('contactdeleting'), 'loading'));
    return true;
  };
  // update a contact record in the list
  this.update_contact_row = function(cid, cols_arr, newcid)
  this.update_contact_row = function(cid, cols_arr, newcid, source)
  {
    var c, row, list = this.contact_list;
    cid = String(cid).replace(this.identifier_expr, '_');
    // when in searching mode, concat cid with the source name
    if (!list.rows[cid]) {
      cid = cid+'-'+source;
      if (newcid)
        newcid = newcid+'-'+source;
    }
    if (list.rows[cid] && (row = list.rows[cid].obj)) {
      for (c=0; c<cols_arr.length; c++)
@@ -3808,31 +4108,29 @@
  // add row to contacts list
  this.add_contact_row = function(cid, cols, select)
  {
    if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
    if (!this.gui_objects.contactslist)
      return false;
    var tbody = this.gui_objects.contactslist.tBodies[0],
      rowcount = tbody.rows.length,
      even = rowcount%2,
    var c, list = this.contact_list,
      row = document.createElement('tr');
    row.id = 'rcmrow'+String(cid).replace(this.identifier_expr, '_');
    row.className = 'contact '+(even ? 'even' : 'odd');
    row.className = 'contact';
    if (this.contact_list.in_selection(cid))
    if (list.in_selection(cid))
      row.className += ' selected';
    // add each submitted col
    for (var c in cols) {
    for (c in cols) {
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
      col.innerHTML = cols[c];
      row.appendChild(col);
    }
    this.contact_list.insert_row(row);
    list.insert_row(row);
    this.enable_command('export', (this.contact_list.rowcount > 0));
    this.enable_command('export', list.rowcount > 0);
  };
  this.init_contact_form = function()
@@ -3854,24 +4152,27 @@
      this.selectedIndex = 0;
    });
    // enable date pickers on date fields
    if ($.datepicker && this.env.date_format) {
      $.datepicker.setDefaults({
        dateFormat: this.env.date_format,
        changeMonth: true,
        changeYear: true,
        yearRange: '-100:+10',
        showOtherMonths: true,
        selectOtherMonths: true,
        monthNamesShort: this.env.month_names,
        onSelect: function(dateText) { $(this).focus().val(dateText) }
      });
      $('input.datepicker').datepicker();
    }
    $("input[type='text']:visible").first().focus();
  };
  this.group_create = function()
  {
    if (!this.gui_objects.folderlist || !this.env.address_sources[this.env.source].groups)
      return;
    if (!this.name_input) {
      this.name_input = $('<input>').attr('type', 'text');
      this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
      this.name_input_li = $('<li>').addClass('contactgroup').append(this.name_input);
      var li = this.get_folder_li(this.env.source)
      this.name_input_li.insertAfter(li);
    }
    this.name_input.select().focus();
    this.add_input_row('contactgroup');
  };
  this.group_rename = function()
@@ -3896,8 +4197,10 @@
  this.group_delete = function()
  {
    if (this.env.group)
      this.http_post('group-delete', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group), true);
    if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
      var lock = this.set_busy(true, 'groupdeleting');
      this.http_post('group-delete', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group), lock);
    }
  };
  // callback from server upon group-delete command
@@ -3915,18 +4218,40 @@
    this.list_contacts(prop.source, 0);
  };
  // @TODO: maybe it would be better to use popup instead of inserting input to the list?
  this.add_input_row = function(type)
  {
    if (!this.gui_objects.folderlist)
      return;
    if (!this.name_input) {
      this.name_input = $('<input>').attr('type', 'text').data('tt', type);
      this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
      this.name_input_li = $('<li>').addClass(type).append(this.name_input);
      var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : this.get_folder_li(this.env.source);
      this.name_input_li.insertAfter(li);
    }
    this.name_input.select().focus();
  };
  // handler for keyboard events on the input field
  this.add_input_keydown = function(e)
  {
    var key = rcube_event.get_keycode(e);
    var key = rcube_event.get_keycode(e),
      input = $(e.target), itype = input.data('tt');
    // enter
    if (key == 13) {
      var newname = this.name_input.val();
      var newname = input.val();
      if (newname) {
        var lock = this.set_busy(true, 'loading');
        if (this.env.group_renaming)
        if (itype == 'contactsearch')
          this.http_post('search-create', '_search='+urlencode(this.env.search_request)+'&_name='+urlencode(newname), lock);
        else if (this.env.group_renaming)
          this.http_post('group-rename', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group)+'&_name='+urlencode(newname), lock);
        else
          this.http_post('group-create', '_source='+urlencode(this.env.source)+'&_name='+urlencode(newname), lock);
@@ -3966,17 +4291,16 @@
    this.reset_add_input();
    prop.type = 'group';
    var key = 'G'+prop.source+prop.id;
    this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
    var key = 'G'+prop.source+prop.id,
      link = $('<a>').attr('href', '#')
        .attr('rel', prop.source+':'+prop.id)
        .click(function() { return rcmail.command('listgroup', prop, this); })
        .html(prop.name),
      li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactgroup'})
        .append(link);
    var link = $('<a>').attr('href', '#')
      .attr('rel', prop.source+':'+prop.id)
      .bind('click', function() { return rcmail.command('listgroup', prop, this);})
      .html(prop.name);
    var li = $('<li>').attr('id', 'rcmli'+key.replace(this.identifier_expr, '_'))
      .addClass('contactgroup')
      .append(link)
      .insertAfter(this.get_folder_li(prop.source));
    this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
    this.add_contact_group_row(prop, li);
    this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] });
  };
@@ -3992,19 +4316,23 @@
    // group ID has changed, replace link node and identifiers
    if (li && prop.newid) {
      var newkey = 'G'+prop.source+prop.newid;
      var newkey = 'G'+prop.source+prop.newid,
        newprop = $.extend({}, prop);;
      li.id = String('rcmli'+newkey).replace(this.identifier_expr, '_');
      this.env.contactfolders[newkey] = this.env.contactfolders[key];
      this.env.contactfolders[newkey].id = prop.newid;
      this.env.group = prop.newid;
      var newprop = $.extend({}, prop);
      delete this.env.contactfolders[key];
      delete this.env.contactgroups[key];
      newprop.id = prop.newid;
      newprop.type = 'group';
      link = $('<a>').attr('href', '#')
        .attr('rel', prop.source+':'+prop.newid)
        .bind('click', function() { return rcmail.command('listgroup', newprop, this);})
        .click(function() { return rcmail.command('listgroup', newprop, this); })
        .html(prop.name);
      $(li).children().replaceWith(link);
    }
@@ -4013,9 +4341,42 @@
      link.innerHTML = prop.name;
    this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
    this.add_contact_group_row(prop, $(li), true);
    this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
  };
  // add contact group row to the list, with sorting
  this.add_contact_group_row = function(prop, li, reloc)
  {
    var row, name = prop.name.toUpperCase(),
      sibling = this.get_folder_li(prop.source),
      prefix = 'rcmliG'+(prop.source).replace(this.identifier_expr, '_');
    // When renaming groups, we need to remove it from DOM and insert it in the proper place
    if (reloc) {
      row = li.clone(true);
      li.remove();
    }
    else
      row = li;
    $('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) {
      if (name >= $(this).text().toUpperCase())
        sibling = elem;
      else
        return false;
    });
    row.insertAfter(sibling);
  };
  this.update_group_commands = function()
  {
    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.init_edit_field = function(col, elem)
  {
@@ -4024,7 +4385,7 @@
    elem.focus(function(){ ref.focus_textfield(this); })
      .blur(function(){ ref.blur_textfield(this); })
      .each(function(){ this._placeholder = this.title = ref.env.coltypes[col].label; ref.blur_textfield(this); });
      .each(function(){ this._placeholder = this.title = (ref.env.coltypes[col].label || ''); ref.blur_textfield(this); });
  };
  this.insert_edit_field = function(col, section, menu)
@@ -4038,7 +4399,7 @@
    else {
      var lastelem = $('.ff_'+col),
        appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
      if (!appendcontainer.length)
        appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last());
@@ -4047,7 +4408,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
@@ -4061,16 +4422,32 @@
            .appendTo(cell);
          this.init_edit_field(col, input);
          if (colprop.type == 'date' && $.datepicker)
            input.datepicker();
        }
        else if (colprop.type == 'composite') {
          var childcol, cp, first;
          for (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', name: '_'+childcol+name_suffix, 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;
          }
@@ -4081,7 +4458,7 @@
            .addClass('ff_'+col)
            .attr('name', '_'+col+name_suffix)
            .appendTo(cell);
          var options = input.attr('options');
          options[options.length] = new Option('---', '');
          if (colprop.options)
@@ -4095,10 +4472,10 @@
            .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)
@@ -4114,7 +4491,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();
@@ -4124,7 +4501,7 @@
      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+'"]');
@@ -4166,7 +4543,7 @@
  this.set_photo_actions = function(id)
  {
    var n, buttons = this.buttons['upload-photo'];
    for (n=0; n < buttons.length; n++)
    for (n=0; buttons && n < buttons.length; n++)
      $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
    $('#ff_photo').val(id);
@@ -4174,40 +4551,143 @@
    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, true);
    return true;
  };
  // unselect directory/group
  this.unselect_directory = function()
  {
    this.select_folder('');
    this.enable_command('search-delete', false);
  };
  // callback for creating a new saved search record
  this.insert_saved_search = function(name, id)
  {
    this.reset_add_input();
    var key = 'S'+id,
      link = $('<a>').attr('href', '#')
        .attr('rel', id)
        .click(function() { return rcmail.command('listsearch', id, this); })
        .html(name),
      li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactsearch'})
        .append(link),
      prop = {name:name, id:id, li:li[0]};
    this.add_saved_search_row(prop, li);
    this.select_folder('S'+id);
    this.enable_command('search-delete', true);
    this.env.search_id = id;
    this.triggerEvent('abook_search_insert', prop);
  };
  // add saved search row to the list, with sorting
  this.add_saved_search_row = function(prop, li, reloc)
  {
    var row, sibling, name = prop.name.toUpperCase();
    // When renaming groups, we need to remove it from DOM and insert it in the proper place
    if (reloc) {
      row = li.clone(true);
      li.remove();
    }
    else
      row = li;
    $('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) {
      if (!sibling)
        sibling = this.previousSibling;
      if (name >= $(this).text().toUpperCase())
        sibling = elem;
      else
        return false;
    });
    if (sibling)
      row.insertAfter(sibling);
    else
      row.appendTo(this.gui_objects.folderlist);
  };
  // creates an input for saved search name
  this.search_create = function()
  {
    this.add_input_row('contactsearch');
  };
  this.search_delete = function()
  {
    if (this.env.search_request) {
      var lock = this.set_busy(true, 'savedsearchdeleting');
      this.http_post('search-delete', '_sid='+urlencode(this.env.search_id), lock);
    }
  };
  // callback from server upon search-delete command
  this.remove_search_item = function(id)
  {
    var li, key = 'S'+id;
    if ((li = this.get_folder_li(key))) {
      this.triggerEvent('search_delete', { id:id, li:li });
      li.parentNode.removeChild(li);
    }
    this.env.search_id = null;
    this.env.search_request = null;
    this.list_contacts_clear();
    this.reset_qsearch();
    this.enable_command('search-delete', 'search-create', false);
  };
  this.listsearch = function(id)
  {
    var folder, lock = this.set_busy(true, 'searching');
    if (this.contact_list) {
      this.list_contacts_clear();
    }
    this.reset_qsearch();
    this.select_folder('S'+id);
    // reset vars
    this.env.current_page = 1;
    this.http_request('search', '_sid='+urlencode(id), lock);
  };
  /*********************************************************/
  /*********        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)
  {
    var id = list.get_single_selection();
    var id = list.get_single_selection(), add_url = '', target = window;
    if (id) {
      var add_url = '', target = window;
      this.set_busy(true);
      if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
        add_url = '&_framed=1';
        target = window.frames[this.env.contentframe];
      }
      this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target);
      this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target, true);
    }
    return true;
@@ -4258,6 +4738,29 @@
    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();
    $('#mailboxroot')
      .mouseover(function(){ p.focus_subscription(this.id); })
      .mouseout(function(){ p.unfocus_subscription(this.id); })
  };
  this.focus_subscription = function(id)
  {
    var row, folder,
@@ -4266,18 +4769,16 @@
    if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
      if (this.env.subscriptionrows[id] &&
          (folder = this.env.subscriptionrows[id][0])) {
          (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(reg, '')) &&
            (!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))) {
          this.set_env('dstfolder', folder);
            (!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))
        ) {
          this.env.dstfolder = folder;
          $(row).addClass('droptarget');
        }
      }
      else if (this.env.mailbox.match(new RegExp(delim))) {
        this.set_env('dstfolder', this.env.delimiter);
        $(this.subscription_list.frame).addClass('droptarget');
      }
  };
@@ -4285,7 +4786,7 @@
  {
    var row = $('#'+id);
    this.set_env('dstfolder', null);
    this.env.dstfolder = null;
    if (this.env.subscriptionrows[id] && row[0])
      row.removeClass('droptarget');
    else
@@ -4299,7 +4800,7 @@
    if (list && (id = list.get_single_selection()) &&
        (folder = this.env.subscriptionrows['rcmrow'+id])
    ) {
      this.set_env('mailbox', folder[0]);
      this.env.mailbox = folder[0];
      this.show_folder(folder[0]);
      this.enable_command('delete-folder', !folder[2]);
    }
@@ -4315,15 +4816,17 @@
    var delim = RegExp.escape(this.env.delimiter),
      reg = RegExp('['+delim+']?[^'+delim+']+$');
    if (this.env.mailbox && this.env.dstfolder && (this.env.dstfolder != this.env.mailbox) &&
    if (this.env.mailbox && this.env.dstfolder !== null && (this.env.dstfolder != this.env.mailbox) &&
        (this.env.dstfolder != this.env.mailbox.replace(reg, ''))
    ) {
      reg = new RegExp('[^'+delim+']*['+delim+']', 'g');
      var lock = this.set_busy(true, 'foldermoving'),
        basename = this.env.mailbox.replace(reg, ''),
        newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename;
      var basename = this.env.mailbox.replace(reg, ''),
        newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename;
      this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), lock);
      if (newname != this.env.mailbox) {
        this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), this.set_busy(true, 'foldermoving'));
        this.subscription_list.draglayer.hide();
      }
    }
    this.drag_active = false;
    this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
@@ -4347,90 +4850,191 @@
    }
  };
  // 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, is_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, rowid, 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(1),
      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: is_protected ? true : false});
      // update subscription checkbox
      $('input[name="_subscribed[]"]', row).val(name).prop('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 = [];
    $.each(this.env.subscriptionrows, function(k,v){ folders.push(v) });
    folders.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0) });
    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;
      }
    }
    this.init_subscription_list();
    if (selection && document.getElementById('rcmrow'+selection))
      this.subscription_list.select_row(selection);
    // check if subfolder of a protected folder
    for (n=0; n<slist.length; n++) {
      if (name.indexOf(slist[n]+this.env.delimiter) == 0)
        rowid = this.get_folder_row_id(slist[n]);
    }
    if (document.getElementById(id).scrollIntoView)
      document.getElementById(id).scrollIntoView();
    // 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]);
    }
    // add row to the table
    if (rowid)
      $('#'+rowid).after(row);
    else
      row.appendTo(tbody);
    // 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, is_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, is_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)
  {
@@ -4451,33 +5055,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
@@ -4498,10 +5081,7 @@
      this.show_contentframe(true);
    }
    else {
      if (!this.env.frame_lock) {
        (parent.rcmail ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
      }
      this.location_href(this.env.comm_path+url, target);
      this.location_href(this.env.comm_path+url, target, true);
    }
  };
@@ -4529,6 +5109,34 @@
  /*********           GUI functionality           *********/
  /*********************************************************/
  var init_button = function(cmd, prop)
  {
    var elm = document.getElementById(prop.id);
    if (!elm)
      return;
    var preload = false;
    if (prop.type == 'image') {
      elm = elm.parentNode;
      preload = true;
    }
    elm._command = cmd;
    elm._id = prop.id;
    if (prop.sel) {
      elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
      elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
      if (preload)
        new Image().src = prop.sel;
    }
    if (prop.over) {
      elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
      elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
      if (preload)
        new Image().src = prop.over;
    }
  };
  // enable/disable buttons for page shifting
  this.set_page_buttons = function()
  {
@@ -4544,31 +5152,7 @@
        continue;
      for (var i=0; i< this.buttons[cmd].length; i++) {
        var prop = this.buttons[cmd][i];
        var elm = document.getElementById(prop.id);
        if (!elm)
          continue;
        var preload = false;
        if (prop.type == 'image') {
          elm = elm.parentNode;
          preload = true;
        }
        elm._command = cmd;
        elm._id = prop.id;
        if (prop.sel) {
          elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
          elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
          if (preload)
            new Image().src = prop.sel;
        }
        if (prop.over) {
          elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
          elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
          if (preload)
            new Image().src = prop.over;
        }
        init_button(cmd, this.buttons[cmd][i]);
      }
    }
  };
@@ -4717,7 +5301,7 @@
    if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder))
      $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder);
  };
  // write to the document/window title
  this.set_pagetitle = function(title)
  {
@@ -4726,27 +5310,29 @@
  };
  // display a system message, list of types in common.css (below #message definition)
  this.display_message = function(msg, type)
  this.display_message = function(msg, type, timeout)
  {
    // pass command to parent window
    if (this.is_framed())
      return parent.rcmail.display_message(msg, type);
      return parent.rcmail.display_message(msg, type, timeout);
    if (!this.gui_objects.message) {
      // save message in order to display after page loaded
      if (type != 'loading')
        this.pending_message = new Array(msg, type);
        this.pending_message = new Array(msg, type, timeout);
      return false;
    }
    type = type ? type : 'notice';
    var ref = this,
      key = msg,
      key = String(msg).replace(this.identifier_expr, '_'),
      date = new Date(),
      id = type + date.getTime(),
      id = type + date.getTime();
    if (!timeout)
      timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
    if (type == 'loading') {
      key = 'loading';
      timeout = this.env.request_timeout * 1000;
@@ -4782,7 +5368,8 @@
      obj.click(function() { return ref.hide_message(obj); });
    }
    window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
    if (timeout > 0)
      window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
    return id;
  };
@@ -4832,20 +5419,20 @@
  };
  // mark a mailbox as selected and set environment variable
  this.select_folder = function(name, old, prefix)
  this.select_folder = function(name, prefix)
  {
    if (this.gui_objects.folderlist) {
      var current_li, target_li;
      if ((current_li = this.get_folder_li(old, prefix))) {
        $(current_li).removeClass('selected').addClass('unfocused');
      if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
        current_li.removeClass('selected').addClass('unfocused');
      }
      if ((target_li = this.get_folder_li(name, prefix))) {
        $(target_li).removeClass('unfocused').addClass('selected');
      }
      // trigger event hook
      this.triggerEvent('selectfolder', { folder:name, old:old, prefix:prefix });
      this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
    }
  };
@@ -4911,22 +5498,26 @@
    this.env.status_col = null;
    if ((n = $.inArray('subject', this.env.coltypes)) >= 0) {
      this.set_env('subject_col', n);
      this.env.subject_col = n;
      if (list)
        list.subject_col = n;
    }
    if ((n = $.inArray('flag', this.env.coltypes)) >= 0)
      this.set_env('flagged_col', n);
      this.env.flagged_col = n;
    if ((n = $.inArray('status', this.env.coltypes)) >= 0)
      this.set_env('status_col', n);
      this.env.status_col = n;
    if (list)
      list.init_header();
  };
  // replace content of row count display
  this.set_rowcount = function(text)
  this.set_rowcount = function(text, mbox)
  {
    // #1487752
    if (mbox && mbox != this.env.mailbox)
      return false;
    $(this.gui_objects.countdisplay).html(text);
    // update page navigation buttons
@@ -5015,16 +5606,6 @@
      this.set_pagetitle(new_title);
    }
  };
  // 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)
@@ -5150,11 +5731,11 @@
      url = '?_task=utils&_action=html2text',
      lock = this.set_busy(true, 'converting');
    console.log('HTTP POST: ' + url);
    this.log('HTTP POST: ' + url);
    $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
      error: function(o, status, err) { rcmail.http_error(o, status, err, lock); },
      success: function(data) { rcmail.set_busy(false, null, lock); $(document.getElementById(id)).val(data); console.log(data); }
      success: function(data) { rcmail.set_busy(false, null, lock); $(document.getElementById(id)).val(data); rcmail.log(data); }
    });
  };
@@ -5169,7 +5750,7 @@
  /********************************************************/
  /*********        remote request methods        *********/
  /********************************************************/
  // compose a valid url with the given parameters
  this.url = function(action, query)
  {
@@ -5188,7 +5769,7 @@
    var base = this.env.comm_path;
    // overwrite task name
    if (query._action.match(/([a-z]+)\/([a-z-_]+)/)) {
    if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) {
      query._action = RegExp.$2;
      base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
    }
@@ -5219,8 +5800,11 @@
    this.redirect(this.url(action, query));
  };
  this.location_href = function(url, target)
  this.location_href = function(url, target, frame)
  {
    if (frame)
      this.lock_frame();
    // simulate real link click to force IE to send referer header
    if (bw.ie && target == window)
      $('<a>').attr('href', url).appendTo(document.body).get(0).click();
@@ -5247,8 +5831,9 @@
    url += '&_remote=1';
    // send request
    console.log('HTTP GET: ' + url);
    $.ajax({
    this.log('HTTP GET: ' + url);
    return $.ajax({
      type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
      success: function(data){ ref.http_response(data); },
      error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
@@ -5278,12 +5863,22 @@
    }
    // send request
    console.log('HTTP POST: ' + url);
    $.ajax({
    this.log('HTTP POST: ' + url);
    return $.ajax({
      type: 'POST', url: url, data: postdata, dataType: 'json',
      success: function(data){ ref.http_response(data); },
      error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
    });
  };
  // aborts ajax request
  this.abort_request = function(r)
  {
    if (r.request)
      r.request.abort();
    if (r.lock)
      this.set_busy(false, null, r.lock);
  };
  // handle HTTP response
@@ -5311,7 +5906,7 @@
    // if we get javascript code from server -> execute it
    if (response.exec) {
      console.log(response.exec);
      this.log(response.exec);
      eval(response.exec);
    }
@@ -5325,9 +5920,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));
        }
@@ -5361,6 +5967,7 @@
      case 'check-recent':
      case 'getunread':
      case 'search':
        this.env.qsearch = null;
      case 'list':
        if (this.task == 'mail') {
          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
@@ -5376,10 +5983,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));
            this.enable_command('search-create', this.env.source == '');
            this.enable_command('search-delete', this.env.search_id);
            this.update_group_commands();
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
          }
        }
@@ -5411,6 +6017,19 @@
    var ts = new Date().getTime(),
      frame_name = 'rcmupload'+ts;
    // upload progress support
    if (this.env.upload_progress_name) {
      var fname = this.env.upload_progress_name,
        field = $('input[name='+fname+']', form);
      if (!field.length) {
        field = $('<input>').attr({type: 'hidden', name: fname});
        field.prependTo(form);
      }
      field.val(ts);
    }
    // have to do it this way for IE
    // otherwise the form will be posted to a new window
    if (document.all) {
@@ -5430,14 +6049,16 @@
    // handle upload errors, parsing iframe content in onload
    $(frame_name).bind('load', {ts:ts}, onload);
    form.target = frame_name;
    form.action = this.url(action, { _id:this.env.compose_id||'', _uploadid:ts });
    form.setAttribute('enctype', 'multipart/form-data');
    form.submit();
    $(form).attr({
        target: frame_name,
        action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
        method: 'POST'})
      .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
      .submit();
    return frame_name;
  };
  // starts interval for keep-alive/check-recent signal
  this.start_keepalive = function()
  {
@@ -5447,7 +6068,14 @@
    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.http_request('keep-alive'); }, this.env.keep_alive * 1000);
      this._int = setInterval(function(){ ref.keep_alive(); }, this.env.keep_alive * 1000);
  };
  // sends keep-alive signal
  this.keep_alive = function()
  {
    if (!this.busy)
      this.http_request('keep-alive');
  };
  // sends request to check for recent messages
@@ -5548,11 +6176,12 @@
      if (elm.type == 'hidden')
        continue;
      // remember which elem was disabled before lock
      if (lock && elm.disabled)
        this.disabled_form_elements.push(elm);
      else if (lock || $.inArray(elm, this.disabled_form_elements)<0)
      // check this.disabled_form_elements before inArray() as a workaround for FF5 bug
      // http://bugs.jquery.com/ticket/9873
      else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
        elm.disabled = lock;
    }
  };
@@ -5570,6 +6199,23 @@
  }
};
rcube_webmail.long_subject_title_ie = function(elem, indent)
{
  if (!elem.title) {
    var $elem = $(elem),
      txt = $.trim($elem.text()),
      tmp = $('<span>').text(txt)
        .css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
          'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
        .appendTo($('body')),
      w = tmp.width();
    tmp.remove();
    if (w + indent * 15 > $elem.width())
      elem.title = txt;
  }
};
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;