thomascube
2011-07-30 fcc7f861b170596c6970aecb1ddc87a3567b112f
program/js/app.js
@@ -70,9 +70,12 @@
  };
  // add a localized label to the client environment
  this.add_label = function(key, value)
  this.add_label = function(p, value)
  {
    this.labels[key] = value;
    if (typeof p == 'string')
      this.labels[p] = value;
    else if (typeof p == 'object')
      $.extend(this.labels, p);
  };
  // add a button to the button list
@@ -135,14 +138,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 +156,7 @@
    }
    // enable general commands
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', true);
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'undo', true);
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
@@ -206,7 +201,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 +252,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) {
@@ -276,7 +274,6 @@
        }
        break;
      case 'addressbook':
        if (this.gui_objects.folderlist)
@@ -302,14 +299,11 @@
          if (this.gui_objects.qsearchbox) {
            $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
          }
          this.update_group_commands();
        }
        this.set_page_buttons();
        if (this.env.address_sources && this.env.address_sources[this.env.source] && !this.env.address_sources[this.env.source].readonly) {
          this.enable_command('add', 'import', true);
          this.enable_command('group-create', this.env.address_sources[this.env.source].groups);
        }
        if (this.env.cid) {
          this.enable_command('show', 'edit', true);
@@ -324,11 +318,12 @@
          }
        }
        if ((this.env.action == 'add' || this.env.action == 'edit') && this.gui_objects.editform) {
        if (this.gui_objects.editform) {
          this.enable_command('save', true);
          this.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();
        }
@@ -336,7 +331,12 @@
        if (this.contact_list && this.contact_list.rowcount > 0)
          this.enable_command('export', true);
        this.enable_command('list', 'listgroup', true);
        this.enable_command('add', 'import', this.env.writable_source);
        this.enable_command('list', 'listgroup', 'advanced-search', true);
        // load contacts of selected source
        if (!this.env.action)
          this.command('list', this.env.source);
        break;
@@ -410,7 +410,7 @@
    // 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 +432,11 @@
    this.start_keepalive();
  };
  this.log = function(msg)
  {
    if (window.console && console.log)
      console.log(msg);
  };
  /*********************************************************/
  /*********       client command interface        *********/
@@ -525,15 +530,15 @@
          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') {
        else if (this.task == 'addressbook') {
          if (!this.env.search_request || (props != this.env.source))
            this.reset_qsearch();
          this.list_contacts(props);
          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
          this.enable_command('add', 'import', this.env.writable_source);
        }
        break;
@@ -585,7 +590,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 +599,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 +628,33 @@
        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() == '') {
            // reload form
            if (props == 'reload') {
              form.action += '?_reload=1';
            }
            else if ((input = $("input[name='_name']", form)) &&input.length && input.val() == '') {
              alert(this.get_label('nonamewarning'));
              input_name.focus();
              input.focus();
              break;
            }
            else if (this.task == 'settings' && input_email.length && (this.env.identities_level % 2) == 0 && !rcube_check_email(input_email.val())) {
            else if (this.task == 'settings' && (this.env.identities_level % 2) == 0  &&
              (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
            ) {
              alert(this.get_label('noemailwarning'));
              input_email.focus();
              input.focus();
              break;
            }
@@ -651,7 +662,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;
@@ -890,12 +905,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)
@@ -983,8 +1000,14 @@
        if (s && this.env.mailbox)
          this.list_mailbox(this.env.mailbox);
        else if (s && this.task == 'addressbook')
        else if (s && this.task == 'addressbook') {
          if (this.env.source == '') {
            for (var n in this.env.address_sources) break;
            this.env.source = n;
            this.env.group = '';
          }
          this.list_contacts(this.env.source, this.env.group);
        }
        break;
      case 'listgroup':
@@ -1025,6 +1048,10 @@
      case 'identities':
      case 'folders':
        this.goto_url('settings/' + command);
        break;
      case 'undo':
        this.http_request('undo', '', this.display_message('', 'loading'));
        break;
      // unified command call (command name == function name)
@@ -1173,10 +1200,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;
@@ -1277,7 +1304,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');
@@ -1853,9 +1880,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.lock_frame();
      this.location_href(this.env.comm_path+url, target);
      // mark as read and change mbox unread counter
@@ -1888,6 +1913,12 @@
    if (!show && this.busy)
      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
@@ -2601,12 +2632,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 +2646,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 +2671,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 +2684,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 +2702,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 +2774,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);
    }
