thomascube
2011-09-19 50077da8e6daab205f8ee9512b2f37b5378f9938
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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
@@ -39,11 +40,6 @@
  this.message_time = 2000;
  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
@@ -90,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
@@ -156,7 +155,7 @@
    }
    // enable general commands
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'undo', true);
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', true);
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
@@ -165,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) {
@@ -309,11 +308,8 @@
          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);
            });
          }
        }
@@ -325,15 +321,14 @@
        }
        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('add', 'import', this.env.writable_source);
        this.enable_command('list', 'listgroup', 'advanced-search', true);
        this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
        // load contacts of selected source
        if (!this.env.action)
          this.command('list', this.env.source);
@@ -404,6 +399,10 @@
      default:
        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;
@@ -524,21 +523,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 && !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();
          this.list_contacts(props);
          this.enable_command('add', 'import', this.env.writable_source);
        }
        break;
@@ -645,11 +638,6 @@
            if (props == 'reload') {
              form.action += '?_reload=1';
            }
            else if ((input = $("input[name='_name']", form)) &&input.length && input.val() == '') {
              alert(this.get_label('nonamewarning'));
              input.focus();
              break;
            }
            else if (this.task == 'settings' && (this.env.identities_level % 2) == 0  &&
              (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
            ) {
@@ -752,7 +740,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');
@@ -821,7 +809,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);
@@ -995,22 +983,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);
          this.list_mailbox(this.env.mailbox, 1);
        else if (s && this.task == 'addressbook') {
          if (this.env.source == '') {
            for (var n in this.env.address_sources) break;
            for (n in this.env.address_sources) break;
            this.env.source = n;
            this.env.group = '';
          }
          this.list_contacts(this.env.source, 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;
@@ -1218,12 +1209,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;
    }
@@ -1232,9 +1223,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;
@@ -1381,12 +1372,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)
@@ -1531,11 +1522,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');
@@ -1571,10 +1563,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' });
  };
@@ -1655,8 +1647,8 @@
    // 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,
@@ -1679,10 +1671,10 @@
      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'),
@@ -1697,12 +1689,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';
@@ -1770,7 +1762,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';
@@ -1780,8 +1772,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];
@@ -1880,10 +1881,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) {
@@ -1915,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
@@ -1988,7 +1992,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
@@ -2052,8 +2056,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);
      }
@@ -2493,7 +2496,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();
      }
@@ -2796,14 +2799,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);
@@ -2878,7 +2882,7 @@
    if (this.env.autocomplete_threads > 0) {
      ac_props = {
        threads: this.env.autocomplete_threads,
        sources: this.env.autocomplete_sources,
        sources: this.env.autocomplete_sources
      };
    }
@@ -3258,11 +3262,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 {
@@ -3394,7 +3408,7 @@
  this.qsearch = function(value)
  {
    if (value != '') {
      var n, addurl = '', mods_arr = [],
      var n, r, addurl = '', mods_arr = [],
        mods = this.env.search_mods,
        mbox = this.env.mailbox,
        lock = this.set_busy(true, 'searching');
@@ -3418,13 +3432,14 @@
      // reset vars
      this.env.current_page = 1;
      this.http_request('search', '_q='+urlencode(value)
      r = this.http_request('search', '_q='+urlencode(value)
        + (mbox ? '&_mbox='+urlencode(mbox) : '')
        + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
        + (this.env.group ? '&_gid='+urlencode(this.env.group) : '')
        + (addurl ? addurl : ''), lock);
      this.env.qsearch = {lock: lock, request: r};
    }
    return true;
  };
  // reset quick-search form
@@ -3433,8 +3448,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)
@@ -3492,7 +3511,7 @@
      case 27:  // escape
        this.ksearch_hide();
        break;
        return;
      case 37:  // left
      case 39:  // right
@@ -3522,7 +3541,7 @@
  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
@@ -3607,6 +3626,8 @@
    var old_value = this.ksearch_value;
    this.ksearch_value = q;
    this.ksearch_destroy();
    // ...string is empty
    if (!q.length)
      return;
@@ -3614,8 +3635,6 @@
    // ...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)
      return;
    this.ksearch_destroy();
    var i, lock, source, xhr, reqid = new Date().getTime(),
      threads = props && props.threads ? props.threads : 1,
@@ -3640,8 +3659,12 @@
  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_input && this.ksearch_value && search != this.ksearch_value)
    if (this.ksearch_input && search != this.ksearch_value)
      return;
    // display search results
@@ -3681,7 +3704,7 @@
        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;
        li._rcm_id = this.env.contacts.length + i;
        ul.appendChild(li);
        maxlen -= 1;
      }
@@ -3750,10 +3773,8 @@
    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
    }
    for (i=0, len=ac.locks.length; i<len; i++)
      this.abort_request({request: ac.requests[i], lock: ac.locks[i]});
    this.ksearch_data = null;
  }
@@ -3807,7 +3828,7 @@
  this.list_contacts = function(src, group, page)
  {
    var add_url = '',
    var folder, add_url = '',
      target = window;
    if (!src)
@@ -3823,7 +3844,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;
@@ -3868,8 +3894,8 @@
    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);
@@ -3906,10 +3932,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)
@@ -3918,23 +3956,22 @@
    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);
    }
  };
@@ -3975,11 +4012,18 @@
  };
  // 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++)
@@ -4052,19 +4096,7 @@
  this.group_create = function()
  {
    if (!this.gui_objects.folderlist)
      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()
@@ -4089,8 +4121,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
@@ -4108,18 +4142,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);
@@ -4408,7 +4464,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);
@@ -4427,7 +4483,7 @@
      this.contact_list.clear_selection();
    }
    this.location_href(this.env.comm_path+'&_action=search'+add_url, target);
    this.location_href(this.env.comm_path+'&_action=search'+add_url, target, true);
    return true;
  };
@@ -4435,11 +4491,106 @@
  // 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 = '';
    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);
  };
@@ -4450,17 +4601,14 @@
  // 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;
@@ -4532,7 +4680,6 @@
    $('#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)
@@ -4543,22 +4690,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 (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);
        $(this.subscription_list.frame).addClass('droptarget');
      }
  };
@@ -4566,7 +4707,7 @@
  {
    var row = $('#'+id);
    this.set_env('dstfolder', null);
    this.env.dstfolder = null;
    if (this.env.subscriptionrows[id] && row[0])
      row.removeClass('droptarget');
    else
@@ -4580,7 +4721,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]);
    }
@@ -4596,12 +4737,12 @@
    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 basename = this.env.mailbox.replace(reg, ''),
        newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename;
        newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename;
      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'));
@@ -4861,10 +5002,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);
    }
  };
@@ -4892,6 +5030,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()
  {
@@ -4907,31 +5073,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]);
      }
    }
  };
@@ -5147,7 +5289,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;
  };
@@ -5197,20 +5340,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 });
    }
  };
@@ -5276,14 +5419,14 @@
    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();
@@ -5543,7 +5686,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);
    }
@@ -5574,8 +5717,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();
@@ -5641,6 +5787,15 @@
      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
@@ -5729,6 +5884,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));
@@ -5744,6 +5900,8 @@
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
          if (response.action == 'list' || response.action == 'search') {
            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 });
          }
@@ -5808,11 +5966,12 @@
    // 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('method', 'POST');
    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;
  };
@@ -5957,6 +6116,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;