thomascube
2010-03-02 7c9d922b96f9a88b350d6e07f5bde84ccc40f9b0
program/js/app.js
@@ -22,6 +22,7 @@
  this.env = new Object();
  this.labels = new Object();
  this.buttons = new Object();
  this.buttons_sel = new Object();
  this.gui_objects = new Object();
  this.gui_containers = new Object();
  this.commands = new Object();
@@ -55,7 +56,7 @@
  // set jQuery ajax options
  jQuery.ajaxSetup({ cache:false,
    error:function(request, status, err){ ref.http_error(request, status, err); },
    beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid')); }
    beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-RoundCube-Request', ref.env.request_token); }
  });
  // set environment variable(s)
@@ -134,7 +135,7 @@
      this.goto_url('error', '_code=0x199');
      return;
      }
    // find all registered gui containers
    for (var n in this.gui_containers)
      this.gui_containers[n] = $('#'+this.gui_containers[n]);
@@ -142,6 +143,9 @@
    // find all registered gui objects
    for (var n in this.gui_objects)
      this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
    // init registered buttons
    this.init_buttons();
    // tell parent window that this frame is loaded
    if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
@@ -152,7 +156,7 @@
    
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
    switch (this.task)
      {
      case 'mail':
@@ -164,8 +168,8 @@
          this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
          this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
          this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
          this.message_list.addEventListener('dragmove', function(o, e){ p.drag_move(e); });
          this.message_list.addEventListener('dragend', function(o){ p.drag_active = false; });
          this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
          this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
          document.onmouseup = function(e){ return p.doc_mouse_up(e); };
          this.message_list.init();
@@ -188,7 +192,9 @@
        
        if (this.env.action=='show' || this.env.action=='preview')
          {
          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 'mark', 'viewsource', 'print', 'load-attachment', 'load-headers', true);
          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete',
            'open', 'mark', 'edit', 'viewsource', 'download', 'print', 'load-attachment', 'load-headers', true);
          if (this.env.next_uid)
            {
            this.enable_command('nextmessage', true);
@@ -198,6 +204,13 @@
            {
            this.enable_command('previousmessage', true);
            this.enable_command('firstmessage', true);
            }
          if (this.env.blockedobjects)
            {
            if (this.gui_objects.remoteobjectsmsg)
              this.gui_objects.remoteobjectsmsg.style.display = 'block';
            this.enable_command('load-images', 'always-load', true);
            }
          }
@@ -209,13 +222,6 @@
          {
          this.enable_command('compose', 'add-contact', false);
          parent.rcmail.show_contentframe(true);
          }
        if ((this.env.action=='show' || this.env.action=='preview') && this.env.blockedobjects)
          {
          if (this.gui_objects.remoteobjectsmsg)
            this.gui_objects.remoteobjectsmsg.style.display = 'block';
          this.enable_command('load-images', 'always-load', true);
          }
        if (this.env.action=='compose')
@@ -232,6 +238,9 @@
            this.enable_command('savedraft', true);
            
          document.onmouseup = function(e){ return p.doc_mouse_up(e); };
          // init message compose form
          this.init_messageform();
          }
        if (this.env.messagecount)
@@ -241,10 +250,6 @@
          this.enable_command('purge', true);
        this.set_page_buttons();
        // init message compose form
        if (this.env.action=='compose')
          this.init_messageform();
        // show printing dialog
        if (this.env.action=='print')
@@ -275,11 +280,12 @@
        if (this.gui_objects.contactslist)
          {
          this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:true, keyboard:true});
          this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); };
          this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
          this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
          this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
          this.contact_list.addEventListener('dragmove', function(o, e){ p.drag_move(e); });
          this.contact_list.addEventListener('dragend', function(o){ p.drag_active = false; });
          this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
          this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); });
          this.contact_list.init();
          if (this.env.cid)
@@ -299,7 +305,7 @@
        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', true);
          this.enable_command('add', 'import', true);
        
        if (this.env.cid)
          this.enable_command('show', 'edit', true);
@@ -307,7 +313,7 @@
        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
          this.enable_command('save', true);
        else
          this.enable_command('search', 'reset-search', 'moveto', 'import', true);
          this.enable_command('search', 'reset-search', 'moveto', true);
          
        if (this.contact_list && this.contact_list.rowcount > 0)
          this.enable_command('export', true);
@@ -319,15 +325,14 @@
      case 'settings':
        this.enable_command('preferences', 'identities', 'save', 'folders', true);
        
        if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity') {
        if (this.env.action=='identities') {
          this.enable_command('add', this.env.identities_level < 2);
          this.enable_command('delete', 'edit', true);
        }
        if (this.env.action=='edit-identity' || this.env.action=='add-identity')
          this.enable_command('save', true);
        if (this.env.action=='folders')
        else if (this.env.action=='edit-identity' || this.env.action=='add-identity') {
          this.enable_command('add', this.env.identities_level < 2);
          this.enable_command('save', 'delete', 'edit', true);
        }
        else if (this.env.action=='folders')
          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
        if (this.gui_objects.identitieslist)
@@ -340,8 +345,15 @@
          if (this.env.iid)
            this.identity_list.highlight_row(this.env.iid);
          }
        if (this.gui_objects.subscriptionlist)
        else if (this.gui_objects.sectionslist)
          {
          this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
          this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
          this.sections_list.init();
          this.sections_list.focus();
          this.sections_list.select_first();  // open first section by default
        }
        else if (this.gui_objects.subscriptionlist)
          this.init_subscription_list();
        break;
@@ -364,9 +376,6 @@
      default:
        break;
      }
    // enable basic commands
    this.enable_command('logout', true);
    // flag object as complete
    this.loaded = true;
@@ -418,7 +427,7 @@
      }
    // set eventhandler to message icon
    if (row.icon = row.obj.getElementsByTagName('TD')[0].getElementsByTagName('IMG')[0])
    if (row.icon = row.obj.getElementsByTagName('td')[0].getElementsByTagName('img')[0])
      {
      var p = this;
      row.icon.id = 'msgicn_'+row.uid;
@@ -435,13 +444,15 @@
      }
    // set eventhandler to flag icon, if icon found
    if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('TD')[this.env.flagged_col].getElementsByTagName('IMG')[0]))
    if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('td')[this.env.flagged_col].getElementsByTagName('img')[0]))
      {
      var p = this;
      row.flagged_icon.id = 'flaggedicn_'+row.uid;
      row.flagged_icon._row = row.obj;
      row.flagged_icon.onmousedown = function(e) { p.command('toggle_flag', this); };
      }
    this.triggerEvent('insertrow', { uid:uid, row:row });
  };
  // init message compose form: set focus and eventhandlers
