Aleksander Machniak
2012-10-28 111acf180dfacf7f049d4d7b6f93b79560f51fc4
program/js/app.js
@@ -48,7 +48,7 @@
  this.env.request_timeout = 180;  // seconds
  this.env.draft_autosave = 0;     // seconds
  this.env.comm_path = './';
  this.env.blankpage = 'program/blank.gif';
  this.env.blankpage = 'program/resources/blank.gif';
  // set jQuery ajax options
  $.ajaxSetup({
@@ -57,6 +57,8 @@
    error: function(request, status, err){ ref.http_error(request, status, err); },
    beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
  });
  $(window).bind('beforeunload', function() { rcmail.unload = true; });
  // set environment variable(s)
  this.set_env = function(p, value)
@@ -174,10 +176,10 @@
    }
    // enable general commands
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'about', 'switch-task', true);
    this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'about', 'switch-task', true);
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
      this.enable_command('permaurl', 'extwin', true);
    switch (this.task) {
@@ -206,7 +208,7 @@
          this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
          this.message_list.init();
          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true);
          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', 'sort', true);
          // load messages
          this.command('list');
@@ -247,7 +249,7 @@
          }
        }
        else if (this.env.action == 'compose') {
          this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses'];
          this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'extwin'];
          if (this.env.drafts_mailbox)
            this.env.compose_commands.push('savedraft')
@@ -458,6 +460,14 @@
    if (this.gui_objects.folderlist)
      this.gui_containers.foldertray = $(this.gui_objects.folderlist);
    // activate html5 file drop feature (if browser supports it and if configured)
    if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) {
      $(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); });
      $(this.gui_objects.filedrop).addClass('droptarget')
        .bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); })
        .get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false);
    }
    // trigger init event hook
    this.triggerEvent('init', { task:this.task, action:this.env.action });
@@ -485,7 +495,7 @@
  /*********************************************************/
  // execute a specific command on the web client
  this.command = function(command, props, obj)
  this.command = function(command, props, obj, event)
  {
    var ret, uid, cid, url, flag;
@@ -560,6 +570,19 @@
          parent.location.href = this.env.permaurl;
        break;
      case 'extwin':
        if (this.env.action == 'compose') {
          var prevstate = this.env.compose_extwin;
          $("input[name='_action']", this.gui_objects.messageform).val('compose');
          this.gui_objects.messageform.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
          this.gui_objects.messageform.target = this.open_window('', 1150, 900);
          this.gui_objects.messageform.submit();
        }
        else {
          this.open_window(this.env.permaurl, 1000, 1200);
        }
        break;
      case 'menu-open':
      case 'menu-save':
        this.triggerEvent(command, {props:props});
@@ -572,10 +595,18 @@
        }
        break;
      case 'close':
        if (this.env.extwin)
          window.close();
        break;
      case 'list':
        if (props && props != '')
          this.reset_qsearch();
        if (this.task == 'mail') {
        if (this.env.action == 'compose' && this.env.extwin) {
          window.close();
        }
        else if (this.task == 'mail') {
          this.list_mailbox(props);
          this.set_button_titles();
        }
@@ -584,12 +615,11 @@
        break;
      case 'sort':
        var sort_order, sort_col = props;
        var sort_order = this.env.sort_order,
          sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
        if (this.env.sort_col==sort_col)
          sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
        else
          sort_order = 'ASC';
        if (!this.env.disabled_sort_order)
          sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
        // set table header and update env
        this.set_list_sorting(sort_col, sort_order);
@@ -631,7 +661,7 @@
          uid = this.get_single_uid();
          if (uid && (!this.env.uid || uid != this.env.uid)) {
            if (this.env.mailbox == this.env.drafts_mailbox)
              this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
              this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
            else
              this.show_message(uid);
          }
@@ -653,13 +683,14 @@
        break;
      case 'edit':
        if (this.task=='addressbook' && (cid = this.get_single_cid()))
        if (this.task == 'addressbook' && (cid = this.get_single_cid()))
          this.load_contact(cid, 'edit');
        else if (this.task=='settings' && props)
        else if (this.task == 'settings' && props)
          this.load_identity(props, 'edit-identity');
        else if (this.task=='mail' && (cid = this.get_single_uid())) {
          url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
          this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
        else if (this.task == 'mail' && (cid = this.get_single_uid())) {
          url = { _mbox: this.env.mailbox };
          url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = cid;
          this.open_compose_step(url);
        }
        break;
@@ -704,7 +735,7 @@
      case 'delete':
        // mail task
        if (this.task == 'mail')
          this.delete_messages();
          this.delete_messages(event);
        // addressbook task
        else if (this.task == 'addressbook')
          this.delete_contacts();
@@ -852,47 +883,46 @@
        break;
      case 'compose':
        url = this.url('mail/compose');
        url = {};
        if (this.task == 'mail') {
          url += '&_mbox='+urlencode(this.env.mailbox);
          url._mbox = this.env.mailbox;
          if (props)
             url += '&_to='+urlencode(props);
             url._to = props;
          // also send search request so we can go back to search result after message is sent
          if (this.env.search_request)
            url += '&_search='+this.env.search_request;
            url._search = this.env.search_request;
        }
        // modify url if we're in addressbook
        else if (this.task == 'addressbook') {
          // switch to mail compose step directly
          if (props && props.indexOf('@') > 0) {
            url = this.get_task_url('mail', url);
            this.redirect(url + '&_to='+urlencode(props));
            url._to = props;
          }
          else {
            // use contact_id passed as command parameter
            var n, len, a_cids = [];
            if (props)
              a_cids.push(props);
            // get selected contacts
            else if (this.contact_list) {
              var selection = this.contact_list.get_selection();
              for (n=0, len=selection.length; n<len; n++)
                a_cids.push(selection[n]);
            }
            if (a_cids.length)
              this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
            else if (this.env.group)
              this.http_post('mailto', { _gid: this.env.group, _source: this.env.source }, true);
            break;
          }
          // use contact_id passed as command parameter
          var n, len, a_cids = [];
          if (props)
            a_cids.push(props);
          // get selected contacts
          else if (this.contact_list) {
            var selection = this.contact_list.get_selection();
            for (n=0, len=selection.length; n<len; n++)
              a_cids.push(selection[n]);
          }
          if (a_cids.length)
            this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source}, true);
          else if (this.env.group)
            this.http_post('mailto', { _gid: this.env.group, _source: this.env.source}, true);
          break;
        }
        else if (props)
          url += '&_to='+urlencode(props);
          url._to = props;
        this.redirect(url);
        this.open_compose_step(url);
        break;
      case 'spellcheck':
