Aleksander Machniak
2014-05-08 3cf97b112be8aaafe27f4aa3d05c69bf1e3d961c
program/js/app.js
@@ -1,23 +1,37 @@
/*
 +-----------------------------------------------------------------------+
 | Roundcube Webmail Client Script                                       |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2014, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2014, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
 |          Aleksander 'A.L.E.C' Machniak <alec@alec.pl>                 |
 |          Charles McNulty <charles@charlesmcnulty.com>                 |
 +-----------------------------------------------------------------------+
 | Requires: jquery.js, common.js, list.js                               |
 +-----------------------------------------------------------------------+
*/
/**
 * Roundcube Webmail Client Script
 *
 * This file is part of the Roundcube Webmail client
 *
 * @licstart  The following is the entire license notice for the
 * JavaScript code in this file.
 *
 * Copyright (C) 2005-2014, The Roundcube Dev Team
 * Copyright (C) 2011-2014, Kolab Systems AG
 *
 * The JavaScript code in this page is free software: you can
 * redistribute it and/or modify it under the terms of the GNU
 * General Public License (GNU GPL) as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 *
 * @licend  The above is the entire license notice
 * for the JavaScript code in this file.
 *
 * @author Thomas Bruederli <roundcube@gmail.com>
 * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
 * @author Charles McNulty <charles@charlesmcnulty.com>
 *
 * @requires jquery.js, common.js, list.js
 */
function rcube_webmail()
{
@@ -514,23 +528,19 @@
    if (this.pending_message)
      this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
    // map implicit containers
    if (this.gui_objects.folderlist) {
      this.gui_containers.foldertray = $(this.gui_objects.folderlist);
      // init treelist widget
      if (window.rcube_treelist_widget) {
        this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
    // init treelist widget
    if (this.gui_objects.folderlist && window.rcube_treelist_widget) {
      this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
          id_prefix: 'rcmli',
          id_encode: this.html_identifier_encode,
          id_decode: this.html_identifier_decode,
          check_droptarget: function(node) { return !node.virtual && ref.check_droptarget(node.id) }
        });
        this.treelist
          .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
          .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
          .addEventListener('select', function(node) { ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) });
      }
      });
      this.treelist
        .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
        .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
        .addEventListener('select', function(node) { ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) });
    }
    // activate html5 file drop feature (if browser supports it and if configured)
@@ -710,19 +720,12 @@
        break;
      case 'list':
        // re-send search query for the selected folder
        if (props && props != '' && this.env.search_request && this.gui_objects.qsearchbox.value) {
          var oldmbox = this.env.search_scope == 'all' ? '*' : this.env.mailbox;
          this.env.search_mods[props] = this.env.search_mods[oldmbox];  // copy search mods from active search
          this.env.mailbox = props;
          this.env.search_scope = 'sub';
          this.qsearch(this.gui_objects.qsearchbox.value);
          this.select_folder(this.env.mailbox, '', true);
          break;
        if (props && props != '') {
          this.reset_qsearch();
        }
        if (this.env.action == 'compose' && this.env.extwin)
        if (this.env.action == 'compose' && this.env.extwin) {
          window.close();
        }
        else if (this.task == 'mail') {
          this.list_mailbox(props);
          this.set_button_titles();
@@ -1876,12 +1879,13 @@
      list = this.message_list,
      rows = list.rows,
      message = this.env.messages[uid],
      msg_id = this.html_identifier(uid,true),
      row_class = 'message'
        + (!flags.seen ? ' unread' : '')
        + (flags.deleted ? ' deleted' : '')
        + (flags.flagged ? ' flagged' : '')
        + (message.selected ? ' selected' : ''),
      row = { cols:[], style:{}, id:'rcmrow'+this.html_identifier(uid,true), uid:uid };
      row = { cols:[], style:{}, id:'rcmrow'+msg_id, uid:uid };
    // message status icons
    css_class = 'msgicon';
@@ -1907,7 +1911,7 @@
    if (this.env.threading) {
      if (message.depth) {
        // This assumes that div width is hardcoded to 15px,
        tree += '<span id="rcmtab' + row.id + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
        tree += '<span id="rcmtab' + msg_id + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
        if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
          || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
@@ -1991,6 +1995,9 @@
          html = '<span class="prio'+flags.prio+'">&nbsp;</span>';
        else
          html = '&nbsp;';
      }
      else if (c == 'folder') {
        html = '<span onmouseover="rcube_webmail.long_subject_title(this)">' + cols[c] + '<span>';
      }
      else
        html = cols[c];
@@ -3027,7 +3034,7 @@
      rows = this.message_list ? this.message_list.rows : {};
    if (typeof uids == 'string')
      uids = String(uids).split(',');
      uids = uids.split(',');
    for (i=0, len=uids.length; i<len; i++) {
      uid = uids[i];
@@ -3441,17 +3448,8 @@
          $(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font);
        }, 500);
    }
    else {
      var thisMCE = tinyMCE.get(props.id), existingHtml;
      if (existingHtml = thisMCE.getContent()) {
        if (!confirm(this.get_label('editorwarning'))) {
          return false;
        }
        this.html2plain(existingHtml, props.id);
      }
    else if (this.html2plain(tinyMCE.get(props.id).getContent(), props.id))
      tinyMCE.execCommand('mceRemoveControl', false, props.id);
    }
    return true;
  };