@@ -455,24 +466,30 @@
    var input_to = $("[name='_to']");
    var input_subject = $("input[name='_subject']");
    var input_message = $("[name='_message']").get(0);
    var html_mode = $("input[name='_is_html']").val() == '1';
    // init live search events
    this.init_address_input_events(input_to);
    this.init_address_input_events($("[name='_cc']"));
    this.init_address_input_events($("[name='_bcc']"));
    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 (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == ''
        && $("input[name='_is_html']").val() != '1') {  // if we have HTML editor, signature is added in callback
        && !html_mode) {  // if we have HTML editor, signature is added in callback
      this.change_identity(input_from[0]);
    }
    else if (!html_mode)
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
    if (input_to.val() == '')
      input_to.focus();
    else if (input_subject.val() == '')
      input_subject.focus();
    else if (input_message)
      this.set_caret2start(input_message);
    else if (input_message && !html_mode)
      input_message.focus();
    // get summary of all field values
    this.compose_field_hash(true);
@@ -484,7 +501,7 @@
  this.init_address_input_events = function(obj)
    {
    var handler = function(e){ return ref.ksearch_keypress(e,this); };
    obj.bind((bw.safari ? 'keydown' : 'keypress'), handler);
    obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler);
    obj.attr('autocomplete', 'off');
    };
@@ -549,14 +566,11 @@
          this.gui_objects.loginform.submit();
        break;
      case 'logout':
        this.goto_url('logout', '', true);
        break;
      // commands to switch task
      case 'mail':
      case 'addressbook':
      case 'settings':
      case 'logout':
        this.switch_task(command);
        break;
@@ -565,7 +579,16 @@
          return true;
        else if (this.env.permaurl)
          parent.location.href = this.env.permaurl;
          break;
        break;
      case 'open':
        var uid;
        if (uid = this.get_single_uid())
        {
          obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid;
          return true;
        }
        break;
      // misc list commands
      case 'list':
@@ -585,7 +608,7 @@
            this.reset_qsearch();
          this.list_contacts(props);
          this.enable_command('add', (this.env.address_sources && !this.env.address_sources[props].readonly));
          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[props].readonly));
          }
        break;
@@ -596,22 +619,12 @@
      case 'sort':
        // get the type of sorting
        var a_sort = props.split('_');
        var sort_col = a_sort[0];
        var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null;
        var sort_order, sort_col = props;
        // no sort order specified: toggle
        if (sort_order==null)
          {
          if (this.env.sort_col==sort_col)
            sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
          else
            sort_order = this.env.sort_order;
          }
        if (this.env.sort_col==sort_col && this.env.sort_order==sort_order)
          break;
        if (this.env.sort_col==sort_col)
          sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
        else
          sort_order = 'ASC';
        // set table header class
        $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
@@ -690,6 +703,10 @@
          this.load_contact(cid, 'edit');
        else if (this.task=='settings' && props)
          this.load_identity(props, 'edit-identity');
        else if (this.task=='mail' && (cid = this.get_single_uid())) {
          var url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
          this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
        }
        break;
      case 'save-identity':
@@ -825,7 +842,10 @@
        break;
        
      case 'select-all':
        this.message_list.select_all(props);
        if (props == 'invert')
          this.message_list.invert_selection();
        else
          this.message_list.select_all(props);
        break;
      case 'select-none':
@@ -908,11 +928,11 @@
        break;
        
      case 'spellcheck':
        if (window.tinyMCE && tinyMCE.get('compose-body')) {
        if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
          tinyMCE.execCommand('mceSpellCheck', true);
        }
        else if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) {
          this.env.spellcheck.spellCheck(this.env.spellcheck.check_link);
          this.env.spellcheck.spellCheck();
          this.set_spellcheck_state('checking');
        }
        break;
@@ -971,6 +991,10 @@
        this.remove_attachment(props);
        break;
      case 'insert-sig':
        this.change_identity($("[name='_from']")[0], true);
        break;
      case 'reply-all':
      case 'reply':
        var uid;
@@ -1002,10 +1026,16 @@
        var uid;
        if (uid = this.get_single_uid())
          {
          ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox));
          ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
          if (this.sourcewin)
            window.setTimeout(function(){ ref.sourcewin.focus(); }, 20);
          }
        break;
      case 'download':
        var uid;
        if (uid = this.get_single_uid())
          this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
        break;
      case 'add-contact':
@@ -1045,7 +1075,7 @@
          this.lock_form(this.gui_objects.importform, true);
        }
        else
          this.goto_url('import');
          this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
        break;
        
      case 'export':
@@ -1195,6 +1225,16 @@
    this.set_busy(false);
    this.display_message('Request timed out!', 'error');
    };
  this.reload = function(delay)
  {
    if (this.env.framed && parent.rcmail)
      parent.rcmail.reload(delay);
    else if (delay)
      window.setTimeout(function(){ rcmail.reload(); }, delay);
    else if (window.location)
      location.href = this.env.comm_path;
  };
  /*********************************************************/