@@ -911,14 +941,8 @@
        break;
      case 'savedraft':
        var form = this.gui_objects.messageform, msgid;
        // Reset the auto-save timer
        clearTimeout(this.save_timer);
        // saving Drafts is disabled
        if (!form)
          break;
        // compose form did not change
        if (this.cmp_hash == this.compose_field_hash()) {
@@ -929,35 +953,17 @@
        // re-set keep-alive timeout
        this.start_keepalive();
        msgid = this.set_busy(true, 'savingmessage');
        form.target = "savetarget";
        form._draft.value = '1';
        form.action = this.add_url(form.action, '_unlock', msgid);
        form.submit();
        this.submit_messageform(true);
        break;
      case 'send':
        if (!this.gui_objects.messageform)
          break;
        if (!props.nocheck && !this.check_compose_input(command))
          break;
        // Reset the auto-save timer
        clearTimeout(this.save_timer);
        // all checks passed, send message
        var lang = this.spellcheck_lang(),
          form = this.gui_objects.messageform,
          msgid = this.set_busy(true, 'sendingmessage');
        form.target = 'savetarget';
        form._draft.value = '';
        form.action = this.add_url(form.action, '_unlock', msgid);
        form.action = this.add_url(form.action, '_lang', lang);
        form.submit();
        this.submit_messageform();
        break;
      case 'send-attachment':
@@ -984,24 +990,24 @@
      case 'reply-list':
      case 'reply':
        if (uid = this.get_single_uid()) {
          url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
          url = {_reply_uid: uid, _mbox: this.env.mailbox};
          if (command == 'reply-all')
            // do reply-list, when list is detected and popup menu wasn't used 
            url += '&_all=' + (!props && this.commands['reply-list'] ? 'list' : 'all');
            url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
          else if (command == 'reply-list')
            url += '&_all=list';
            url._all = 'list';
          this.goto_url('compose', url, true);
          this.open_compose_step(url);
        }
        break;
      case 'forward-attachment':
      case 'forward':
        if (uid = this.get_single_uid()) {
          url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
          url = { _forward_uid: uid, _mbox: this.env.mailbox };
          if (command == 'forward-attachment' || (!props && this.env.forward_attachment))
            url += '&_attachment=1';
          this.goto_url('compose', url, true);
            url._attachment = 1;
          this.open_compose_step(url);
        }
        break;
@@ -1026,7 +1032,7 @@
      case 'download':
        if (uid = this.get_single_uid())
          this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
          this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 });
        break;
      // quicksearch
