alecpl
2011-07-07 65b61cdd1ce5b011ca9f846847e81f16f10ef0d0
program/js/app.js
@@ -164,7 +164,7 @@
    }
    // enable general commands
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', true);
    this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'undo', true);
    if (this.env.permaurl)
      this.enable_command('permaurl', true);
@@ -304,14 +304,11 @@
          if (this.gui_objects.qsearchbox) {
            $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
          }
          this.update_group_commands();
        }
        this.set_page_buttons();
        if (this.env.address_sources && this.env.address_sources[this.env.source] && !this.env.address_sources[this.env.source].readonly) {
          this.enable_command('add', 'import', true);
          this.enable_command('group-create', this.env.address_sources[this.env.source].groups);
        }
        if (this.env.cid) {
          this.enable_command('show', 'edit', true);
@@ -339,6 +336,7 @@
        if (this.contact_list && this.contact_list.rowcount > 0)
          this.enable_command('export', true);
        this.enable_command('add', 'import', this.env.writable_source);
        this.enable_command('list', 'listgroup', 'advanced-search', true);
        break;
@@ -413,7 +411,7 @@
    // show message
    if (this.pending_message)
      this.display_message(this.pending_message[0], this.pending_message[1]);
      this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
    // map implicit containers
    if (this.gui_objects.folderlist)
@@ -531,12 +529,12 @@
          if (this.env.trash_mailbox)
            this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
        }
        else if (this.task=='addressbook') {
        else if (this.task == 'addressbook') {
          if (!this.env.search_request || (props != this.env.source))
            this.reset_qsearch();
          this.list_contacts(props);
          this.enable_command('add', 'import', (this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
          this.enable_command('add', 'import', this.env.writable_source);
        }
        break;
@@ -639,13 +637,17 @@
          }
          // contacts/identities
          else {
            if ((input = $("input[name='_name']", form)) &&input.length && input.val() == '') {
            // reload form
            if (props == 'reload') {
              form.action += '?_reload=1';
            }
            else if ((input = $("input[name='_name']", form)) &&input.length && input.val() == '') {
              alert(this.get_label('nonamewarning'));
              input.focus();
              break;
            }
            else if (this.task == 'settings' && (this.env.identities_level % 2) == 0  &&
              (input = $("input[name='_email']", form)) && input.length&& !rcube_check_email(input.val())
              (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
            ) {
              alert(this.get_label('noemailwarning'));
              input.focus();
@@ -655,6 +657,10 @@
            // clear empty input fields
            $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
          }
          // add selected source (on the list)
          if (parent.rcmail && parent.rcmail.env.source)
            form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
          form.submit();
        }
@@ -990,8 +996,14 @@
        if (s && this.env.mailbox)
          this.list_mailbox(this.env.mailbox);
        else if (s && this.task == 'addressbook')
        else if (s && this.task == 'addressbook') {
          if (this.env.source == '') {
            for (var n in this.env.address_sources) break;
            this.env.source = n;
            this.env.group = '';
          }
          this.list_contacts(this.env.source, this.env.group);
        }
        break;
      case 'listgroup':
@@ -1032,6 +1044,10 @@
      case 'identities':
      case 'folders':
        this.goto_url('settings/' + command);
        break;
      case 'undo':
        this.http_request('undo', '', this.display_message('', 'loading'));
        break;
      // unified command call (command name == function name)
@@ -1284,7 +1300,7 @@
      var toffset = -moffset-boffset;
      var li, div, pos, mouse, check, oldclass,
        layerclass = 'draglayernormal';
      if (this.contact_list && this.contact_list.draglayer)
        oldclass = this.contact_list.draglayer.attr('class');
@@ -2863,7 +2879,7 @@
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
      // add signature according to selected identity
      // if we have HTML editor, signature is added in callback
      if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
      if (input_from.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
        this.change_identity(input_from[0]);
      }
    }
@@ -2902,7 +2918,7 @@
      input_message = $("[name='_message']");
    // check sender (if have no identities)
    if (input_from.attr('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
    if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
      alert(this.get_label('nosenderwarning'));
      input_from.focus();
      return false;
@@ -3256,7 +3272,7 @@
      });
      // display upload indicator and cancel button
      var content = this.get_label('uploading' + (files > 1 ? 'many' : '')),
      var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
        ts = frame_name.replace(/^rcmupload/, '');
      if (this.env.loadingicon)
@@ -3264,6 +3280,11 @@
      if (this.env.cancelicon)
        content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content;
      this.add2attachment_list(ts, { name:'', html:content, complete:false });
      // upload progress support
      if (this.env.upload_progress_time) {
        this.upload_progress_start('upload', ts);
      }
    }
    // set reference to the form object
@@ -3326,6 +3347,25 @@
    this.remove_from_attachment_list(name);
    $("iframe[name='"+frame_name+"']").remove();
    return false;
  };
  this.upload_progress_start = function(action, name)
  {
    window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
      this.env.upload_progress_time * 1000);
  };
  this.upload_progress_update = function(param)
  {
    var elem = $('#'+param.name + '> span');
    if (!elem.length || !param.text)
      return;
    elem.text(param.text);
    if (!param.done)
      this.upload_progress_start(param.action, param.name);
  };
  // send remote request to add a new contact