@@ -1228,13 +1268,18 @@
      this.env.last_folder_target = null;
      list.draglayer.hide();
    }
    // reset 'pressed' buttons
    if (this.buttons_sel) {
      for (var id in this.buttons_sel)
        if (typeof id != 'function')
          this.button_out(this.buttons_sel[id], id);
      this.buttons_sel = {};
    }
  };
  this.drag_start = function(list)
  {
    this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset;
    this.initialMailBoxScrollTop = document.getElementById("mailboxlist-container").scrollTop;
    var model = this.task == 'mail' ? this.env.mailboxes : this.env.address_sources;
    this.drag_active = true;
@@ -1244,6 +1289,9 @@
    // save folderlist and folders location/sizes for droptarget calculation in drag_move()
    if (this.gui_objects.folderlist && model)
      {
      this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset;
      this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop;
      var li, pos, list, height;
      list = $(this.gui_objects.folderlist);
      pos = list.offset();
@@ -1252,55 +1300,97 @@
      this.env.folder_coords = new Array();
      for (var k in model) {
        if (li = this.get_folder_li(k)) {
          pos = $(li.firstChild).offset();
          // only visible folders
          if (height = li.firstChild.offsetHeight)
            this.env.folder_coords[k] = { x1:pos.left, y1:pos.top, x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height };
          if (height = li.firstChild.offsetHeight) {
            pos = $(li.firstChild).offset();
            this.env.folder_coords[k] = { x1:pos.left, y1:pos.top,
              x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 };
          }
        }
      }
    }
  };
  this.drag_end = function(e)
  {
    this.drag_active = false;
    this.env.last_folder_target = null;
    if (this.folder_auto_timer) {
      window.clearTimeout(this.folder_auto_timer);
      this.folder_auto_timer = null;
      this.folder_auto_expand = null;
    }
    // over the folders
    if (this.gui_objects.folderlist && this.env.folder_coords) {
      for (var k in this.env.folder_coords) {
        if (this.env.folder_coords[k].on)
          $(this.get_folder_li(k)).removeClass('droptarget');
      }
    }
  };
  this.drag_move = function(e)
  {
    if (this.gui_objects.folderlist && this.env.folder_coords) {
      // offsets to compensate for scrolling while dragging a message
      var boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop;
      var moffset = this.initialMailBoxScrollTop-document.getElementById('mailboxlist-container').scrollTop;
      var moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop;
      var toffset = -moffset-boffset;
      var li, pos, mouse;
      var li, div, pos, mouse;
      mouse = rcube_event.get_mouse_pos(e);
      pos = this.env.folderlist_coords;
      mouse.y += toffset;
      // if mouse pointer is outside of folderlist
      if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) {
        if (this.env.last_folder_target) {
          $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
          this.env.folder_coords[this.env.last_folder_target].on = 0;
          this.env.last_folder_target = null;
        }
        return;
      }
      // over the folders
      for (var k in this.env.folder_coords) {
        pos = this.env.folder_coords[k];
        if (this.check_droptarget(k) && ((mouse.x >= pos.x1) && (mouse.x < pos.x2)
            && (mouse.y >= pos.y1) && (mouse.y < pos.y2))) {
          $(this.get_folder_li(k)).addClass('droptarget');
        if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2
            && this.check_droptarget(k)) {
          li = this.get_folder_li(k);
          div = $(li.getElementsByTagName("div")[0]);
          // if the folder is collapsed, expand it after 1sec and restart the drag & drop process.
          if (div.hasClass('collapsed')) {
            if (this.folder_auto_timer)
              window.clearTimeout(this.folder_auto_timer);
            this.folder_auto_expand = k;
            this.folder_auto_timer = window.setTimeout(function() {
                rcmail.command("collapse-folder", rcmail.folder_auto_expand);
                rcmail.drag_start(null);
              }, 1000);
          } else if (this.folder_auto_timer) {
            window.clearTimeout(this.folder_auto_timer);
            this.folder_auto_timer = null;
            this.folder_auto_expand = null;
          }
          $(li).addClass('droptarget');
          this.env.last_folder_target = k;
          this.env.folder_coords[k].on = 1;
        }
        else {
        else if (pos.on) {
          $(this.get_folder_li(k)).removeClass('droptarget');
          if (k == this.env.last_folder_target)
            this.env.last_folder_target = null;
          this.env.folder_coords[k].on = 0;
        }
      }
    }
  };
  this.collapse_folder = function(id)
    {
    var div;
@@ -1367,12 +1457,12 @@
    if (this.env.mailbox == this.env.drafts_mailbox)
      {
      this.enable_command('reply', 'reply-all', 'forward', false);
      this.enable_command('show', 'print', selected);
      this.enable_command('show', 'print', 'open', 'edit', 'download', 'viewsource', selected);
      this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false));
      }
    else
      {
      this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected);
      this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', 'edit', 'open', 'download', 'viewsource', selected);
      this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false));
      }
@@ -1528,15 +1618,14 @@
      this.env.current_page = 1;
      this.set_busy(true, 'searching');
      this.http_request('search', '_filter='+filter
           + (search ? '&_q='+urlencode(search) : '')
           + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), true);
          + (search ? '&_q='+urlencode(search) : '')
          + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), true);
    }
  // list messages of a specific mailbox
  this.list_mailbox = function(mbox, page, sort)
    {
    this.last_selected = 0;
    var add_url = '';
    var target = window;
@@ -1550,19 +1639,22 @@
    // also send search request to get the right messages
    if (this.env.search_request)
      add_url += '&_search='+this.env.search_request;
    // set page=1 if changeing to another mailbox
    if (!page && mbox != this.env.mailbox)
    if (!page && this.env.mailbox != mbox)
      {
      page = 1;
      this.env.current_page = page;
      if (this.message_list)
        this.message_list.clear_selection();
      this.show_contentframe(false);
      }
    if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
      add_url += '&_refresh=1';
    // unselect selected messages
    this.last_selected = 0;
    if (this.message_list)
      this.message_list.clear_selection();
    
    this.select_folder(mbox, this.env.mailbox);
    this.env.mailbox = mbox;
@@ -1771,13 +1863,13 @@
      lock = true;
      this.set_busy(true, 'movingmessage');
      }
    else if (!this.env.flag_for_deletion)
    else
      this.show_contentframe(false);
    // Hide message command buttons until a message is selected
    this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', false);
    this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', 'open', 'edit', 'viewsource', 'download', false);
    this._with_selected_messages('moveto', lock, add_url, (this.env.flag_for_deletion ? false : true));
    this._with_selected_messages('moveto', lock, add_url);
    };
  // delete selected messages from the current mailbox
@@ -1787,11 +1879,16 @@
    // exit if no mailbox specified or if selection is empty
    if (!this.env.uid && !selection.length)
        return;
      return;
    // if there is a trash mailbox defined and we're not currently in it:
    if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase() != String(this.env.trash_mailbox).toLowerCase())
      {
    // if config is set to flag for deletion
    if (this.env.flag_for_deletion)
      this.mark_message('delete');
    // if there isn't a defined trash mailbox or we are in it
    else if (!this.env.trash_mailbox || this.env.mailbox == this.env.trash_mailbox)
      this.permanently_remove_messages();
    // if there is a trash mailbox defined and we're not currently in it
    else {
      // if shift was pressed delete it immediately
      if (this.message_list && this.message_list.shiftkey)
        {
@@ -1801,21 +1898,6 @@
      else
        this.move_messages(this.env.trash_mailbox);
      }
    // if there is a trash mailbox defined but we *are* in it:
    else if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase())
      this.permanently_remove_messages();
    // if there isn't a defined trash mailbox and the config is set to flag for deletion
    else if (!this.env.trash_mailbox && this.env.flag_for_deletion)
      {
      this.mark_message('delete');
      if(this.env.action=="show")
        this.command('nextmessage','',this);
      else if (selection.length == 1)
        this.message_list.select_next();
      }
    // if there isn't a defined trash mailbox and the config is set NOT to flag for deletion
    else if (!this.env.trash_mailbox)
      this.permanently_remove_messages();
  };
  // delete the selected messages permanently
@@ -1826,7 +1908,7 @@
      return;
      
    this.show_contentframe(false);
    this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''), true);
    this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
    };
  // Send a specifc request with UIDs of all selected messages