@@ -1079,7 +1085,7 @@
      case 'export':
        if (this.contact_list.rowcount > 0) {
          this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
          this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request });
        }
        break;
@@ -1460,29 +1466,21 @@
  this.doc_mouse_up = function(e)
  {
    var model, list, li, id;
    var model, list, id;
    // ignore event if jquery UI dialog is open
    if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
      return;
    if (list = this.message_list) {
      if (!rcube_mouse_is_over(e, list.list.parentNode))
        list.blur();
      else
        list.focus();
    if (list = this.message_list)
      model = this.env.mailboxes;
    }
    else if (list = this.contact_list) {
      if (!rcube_mouse_is_over(e, list.list.parentNode))
        list.blur();
      else
        list.focus();
    else if (list = this.contact_list)
      model = this.env.contactfolders;
    }
    else if (this.ksearch_value) {
    else if (this.ksearch_value)
      this.ksearch_blur();
    }
    if (list && !rcube_mouse_is_over(e, list.list.parentNode))
      list.blur();
    // handle mouse release when dragging
    if (this.drag_active && model && this.env.last_folder_target) {
@@ -1559,14 +1557,17 @@
    if (list.multi_selecting || !this.env.contentframe)
      return;
    if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
      if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
        if (this.preview_timer)
          clearTimeout(this.preview_timer);
        if (this.preview_read_timer)
          clearTimeout(this.preview_read_timer);
        this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
      }
    if (list.get_single_selection())
      return;
    var win = this.get_frame_window(this.env.contentframe);
    if (win && win.location.href.indexOf(this.env.blankpage)>=0) {
      if (this.preview_timer)
        clearTimeout(this.preview_timer);
      if (this.preview_read_timer)
        clearTimeout(this.preview_read_timer);
      this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
    }
  };
@@ -1580,7 +1581,7 @@
    var uid = list.get_single_selection();
    if (uid && this.env.mailbox == this.env.drafts_mailbox)
      this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
      this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
    else if (uid)
      this.show_message(uid, false, false);
  };
@@ -1625,7 +1626,7 @@
    for (i=0; i<cols.length; i++)
      if (cols[i].id && cols[i].id.match(/^rcm/)) {
        name = cols[i].id.replace(/^rcm/, '');
        this.env.coltypes.push(name == 'to' ? 'from' : name);
        this.env.coltypes.push(name);
      }
    if ((found = $.inArray('flag', this.env.coltypes)) >= 0)