@@ -3653,15 +3693,34 @@
    if (this.preview_timer)
      clearTimeout(this.preview_timer);
    var id, frame, ref = this;
    var n, id, sid, ref = this, writable = false,
      source = this.env.source ? this.env.address_sources[this.env.source] : null;
    if (id = list.get_single_selection())
      this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
    else if (this.env.contentframe)
      this.show_contentframe(false);
    // no source = search result, we'll need to detect if any of
    // selected contacts are in writable addressbook to enable edit/delete
    if (list.selection.length) {
      if (!source) {
        for (n in list.selection) {
          sid = String(list.selection[n]).replace(/^[^-]+-/, '');
          if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
            writable = true;
            break;
          }
        }
      }
      else {
        writable = !source.readonly;
      }
    }
    this.enable_command('compose', list.selection.length > 0);
    this.enable_command('edit', (id && this.env.address_sources && !this.env.address_sources[this.env.source].readonly) ? true : false);
    this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
    return false;
  };
@@ -3752,6 +3811,13 @@
      add_url = '&_framed=1';
      target = window.frames[this.env.contentframe];
      this.show_contentframe(true);
      // load dummy content
      if (!cid) {
        // unselect selected row(s)
        this.contact_list.clear_selection();
        this.enable_command('delete', 'compose', false);
      }
    }
    else if (framed)
      return false;