@@ -1842,26 +1924,22 @@
      var selection = this.message_list.get_selection();
      var rows = this.message_list.rows;
      var id;
      for (var n=0; n<selection.length; n++)
        {
      for (var n=0; n<selection.length; n++) {
        id = selection[n];
        a_uids[a_uids.length] = id;
        if (remove)
          this.message_list.remove_row(id, (n == selection.length-1));
        else
        {
          this.set_message_status(id, 'deleted', true);
          if (this.env.read_when_deleted)
            this.set_message_status(id, 'unread', false);
          this.set_message(id);
        }
        this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
      }
      // make sure there are no selected rows
      if (!this.env.display_next)
        this.message_list.clear_selection();
    }
    // also send search request to get the right messages 
    if (this.env.search_request) 
      add_url += '&_search='+this.env.search_request;
    if (this.env.display_next && this.env.next_uid)
      add_url += '&_next_uid='+this.env.next_uid;
    // send request to server
    this.http_post(action, '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+add_url, lock);
@@ -2000,15 +2078,33 @@
      uid = a_uids[i];
      if (rows[uid])
        {
   this.set_message(uid, 'deleted', true);
        if (rows[uid].unread)
          r_uids[r_uids.length] = uid;
   if (this.env.skip_deleted)
          this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
   else
     this.set_message(uid, 'deleted', true);
        }
      }
    if (r_uids.length)
      add_url = '&_ruid='+r_uids.join(',');
    // make sure there are no selected rows
    if (this.env.skip_deleted && !this.env.display_next && this.message_list)
      this.message_list.clear_selection();
    add_url = '&_from='+(this.env.action ? this.env.action : '');
    if (r_uids.length)
      add_url += '&_ruid='+r_uids.join(',');
    if (this.env.skip_deleted) {
      // also send search request to get the right messages
      if (this.env.search_request)
        add_url += '&_search='+this.env.search_request;
      if (this.env.display_next && this.env.next_uid)
        add_url += '&_next_uid='+this.env.next_uid;
    }
    this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag=delete'+add_url);
    return true;  
  };
@@ -2085,6 +2181,14 @@
      return false;
      }
    // check if all files has been uploaded
    for (var key in this.env.attachments) {
      if (typeof this.env.attachments[key] == 'object' && !this.env.attachments[key].complete) {
        alert(this.get_label('notuploadedwarning'));
        return false;
      }
    }
    // display localized warning for missing subject
    if (input_subject.val() == '')
      {
@@ -2103,19 +2207,26 @@
      }
    // check for empty body
    if ((!window.tinyMCE || !tinyMCE.get('compose-body')) && input_message.val() == '' && !confirm(this.get_label('nobodywarning')))
    if ((!window.tinyMCE || !tinyMCE.get(this.env.composebody))
   && input_message.val() == '' && !confirm(this.get_label('nobodywarning')))
      {
      input_message.focus();
      return false;
      }
    else if (window.tinyMCE && tinyMCE.get('compose-body') && !tinyMCE.get('compose-body').getContent() && !confirm(this.get_label('nobodywarning')))
    else if (window.tinyMCE && tinyMCE.get(this.env.composebody)
   && !tinyMCE.get(this.env.composebody).getContent()
   && !confirm(this.get_label('nobodywarning')))
      {
      tinyMCE.get('compose-body').focus();
      tinyMCE.get(this.env.composebody).focus();
      return false;
      }
    // Apply spellcheck changes if spell checker is active
    this.stop_spellchecking();
    // move body from html editor to textarea (just to be sure, #1485860)
    if (window.tinyMCE && tinyMCE.get(this.env.composebody))
      tinyMCE.triggerSave();
    return true;
    };
@@ -2123,7 +2234,7 @@
  this.stop_spellchecking = function()
    {
    if (this.env.spellcheck && !this.spellcheck_ready) {
      exec_event(this.env.spellcheck.check_link, 'click');
      $(this.env.spellcheck.spell_span).trigger('click');
      this.set_spellcheck_state('ready');
      }
    };
@@ -2134,15 +2245,14 @@
      // stop spellchecking process
      if (!vis)
   this.stop_spellchecking();
      this.env.spellcheck.check_link.style.visibility = vis ? 'visible' : 'hidden';
      this.env.spellcheck.switch_lan_pic.style.visibility = vis ? 'visible' : 'hidden';
      $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden');
      }
    };
  this.set_spellcheck_state = function(s)
    {
    this.spellcheck_ready = (s=='check_spelling' || s=='ready');
    this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
    this.enable_command('spellcheck', this.spellcheck_ready);
    };