@@ -2840,19 +2875,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 +2917,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 +2935,7 @@
      input_message = $("[name='_message']");
    // check sender (if have no identities)
    if (input_from.attr('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
    if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
      alert(this.get_label('nosenderwarning'));
      input_from.focus();
      return false;
@@ -3003,6 +3047,36 @@
    this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
    this.enable_command('spellcheck', this.spellcheck_ready);
  };
  // get selected language
  this.spellcheck_lang = function()
  {
    var ed;
    if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins.spellchecker) {
      return ed.plugins.spellchecker.selectedLang;
    }
    else if (this.env.spellcheck) {
      return GOOGIE_CUR_LANG;
    }
  };
  // resume spellchecking, highlight provided mispellings without new ajax request
  this.spellcheck_resume = function(ishtml, data)
  {
    if (ishtml) {
      var ed = tinyMCE.get(this.env.composebody);
        sp = ed.plugins.spellchecker;
      sp.active = 1;
      sp._markWords(data);
      ed.nodeChanged();
    }
    else {
      var sp = this.env.spellcheck;
      sp.prepare(false, true);
      sp.processData(data);
    }
  }
  this.set_draft_id = function(id)
  {
@@ -3215,7 +3289,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 +3297,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 +3366,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,21 +3398,23 @@
  this.qsearch = function(value)
  {
    if (value != '') {
      var addurl = '';
      var n, addurl = '', mods_arr = [],
        mods = this.env.search_mods,
        mbox = this.env.mailbox,
        lock = this.set_busy(true, 'searching');
      if (this.message_list) {
        this.clear_message_list();
        if (this.env.search_mods) {
          var mods = this.env.search_mods[this.env.mailbox] ? this.env.search_mods[this.env.mailbox] : this.env.search_mods['*'];
          if (mods) {
            var head_arr = [];
            for (var n in mods)
              head_arr.push(n);
            addurl += '&_headers='+head_arr.join(',');
          }
        }
        if (mods)
          mods = mods[mbox] ? mods[mbox] : mods['*'];
      } else if (this.contact_list) {
        this.contact_list.clear(true);
        this.show_contentframe(false);
        this.list_contacts_clear();
      }
      if (mods) {
        for (n in mods)
          mods_arr.push(n);
        addurl += '&_headers='+mods_arr.join(',');
      }
      if (this.gui_objects.search_filter)
@@ -3322,9 +3422,8 @@
      // reset vars
      this.env.current_page = 1;
      var lock = this.set_busy(true, 'searching');
      this.http_request('search', '_q='+urlencode(value)
        + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : '')
        + (mbox ? '&_mbox='+urlencode(mbox) : '')
        + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : '')
        + (addurl ? addurl : ''), lock);
@@ -3355,14 +3454,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
@@ -3385,8 +3484,8 @@
        if (mod == SHIFT_KEY)
          break;
     case 13:  // enter
        if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
      case 13:  // enter
        if (this.ksearch_selected === null || !this.ksearch_value)
          break;
        // insert selected address and hide ksearch pane
@@ -3406,7 +3505,7 @@
    }
    // 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;
@@ -3434,21 +3533,25 @@
    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 +3559,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;
@@ -3512,59 +3619,102 @@
    if (old_value && old_value.length && this.env.contacts && !this.env.contacts.length && q.indexOf(old_value) == 0)
      return;
    var lock = this.display_message(this.get_label('searching'), 'loading');
    this.http_post('autocomplete', '_search='+urlencode(q), lock);
    this.ksearch_destroy();
    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: []};
    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)
  {
    // ignore this outdated search response
    if (this.ksearch_value && search != this.ksearch_value)
    if (this.ksearch_input && this.ksearch_value && 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 p, ul, li, text, init, s_val = this.ksearch_value,
      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.onmouseover = function(){ ref.ksearch_select(this); };
        li.onmouseup = function(){ ref.ksearch_click(this) };
        li._rcm_id = 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 (maxlen > 0 && this.ksearch_data.id == reqid && this.ksearch_data.sources.length) {
      var lock, xhr, props = this.ksearch_data, source = props.sources.shift();
      if (source) {
        lock = this.display_message(this.get_label('searching'), 'loading');
        xhr = this.http_post(props.action, '_search='+urlencode(s_val)+'&_id='+reqid
          +'&_source='+urlencode(source), lock);
        this.ksearch_data.locks.push(lock);
        this.ksearch_data.requests.push(xhr);
      }
    }
  };
  this.ksearch_click = function(node)
@@ -3581,20 +3731,36 @@
    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();
  };
  // Aborts pending autocomplete requests
  this.ksearch_destroy = function()
  {
    var i, len, ac = this.ksearch_data;
    if (!ac)
      return;
    for (i=0, len=ac.locks.length; i<len; i++) {
      this.hide_message(ac.locks[i]); // hide loading message
      ac.requests[i].abort(); // abort ajax request
    }
    this.ksearch_data = null;
  }
  /*********************************************************/
  /*********         address book methods          *********/