@@ -3799,12 +3865,12 @@
    if (!(selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
      return;
    var id, a_cids = [], qs = '';
    var id, n, a_cids = [], qs = '';
    if (this.env.cid)
      a_cids.push(this.env.cid);
    else {
      for (var n=0; n<selection.length; n++) {
      for (n=0; n<selection.length; n++) {
        id = selection[n];
        a_cids.push(id);
        this.contact_list.remove_row(id, (n == selection.length-1));
@@ -3819,7 +3885,7 @@
      qs += '&_gid='+urlencode(this.env.group);
    // also send search request to get the right records from the next page
    if (this.env.search_request)
    if (this.env.search_request)
      qs += '&_search='+this.env.search_request;
    // send request to server
@@ -3906,7 +3972,7 @@
  this.group_create = function()
  {
    if (!this.gui_objects.folderlist || !this.env.address_sources[this.env.source].groups)
    if (!this.gui_objects.folderlist)
      return;
    if (!this.name_input) {
@@ -4013,17 +4079,16 @@
    this.reset_add_input();
    prop.type = 'group';
    var key = 'G'+prop.source+prop.id;
    this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
    var key = 'G'+prop.source+prop.id,
      link = $('<a>').attr('href', '#')
        .attr('rel', prop.source+':'+prop.id)
        .click(function() { return rcmail.command('listgroup', prop, this); })
        .html(prop.name),
      li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactgroup'})
        .append(link);
    var link = $('<a>').attr('href', '#')
      .attr('rel', prop.source+':'+prop.id)
      .bind('click', function() { return rcmail.command('listgroup', prop, this);})
      .html(prop.name);
    var li = $('<li>').attr('id', 'rcmli'+key.replace(this.identifier_expr, '_'))
      .addClass('contactgroup')
      .append(link)
      .insertAfter(this.get_folder_li(prop.source));
    this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
    this.add_contact_group_row(prop, li);
    this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] });
  };
@@ -4039,19 +4104,23 @@
    // group ID has changed, replace link node and identifiers
    if (li && prop.newid) {
      var newkey = 'G'+prop.source+prop.newid;
      var newkey = 'G'+prop.source+prop.newid,
        newprop = $.extend({}, prop);;
      li.id = String('rcmli'+newkey).replace(this.identifier_expr, '_');
      this.env.contactfolders[newkey] = this.env.contactfolders[key];
      this.env.contactfolders[newkey].id = prop.newid;
      this.env.group = prop.newid;
      var newprop = $.extend({}, prop);
      delete this.env.contactfolders[key];
      delete this.env.contactgroups[key];
      newprop.id = prop.newid;
      newprop.type = 'group';
      link = $('<a>').attr('href', '#')
        .attr('rel', prop.source+':'+prop.newid)
        .bind('click', function() { return rcmail.command('listgroup', newprop, this);})
        .click(function() { return rcmail.command('listgroup', newprop, this); })
        .html(prop.name);
      $(li).children().replaceWith(link);
    }
@@ -4060,7 +4129,41 @@
      link.innerHTML = prop.name;
    this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
    this.add_contact_group_row(prop, $(li), true);
    this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
  };
  // add contact group row to the list, with sorting
  this.add_contact_group_row = function(prop, li, reloc)
  {
    var row, name = prop.name.toUpperCase(),
      sibling = this.get_folder_li(prop.source),
      prefix = 'rcmliG'+(prop.source).replace(this.identifier_expr, '_');
    // When renaming groups, we need to remove it from DOM and insert it in the proper place
    if (reloc) {
      row = li.clone(true);
      li.remove();
    }
    else
      row = li;
    $('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) {
      if (name >= $(this).text().toUpperCase())
        sibling = elem;
      else
        return false;
    });
    row.insertAfter(sibling);
  };
  this.update_group_commands = function()
  {
    var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
    this.enable_command('group-create', (source && source.groups && !source.readonly));
    this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
  };
  this.init_edit_field = function(col, elem)
@@ -4109,14 +4212,27 @@
          this.init_edit_field(col, input);
        }
        else if (colprop.type == 'composite') {
          var childcol, cp, first;
          for (childcol in colprop.childs) {
          var childcol, cp, first, templ, cols = [], suffices = [];
          // read template for composite field order
          if ((templ = this.env[col+'_template'])) {
            for (var j=0; j < templ.length; j++) {
              cols.push(templ[j][1]);
              suffices.push(templ[j][2]);
            }
          }
          else {  // list fields according to appearance in colprop
            for (childcol in colprop.childs)
              cols.push(childcol);
          }
          for (var i=0; i < cols.length; i++) {
            childcol = cols[i];
            cp = colprop.childs[childcol];
            input = $('<input>')
              .addClass('ff_'+childcol)
              .attr({type: 'text', name: '_'+childcol+name_suffix, size: cp.size})
              .attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
              .appendTo(cell);
            cell.append(" ");
            cell.append(suffices[i] || " ");
            this.init_edit_field(childcol, input);
            if (!first) first = input;
          }
@@ -4230,14 +4346,20 @@
      target = window.frames[this.env.contentframe];
      this.contact_list.clear_selection();
    }
    else if (framed)
      return false;
    this.location_href(this.env.comm_path+'&_action=search'+add_url
      +'&_source='+urlencode(this.env.source)
      +(this.env.group ? '&_gid='+urlencode(this.env.group) : ''), target);
    this.location_href(this.env.comm_path+'&_action=search'+add_url, target);
    return true;
  };
  // unselect directory/group
  this.unselect_directory = function()
  {
    if (this.env.address_sources.length > 1 || this.env.group != '') {
      this.select_folder('', (this.env.group ? 'G'+this.env.source+this.env.group : this.env.source));
      this.env.group = '';
      this.env.source = '';
    }
  };
@@ -4419,7 +4541,7 @@
  };
  // Add folder row to the table and initialize it
  this.add_folder_row = function (name, display_name, protected, subscribed, skip_init)
  this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
  {
    if (!this.gui_objects.subscriptionlist)
      return false;
@@ -4436,15 +4558,18 @@
    }
    // clone a table row if there are existing rows
    row    = $(refrow).clone(true);
    row = $(refrow).clone(true);
    // set ID, reset css class
    row.attr('id', id);
    row.attr('class', class_name);
    // set folder name
    row.find('td:first').html(display_name);
    // update subscription checkbox
    $('input[name="_subscribed[]"]', row).val(name)
      .prop({checked: subscribed ? true : false, disabled: protected ? true : false});
      .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
    // add to folder/row-ID map
    this.env.subscriptionrows[id] = [name, display_name, 0];