@@ -2178,112 +2288,147 @@
    if (value_subject)
      str += value_subject+':';
    
    var editor = tinyMCE.get('compose-body');
    var editor = tinyMCE.get(this.env.composebody);
    if (editor)
      str += editor.getContent();
    else
      str += $("[name='_message']").val();
    if (this.env.attachments)
      for (var upload_id in this.env.attachments)
        str += upload_id;
    if (save)
      this.cmp_hash = str;
    return str;
    };
    
  this.change_identity = function(obj)
    {
  this.change_identity = function(obj, show_sig)
  {
    if (!obj || !obj.options)
      return false;
    if (!show_sig)
      show_sig = this.env.show_sig;
    var id = obj.options[obj.selectedIndex].value;
    var input_message = $("[name='_message']");
    var message = input_message.val();
    var is_html = ($("input[name='_is_html']").val() == '1');
    var sig, p;
    var sig, cursor_pos, p = -1;
    if (!this.env.identity)
      this.env.identity = id
  
    if (!is_html)
      {
      // remove the 'old' signature
      if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity])
        {
        if (this.env.signatures[this.env.identity]['is_html'])
          sig = this.env.signatures[this.env.identity]['plain_text'];
        else
          sig = this.env.signatures[this.env.identity]['text'];
        if (sig.indexOf('-- ')!=0)
          sig = '-- \n'+sig;
        p = message.lastIndexOf(sig);
        if (p>=0)
          message = message.substring(0, p-1) + message.substring(p+sig.length, message.length);
        }
      message = message.replace(/[\r\n]+$/, '');
      // add the new signature string
      if (this.env.signatures && this.env.signatures[id])
        {
        sig = this.env.signatures[id]['text'];
        if (this.env.signatures[id]['is_html'])
          {
          sig = this.env.signatures[id]['plain_text'];
          }
        if (sig.indexOf('-- ')!=0)
          sig = '-- \n'+sig;
        message += '\n\n'+sig;
        }
      }
    // enable manual signature insert
    if (this.env.signatures && this.env.signatures[id])
      this.enable_command('insert-sig', true);
    else
      {
      var editor = tinyMCE.get('compose-body');
      this.enable_command('insert-sig', false);
      if (this.env.signatures)
        {
        // Append the signature as a div within the body
        var sigElem = editor.dom.get('_rc_sig');
        var newsig = '';
        var htmlsig = true;
    if (!is_html) {
      // remove the 'old' signature
      if (show_sig && this.env.identity && this.env.signatures && this.env.signatures[this.env.identity]) {
        sig = this.env.signatures[this.env.identity].is_html ? this.env.signatures[this.env.identity].plain_text : this.env.signatures[this.env.identity].text;
        if (sig.indexOf('-- ') != 0)
          sig = '-- \n'+sig;
        if (!sigElem)
          {
          // add empty line before signature on IE
          if (bw.ie)
            editor.getBody().appendChild(editor.getDoc().createElement('br'));
        p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
        if (p >= 0)
          message = message.substring(0, p) + message.substring(p+sig.length, message.length);
      }
          sigElem = editor.getDoc().createElement('div');
          sigElem.setAttribute('id', '_rc_sig');
          editor.getBody().appendChild(sigElem);
      // add the new signature string
      if (show_sig && this.env.signatures && this.env.signatures[id]) {
        sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
        if (sig.indexOf('-- ') != 0)
          sig = '-- \n'+sig;
        if (this.env.sig_above) {
          if (p >= 0) { // in place of removed signature
            message = message.substring(0, p) + sig + message.substring(p, message.length);
            cursor_pos = p - 1;
          }
          else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position
            message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
            cursor_pos = pos;
          }
          else { // on top
            cursor_pos = 0;
            message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
          }
        }
        else {
          message = message.replace(/[\r\n]+$/, '');
          cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0;
          message += '\n\n' + sig;
        }
      }
      else
        cursor_pos = this.env.top_posting ? 0 : message.length;
        if (this.env.signatures[id])
        {
          newsig = this.env.signatures[id]['text'];
          htmlsig = this.env.signatures[id]['is_html'];
      input_message.val(message);
      // move cursor before the signature
      this.set_caret_pos(input_message.get(0), cursor_pos);
    }
    else if (is_html && show_sig && this.env.signatures) {  // html
      var editor = tinyMCE.get(this.env.composebody);
      var sigElem = editor.dom.get('_rc_sig');
          if (newsig) {
            if (htmlsig && this.env.signatures[id]['plain_text'].indexOf('-- ')!=0)
              newsig = '<p>-- </p>' + newsig;
            else if (!htmlsig && newsig.indexOf('-- ')!=0)
              newsig = '-- \n' + newsig;
      // Append the signature as a div within the body
      if (!sigElem) {
        var body = editor.getBody();
        var doc = editor.getDoc();
        sigElem = doc.createElement('div');
        sigElem.setAttribute('id', '_rc_sig');
        if (this.env.sig_above) {
          // if no existing sig and top posting then insert at caret pos
          editor.getWin().focus(); // correct focus in IE
          var node = editor.selection.getNode();
          if (node.nodeName == 'BODY') {
            // no real focus, insert at start
            body.insertBefore(sigElem, body.firstChild);
            body.insertBefore(doc.createElement('br'), body.firstChild);
          }
          else {
            body.insertBefore(sigElem, node.nextSibling);
            body.insertBefore(doc.createElement('br'), node.nextSibling);
          }
        }
        else {
          if (bw.ie)  // add empty line before signature on IE
            body.appendChild(doc.createElement('br'));
        if (htmlsig)
          sigElem.innerHTML = newsig;
        else
          sigElem.innerHTML = '<pre>' + newsig + '</pre>';
          body.appendChild(sigElem);
        }
      }
    input_message.val(message);
      if (this.env.signatures[id]) {
        if (this.env.signatures[id].is_html) {
          sig = this.env.signatures[id].text;
          if (this.env.signatures[id].plain_text.indexOf('-- ') != 0)
            sig = '-- <br />' + sig;
        }
        else {
          sig = this.env.signatures[id].text;
          if (sig.indexOf('-- ') != 0)
            sig = '-- \n' + sig;
          sig = '<pre>' + sig + '</pre>';
        }
        sigElem.innerHTML = sig;
      }
    }
    this.env.identity = id;
    return true;
    };
  };
  this.show_attachment_form = function(a)
    {
@@ -2343,7 +2488,7 @@
        }
      else  // for standards-compilant browsers
        {
        var frame = document.createElement('IFRAME');
        var frame = document.createElement('iframe');
        frame.name = frame_name;
        frame.style.border = 'none';
        frame.style.width = 0;
@@ -2352,10 +2497,42 @@
        document.body.appendChild(frame);
        }
      // handle upload errors, parsing iframe content in onload
      var fr = document.getElementsByName(frame_name)[0];
      $(fr).bind('load', {ts:ts}, function(e) {
        var content = '';
        try {
          if (this.contentDocument) {
            var d = this.contentDocument;
          } else if (this.contentWindow) {
            var d = this.contentWindow.document;
          }
          content = d.childNodes[0].innerHTML;
        } catch (e) {}
        if (!String(content).match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
          rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
          rcmail.remove_from_attachment_list(e.data.ts);
        }
        // Opera hack: handle double onload
        if (bw.opera)
          rcmail.env.uploadframe = e.data.ts;
      });
      form.target = frame_name;
      form.action = this.env.comm_path+'&_action=upload';
      form.action = this.env.comm_path+'&_action=upload&_uploadid='+ts;
      form.setAttribute('enctype', 'multipart/form-data');
      form.submit();
      // hide upload form
      this.show_attachment_form(false);
      // display upload indicator and cancel button
      var content = this.get_label('uploading');
      if (this.env.loadingicon)
        content = '<img src="'+this.env.loadingicon+'" alt="" />'+content;
      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 });
      }
    
    // set reference to the form object
@@ -2365,17 +2542,35 @@
  // add file name to attachment list
  // called from upload page
  this.add2attachment_list = function(name, content)
    {
  this.add2attachment_list = function(name, att, upload_id)
  {
    if (!this.gui_objects.attachmentlist)
      return false;
    $('<li>').attr('id', name).html(content).appendTo(this.gui_objects.attachmentlist);
    var li = $('<li>').attr('id', name).html(att.html);
    var indicator;
    // replace indicator's li
    if (upload_id && (indicator = document.getElementById(upload_id))) {
      li.replaceAll(indicator);
    }
    else { // add new li
      li.appendTo(this.gui_objects.attachmentlist);
    }
    if (upload_id && this.env.attachments[upload_id])
      delete this.env.attachments[upload_id];
    this.env.attachments[name] = att;
    return true;
    };
  };
  this.remove_from_attachment_list = function(name)
    {
  {
    if (this.env.attachments[name])
      delete this.env.attachments[name];
    if (!this.gui_objects.attachmentlist)
      return false;
@@ -2383,14 +2578,24 @@
    for (i=0;i<list.length;i++)
      if (list[i].id == name)
        this.gui_objects.attachmentlist.removeChild(list[i]);
    };
  };
  this.remove_attachment = function(name)
    {
    if (name)
    if (name && this.env.attachments[name])
      this.http_post('remove-attachment', '_file='+urlencode(name));
    return true;
    };
  this.cancel_attachment_upload = function(name, frame_name)
    {
    if (!name || !frame_name)
      return false;
    this.remove_from_attachment_list(name);
    $("iframe[name='"+frame_name+"']").remove();
    return false;
    };
  // send remote request to add a new contact