@@ -3611,15 +3777,34 @@
    if (this.preview_timer)
      clearTimeout(this.preview_timer);
    var id, frame, ref = this;
    var n, id, sid, ref = this, writable = false,
      source = this.env.source ? this.env.address_sources[this.env.source] : null;
    if (id = list.get_single_selection())
      this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
    else if (this.env.contentframe)
      this.show_contentframe(false);
    // no source = search result, we'll need to detect if any of
    // selected contacts are in writable addressbook to enable edit/delete
    if (list.selection.length) {
      if (!source) {
        for (n in list.selection) {
          sid = String(list.selection[n]).replace(/^[^-]+-/, '');
          if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
            writable = true;
            break;
          }
        }
      }
      else {
        writable = !source.readonly;
      }
    }
    this.enable_command('compose', list.selection.length > 0);
    this.enable_command('edit', (id && this.env.address_sources && !this.env.address_sources[this.env.source].readonly) ? true : false);
    this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
    return false;
  };
@@ -3675,9 +3860,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 : ''),
@@ -3696,6 +3879,13 @@
    this.http_request('list', url, lock);
  };
  this.list_contacts_clear = function()
  {
    this.contact_list.clear(true);
    this.show_contentframe(false);
    this.enable_command('delete', 'compose', false);
  };
  // load contact record
  this.load_contact = function(cid, action, framed)
  {
@@ -3705,6 +3895,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,8 +3910,10 @@
      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.lock_frame();
      this.location_href(this.env.comm_path+'&_action='+action
        +'&_source='+urlencode(this.env.source)
        +'&_cid='+urlencode(cid) + add_url, target);
    }
    return true;
  };
@@ -3752,12 +3951,12 @@
    if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
      return;
    var id, a_cids = [], qs = '';
    var id, n, a_cids = [], qs = '';
    if (this.env.cid)
      a_cids.push(this.env.cid);
    else {
      for (var n=0; n<selection.length; n++) {
      for (n=0; n<selection.length; n++) {
        id = selection[n];
        a_cids.push(id);
        this.contact_list.remove_row(id, (n == selection.length-1));
@@ -3772,7 +3971,7 @@
      qs += '&_gid='+urlencode(this.env.group);
    // also send search request to get the right records from the next page
    if (this.env.search_request)
    if (this.env.search_request)
      qs += '&_search='+this.env.search_request;
    // send request to server
@@ -3859,7 +4058,7 @@
  this.group_create = function()
  {
    if (!this.gui_objects.folderlist || !this.env.address_sources[this.env.source].groups)
    if (!this.gui_objects.folderlist)
      return;
    if (!this.name_input) {
@@ -3966,17 +4165,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 +4190,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,7 +4215,41 @@
      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)
@@ -4037,7 +4273,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());
@@ -4046,7 +4282,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
@@ -4062,14 +4298,27 @@
          this.init_edit_field(col, input);
        }
        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;
          }
@@ -4080,7 +4329,7 @@
            .addClass('ff_'+col)
            .attr('name', '_'+col+name_suffix)
            .appendTo(cell);
          var options = input.attr('options');
          options[options.length] = new Option('---', '');
          if (colprop.options)
@@ -4094,10 +4343,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)
@@ -4113,7 +4362,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();
@@ -4123,7 +4372,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+'"]');
@@ -4173,6 +4422,33 @@
    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.lock_frame();
    this.location_href(this.env.comm_path+'&_action=search'+add_url, target);
    return true;
  };
  // unselect directory/group
  this.unselect_directory = function()
  {
    if (this.env.address_sources.length > 1 || this.env.group != '') {
      this.select_folder('', (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
      this.env.group = '';
      this.env.source = '';
    }
  };
  /*********************************************************/
  /*********        user settings methods          *********/
@@ -4191,6 +4467,7 @@
        add_url = '&_framed=1';
        target = window.frames[this.env.contentframe];
      }
      this.lock_frame();
      this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target);
    }
@@ -4260,6 +4537,10 @@
      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); })
      .mouseup(function(){ if (p.drag_active) p.subscription_move_folder(); });
  };
  this.focus_subscription = function(id)