@@ -1660,6 +1661,28 @@
    }
    return allow ? (copy ? 2 : 1) : 0;
  };
  this.open_window = function(url, width, height)
  {
    var w = Math.min(width, screen.width - 10),
      h = Math.min(height, screen.height - 100),
      l = (screen.width - w) / 2 + (screen.left || 0),
      t = Math.max(0, (screen.height - h) / 2 + (screen.top || 0) - 20);
    var wname = 'rcmextwin' + new Date().getTime(),
      extwin = window.open(url + '&_extwin=1', wname, 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no');
    extwin.moveTo(l,t);
    // write loading... message to empty windows
    if (!url && extwin.document) {
      extwin.document.write('<html><body>' + this.get_label('loading') + '</body></html>');
    }
    // focus window, delayed to bring to front
    window.setTimeout(function(){ extwin.focus(); }, 10);
    return wname;
  };
@@ -1818,7 +1841,7 @@
        html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
      }
      else if (c == 'attachment') {
        if (/application\/|multipart\/m/.test(flags.ctype))
        if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
          html = '<span class="attachment">&nbsp;</span>';
        else if (/multipart\/report/.test(flags.ctype))
          html = '<span class="report">&nbsp;</span>';
@@ -1904,7 +1927,7 @@
      // make sure new columns are added at the end of the list
      var i, idx, name, newcols = [], oldcols = this.env.coltypes;
      for (i=0; i<oldcols.length; i++) {
        name = oldcols[i] == 'to' ? 'from' : oldcols[i];
        name = oldcols[i];
        idx = $.inArray(name, cols);
        if (idx != -1) {
          newcols.push(name);
@@ -1925,18 +1948,18 @@
      this.list_mailbox('', '', sort_col+'_'+sort_order, post_data);
  };
  // when user doble-clicks on a row
  // when user double-clicks on a row
  this.show_message = function(id, safe, preview)
  {
    if (!id)
      return;
    var target = window,
    var win, target = window,
      action = preview ? 'preview': 'show',
      url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
    if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (preview && (win = this.get_frame_window(this.env.contentframe))) {
      target = win;
      url += '&_framed=1';
    }
@@ -1950,10 +1973,17 @@
    // add browser capabilities, so we can properly handle attachments
    url += '&_caps='+urlencode(this.browser_capabilities());
    if (preview && String(target.location.href).indexOf(url) >= 0)
    if (this.env.extwin)
      url += '&_extwin=1';
    if (preview && String(target.location.href).indexOf(url) >= 0) {
      this.show_contentframe(true);
    }
    else {
      this.location_href(this.env.comm_path+url, target, true);
      if (!preview && this.env.message_extwin && !this.env.extwin)
        this.open_window(this.env.comm_path+url, 1000, 1200);
      else
        this.location_href(this.env.comm_path+url, target, true);
      // mark as read and change mbox unread counter
      if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
@@ -1973,18 +2003,35 @@
  this.show_contentframe = function(show)
  {
    var frm, win;
    if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
      if (!show && (win = window.frames[this.env.contentframe])) {
    var frame, win, name = this.env.contentframe;
    if (name && (frame = this.get_frame_element(name))) {
      if (!show && (win = this.get_frame_window(name))) {
        if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
          win.location.href = this.env.blankpage;
      }
      else if (!bw.safari && !bw.konq)
        frm[show ? 'show' : 'hide']();
      }
        $(frame)[show ? 'show' : 'hide']();
    }
    if (!show && this.busy)
      this.set_busy(false, null, this.env.frame_lock);
  };
  this.get_frame_element = function(id)
  {
    var frame;
    if (id && (frame = document.getElementById(id)))
      return frame;
  };
  this.get_frame_window = function(id)
  {
    var frame = this.get_frame_element(id);
    if (frame && frame.name && window.frames)
      return window.frames[frame.name];
  };
  this.lock_frame = function()
@@ -2030,7 +2077,7 @@
  // list messages of a specific mailbox
  this.list_mailbox = function(mbox, page, sort, url)
  {
    var target = window;
    var win, target = window;
    if (typeof url != 'object')
      url = {};
@@ -2069,8 +2116,8 @@
      return;
    }
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url._framed = 1;
    }
@@ -2448,7 +2495,7 @@
  // set message row status, class and icon
  this.set_message = function(uid, flag, status)
  {
    var row = this.message_list.rows[uid];
    var row = this.message_list && this.message_list.rows[uid];
    if (!row)
      return false;
@@ -2546,7 +2593,7 @@
  };
  // delete selected messages from the current mailbox
  this.delete_messages = function()
  this.delete_messages = function(event)
  {
    var uid, i, len, trash = this.env.trash_mailbox,
      list = this.message_list,
@@ -2578,7 +2625,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.modkey == SHIFT_KEY) {
      if ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
        if (confirm(this.get_label('deletemessagesconfirm')))
          this.permanently_remove_messages();
      }
@@ -2665,34 +2712,37 @@
  // set a specific flag to one or more messages
  this.mark_message = function(flag, uid)
  {
    var a_uids = [], r_uids = [], len, n, id,
      selection = this.message_list ? this.message_list.get_selection() : [];
    var a_uids = [], r_uids = [], len, n, id, selection,
      list = this.message_list;
    if (uid)
      a_uids[0] = uid;
    else if (this.env.uid)
      a_uids[0] = this.env.uid;
    else if (this.message_list) {
    else if (list) {
      selection = list.get_selection();
      for (n=0, len=selection.length; n<len; n++) {
          a_uids.push(selection[n]);
      }
    }
    if (!this.message_list)
    if (!list)
      r_uids = a_uids;
    else
    else {
      list.focus();
      for (n=0, len=a_uids.length; n<len; n++) {
        id = a_uids[n];
        if ((flag=='read' && this.message_list.rows[id].unread)
            || (flag=='unread' && !this.message_list.rows[id].unread)
            || (flag=='delete' && !this.message_list.rows[id].deleted)
            || (flag=='undelete' && this.message_list.rows[id].deleted)
            || (flag=='flagged' && !this.message_list.rows[id].flagged)
            || (flag=='unflagged' && this.message_list.rows[id].flagged))
        if ((flag=='read' && list.rows[id].unread)
            || (flag=='unread' && !list.rows[id].unread)
            || (flag=='delete' && !list.rows[id].deleted)
            || (flag=='undelete' && list.rows[id].deleted)
            || (flag=='flagged' && !list.rows[id].flagged)
            || (flag=='unflagged' && list.rows[id].flagged))
        {
          r_uids.push(id);
        }
      }
    }
    // nothing to do
    if (!r_uids.length && !this.select_all_mode)
@@ -2961,6 +3011,17 @@
  /*********        message compose methods        *********/
  /*********************************************************/
  this.open_compose_step = function(p)
  {
    var url = this.url('mail/compose', p);
    // open new compose window
    if (this.env.compose_extwin)
      this.open_window(url, 1150, 900);
    else
      this.redirect(url);
  };
  // init message compose form: set focus and eventhandlers
  this.init_messageform = function()
  {
@@ -2974,6 +3035,11 @@
      html_mode = $("input[name='_is_html']").val() == '1',
      ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
      ac_props;
    // close compose step in opener
    if (window.opener && opener.rcmail && opener.rcmail.env.action == 'compose') {
      setTimeout(function(){ opener.history.back(); }, 100);
    }
    // configure parallel autocompletion
    if (this.env.autocomplete_threads > 0) {
@@ -3020,6 +3086,29 @@
    obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
      .attr('autocomplete', 'off');
  };
  this.submit_messageform = function(draft)
  {
    var form = this.gui_objects.messageform;
    if (!form)
      return;
    // all checks passed, send message
    var msgid = this.set_busy(true, draft ? 'savingmessage' : 'sendingmessage'),
      lang = this.spellcheck_lang(),
      files = [];
    // send files list
    $('li', this.gui_objects.attachmentlist).each(function() { files.push(this.id.replace(/^rcmfile/, '')); });
    $('input[name="_attachments"]', form).val(files.join());
    form.target = 'savetarget';
    form._draft.value = draft ? '1' : '';
    form.action = this.add_url(form.action, '_unlock', msgid);
    form.action = this.add_url(form.action, '_lang', lang);
    form.submit();
  };
  this.compose_recipient_select = function(list)
@@ -3288,8 +3377,7 @@
      input_message = $("[name='_message']"),
      message = input_message.val(),
      is_html = ($("input[name='_is_html']").val() == '1'),
      sig = this.env.identity,
      sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
      sig = this.env.identity;
    // enable manual signature insert
    if (this.env.signatures && this.env.signatures[id]) {
@@ -3302,12 +3390,8 @@
    if (!is_html) {
      // remove the 'old' signature
      if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
        sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
        sig = this.env.signatures[sig].text;
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
        if (p >= 0)
@@ -3315,11 +3399,8 @@
      }
      // 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'];
        sig = this.env.signatures[id].text;
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        if (this.env.sig_above) {
          if (p >= 0) { // in place of removed signature
@@ -3384,21 +3465,8 @@
        }
      }
      if (this.env.signatures[id]) {
        if (this.env.signatures[id].is_html) {
          sig = this.env.signatures[id].text;
          if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '<br />' + sig;
        }
        else {
          sig = this.env.signatures[id].text;
          if (!sig.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '\n' + sig;
          sig = '<pre>' + sig + '</pre>';
        }
        sigElem.innerHTML = sig;
      }
      if (this.env.signatures[id])
        sigElem.innerHTML = this.env.signatures[id].html;
    }
    this.env.identity = id;
@@ -3452,12 +3520,7 @@
      var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
        ts = frame_name.replace(/^rcmupload/, '');
      if (this.env.loadingicon)
        content = '<img src="'+this.env.loadingicon+'" alt="" class="uploading" />'+content;
      content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload" class="cancelupload">'
        + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + content;
      this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false });
      this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false });
      // upload progress support
      if (this.env.upload_progress_time) {
@@ -3476,6 +3539,13 @@
  {
    if (!this.gui_objects.attachmentlist)
      return false;
    if (!att.complete && ref.env.loadingicon)
      att.html = '<img src="'+ref.env.loadingicon+'" alt="" class="uploading" />' + att.html;
    if (!att.complete && att.frame)
      att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
        + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
    var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
@@ -3625,8 +3695,16 @@
  this.sent_successfully = function(type, msg)
  {
    this.display_message(msg, type);
    // before redirect we need to wait some time for Chrome (#1486177)
    setTimeout(function(){ ref.list_mailbox(); }, 500);
    if (this.env.extwin && window.opener && opener.rcmail) {
      this.lock_form(this.gui_objects.messageform);
      opener.rcmail.display_message(msg, type);
      setTimeout(function(){ window.close() }, 1000);
    }
    else {
      // before redirect we need to wait some time for Chrome (#1486177)
      setTimeout(function(){ ref.list_mailbox(); }, 500);
    }
  };
@@ -3803,7 +3881,7 @@
      return;
    // ...new search value contains old one and previous search was not finished or its result was empty
    if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || !ac.num) && this.env.contacts && !this.env.contacts.length)
    if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || ac.num <= 0) && this.env.contacts && !this.env.contacts.length)
      return;
    var i, lock, source, xhr, reqid = new Date().getTime(),
@@ -4019,8 +4097,7 @@
    // if a group is currently selected, and there is at least one contact selected
    // thend we can enable the group-remove-selected command
    this.enable_command('group-remove-selected', typeof this.env.group != 'undefined' && list.selection.length > 0);
    this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0);
    this.enable_command('compose', this.env.group || list.selection.length > 0);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