@@ -2403,19 +2608,26 @@
    };
  // send remote request to search mail or contacts
  this.qsearch = function(value, addurl)
  this.qsearch = function(value)
    {
    if (value != '')
      {
      if (this.message_list)
      var addurl = '';
      if (this.message_list) {
        this.message_list.clear();
      else if (this.contact_list) {
        if (this.env.search_mods) {
          var head_arr = new Array();
          for (var n in this.env.search_mods)
            head_arr.push(n);
          addurl += '&_headers='+head_arr.join(',');
          }
        } else if (this.contact_list) {
        this.contact_list.clear(true);
        this.show_contentframe(false);
        }
      if (this.gui_objects.search_filter)
      addurl = '&_filter=' + this.gui_objects.search_filter.value;
        addurl += '&_filter=' + this.gui_objects.search_filter.value;
      // reset vars
      this.env.current_page = 1;
@@ -2527,7 +2739,7 @@
      return;
    
    // get cursor pos
    var inp_value = this.ksearch_input.value.toLowerCase();
    var inp_value = this.ksearch_input.value;
    var cpos = this.get_caret_pos(this.ksearch_input);
    var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
@@ -2559,13 +2771,22 @@
    var q = inp_value.substring(p+1, cpos);
    // trim query string
    q = q.replace(/(^\s+|\s+$)/g, '').toLowerCase();
    q = q.replace(/(^\s+|\s+$)/g, '');
    // Don't (re-)search if string is empty or if the last results are still active
    if (!q.length || q == this.ksearch_value)
    // Don't (re-)search if the last results are still active
    if (q == this.ksearch_value)
      return;
    var old_value = this.ksearch_value;
    this.ksearch_value = q;
    // ...string is empty
    if (!q.length)
      return;
    this.ksearch_value = q;
    // ...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.display_message(this.get_label('searching'), 'loading', true);
    this.http_post('autocomplete', '_search='+urlencode(q));
@@ -2574,7 +2795,7 @@
  this.ksearch_query_results = function(results, search)
  {
    // ignore this outdated search response
    if (search != this.ksearch_value)
    if (this.ksearch_value && search != this.ksearch_value)
      return;
      
    this.hide_message();
@@ -2585,8 +2806,8 @@
  this.ksearch_display_results = function (a_results)
  {
    // display search results
    if (a_results.length && this.ksearch_input) {
      var p, ul, li;
    if (a_results.length && this.ksearch_input && this.ksearch_value) {
      var p, ul, li, s_val = this.ksearch_value;
      
      // create results pane if not present
      if (!this.ksearch_pane) {
@@ -2598,11 +2819,11 @@
      // remove all search results
      ul = this.ksearch_pane.__ul;
      ul.innerHTML = '';
      // add each result line to list
      for (i=0; i<a_results.length; i++) {
        li = document.createElement('LI');
        li.innerHTML = a_results[i].replace(new RegExp('('+this.ksearch_value+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
        li.innerHTML = a_results[i].replace(new RegExp('('+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;
@@ -2812,7 +3033,7 @@
    };
  // update a contact record in the list
  this.update_contact_row = function(cid, cols_arr)
  this.update_contact_row = function(cid, cols_arr, newcid)
  {
    var row;
    if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj)) {
@@ -2820,11 +3041,50 @@
        if (row.cells[c])
          $(row.cells[c]).html(cols_arr[c]);
      // cid change
      if (newcid) {
   row.id = 'rcmrow' + newcid;
        this.contact_list.remove_row(cid);
        this.contact_list.init_row(row);
   this.contact_list.selection[0] = newcid;
   row.style.display = '';
      }
      return true;
    }
    return false;
  };
  // add row to contacts list
  this.add_contact_row = function(cid, cols, select)
    {
    if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
      return false;
    var tbody = this.gui_objects.contactslist.tBodies[0];
    var rowcount = tbody.rows.length;
    var even = rowcount%2;
    var row = document.createElement('tr');
    row.id = 'rcmrow'+cid;
    row.className = 'contact '+(even ? 'even' : 'odd');
    if (this.contact_list.in_selection(cid))
      row.className += ' selected';
    // add each submitted col
    for (var c in cols) {
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
      col.innerHTML = cols[c];
      row.appendChild(col);
    }
    this.contact_list.insert_row(row);
    this.enable_command('export', (this.contact_list.rowcount > 0));
    };
  /*********************************************************/
@@ -2840,7 +3100,7 @@
    this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
    this.subscription_list.row_init = function (row)
      {
      var anchors = row.obj.getElementsByTagName('A');
      var anchors = row.obj.getElementsByTagName('a');
      if (anchors[0])
        anchors[0].onclick = function() { p.rename_folder(row.id); return false; };
      if (anchors[1])
@@ -2851,6 +3111,26 @@
    this.subscription_list.init();
    }
  // preferences section select and load options frame
  this.section_select = function(list)
    {
    var id = list.get_single_selection();
    if (id) {
      var add_url = '';
      var 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];
        }
      target.location.href = this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url;
      }
    return true;
    };
  this.identity_select = function(list)
    {
    var id;
@@ -2858,7 +3138,7 @@
      this.load_identity(id, 'edit-identity');
    };
  // load contact record
  // load identity record
  this.load_identity = function(id, action)
    {
    if (action=='edit-identity' && (!id || id==this.env.iid))
@@ -2891,8 +3171,9 @@
    if (!id)
      id = this.env.iid ? this.env.iid : selection[0];
    // if (this.env.framed && id)
    this.goto_url('delete-identity', '_iid='+id, true);
    // append token to request
    this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
    return true;
    };
@@ -3006,7 +3287,8 @@
    if (id && this.env.subscriptionrows[id] && (row = document.getElementById(id)))
      {
      var reg = new RegExp('.*['+RegExp.escape(this.env.delimiter)+']');
      this.name_input = document.createElement('INPUT');
      this.name_input = document.createElement('input');
      this.name_input.type = 'text';
      this.name_input.value = this.env.subscriptionrows[id][0].replace(reg, '');
      reg = new RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$');
@@ -3086,10 +3368,13 @@
    if (!this.gui_objects.subscriptionlist)
      return false;
    // find not protected folder
    for (var refid in this.env.subscriptionrows)
      if (this.env.subscriptionrows[refid]!=null && !this.env.subscriptionrows[refid][2])
    // find not protected folder
    var refid;
    for (var rid in this.env.subscriptionrows)
      if (this.env.subscriptionrows[rid]!=null && !this.env.subscriptionrows[rid][2]) {
        refid = rid;
        break;
      }
    var refrow, form;
    var tbody = this.gui_objects.subscriptionlist.tBodies[0];
@@ -3102,10 +3387,11 @@
      refid = replace.id;
    }
    if (!id || !(refrow = document.getElementById(refid)))
    if (!id || !refid || !(refrow = document.getElementById(refid)))
      {
      // Refresh page if we don't have a table row to clone
      this.goto_url('folders');
      return false;
      }
    else
      {
@@ -3131,8 +3417,8 @@
    // set messages count to zero
    if (!replace)
      row.cells[1].innerHTML = '*';
    if (!replace && row.cells[2] && row.cells[2].firstChild.tagName=='INPUT')
    if (!replace && row.cells[2] && row.cells[2].firstChild.tagName.toLowerCase()=='input')
      {
      row.cells[2].firstChild.value = name;
      row.cells[2].firstChild.checked = true;
@@ -3235,11 +3521,11 @@
  this.clone_table_row = function(row)
    {
    var cell, td;
    var new_row = document.createElement('TR');
    var new_row = document.createElement('tr');
    for(var n=0; n<row.cells.length; n++)
      {
      cell = row.cells[n];
      td = document.createElement('TD');
      td = document.createElement('td');
      if (cell.className)
        td.className = cell.className;
@@ -3260,12 +3546,49 @@
  // eable/disable buttons for page shifting
  this.set_page_buttons = function()
    {
  {
    this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
    this.enable_command('lastpage', (this.env.pagecount > this.env.current_page));
    this.enable_command('previouspage', (this.env.current_page > 1));
    this.enable_command('firstpage', (this.env.current_page > 1));
  };
  // set event handlers on registered buttons
  this.init_buttons = function()
  {
    for (var cmd in this.buttons) {
      if (typeof cmd != 'string')
        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;
        }
      }
    }
  };
  // set button to a specific state
  this.set_button = function(command, state)
@@ -3327,7 +3650,7 @@
        if (button.type=='image' && obj)
        {
          obj.setAttribute('alt', this.get_label(label));
          if ((link = obj.parentNode) && link.tagName == 'A')
          if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
            link.setAttribute('title', this.get_label(label));
        }
        else if (obj)
@@ -3337,67 +3660,79 @@
  // mouse over button
  this.button_over = function(command, id)
    {
  {
    var a_buttons = this.buttons[command];
    var button, img;
    var button, elm;
    if(!a_buttons || !a_buttons.length)
      return false;
    for(var n=0; n<a_buttons.length; n++)
      {
    {
      button = a_buttons[n];
      if(button.id==id && button.status=='act')
        {
        img = document.getElementById(button.id);
        if (img && button.over)
          img.src = button.over;
      {
        elm = document.getElementById(button.id);
        if (elm && button.over) {
          if (button.type == 'image')
            elm.src = button.over;
          else
            elm.className = button.over;
        }
      }
    };
    }
  };
  // mouse down on button
  this.button_sel = function(command, id)
    {
  {
    var a_buttons = this.buttons[command];
    var button, img;
    var button, elm;
    if(!a_buttons || !a_buttons.length)
      return;
    for(var n=0; n<a_buttons.length; n++)
      {
    {
      button = a_buttons[n];
      if(button.id==id && button.status=='act')
        {
        img = document.getElementById(button.id);
        if (img && button.sel)
          img.src = button.sel;
      {
        elm = document.getElementById(button.id);
        if (elm && button.sel) {
          if (button.type == 'image')
            elm.src = button.sel;
          else
            elm.className = button.sel;
        }
        this.buttons_sel[id] = command;
      }
    };
    }
  };
  // mouse out of button
  this.button_out = function(command, id)
    {
  {
    var a_buttons = this.buttons[command];
    var button, img;
    var button, elm;
    if(!a_buttons || !a_buttons.length)
      return;
    for(var n=0; n<a_buttons.length; n++)
      {
    {
      button = a_buttons[n];
      if(button.id==id && button.status=='act')
        {
        img = document.getElementById(button.id);
        if (img && button.act)
          img.src = button.act;
      {
        elm = document.getElementById(button.id);
        if (elm && button.act) {
          if (button.type == 'image')
            elm.src = button.act;
          else
            elm.className = button.act;
        }
      }
    };
    }
  };
  // write to the document/window title
  this.set_pagetitle = function(title)
@@ -3455,10 +3790,12 @@
      if ((current_li = this.get_folder_li(old))) {
        $(current_li).removeClass('selected').removeClass('unfocused');
      }
      if ((target_li = this.get_folder_li(name))) {
        $(target_li).removeClass('unfocused').addClass('selected');
      }
      // trigger event hook
      this.triggerEvent('selectfolder', { folder:name, old:old });
    }
  };
@@ -3467,7 +3804,7 @@
  {
    if (this.gui_objects.folderlist)
    {
      name = String(name).replace(this.identifier_expr, '');
      name = String(name).replace(this.identifier_expr, '_');
      return document.getElementById('rcmli'+name);
    }
@@ -3488,7 +3825,7 @@
      if ((cell = thead.rows[0].cells[n+1]) && (col=='from' || col=='to'))
        {
        // if we have links for sorting, it's a bit more complicated...
        if (cell.firstChild && cell.firstChild.tagName=='A')
        if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a')
          {
          cell.firstChild.innerHTML = this.get_label(this.coltypes[n]);
          cell.firstChild.onclick = function(){ return rcmail.command('sort', this.__col, this); };
@@ -3531,10 +3868,10 @@
        + (flags.unread ? ' unread' : '')
        + (flags.deleted ? ' deleted' : '')
        + (flags.flagged ? ' flagged' : '')
   + (this.message_list.in_selection(uid) ? ' selected' : '');
        + (this.message_list.in_selection(uid) ? ' selected' : '');
    // for performance use DOM instead of jQuery here
    var row = document.createElement('TR');
    var row = document.createElement('tr');
    row.id = 'rcmrow'+uid;
    row.className = css_class;
    
@@ -3554,7 +3891,7 @@
      icon = this.env.unreadicon;
    
    // add icon col
    var col = document.createElement('TD');
    var col = document.createElement('td');
    col.className = 'icon';
    col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : '';
    row.appendChild(col);
@@ -3562,7 +3899,7 @@
    // add each submitted col
    for (var n = 0; n < this.coltypes.length; n++) {
      var c = this.coltypes[n];
      col = document.createElement('TD');
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
            
      if (c=='flag') {
@@ -3615,8 +3952,12 @@
  // replace content of quota display
  this.set_quota = function(content)
    {
    if (content && this.gui_objects.quotadisplay)
      $(this.gui_objects.quotadisplay).html(content);
    if (content && this.gui_objects.quotadisplay) {
      if (typeof(content) == 'object')
        this.percent_indicator(this.gui_objects.quotadisplay, content);
      else
        $(this.gui_objects.quotadisplay).html(content);
      }
    };
  // update the mailboxlist
@@ -3696,30 +4037,6 @@
      window.focus();
    }
  // add row to contacts list
  this.add_contact_row = function(cid, cols, select)
    {
    if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
      return false;
    var tbody = this.gui_objects.contactslist.tBodies[0];
    var rowcount = tbody.rows.length;
    var even = rowcount%2;
    var row = $('<tr>').attr('id', 'rcmrow'+cid).addClass('class').addClass(even ? 'even' : 'odd');
    if (this.contact_list.in_selection(cid))
      row.addClass('selected');
    // add each submitted col
    for (var c in cols) {
      col = $('<td>').addClass(String(c).toLowerCase()).html(cols[c]).appendTo(row);
    }
    this.contact_list.insert_row(row);
    this.enable_command('export', (this.contact_list.rowcount > 0));
    };
  this.toggle_prefer_html = function(checkbox)
    {
    var addrbook_show_images;
@@ -3769,6 +4086,61 @@
    elem.onclick = function() { rcmail.load_headers(elem); }
    }
  // percent (quota) indicator
  this.percent_indicator = function(obj, data)
    {
    if (!data || !obj)
      return false;
    var limit_high = 80;
    var limit_mid  = 55;
    var width = data.width ? data.width : this.env.indicator_width ? this.env.indicator_width : 100;
    var height = data.height ? data.height : this.env.indicator_height ? this.env.indicator_height : 14;
    var quota = data.percent ? Math.abs(parseInt(data.percent)) : 0;
    var quota_width = parseInt(quota / 100 * width);
    var pos = $(obj).position();
    this.env.indicator_width = width;
    this.env.indicator_height = height;
    // overlimit
    if (quota_width > width) {
      quota_width = width;
      quota = 100;
      }
    // main div
    var main = $('<div>');
    main.css({position: 'absolute', top: pos.top, left: pos.left,
   width: width + 'px', height: height + 'px', zIndex: 100, lineHeight: height + 'px'})
   .attr('title', data.title).addClass('quota_text').html(quota + '%');
    // used bar
    var bar1 = $('<div>');
    bar1.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
   width: quota_width + 'px', height: height + 'px', zIndex: 99});
    // background
    var bar2 = $('<div>');
    bar2.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
   width: width + 'px', height: height + 'px', zIndex: 98})
   .addClass('quota_bg');
    if (quota >= limit_high) {
      main.addClass(' quota_text_high');
      bar1.addClass('quota_high');
      }
    else if(quota >= limit_mid) {
      main.addClass(' quota_text_mid');
      bar1.addClass('quota_mid');
      }
    else {
      main.addClass(' quota_text_normal');
      bar1.addClass('quota_low');
      }
    // replace quota image
    obj.innerHTML = '';
    $(obj).append(bar1).append(bar2).append(main);
    }
  /********************************************************/
  /*********  html to text conversion functions   *********/
@@ -3807,7 +4179,7 @@
    if (this.env.framed && window.parent)
      parent.location.href = url;
    else
    else
      location.href = url;
    };
@@ -3824,7 +4196,7 @@
    var url = this.env.comm_path + '&_action=' + action + '&' + querystring
    
    // send request
    console.log('HTTP POST: ' + url);
    console.log('HTTP GET: ' + url);
    jQuery.get(url, { _unlock:(lock?1:0) }, function(data){ ref.http_response(data); }, 'json');
  };
@@ -3869,6 +4241,12 @@
      console.log(response.exec);
      eval(response.exec);
    }
    // execute callback functions of plugins
    if (response.callbacks && response.callbacks.length) {
      for (var i=0; i < response.callbacks.length; i++)
        this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
    }
 
    // process the response data according to the sent action
    switch (response.action) {
@@ -3881,9 +4259,10 @@
        }
      
      case 'moveto':
        if (this.env.action == 'show')
          this.command('list');
        else if (this.message_list)
        if (this.env.action == 'show') {
          // re-enable commands on move/delete error
          this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', 'open', 'edit', 'viewsource', 'download', true);
        } else if (this.message_list)
          this.message_list.init();
        break;
        
@@ -3894,8 +4273,9 @@
          if (this.env.contentframe)
            this.show_contentframe(false);
          // disable commands useless when mailbox is empty
          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 'mark', 'viewsource',
            'print', 'load-attachment', 'purge', 'expunge', 'select-all', 'select-none', 'sort', false);
          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete',
            'mark', 'viewsource', 'open', 'edit', 'download', 'print', 'load-attachment',
            'purge', 'expunge', 'select-all', 'select-none', 'sort', false);
        }
        break;
@@ -3907,9 +4287,16 @@
            this.msglist_select(this.message_list);
          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
          this.enable_command('purge', this.purge_mailbox_test());
          if (response.action == 'list')
            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
        }
        else if (this.task == 'addressbook')
        else if (this.task == 'addressbook') {
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
          if (response.action == 'list')
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
        }
        break;
    }
  };
@@ -3922,7 +4309,8 @@
    this.set_busy(false);
    request.abort();
    
    this.display_message('Unknown Server Error!' + (errmsg ? ' ('+errmsg+')' : ''), 'error');
    if (errmsg)
      this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
    };
  // use an image to send a keep-alive siganl to the server
@@ -3933,15 +4321,17 @@
    };
  // send periodic request to check for recent messages
  this.check_for_recent = function(setbusy)
  this.check_for_recent = function(refresh)
    {
    if (this.busy)
      return;
    if (setbusy)
      this.set_busy(true, 'checkingmail');
    var addurl = '_t=' + (new Date().getTime());
    if (refresh) {
      this.set_busy(true, 'checkingmail');
      addurl += '&_refresh=1';
    }
    if (this.gui_objects.messagelist)
      addurl += '&_list=1';
@@ -3997,19 +4387,19 @@
      return obj.value.length;
    };
  this.set_caret2start = function(obj)
  this.set_caret_pos = function(obj, pos)
    {
    if (obj.createTextRange)
    if (obj.setSelectionRange)
      obj.setSelectionRange(pos, pos);
    else if (obj.createTextRange)
      {
      var range = obj.createTextRange();
      range.collapse(true);
      range.moveEnd('character', pos);
      range.moveStart('character', pos);
      range.select();
      }
    else if (obj.setSelectionRange)
      obj.setSelectionRange(0,0);
    obj.focus();
    };
    }
  // set all fields of a form disabled
  this.lock_form = function(form, lock)
@@ -4035,4 +4425,3 @@
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;