@@ -4495,7 +4620,7 @@
  };
  // replace an existing table row with a new folder line (with subfolders)
  this.replace_folder_row = function(oldfolder, newfolder, display_name, protected)
  this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
  {
    if (!this.gui_objects.subscriptionlist)
      return false;
@@ -4511,7 +4636,7 @@
    // replace an existing table row
    this._remove_folder_row(id);
    row = $(this.add_folder_row(newfolder, display_name, protected, subscribed, true));
    row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
    // detect tree depth change
    if (len = list.length) {
@@ -4859,7 +4984,7 @@
    if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder))
      $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder);
  };
  // write to the document/window title
  this.set_pagetitle = function(title)
  {
@@ -4868,27 +4993,29 @@
  };
  // display a system message, list of types in common.css (below #message definition)
  this.display_message = function(msg, type)
  this.display_message = function(msg, type, timeout)
  {
    // pass command to parent window
    if (this.is_framed())
      return parent.rcmail.display_message(msg, type);
      return parent.rcmail.display_message(msg, type, timeout);
    if (!this.gui_objects.message) {
      // save message in order to display after page loaded
      if (type != 'loading')
        this.pending_message = new Array(msg, type);
        this.pending_message = new Array(msg, type, timeout);
      return false;
    }
    type = type ? type : 'notice';
    var ref = this,
      key = msg,
      key = String(msg).replace(this.identifier_expr, '_'),
      date = new Date(),
      id = type + date.getTime(),
      id = type + date.getTime();
    if (!timeout)
      timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
    if (type == 'loading') {
      key = 'loading';
      timeout = this.env.request_timeout * 1000;
@@ -5457,9 +5584,20 @@
    switch (response.action) {
      case 'delete':
        if (this.task == 'addressbook') {
          var uid = this.contact_list.get_selection();
          var sid, uid = this.contact_list.get_selection(), writable = false;
          if (uid && this.contact_list.rows[uid]) {
            // search results, get source ID from record ID
            if (this.env.source == '') {
              sid = String(uid).replace(/^[^-]+-/, '');
              writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
            }
            else {
              writable = !this.env.address_sources[this.env.source].readonly;
            }
          }
          this.enable_command('compose', (uid && this.contact_list.rows[uid]));
          this.enable_command('delete', 'edit', (uid && this.contact_list.rows[uid] && this.env.address_sources && !this.env.address_sources[this.env.source].readonly));
          this.enable_command('delete', 'edit', writable);
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
        }
@@ -5508,10 +5646,7 @@
          this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
          if (response.action == 'list' || response.action == 'search') {
            this.enable_command('group-create',
              (this.env.address_sources[this.env.source].groups && !this.env.address_sources[this.env.source].readonly));
            this.enable_command('group-rename', 'group-delete',
              (this.env.address_sources[this.env.source].groups && this.env.group && !this.env.address_sources[this.env.source].readonly));
            this.update_group_commands();
            this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
          }
        }
@@ -5543,6 +5678,19 @@
    var ts = new Date().getTime(),
      frame_name = 'rcmupload'+ts;
    // upload progress support
    if (this.env.upload_progress_name) {
      var fname = this.env.upload_progress_name,
        field = $('input[name='+fname+']', form);
      if (!field.length) {
        field = $('<input>').attr({type: 'hidden', name: fname});
        field.prependTo(form);
      }
      field.val(ts);
    }
    // have to do it this way for IE
    // otherwise the form will be posted to a new window
    if (document.all) {
@@ -5564,12 +5712,13 @@
    form.target = frame_name;
    form.action = this.url(action, { _id:this.env.compose_id||'', _uploadid:ts });
    form.setAttribute('method', 'POST');
    form.setAttribute('enctype', 'multipart/form-data');
    form.submit();
    return frame_name;
  };
  // starts interval for keep-alive/check-recent signal
  this.start_keepalive = function()
  {
@@ -5579,7 +5728,14 @@
    if (this.env.keep_alive && !this.env.framed && this.task == 'mail' && this.gui_objects.mailboxlist)
      this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
    else if (this.env.keep_alive && !this.env.framed && this.task != 'login' && this.env.action != 'print')
      this._int = setInterval(function(){ ref.http_request('keep-alive'); }, this.env.keep_alive * 1000);
      this._int = setInterval(function(){ ref.keep_alive(); }, this.env.keep_alive * 1000);
  };
  // sends keep-alive signal
  this.keep_alive = function()
  {
    if (!this.busy)
      this.http_request('keep-alive');
  };
  // sends request to check for recent messages