@@ -4030,7 +4107,7 @@
  this.list_contacts = function(src, group, page)
  {
    var folder, url = {},
    var win, folder, url = {},
      target = window;
    if (!src)
@@ -4062,8 +4139,8 @@
      return;
    }
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url._framed = 1;
    }
@@ -4119,11 +4196,11 @@
  // load contact record
  this.load_contact = function(cid, action, framed)
  {
    var url = {}, target = window;
    var win, url = {}, target = window;
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      target = win;
      this.show_contentframe(true);
      // load dummy content
@@ -4264,7 +4341,7 @@
    if (!this.gui_objects.contactslist)
      return false;
    var c, list = this.contact_list,
    var c, col, list = this.contact_list,
      row = document.createElement('tr');
    row.id = 'rcmrow'+this.html_identifier(cid);
@@ -4704,11 +4781,11 @@
  {
    if (form && form.elements._photo.value) {
      this.async_upload_form(form, 'upload-photo', function(e) {
        rcmail.set_busy(false, null, rcmail.photo_upload_id);
        rcmail.set_busy(false, null, rcmail.file_upload_id);
      });
      // display upload indicator
      this.photo_upload_id = this.set_busy(true, 'uploading');
      this.file_upload_id = this.set_busy(true, 'uploading');
    }
  };
@@ -4723,8 +4800,8 @@
  this.photo_upload_end = function()
  {
    this.set_busy(false, null, this.photo_upload_id);
    delete this.photo_upload_id;
    this.set_busy(false, null, this.file_upload_id);
    delete this.file_upload_id;
  };
  this.set_photo_actions = function(id)
@@ -4741,11 +4818,11 @@
  // load advanced search page
  this.advanced_search = function()
  {
    var url = {_form: 1, _action: 'search'}, target = window;
    var win, url = {_form: 1, _action: 'search'}, target = window;
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      target = win;
      this.contact_list.clear_selection();
    }
@@ -4867,13 +4944,13 @@
  // preferences section select and load options frame
  this.section_select = function(list)
  {
    var id = list.get_single_selection(), target = window,
    var win, id = list.get_single_selection(), target = window,
      url = {_action: 'edit-prefs', _section: id};
    if (id) {
      if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      if (win = this.get_frame_window(this.env.contentframe)) {
        url._framed = 1;
        target = window.frames[this.env.contentframe];
        target = win;
      }
      this.location_href(url, target, true);
    }
@@ -4896,13 +4973,12 @@
    if (action == 'edit-identity' && (!id || id == this.env.iid))
      return false;
    var target = window,
    var win, target = window,
      url = {_action: action, _iid: id};
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
      target = win;
    }
    if (action && (id || action == 'add-identity')) {
@@ -4925,7 +5001,7 @@
    // submit request with appended token
    if (confirm(this.get_label('deleteidentityconfirm')))
      this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
      this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true);
    return true;
  };