@@ -4214,6 +4212,17 @@
    return false;
  };
  this.continue_search = function(request_id)
  {
    var lock = ref.set_busy(true, 'stillsearching');
    setTimeout(function(){
      var url = ref.search_params();
      url._continue = request_id;
      ref.env.qsearch = { lock: lock, request: ref.http_request('search', url, lock) };
    }, 100);
  };
  // build URL params for search
  this.search_params = function(search, filter, smods)
  {
@@ -4442,9 +4451,7 @@
    this.ksearch_input.value = pre + insert + end;
    // set caret to insert pos
    cpos = p+insert.length;
    if (this.ksearch_input.setSelectionRange)
      this.ksearch_input.setSelectionRange(cpos, cpos);
    this.set_caret_pos(this.ksearch_input, p + insert.length);
    if (trigger) {
      this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert, data:this.env.contacts[id] });
@@ -6803,6 +6810,16 @@
  this.html2plain = function(htmlText, id)
  {
    // warn the user (if converted content is not empty)
    if (!htmlText || !(htmlText.replace(/<[^>]+>|&nbsp;|\s/g, '')).length) {
      // without setTimeout() here, textarea is filled with initial (onload) content
      setTimeout(function() { $('#'+id).val(''); }, 50);
      return true;
    }
    if (!confirm(this.get_label('editorwarning')))
      return false;
    var url = '?_task=utils&_action=html2text',
      lock = this.set_busy(true, 'converting');
@@ -6812,6 +6829,8 @@
      error: function(o, status, err) { ref.http_error(o, status, err, lock); },
      success: function(data) { ref.set_busy(false, null, lock); $('#'+id).val(data); ref.log(data); }
    });
    return true;
  };
  this.plain2html = function(plain, id)
@@ -7221,12 +7240,12 @@
  // helper method to send an HTTP request with the given iterator value
  this.multi_thread_send_request = function(prop, item)
  {
    var postdata, query;
    var k, postdata, query;
    // replace %s in post data
    if (prop.postdata) {
      postdata = {};
      for (var k in prop.postdata) {
      for (k in prop.postdata) {
        postdata[k] = String(prop.postdata[k]).replace('%s', item);
      }
      postdata._reqid = prop.reqid;
@@ -7238,7 +7257,7 @@
    }
    else if (typeof prop.query == 'object' && prop.query) {
      query = {};
      for (var k in prop.query) {
      for (k in prop.query) {
        query[k] = String(prop.query[k]).replace('%s', item);
      }
      query._reqid = prop.reqid;
@@ -7581,7 +7600,7 @@
  {
    var msg = this.env.messages ? this.env.messages[uid] : {};
    return msg.mbox || this.env.mailbox;
  }
  };
  // gets cursor position
  this.get_caret_pos = function(obj)
@@ -7589,89 +7608,31 @@
    if (obj.selectionEnd !== undefined)
      return obj.selectionEnd;
    if (document.selection && document.selection.createRange) {
      var range = document.selection.createRange();
      if (range.parentElement() != obj)
        return 0;
      var gm = range.duplicate();
      if (obj.tagName == 'TEXTAREA')
        gm.moveToElementText(obj);
      else
        gm.expand('textedit');
      gm.setEndPoint('EndToStart', range);
      var p = gm.text.length;
      return p <= obj.value.length ? p : -1;
    }
    return obj.value.length;
  };
  // moves cursor to specified position
  this.set_caret_pos = function(obj, pos)
  {
    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();
    try {
      if (obj.setSelectionRange)
        obj.setSelectionRange(pos, pos);
    }
    catch(e) {}; // catch Firefox exception if obj is hidden
  };
  // get selected text from an input field
  // http://stackoverflow.com/questions/7186586/how-to-get-the-selected-text-in-textarea-using-jquery-in-internet-explorer-7
  this.get_input_selection = function(obj)
  {
    var start = 0, end = 0,
      normalizedValue, range,
      textInputRange, len, endRange;
    var start = 0, end = 0, normalizedValue = '';
    if (typeof obj.selectionStart == "number" && typeof obj.selectionEnd == "number") {
      normalizedValue = obj.value;
      start = obj.selectionStart;
      end = obj.selectionEnd;
    }
    else {
      range = document.selection.createRange();
      if (range && range.parentElement() == obj) {
        len = obj.value.length;
        normalizedValue = obj.value; //.replace(/\r\n/g, "\n");
        // create a working TextRange that lives only in the input
        textInputRange = obj.createTextRange();
        textInputRange.moveToBookmark(range.getBookmark());
        // Check if the start and end of the selection are at the very end
        // of the input, since moveStart/moveEnd doesn't return what we want
        // in those cases
        endRange = obj.createTextRange();
        endRange.collapse(false);
        if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
          start = end = len;
        }
        else {
          start = -textInputRange.moveStart("character", -len);
          start += normalizedValue.slice(0, start).split("\n").length - 1;
          if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
            end = len;
          }
          else {
            end = -textInputRange.moveEnd("character", -len);
            end += normalizedValue.slice(0, end).split("\n").length - 1;
          }
        }
      }
    }
    return { start:start, end:end, text:normalizedValue.substr(start, end-start) };
    return {start: start, end: end, text: normalizedValue.substr(start, end-start)};
  };
  // disable/enable all fields of a form
@@ -7693,9 +7654,7 @@
      // remember which elem was disabled before lock
      if (lock && elm.disabled)
        this.disabled_form_elements.push(elm);
      // check this.disabled_form_elements before inArray() as a workaround for FF5 bug
      // http://bugs.jquery.com/ticket/9873
      else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
      else if (lock || $.inArray(elm, this.disabled_form_elements) < 0)
        elm.disabled = lock;
    }
  };
@@ -7870,7 +7829,7 @@
{
  if (!elem.title) {
    var $elem = $(elem);
    if ($elem.width() + indent * 15 > $elem.parent().width())
    if ($elem.width() + (indent || 0) * 15 > $elem.parent().width())
      elem.title = $elem.text();
  }
};