Aleksander Machniak
2012-06-28 669747a81c4b2ff823d1f20dc50899163c0a8a4a
program/js/app.js
@@ -17,8 +17,6 @@
 +-----------------------------------------------------------------------+
 | Requires: jquery.js, common.js, list.js                               |
 +-----------------------------------------------------------------------+
  $Id$
*/
function rcube_webmail()
@@ -309,6 +307,10 @@
          this.http_post(postact, postdata);
        }
        // detect browser capabilities
        if (!this.is_framed())
          this.browser_capabilities_check();
        break;
      case 'addressbook':
@@ -456,6 +458,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 && ((XMLHttpRequest && 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 });
@@ -483,7 +493,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;
@@ -629,7 +639,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.goto_url('compose', { _draft_uid: uid, _mbox: this.env.mailbox }, true);
            else
              this.show_message(uid);
          }
@@ -651,13 +661,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 ? '_draft_uid' : '_uid'] = cid;
          this.goto_url('compose', url, true);
        }
        break;
@@ -702,7 +713,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();
@@ -767,7 +778,7 @@
      case 'always-load':
        if (this.env.uid && this.env.sender) {
          this.add_contact(urlencode(this.env.sender));
          this.add_contact(this.env.sender);
          setTimeout(function(){ ref.command('load-images'); }, 300);
          break;
        }
@@ -982,12 +993,12 @@
      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);
        }
@@ -996,9 +1007,9 @@
      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';
            url._attachment = 1;
          this.goto_url('compose', url, true);
        }
        break;
@@ -1024,7 +1035,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
@@ -1077,7 +1088,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;
@@ -1578,7 +1589,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.goto_url('compose', { _draft_uid: uid, _mbox: this.env.mailbox }, true);
    else if (uid)
      this.show_message(uid, false, false);
  };
@@ -1816,7 +1827,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>';
@@ -1945,13 +1956,16 @@
    if (this.env.search_request)
      url += '&_search='+this.env.search_request;
    if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
    // add browser capabilities, so we can properly handle attachments
    url += '&_caps='+urlencode(this.browser_capabilities());
    if (preview && String(target.location.href).indexOf(url) >= 0)
      this.show_contentframe(true);
    else {
      this.location_href(this.env.comm_path+url, target, true);
      // mark as read and change mbox unread counter
      if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
      if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
        this.preview_read_timer = setTimeout(function() {
          ref.set_message(id, 'unread', false);
          ref.update_thread_root(id, 'read');
@@ -2541,7 +2555,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,
@@ -2573,7 +2587,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();
      }
@@ -2988,7 +3002,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.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
      if (input_from.prop('type') == 'select-one') {
        this.change_identity(input_from[0]);
      }
    }
@@ -3447,12 +3461,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) {
@@ -3471,6 +3480,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);
@@ -3798,7 +3814,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(),
@@ -4699,11 +4715,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');
    }
  };
@@ -4718,8 +4734,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)
@@ -4920,7 +4936,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;
  };
@@ -6214,6 +6230,125 @@
    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,
        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()
  {
@@ -6370,6 +6505,99 @@
      $(elem).click(function() { rcmail.register_protocol_handler(name); return false; });
  };
  // Checks browser capabilities eg. PDF support, TIF support
  this.browser_capabilities_check = function()
  {
    if (!this.env.browser_capabilities)
      this.env.browser_capabilities = {};
    if (this.env.browser_capabilities.pdf === undefined)
      this.env.browser_capabilities.pdf = this.pdf_support_check();
    if (this.env.browser_capabilities.flash === undefined)
      this.env.browser_capabilities.flash = this.flash_support_check();
    if (this.env.browser_capabilities.tif === undefined)
      this.tif_support_check();
  };
  // Returns browser capabilities string
  this.browser_capabilities = function()
  {
    if (!this.env.browser_capabilities)
      return '';
    var n, ret = [];
    for (n in this.env.browser_capabilities)
      ret.push(n + '=' + this.env.browser_capabilities[n]);
    return ret.join();
  };
  this.tif_support_check = function()
  {
    var img = new Image();
    img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
    img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
    img.src = 'program/blank.tif';
  };
  this.pdf_support_check = function()
  {
    var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
      plugins = navigator.plugins,
      len = plugins.length,
      regex = /Adobe Reader|PDF|Acrobat/i;
    if (plugin && plugin.enabledPlugin)
        return 1;
    if (window.ActiveXObject) {
      try {
        if (axObj = new ActiveXObject("AcroPDF.PDF"))
          return 1;
      }
      catch (e) {}
      try {
        if (axObj = new ActiveXObject("PDF.PdfCtrl"))
          return 1;
      }
      catch (e) {}
    }
    for (i=0; i<len; i++) {
      plugin = plugins[i];
      if (typeof plugin === 'String') {
        if (regex.test(plugin))
          return 1;
      }
      else if (plugin.name && regex.test(plugin.name))
        return 1;
    }
    return 0;
  };
  this.flash_support_check = function()
  {
    var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
    if (plugin && plugin.enabledPlugin)
        return 1;
    if (window.ActiveXObject) {
      try {
        if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
          return 1;
      }
      catch (e) {}
    }
    return 0;
  };
}  // end object rcube_webmail