@@ -5278,14 +5354,14 @@
  // when user select a folder in manager
  this.show_folder = function(folder, path, force)
  {
    var target = window,
    var win, target = window,
      url = '&_action=edit-folder&_mbox='+urlencode(folder);
    if (path)
      url += '&_path='+urlencode(path);
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url += '&_framed=1';
    }
@@ -5347,13 +5423,6 @@
    }
  };
  // enable/disable buttons for page shifting
  this.set_page_buttons = function()
  {
    this.enable_command('nextpage', 'lastpage', (this.env.pagecount > this.env.current_page));
    this.enable_command('previouspage', 'firstpage', (this.env.current_page > 1));
  };
  // set event handlers on registered buttons
  this.init_buttons = function()
  {
@@ -5361,7 +5430,7 @@
      if (typeof cmd !== 'string')
        continue;
      for (var i=0; i< this.buttons[cmd].length; i++) {
      for (var i=0; i<this.buttons[cmd].length; i++) {
        init_button(cmd, this.buttons[cmd][i]);
      }
    }
@@ -5380,28 +5449,31 @@
      button = a_buttons[n];
      obj = document.getElementById(button.id);
      if (!obj)
        continue;
      // get default/passive setting of the button
      if (obj && button.type == 'image' && !button.status) {
      if (button.type == 'image' && !button.status) {
        button.pas = obj._original_src ? obj._original_src : obj.src;
        // respect PNG fix on IE browsers
        if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
          button.pas = RegExp.$1;
      }
      else if (obj && !button.status)
      else if (!button.status)
        button.pas = String(obj.className);
      // set image according to button state
      if (obj && button.type == 'image' && button[state]) {
      if (button.type == 'image' && button[state]) {
        button.status = state;
        obj.src = button[state];
      }
      // set class name according to button state
      else if (obj && button[state] !== undefined) {
      else if (button[state] !== undefined) {
        button.status = state;
        obj.className = button[state];
      }
      // disable/enable input buttons
      if (obj && button.type=='input') {
      if (button.type == 'input') {
        button.status = state;
        obj.disabled = !state;
      }
@@ -5624,6 +5696,39 @@
    this.messages = {};
  };
  // open a jquery UI dialog with the given content
  this.show_popup_dialog = function(html, title)
  {
    // forward call to parent window
    if (this.is_framed()) {
      parent.rcmail.show_popup_dialog(html, title);
      return;
    }
    var popup = $('<div class="popup">')
      .html(html)
      .dialog({
        title: title,
        modal: true,
        resizable: true,
        width: 580,
        close: function(event, ui) { $(this).remove() }
      });
      // resize and center popup
      var win = $(window), w = win.width(), h = win.height(),
        width = popup.width(), height = popup.height();
      popup.dialog('option', { height: Math.min(h-40, height+50), width: Math.min(w-20, width+50) })
        .dialog('option', 'position', ['center', 'center']);  // only works in a separate call (!?)
  };
  // enable/disable buttons for page shifting
  this.set_page_buttons = function()
  {
    this.enable_command('nextpage', 'lastpage', (this.env.pagecount > this.env.current_page));
    this.enable_command('previouspage', 'firstpage', (this.env.current_page > 1));
  };
  // mark a mailbox as selected and set environment variable
  this.select_folder = function(name, prefix, encode)
  {
@@ -5670,7 +5775,7 @@
  // for reordering column array (Konqueror workaround)
  // and for setting some message list global variables
  this.set_message_coltypes = function(coltypes, repl)
  this.set_message_coltypes = function(coltypes, repl, smart_col)
  {
    var list = this.message_list,
      thead = list ? list.list.tHead : null,
@@ -5698,15 +5803,13 @@
      for (n=0, len=this.env.coltypes.length; n<len; n++) {
        col = this.env.coltypes[n];
        if ((cell = thead.rows[0].cells[n]) && (col=='from' || col=='to')) {
        if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) {
          cell.id = 'rcm'+col;
          $('span,a', cell).text(this.get_label(col == 'fromto' ? smart_col : col));
          // if we have links for sorting, it's a bit more complicated...
          if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a') {
            cell = cell.firstChild;
            cell.onclick = function(){ return rcmail.command('sort', this.__col, this); };
            cell.__col = col;
          }
          cell.innerHTML = this.get_label(col);
          $('a', cell).click(function(){
            return rcmail.command('sort', this.id.replace(/^rcm/, ''), this);
          });
        }
      }
    }
@@ -6107,7 +6210,7 @@
              this.show_contentframe(false);
            // disable commands useless when mailbox is empty
            this.enable_command(this.env.message_commands, 'purge', 'expunge',
              'select-all', 'select-none', 'sort', 'expand-all', 'expand-unread', 'collapse-all', false);
              'select-all', 'select-none', 'expand-all', 'expand-unread', 'collapse-all', false);
          }
          if (this.message_list)
            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
@@ -6120,7 +6223,7 @@
        this.env.qsearch = null;
      case 'list':
        if (this.task == 'mail') {
          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
          this.enable_command('show', 'expunge', 'select-all', 'select-none', (this.env.messagecount > 0));
          this.enable_command('purge', this.purge_mailbox_test());
          this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
@@ -6156,6 +6259,10 @@
    this.set_busy(false, null, lock);
    request.abort();
    // don't display error message on page unload (#1488547)
    if (this.unload)
      return;
    if (request.status && errmsg)
      this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
@@ -6193,7 +6300,7 @@
    // have to do it this way for IE
    // otherwise the form will be posted to a new window
    if (document.all) {
      var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
      var html = '<iframe name="'+frame_name+'" src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
      document.body.insertAdjacentHTML('BeforeEnd', html);
    }
    else { // for standards-compilant browsers
@@ -6218,6 +6325,126 @@
    return frame_name;
  };
  // html5 file-drop API
  this.document_drag_hover = function(e, over)
  {
    e.preventDefault();
    $(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active');
  };
  this.file_drag_hover = function(e, over)
  {
    e.preventDefault();
    e.stopPropagation();
    $(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('hover');
  };
  // handler when files are dropped to a designated area.
  // compose a multipart form data and submit it to the server
  this.file_dropped = function(e)
  {
    // abort event and reset UI
    this.file_drag_hover(e, false);
    // prepare multipart form data composition
    var files = e.target.files || e.dataTransfer.files,
      formdata = window.FormData ? new FormData() : null,
      fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'),
      boundary = '------multipartformboundary' + (new Date).getTime(),
      dashdash = '--', crlf = '\r\n',
      multipart = dashdash + boundary + crlf;
    if (!files || !files.length)
      return;
    // inline function to submit the files to the server
    var submit_data = function() {
      var multiple = files.length > 1,
        ts = new Date().getTime(),
        content = '<span>' + (multiple ? ref.get_label('uploadingmany') : files[0].name) + '</span>';
      // add to attachments list
      if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
        ref.file_upload_id = ref.set_busy(true, 'uploading');
      // complete multipart content and post request
      multipart += dashdash + boundary + dashdash + crlf;
      $.ajax({
        type: 'POST',
        dataType: 'json',
        url: ref.url(ref.env.filedrop.action||'upload', { _id:ref.env.compose_id||ref.env.cid||'', _uploadid:ts, _remote:1 }),
        contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
        processData: false,
        data: formdata || multipart,
        headers: {'X-Roundcube-Request': ref.env.request_token},
        beforeSend: function(xhr, s) { if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; },
        success: function(data){ ref.http_response(data); },
        error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); }
      });
    };
    // get contents of all dropped files
    var last = this.env.filedrop.single ? 0 : files.length - 1;
    for (var j=0, i=0, f; j <= last && (f = files[i]); i++) {
      if (!f.name) f.name = f.fileName;
      if (!f.size) f.size = f.fileSize;
      if (!f.type) f.type = 'application/octet-stream';
      // file name contains non-ASCII characters, do UTF8-binary string conversion.
      if (!formdata && /[^\x20-\x7E]/.test(f.name))
        f.name_bin = unescape(encodeURIComponent(f.name));
      // filter by file type if requested
      if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
        // TODO: show message to user
        continue;
      }
      // do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
      if (formdata) {
        formdata.append(fieldname, f);
        if (j == last)
          return submit_data();
      }
      // use FileReader supporetd by Firefox 3.6
      else if (window.FileReader) {
        var reader = new FileReader();
        // closure to pass file properties to async callback function
        reader.onload = (function(file, j) {
          return function(e) {
            multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
            multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
            multipart += 'Content-Length: ' + file.size + crlf;
            multipart += 'Content-Type: ' + file.type + crlf + crlf;
            multipart += e.target.result + crlf;
            multipart += dashdash + boundary + crlf;
            if (j == last)  // we're done, submit the data
              return submit_data();
          }
        })(f,j);
        reader.readAsBinaryString(f);
      }
      // Firefox 3
      else if (f.getAsBinary) {
        multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
        multipart += '; filename="' + (f.name_bin || f.name) + '"' + crlf;
        multipart += 'Content-Length: ' + f.size + crlf;
        multipart += 'Content-Type: ' + f.type + crlf + crlf;
        multipart += f.getAsBinary() + crlf;
        multipart += dashdash + boundary +crlf;
        if (j == last)
          return submit_data();
      }
      j++;
    }
  };
  // starts interval for keep-alive/check-recent signal
  this.start_keepalive = function()
@@ -6411,7 +6638,7 @@
    img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
    img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
    img.src = 'program/blank.tif';
    img.src = 'program/resources/blank.tif';
  };
  this.pdf_support_check = function()
@@ -6468,6 +6695,12 @@
    return 0;
  };
  // Cookie setter
  this.set_cookie = function(name, value, expires)
  {
    setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
  }
}  // end object rcube_webmail
@@ -6498,6 +6731,8 @@
  }
};
rcube_webmail.prototype.get_cookie = getCookie;
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;