@@ -4278,6 +4559,10 @@
          this.set_env('dstfolder', folder);
          $(row).addClass('droptarget');
        }
      }
      else if (id == 'mailboxroot') {
        this.set_env('dstfolder', '');
        $(row).addClass('droptarget');
      }
      else if (this.env.mailbox.match(new RegExp(delim))) {
        this.set_env('dstfolder', this.env.delimiter);
@@ -4323,11 +4608,13 @@
        (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, ''),
      var basename = this.env.mailbox.replace(reg, ''),
        newname = this.env.dstfolder==this.env.delimiter ? 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));
@@ -4352,14 +4639,14 @@
  };
  // Add folder row to the table and initialize it
  this.add_folder_row = function (name, display_name, protected, subscribed, skip_init)
  this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
  {
    if (!this.gui_objects.subscriptionlist)
      return false;
    var row, n, i, tmp, folders, len, list = [], slist = [],
    var row, n, i, tmp, folders, rowid, list = [], slist = [],
      tbody = this.gui_objects.subscriptionlist.tBodies[0],
      refrow = $('tr', tbody).get(0),
      refrow = $('tr', tbody).get(1),
      id = 'rcmrow'+((new Date).getTime());
    if (!refrow) {
@@ -4369,21 +4656,27 @@
    }
    // clone a table row if there are existing rows
    row    = $(refrow).clone(true);
    row = $(refrow).clone(true);
    // set ID, reset css class
    row.attr('id', id);
    row.attr('class', class_name);
    // set folder name
    row.find('td:first').html(display_name);
    // update subscription checkbox
    $('input[name="_subscribed[]"]', row).val(name)
      .prop({checked: subscribed ? true : false, disabled: protected ? true : false});
      .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
    // add to folder/row-ID map
    this.env.subscriptionrows[id] = [name, display_name, 0];
    // sort folders, to find a place where to insert the row
    folders = this.env.subscriptionrows;
    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]) {
@@ -4399,19 +4692,22 @@
        tmp = null;
      }
    }
    list.sort();
    // make sure protected folders (and their subs) are on top
    list = slist.concat(list);
    // 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]);
    }
    // find folder position after sorting
    for (n=0, len=list.length; n<len; n++) {
      if (list[n] == name)
        break;
    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 (n && n < len)
      $('#'+this.get_folder_row_id(list[n-1])).after(row);
    if (rowid)
      $('#'+rowid).after(row);
    else
      row.appendTo(tbody);
@@ -4428,7 +4724,7 @@
  };
  // replace an existing table row with a new folder line (with subfolders)
  this.replace_folder_row = function(oldfolder, newfolder, display_name, protected)
  this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
  {
    if (!this.gui_objects.subscriptionlist)
      return false;
@@ -4444,7 +4740,7 @@
    // replace an existing table row
    this._remove_folder_row(id);
    row = $(this.add_folder_row(newfolder, display_name, protected, subscribed, true));
    row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
    // detect tree depth change
    if (len = list.length) {
@@ -4573,9 +4869,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.lock_frame();
      this.location_href(this.env.comm_path+url, target);
    }
  };
@@ -4792,7 +5086,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)
  {
@@ -4801,27 +5095,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;
@@ -4857,7 +5153,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;
  };
@@ -5215,11 +5512,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); }
    });
  };
@@ -5234,7 +5531,7 @@
  /********************************************************/
  /*********        remote request methods        *********/
  /********************************************************/
  // compose a valid url with the given parameters
  this.url = function(action, query)
  {
@@ -5312,8 +5609,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); }
@@ -5343,8 +5641,9 @@
    }
    // 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); }
@@ -5376,7 +5675,7 @@
    // if we get javascript code from server -> execute it
    if (response.exec) {
      console.log(response.exec);
      this.log(response.exec);
      eval(response.exec);
    }
@@ -5390,9 +5689,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));
        }
@@ -5441,10 +5751,7 @@
          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.update_group_commands();
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
          }
        }
@@ -5476,6 +5783,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) {
@@ -5497,12 +5817,13 @@
    form.target = frame_name;
    form.action = this.url(action, { _id:this.env.compose_id||'', _uploadid:ts });
    form.setAttribute('method', 'POST');
    form.setAttribute('enctype', 'multipart/form-data');
    form.submit();
    return frame_name;
  };
  // starts interval for keep-alive/check-recent signal
  this.start_keepalive = function()
  {
@@ -5512,7 +5833,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
@@ -5613,11 +5941,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;
    }
  };