diff options
Diffstat (limited to 'program/js')
| -rw-r--r-- | program/js/app.js | 374 | ||||
| -rw-r--r-- | program/js/editor.js | 744 | 
2 files changed, 602 insertions, 516 deletions
| diff --git a/program/js/app.js b/program/js/app.js index 8c1fcb44b..fe5815b3b 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -286,12 +286,17 @@ function rcube_webmail()            // add more commands (not enabled)            $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']); -          if (this.env.spellcheck) { -            this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); }; +          if (window.googie) { +            this.env.editor_config.spellchecker = googie; +            this.env.editor_config.spellcheck_observer = function(s) { ref.spellcheck_state(); }; +              this.env.compose_commands.push('spellcheck')              this.enable_command('spellcheck', true);            } +          // initialize HTML editor +          this.editor_init(this.env.editor_config, this.env.composebody); +            // init canned response functions            if (this.gui_objects.responseslist) {              $('a.insertresponse', this.gui_objects.responseslist) @@ -426,6 +431,9 @@ function rcube_webmail()          else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {            this.enable_command('save', 'edit', 'toggle-editor', true);            this.enable_command('delete', this.env.identities_level < 2); + +          // initialize HTML editor +          this.editor_init(this.env.editor_config, 'rcmfd_signature');          }          else if (this.env.action == 'folders') {            this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); @@ -1040,17 +1048,11 @@ function rcube_webmail()        case 'spellcheck':          if (this.spellcheck_state()) { -          this.stop_spellchecking(); +          this.editor.spellcheck_stop();          }          else { -          if (window.tinymce && tinymce.get(this.env.composebody)) { -            tinymce.execCommand('mceSpellCheck', true); -          } -          else if (this.env.spellcheck && this.env.spellcheck.spellCheck) { -            this.env.spellcheck.spellCheck(); -          } +          this.editor.spellcheck_start();          } -        this.spellcheck_state();          break;        case 'savedraft': @@ -1272,7 +1274,7 @@ function rcube_webmail()        default:          var func = command.replace(/-/g, '_');          if (this[func] && typeof this[func] === 'function') { -          ret = this[func](props, obj); +          ret = this[func](props, obj, event);          }          break;      } @@ -3158,7 +3160,7 @@ function rcube_webmail()      if (!this.gui_objects.messageform)        return false; -    var input_from = $("[name='_from']"), +    var i, input_from = $("[name='_from']"),        input_to = $("[name='_to']"),        input_subject = $("input[name='_subject']"),        input_message = $("[name='_message']").get(0), @@ -3187,7 +3189,7 @@ function rcube_webmail()      // init live search events      this.init_address_input_events(input_to, ac_props); -    for (var i in ac_fields) { +    for (i in ac_fields) {        this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);      } @@ -3202,10 +3204,11 @@ function rcube_webmail()      // check for locally stored compose data      if (window.localStorage) { -      var index = this.local_storage_get_item('compose.index', []); +      var key, formdata, index = this.local_storage_get_item('compose.index', []); -      for (var key, i = 0; i < index.length; i++) { -        key = index[i], formdata = this.local_storage_get_item('compose.' + key, null, true); +      for (i = 0; i < index.length; i++) { +        key = index[i]; +        formdata = this.local_storage_get_item('compose.' + key, null, true);          if (!formdata) {            continue;          } @@ -3356,12 +3359,11 @@ function rcube_webmail()    this.check_compose_input = function(cmd)    {      // check input fields -    var ed, input_to = $("[name='_to']"), +    var input_to = $("[name='_to']"),        input_cc = $("[name='_cc']"),        input_bcc = $("[name='_bcc']"),        input_from = $("[name='_from']"), -      input_subject = $("[name='_subject']"), -      input_message = $("[name='_message']"); +      input_subject = $("[name='_subject']");      // check sender (if have no identities)      if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) { @@ -3388,10 +3390,12 @@ function rcube_webmail()      // display localized warning for missing subject      if (input_subject.val() == '') { -      var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body); -      var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject')); +      var buttons = {}, +        myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>') +          .appendTo(document.body), +        prompt_value = $('<input>').attr({type: 'text', size: 30}).val(this.get_label('nosubject')) +          .appendTo(myprompt); -      var buttons = {};        buttons[this.get_label('cancel')] = function(){          input_subject.focus();          $(this).dialog('close'); @@ -3408,100 +3412,31 @@ function rcube_webmail()          buttons: buttons,          close: function(event, ui) { $(this).remove() }        }); +        prompt_value.select();        return false;      } -    // Apply spellcheck changes if spell checker is active -    this.stop_spellchecking(); - -    if (window.tinymce) -      ed = tinymce.get(this.env.composebody); -      // check for empty body -    if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) { -      input_message.focus(); +    if (!this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) { +      this.editor.focus();        return false;      } -    else if (ed) { -      if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) { -        ed.focus(); -        return false; -      } -      // move body from html editor to textarea (just to be sure, #1485860) -      tinymce.triggerSave(); -    } + +    // move body from html editor to textarea (just to be sure, #1485860) +    this.editor.save();      return true;    }; -  this.toggle_editor = function(props) +  this.toggle_editor = function(props, obj, e)    { -    this.stop_spellchecking(); - -    var ed, curr, content, result, -      // these non-printable chars are not removed on text2html and html2text -      // we can use them as temp signature replacement -      sig_mark = "\u0002\u0003", -      input = $('#' + props.id), -      signature = this.env.identity ? this.env.signatures[this.env.identity] : null, -      is_sig = signature && signature.text && signature.text.length > 1; - -    if (props.mode == 'html') { -      content = input.val(); - -      // replace current text signature with temp mark -      if (is_sig) -        content = content.replace(signature.text, sig_mark); - -      // convert to html -      result = this.plain2html(content, function(data) { -        // replace signature mark with html version of the signature -        if (is_sig) -          data = data.replace(sig_mark, '<div id="_rc_sig">' + signature.html + '</div>'); - -        input.val(data); -        tinyMCE.execCommand('mceAddEditor', false, props.id); - -        if (ref.env.default_font) -          setTimeout(function() { -            $(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font); -          }, 500); -      }); -    } -    else { -      ed = tinyMCE.get(props.id); - -      if (is_sig) { -        // get current version of signature, we'll need it in -        // case of html2text conversion abort -        if (curr = ed.dom.get('_rc_sig')) -          curr = curr.innerHTML; - -        // replace current signature with some non-printable characters -        // we use non-printable characters, because this replacement -        // is visible to the user -        // doing this after getContent() would be hard -        ed.dom.setHTML('_rc_sig', sig_mark); -      } +    // @todo: this should work also with many editors on page +    var result = this.editor.toggle(props.html); -      // get html content -      content = ed.getContent(); - -      // convert html to text -      result = this.html2plain(content, function(data) { -        tinyMCE.execCommand('mceRemoveEditor', false, props.id); - -        // replace signture mark with text version of the signature -        if (is_sig) -          data = data.replace(sig_mark, "\n" + signature.text); - -        input.val(data); -      }); - -      // bring back current signature -      if (!result && curr) -        ed.dom.setHTML('_rc_sig', curr); +    if (!result && e) { +      // fix selector value if operation failed +      $(e.target).filter('select').val(props.html ? 'plain' : 'html');      }      return result; @@ -3510,30 +3445,11 @@ function rcube_webmail()    this.insert_response = function(key)    {      var insert = this.env.textresponses[key] ? this.env.textresponses[key].text : null; +      if (!insert)        return false; -    // insert into tinymce editor -    if ($("input[name='_is_html']").val() == '1') { -      var editor = tinymce.get(this.env.composebody); -      editor.getWin().focus(); // correct focus in IE & Chrome -      editor.selection.setContent(this.quote_html(insert).replace(/\r?\n/g, '<br/>'), { format:'text' }); -    } -    // replace selection in compose textarea -    else { -      var textarea = rcube_find_object(this.env.composebody), -        selection = $(textarea).is(':focus') ? this.get_input_selection(textarea) : { start:0, end:0 }, -        inp_value = textarea.value; -        pre = inp_value.substring(0, selection.start), -        end = inp_value.substring(selection.end, inp_value.length); - -      // insert response text -      textarea.value = pre + insert + end; - -      // set caret after inserted text -      this.set_caret_pos(textarea, selection.start + insert.length); -      textarea.focus(); -    } +    this.editor.replace(insert);    };    /** @@ -3541,42 +3457,8 @@ function rcube_webmail()     */    this.save_response = function()    { -    var sigstart, text = '', strip = false; - -    // get selected text from tinymce editor -    if ($("input[name='_is_html']").val() == '1') { -      var editor = tinymce.get(this.env.composebody); -      editor.getWin().focus(); // correct focus in IE & Chrome -      text = editor.selection.getContent({ format:'text' }); - -      if (!text) { -        text = editor.getContent({ format:'text' }); -        strip = true; -      } -    } -    // get selected text from compose textarea -    else { -      var textarea = rcube_find_object(this.env.composebody), sigstart; -      if (textarea && $(textarea).is(':focus')) { -        text = this.get_input_selection(textarea).text; -      } - -      if (!text && textarea) { -        text = textarea.value; -        strip = true; -      } -    } - -    // strip off signature -    if (strip) { -      sigstart = text.indexOf('-- \n'); -      if (sigstart > 0) { -        text = text.substring(0, sigstart); -      } -    } -      // show dialog to enter a name and to modify the text to be saved -    var buttons = {}, +    var buttons = {}, text = this.editor.get_content(true, true),        html = '<form class="propform">' +        '<div class="prop block"><label>' + this.get_label('responsename') + '</label>' +        '<input type="text" name="name" id="ffresponsename" size="40" /></div>' + @@ -3655,30 +3537,10 @@ function rcube_webmail()      return false;    }; -  this.stop_spellchecking = function() -  { -    var ed; - -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) { -      if (ed.plugins && ed.plugins.spellchecker && this.env.spellcheck_active) -        ed.execCommand('mceSpellCheck'); -    } -    else if (ed = this.env.spellcheck) { -      if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found') -        $(ed.spell_span).trigger('click'); -    } - -    this.spellcheck_state(); -  }; - +  // updates spellchecker buttons on state change    this.spellcheck_state = function()    { -    var ed, active; - -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) -      active = this.env.spellcheck_active; -    else if ((ed = this.env.spellcheck) && ed.state) -      active = ed.state != 'ready' && ed.state != 'no_error_found'; +    var active = this.editor.spellcheck_state();      if (this.buttons.spellcheck)        $('#'+this.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected'); @@ -3689,41 +3551,19 @@ function rcube_webmail()    // get selected language    this.spellcheck_lang = function()    { -    var ed; - -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) -      return ed.settings.spellchecker_language || this.env.spell_lang; -    else if (this.env.spellcheck) -      return GOOGIE_CUR_LANG; +    return this.editor.get_language();    };    this.spellcheck_lang_set = function(lang)    { -    var ed; - -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) -      ed.settings.spellchecker_language = lang; -    else if (this.env.spellcheck) -      this.env.spellcheck.setCurrentLanguage(lang); +    this.editor.set_language(lang);    };    // resume spellchecking, highlight provided mispellings without new ajax request -  this.spellcheck_resume = function(ishtml, data) +  this.spellcheck_resume = function(data)    { -    if (ishtml) { -      var ed = tinymce.get(this.env.composebody); -      ed.settings.spellchecker_callback = function(name, text, done, error) { done(data); }; -      ed.execCommand('mceSpellCheck'); -      ed.settings.spellchecker_callback = null; -    } -    else { -      var sp = this.env.spellcheck; -      sp.prepare(false, true); -      sp.processData(data); -    } - -    this.spellcheck_state(); -  } +    this.editor.spellcheck_resume(data); +  };    this.set_draft_id = function(id)    { @@ -3783,16 +3623,13 @@ function rcube_webmail()    this.compose_field_hash = function(save)    {      // check input fields -    var ed, i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject']; +    var i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject'];      for (i=0; i<hash_fields.length; i++)        if (val = $('[name="_' + hash_fields[i] + '"]').val())          str += val + ':'; -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) -      str += ed.getContent(); -    else -      str += $("[name='_message']").val(); +    str += this.editor.get_content();      if (this.env.attachments)        for (id in this.env.attachments) @@ -3811,9 +3648,7 @@ function rcube_webmail()        ed, empty = true;      // get fresh content from editor -    if (window.tinymce && (ed = tinymce.get(this.env.composebody))) { -      tinymce.triggerSave(); -    } +    this.editor.save();      if (this.env.draft_id) {        formdata.draft_id = this.env.draft_id; @@ -3876,15 +3711,8 @@ function rcube_webmail()        });        // initialize HTML editor -      if (formdata._is_html == '1') { -        if (!html_mode) { -          tinymce.execCommand('mceAddEditor', false, this.env.composebody); -          this.triggerEvent('aftertoggle-editor', { mode:'html' }); -        } -      } -      else if (html_mode) { -        tinymce.execCommand('mceRemoveEditor', false, this.env.composebody); -        this.triggerEvent('aftertoggle-editor', { mode:'plain' }); +      if ((formdata._is_html == '1' && !html_mode) || (formdata._is_html != '1' && html_mode)) { +        this.command('toggle-editor', {id: this.env.composebody, html: !html_mode});        }      }    }; @@ -3933,11 +3761,8 @@ function rcube_webmail()          return;      } -    var i, rx, cursor_pos, p = -1, +    var i, rx,        id = obj.options[obj.selectedIndex].value, -      input_message = $("[name='_message']"), -      message = input_message.val(), -      is_html = ($("input[name='_is_html']").val() == '1'),        sig = this.env.identity,        delim = this.env.recipients_separator,        rx_delim = RegExp.escape(delim), @@ -3984,89 +3809,7 @@ function rcube_webmail()      else        this.enable_command('insert-sig', false); -    if (!is_html) { -      // remove the 'old' signature -      if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) { -        sig = this.env.signatures[sig].text; -        sig = sig.replace(/\r\n/g, '\n'); - -        p = this.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig); -        if (p >= 0) -          message = message.substring(0, p) + message.substring(p+sig.length, message.length); -      } -      // add the new signature string -      if (show_sig && this.env.signatures && this.env.signatures[id]) { -        sig = this.env.signatures[id].text; -        sig = sig.replace(/\r\n/g, '\n'); - -        if (this.env.top_posting) { -          if (p >= 0) { // in place of removed signature -            message = message.substring(0, p) + sig + message.substring(p, message.length); -            cursor_pos = p - 1; -          } -          else if (!message) { // empty message -            cursor_pos = 0; -            message = '\n\n' + sig; -          } -          else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position -            message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length); -            cursor_pos = pos; -          } -          else { // on top -            cursor_pos = 0; -            message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, ''); -          } -        } -        else { -          message = message.replace(/[\r\n]+$/, ''); -          cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0; -          message += '\n\n' + sig; -        } -      } -      else -        cursor_pos = this.env.top_posting ? 0 : message.length; - -      input_message.val(message); - -      // move cursor before the signature -      this.set_caret_pos(input_message.get(0), cursor_pos); -    } -    else if (show_sig && this.env.signatures) {  // html -      var editor = tinymce.get(this.env.composebody), -        sigElem = editor.dom.get('_rc_sig'); - -      // Append the signature as a div within the body -      if (!sigElem) { -        var body = editor.getBody(), -          doc = editor.getDoc(); - -        sigElem = doc.createElement('div'); -        sigElem.setAttribute('id', '_rc_sig'); - -        if (this.env.top_posting) { -          // if no existing sig and top posting then insert at caret pos -          editor.getWin().focus(); // correct focus in IE & Chrome - -          var node = editor.selection.getNode(); -          if (node.nodeName == 'BODY') { -            // no real focus, insert at start -            body.insertBefore(sigElem, body.firstChild); -            body.insertBefore(doc.createElement('br'), body.firstChild); -          } -          else { -            body.insertBefore(sigElem, node.nextSibling); -            body.insertBefore(doc.createElement('br'), node.nextSibling); -          } -        } -        else { -          body.appendChild(sigElem); -        } -      } - -      if (this.env.signatures[id]) -        sigElem.innerHTML = this.env.signatures[id].html; -    } - +    this.editor.change_signature(id, show_sig);      this.env.identity = id;      this.triggerEvent('change_identity');      return true; @@ -6864,6 +6607,12 @@ function rcube_webmail()      element.css({left: left + 'px', top: top + 'px'});    }; +  // initialize HTML editor +  this.editor_init = function(config, id) +  { +    this.editor = new rcube_text_editor(config, id); +  }; +    /********************************************************/    /*********  html to text conversion functions   *********/ @@ -7890,7 +7639,6 @@ function rcube_webmail()    {      return localStorage.removeItem(this.get_local_storage_prefix() + key);    }; -  }  // end object rcube_webmail diff --git a/program/js/editor.js b/program/js/editor.js index 280eb2ae1..6540bd56a 100644 --- a/program/js/editor.js +++ b/program/js/editor.js @@ -26,13 +26,18 @@   * for the JavaScript code in this file.   *   * @author Eric Stadtherr <estadtherr@gmail.com> + * @author Aleksander Machniak <alec@alec.pl>   */ -// Initialize HTML editor -function rcmail_editor_init(config) +/** + * Roundcube Text Editor Widget class + * @constructor + */ +function rcube_text_editor(config, id)  { -  var ret, conf = { -      selector: '.mce_editor', +  var ref = this, +    conf = { +      selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),        theme: 'modern',        language: config.lang,        content_css: config.skin_path + '/editor_content.css?v2', @@ -48,14 +53,26 @@ function rcmail_editor_init(config)        paste_data_images: true      }; -  if (config.mode == 'identity') +  // register spellchecker for plain text editor +  this.spellcheck_observer = function() {}; +  if (config.spellchecker) { +    this.spellchecker = config.spellchecker; +    if (config.spellcheck_observer) { +      this.spellchecker.spelling_state_observer = this.spellcheck_observer = config.spellcheck_observer; +    } +  } + +  // minimal editor +  if (config.mode == 'identity') {      $.extend(conf, {        plugins: ['autolink charmap code hr link paste tabfocus textcolor'],        toolbar: 'bold italic underline alignleft aligncenter alignright alignjustify'          + ' | outdent indent charmap hr link unlink code forecolor'          + ' | fontselect fontsizeselect'      }); -  else { // mail compose +  } +  // full-featured editor +  else {      $.extend(conf, {        plugins: ['autolink charmap code directionality emoticons link image media nonbreaking'          + ' paste table tabfocus textcolor searchreplace' + (config.spellcheck ? ' spellchecker' : '')], @@ -65,251 +82,572 @@ function rcmail_editor_init(config)        spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1',        spellchecker_language: rcmail.env.spell_lang,        accessibility_focus: false, -      file_browser_callback: rcmail_file_browser_callback, +      file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); },        // @todo: support more than image (types: file, image, media)        file_browser_callback_types: 'image'      }); - -    conf.setup = function(ed) { -      ed.on('init', rcmail_editor_callback); -      // add handler for spellcheck button state update -      ed.on('SpellcheckStart SpellcheckEnd', function(args) { -        rcmail.env.spellcheck_active = args.type == 'spellcheckstart'; -        rcmail.spellcheck_state(); -      }); -      ed.on('keypress', function() { -        rcmail.compose_type_activity++; -      }); -    }    }    // support external configuration settings e.g. from skin    if (window.rcmail_editor_settings)      $.extend(conf, window.rcmail_editor_settings); +  conf.setup = function(ed) { +    ed.on('init', function(ed) { ref.init_callback(ed); }); +    // add handler for spellcheck button state update +    ed.on('SpellcheckStart SpellcheckEnd', function(args) { +      ref.spellcheck_active = args.type == 'spellcheckstart'; +      ref.spellcheck_observer(); +    }); +    ed.on('keypress', function() { +      rcmail.compose_type_activity++; +    }); +  }; + +  // textarea identifier +  this.id = id; +  // reference to active editor (if in HTML mode) +  this.editor = null; +    tinymce.init(conf); -} -// react to real individual tinyMCE editor init -function rcmail_editor_callback() -{ -  var css = {}, -    elem = rcube_find_object('_from'), -    fe = rcmail.env.compose_focus_elem; +  // react to real individual tinyMCE editor init +  this.init_callback = function(event) +  { +    this.editor = event.target; + +    if (rcmail.env.action != 'compose') { +      return; +    } -  if (rcmail.env.default_font) -    css['font-family'] = rcmail.env.default_font; +    var css = {}, +      elem = rcube_find_object('_from'), +      fe = rcmail.env.compose_focus_elem; -  if (rcmail.env.default_font_size) -    css['font-size'] = rcmail.env.default_font_size; +    if (rcmail.env.default_font) +      css['font-family'] = rcmail.env.default_font; -  if (css['font-family'] || css['font-size']) -    $(tinymce.get(rcmail.env.composebody).getBody()).css(css); +    if (rcmail.env.default_font_size) +      css['font-size'] = rcmail.env.default_font_size; -  if (elem && elem.type == 'select-one') { -    // insert signature (only for the first time) -    if (!rcmail.env.identities_initialized) -      rcmail.change_identity(elem); +    if (css['font-family'] || css['font-size']) +      $(this.editor.getBody()).css(css); -    // Focus previously focused element -    if (fe && fe.id != rcmail.env.composebody) { -      // use setTimeout() for IE9 (#1488541) -      window.setTimeout(function() { -        window.focus(); // for WebKit (#1486674) -        fe.focus(); -      }, 10); +    if (elem && elem.type == 'select-one') { +      // insert signature (only for the first time) +      if (!rcmail.env.identities_initialized) +        rcmail.change_identity(elem); + +      // Focus previously focused element +      if (fe && fe.id != this.id) { +        // use setTimeout() for IE9 (#1488541) +        window.setTimeout(function() { +          window.focus(); // for WebKit (#1486674) +          fe.focus(); +        }, 10); +      }      } -  } -  // set tabIndex and set focus to element that was focused before -  rcmail_editor_tabindex(fe && fe.id == rcmail.env.composebody); -  // Trigger resize (needed for proper editor resizing in some browsers) -  window.setTimeout(function() { $(window).resize(); }, 100); -} +    // set tabIndex and set focus to element that was focused before +    this.tabindex(fe && fe.id == this.id); +    // Trigger resize (needed for proper editor resizing in some browsers) +    window.setTimeout(function() { $(window).resize(); }, 100); +  }; -// set tabIndex on tinymce editor -function rcmail_editor_tabindex(focus) -{ -  if (rcmail.env.task == 'mail') { -    var editor = tinymce.get(rcmail.env.composebody); -    if (editor) { -      var textarea = editor.getElement(), -        node = editor.getContentAreaContainer().childNodes[0]; +  // set tabIndex on tinymce editor +  this.tabindex = function(focus) +  { +    if (rcmail.env.task == 'mail' && this.editor) { +      var textarea = this.editor.getElement(), +        node = this.editor.getContentAreaContainer().childNodes[0];        if (textarea && node)          node.tabIndex = textarea.tabIndex;        if (focus) -        editor.getBody().focus(); +        this.editor.getBody().focus();      } -  } -} +  }; -// switch html/plain mode -function rcmail_toggle_editor(select, textAreaId) -{ -  var ishtml = select.tagName != 'SELECT' ? select.checked : select.value == 'html', -    res = rcmail.command('toggle-editor', {id: textAreaId, mode: ishtml ? 'html' : 'plain'}); +  // switch html/plain mode +  this.toggle = function(ishtml) +  { +    var curr, content, result, +      // these non-printable chars are not removed on text2html and html2text +      // we can use them as temp signature replacement +      sig_mark = "\u0002\u0003", +      input = $('#' + this.id), +      signature = rcmail.env.identity ? rcmail.env.signatures[rcmail.env.identity] : null, +      is_sig = signature && signature.text && signature.text.length > 1; -  if (!res) { -    if (select.tagName == 'SELECT') -      select.value = 'html'; -    else if (select.tagName == 'INPUT') -      select.checked = true; -  } -  else if (ishtml) { -    // #1486593 -    setTimeout("rcmail_editor_tabindex(true);", 500); -  } -  else if (rcmail.env.composebody) { -    rcube_find_object(rcmail.env.composebody).focus(); -  } -} +    // apply spellcheck changes if spell checker is active +    this.spellcheck_stop(); -// image selector -function rcmail_file_browser_callback(field_name, url, type, win) -{ -  var i, elem, dialog, list = [], editor = tinyMCE.activeEditor; +    if (ishtml) { +      content = input.val(); -  // open image selector dialog -  dialog = editor.windowManager.open({ -    title: rcmail.gettext('select' + type), -    width: 500, -    height: 300, -    html: '<div id="image-selector-list"><ul></ul></div>' -      + '<div id="image-selector-form"><div id="image-upload-button" class="mce-widget mce-btn" role="button"></div></div>', -    buttons: [{text: 'Cancel', onclick: function() { rcmail_file_browser_close(); }}] -  }); +      // replace current text signature with temp mark +      if (is_sig) +        content = content.replace(signature.text, sig_mark); -  rcmail.env.file_browser_field = field_name; -  rcmail.env.file_browser_type = type; +      // convert to html +      result = rcmail.plain2html(content, function(data) { +        // replace signature mark with html version of the signature +        if (is_sig) +          data = data.replace(sig_mark, '<div id="_rc_sig">' + signature.html + '</div>'); -  // fill images list with available images -  for (i in rcmail.env.attachments) { -    if (elem = rcmail_file_browser_entry(i, rcmail.env.attachments[i])) { -      list.push(elem); +        input.val(data); +        tinymce.execCommand('mceAddEditor', false, ref.id); + +        setTimeout(function() { +          if (ref.editor) { +            if (rcmail.env.default_font) +              $(ref.editor.getBody()).css('font-family', rcmail.env.default_font); +            // #1486593 +            ref.tabindex(true); +          } +        }, 500); +      });      } -  } +    else if (this.editor) { +      if (is_sig) { +        // get current version of signature, we'll need it in +        // case of html2text conversion abort +        if (curr = this.editor.dom.get('_rc_sig')) +          curr = curr.innerHTML; -  if (list.length) { -    $('#image-selector-list > ul').append(list); -  } +        // replace current signature with some non-printable characters +        // we use non-printable characters, because this replacement +        // is visible to the user +        // doing this after getContent() would be hard +        this.editor.dom.setHTML('_rc_sig', sig_mark); +      } -  // add hint about max file size (in dialog footer) -  $('div.mce-abs-end', dialog.getEl()).append($('<div class="hint">').text($('div.hint', rcmail.gui_objects.uploadform).text())); +      // get html content +      content = this.editor.getContent(); -  // enable (smart) upload button -  elem = $('#image-upload-button').append($('<span>').text(rcmail.gettext('add' + type))); -  hack_file_input(elem, rcmail.gui_objects.uploadform); +      // convert html to text +      result = rcmail.html2plain(content, function(data) { +        tinymce.execCommand('mceRemoveEditor', false, ref.id); +        ref.editor = null; -  // enable drag-n-drop area -  if (rcmail.gui_objects.filedrop && rcmail.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { -    rcmail.env.old_file_drop = rcmail.gui_objects.filedrop; -    rcmail.gui_objects.filedrop = $('#image-selector-form'); -    rcmail.gui_objects.filedrop.addClass('droptarget') -      .bind('dragover dragleave', function(e) { -        e.preventDefault(); -        e.stopPropagation(); -        $(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover'); -      }) -      .get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false); -  } +        // replace signture mark with text version of the signature +        if (is_sig) +          data = data.replace(sig_mark, "\n" + signature.text); -  // register handler for successful file upload -  if (!rcmail.env.file_dialog_event) { -    rcmail.env.file_dialog_event = true; -    rcmail.addEventListener('fileuploaded', function(attr) { -      var elem; -      if (elem = rcmail_file_browser_entry(attr.name, attr.attachment)) { -        $('#image-selector-list > ul').prepend(elem); -      } -    }); -  } -} +        input.val(data).focus(); +      }); -// close file browser window -function rcmail_file_browser_close(url) -{ -  if (url) -    $('#' + rcmail.env.file_browser_field).val(url); +      // bring back current signature +      if (!result && curr) +        this.editor.dom.setHTML('_rc_sig', curr); +    } -  tinyMCE.activeEditor.windowManager.close(); +    return result; +  }; -  if (rcmail.env.old_file_drop) -    rcmail.gui_objects.filedrop = rcmail.env.old_file_drop; -} +  // start spellchecker +  this.spellcheck_start = function() +  { +    if (this.editor) { +      tinymce.execCommand('mceSpellCheck', true); +      this.spellcheck_observer(); +    } +    else if (this.spellchecker && this.spellchecker.spellCheck) { +      this.spellchecker.spellCheck(); +    } +  }; -// creates file browser entry -function rcmail_file_browser_entry(file_id, file) -{ -  if (!file.complete || !file.mimetype) { -    return; -  } +  // stop spellchecker +  this.spellcheck_stop = function() +  { +    var ed = this.editor; + +    if (ed) { +      if (ed.plugins && ed.plugins.spellchecker && this.spellcheck_active) +        ed.execCommand('mceSpellCheck'); +        this.spellcheck_observer(); +    } +    else if (ed = this.spellchecker) { +      if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found') +        $(ed.spell_span).trigger('click'); +    } +  }; -  if (file.mimetype.startsWith('image/')) { -    var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, -      img = $('<img>').attr({title: file.name, src: href + '&_thumbnail=1'}); +  // spellchecker state +  this.spellcheck_state = function() +  { +    var ed; -    return $('<li>').data('url', href) -      .append($('<span class="img">').append(img)) -      .append($('<span class="name">').text(file.name)) -      .click(function() { rcmail_file_browser_close($(this).data('url')); }); -  } -} +    if (this.editor) +      return this.spellcheck_active; +    else if ((ed = this.spellchecker) && ed.state) +      return ed.state != 'ready' && ed.state != 'no_error_found'; +  }; -// create smart files upload button -function hack_file_input(elem, clone_form) -{ -  var link = $(elem), -    file = $('<input>'), -    form = $('<form>').attr({method: 'post', enctype: 'multipart/form-data'}), -    offset = link.offset(); +  // resume spellchecking, highlight provided mispellings without new ajax request +  this.spellcheck_resume = function(data) +  { +    var ed = this.editor; -  // clone existing upload form -  if (clone_form) { -    file.attr('name', $('input[type="file"]', clone_form).attr('name')); -    form.attr('action', $(clone_form).attr('action')) -      .append($('<input>').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token})); -  } +    if (ed) { +      ed.settings.spellchecker_callback = function(name, text, done, error) { done(data); }; +      ed.execCommand('mceSpellCheck'); +      ed.settings.spellchecker_callback = null; -  function move_file_input(e) { -    file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); -  } +      this.spellcheck_observer(); +    } +    else if (ed = this.spellchecker) { +      ed.prepare(false, true); +      ed.processData(data); +    } +  }; + +  // get selected (spellcheker) language +  this.get_language = function() +  { +    if (this.editor) { +      return this.editor.settings.spellchecker_language || rcmail.env.spell_lang; +    } +    else if (this.spellchecker) { +      return GOOGIE_CUR_LANG; +    } +  }; + +  // set language for spellchecking +  this.set_language = function(lang) +  { +    var ed = this.editor; + +    if (ed) { +      ed.settings.spellchecker_language = lang; +    } +    if (ed = this.spellchecker) { +      ed.setCurrentLanguage(lang); +    } +  }; + +  // replace selection with text snippet +  this.replace = function(text) +  { +    var ed = this.editor; + +    // insert into tinymce editor +    if (ed) { +      ed.getWin().focus(); // correct focus in IE & Chrome +      ed.selection.setContent(rcmail.quote_html(text).replace(/\r?\n/g, '<br/>'), { format:'text' }); +    } +    // replace selection in compose textarea +    else if (ed = rcube_find_object(this.id)) { +      var selection = $(ed).is(':focus') ? rcmail.get_input_selection(ed) : { start:0, end:0 }, +        inp_value = ed.value; +        pre = inp_value.substring(0, selection.start), +        end = inp_value.substring(selection.end, inp_value.length); + +      // insert response text +      ed.value = pre + text + end; + +      // set caret after inserted text +      rcmail.set_caret_pos(ed, selection.start + text.length); +      ed.focus(); +    } +  }; + +  // get selected text (if no selection returns all text) from the editor +  this.get_content = function(selected, plain) +  { +    // apply spellcheck changes if spell checker is active +    this.spellcheck_stop(); + +    var sigstart, ed = this.editor, +      format = plain ? 'text' : 'html', +      text = '', strip = false; + +    // get selected text from tinymce editor +    if (ed) { +      ed.getWin().focus(); // correct focus in IE & Chrome +      if (selected) +        text = ed.selection.getContent({format: format}); + +      if (!text) { +        text = ed.getContent({format: format}); +        strip = true; +      } +    } +    // get selected text from compose textarea +    else if (ed = rcube_find_object(this.id)) { +      if (selected && $(ed).is(':focus')) { +        text = rcmail.get_input_selection(ed).text; +      } + +      if (!text) { +        text = ed.value; +        strip = true; +      } +    } + +    // strip off signature +    if (strip) { +      sigstart = text.indexOf('-- \n'); +      if (sigstart > 0) { +        text = text.substring(0, sigstart); +      } +    } + +    return text; +  }; -  file.attr({type: 'file', multiple: 'multiple', size: 5, title: ''}) -    .change(function() { rcmail.upload_file(form, 'upload'); }) -    .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) -    // opacity:0 does the trick, display/visibility doesn't work -    .css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none'}) -    .appendTo(form); +  // change user signature text +  this.change_signature = function(id, show_sig) +  { +    var cursor_pos, p = -1, +      input_message = $('#' + this.id), +      message = input_message.val(), +      sig = rcmail.env.identity; -  // In FF and IE we need to move the browser file-input's button under the cursor -  // Thanks to the size attribute above we know the length of the input field -  if (navigator.userAgent.match(/Firefox|MSIE/)) -    file.css({marginLeft: '-80px'}); +    if (!this.editor) { // plain text mode +      // remove the 'old' signature +      if (show_sig && sig && rcmail.env.signatures && rcmail.env.signatures[sig]) { +        sig = rcmail.env.signatures[sig].text; +        sig = sig.replace(/\r\n/g, '\n'); -  // Note: now, I observe problem with cursor style on FF < 4 only -  link.css({overflow: 'hidden', cursor: 'pointer'}) -    .mouseenter(function() { this.__active = true; }) -    // place button under the cursor -    .mousemove(function(e) { -      if (this.__active) -        move_file_input(e); -      // move the input away if button is disabled +        p = rcmail.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig); +        if (p >= 0) +          message = message.substring(0, p) + message.substring(p+sig.length, message.length); +      } +      // add the new signature string +      if (show_sig && rcmail.env.signatures && rcmail.env.signatures[id]) { +        sig = rcmail.env.signatures[id].text; +        sig = sig.replace(/\r\n/g, '\n'); + +        if (rcmail.env.top_posting) { +          if (p >= 0) { // in place of removed signature +            message = message.substring(0, p) + sig + message.substring(p, message.length); +            cursor_pos = p - 1; +          } +          else if (!message) { // empty message +            cursor_pos = 0; +            message = '\n\n' + sig; +          } +          else if (pos = rcmail.get_caret_pos(input_message.get(0))) { // at cursor position +            message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length); +            cursor_pos = pos; +          } +          else { // on top +            cursor_pos = 0; +            message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, ''); +          } +        } +        else { +          message = message.replace(/[\r\n]+$/, ''); +          cursor_pos = !rcmail.env.top_posting && message.length ? message.length+1 : 0; +          message += '\n\n' + sig; +        } +      }        else -        $(this).mouseleave(); -    }) -    .mouseleave(function() { -      file.css({top: '-10000px', left: '-10000px'}); -      this.__active = false; -    }) -    .click(function(e) { -      // forward click if mouse-enter event was missed -      if (!this.__active) { -        this.__active = true; -        move_file_input(e); -        file.trigger(e); +        cursor_pos = rcmail.env.top_posting ? 0 : message.length; + +      input_message.val(message); + +      // move cursor before the signature +      rcmail.set_caret_pos(input_message.get(0), cursor_pos); +    } +    else if (show_sig && rcmail.env.signatures) {  // html +      var sigElem = this.editor.dom.get('_rc_sig'); + +      // Append the signature as a div within the body +      if (!sigElem) { +        var body = this.editor.getBody(), +          doc = this.editor.getDoc(); + +        sigElem = doc.createElement('div'); +        sigElem.setAttribute('id', '_rc_sig'); + +        if (rcmail.env.top_posting) { +          // if no existing sig and top posting then insert at caret pos +          this.editor.getWin().focus(); // correct focus in IE & Chrome + +          var node = this.editor.selection.getNode(); +          if (node.nodeName == 'BODY') { +            // no real focus, insert at start +            body.insertBefore(sigElem, body.firstChild); +            body.insertBefore(doc.createElement('br'), body.firstChild); +          } +          else { +            body.insertBefore(sigElem, node.nextSibling); +            body.insertBefore(doc.createElement('br'), node.nextSibling); +          } +        } +        else { +          body.appendChild(sigElem); +        } +      } + +      if (rcmail.env.signatures[id]) { +        sigElem.innerHTML = rcmail.env.signatures[id].html;        } -    }) -    .mouseleave() -    .append(form); +    } +  }; + +  // trigger content save +  this.save = function() +  { +    if (this.editor) { +      this.editor.save(); +    } +  }; + +  // focus the editing area +  this.focus = function() +  { +    (this.editor || rcube_find_object(this.id)).focus(); +  }; + +  // image selector +  this.file_browser_callback = function(field_name, url, type) +  { +    var i, elem, dialog, list = []; + +    // open image selector dialog +    dialog = this.editor.windowManager.open({ +      title: rcmail.gettext('select' + type), +      width: 500, +      height: 300, +      html: '<div id="image-selector-list"><ul></ul></div>' +        + '<div id="image-selector-form"><div id="image-upload-button" class="mce-widget mce-btn" role="button"></div></div>', +      buttons: [{text: 'Cancel', onclick: function() { ref.file_browser_close(); }}] +    }); + +    rcmail.env.file_browser_field = field_name; +    rcmail.env.file_browser_type = type; + +    // fill images list with available images +    for (i in rcmail.env.attachments) { +      if (elem = ref.file_browser_entry(i, rcmail.env.attachments[i])) { +        list.push(elem); +      } +    } + +    if (list.length) { +      $('#image-selector-list > ul').append(list); +    } + +    // add hint about max file size (in dialog footer) +    $('div.mce-abs-end', dialog.getEl()).append($('<div class="hint">').text($('div.hint', rcmail.gui_objects.uploadform).text())); + +    // enable (smart) upload button +    elem = $('#image-upload-button').append($('<span>').text(rcmail.gettext('add' + type))); +    this.hack_file_input(elem, rcmail.gui_objects.uploadform); + +    // enable drag-n-drop area +    if (rcmail.gui_objects.filedrop && rcmail.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { +      rcmail.env.old_file_drop = rcmail.gui_objects.filedrop; +      rcmail.gui_objects.filedrop = $('#image-selector-form'); +      rcmail.gui_objects.filedrop.addClass('droptarget') +        .bind('dragover dragleave', function(e) { +          e.preventDefault(); +          e.stopPropagation(); +          $(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover'); +        }) +        .get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false); +    } + +    // register handler for successful file upload +    if (!rcmail.env.file_dialog_event) { +      rcmail.env.file_dialog_event = true; +      rcmail.addEventListener('fileuploaded', function(attr) { +        var elem; +        if (elem = ref.file_browser_entry(attr.name, attr.attachment)) { +          $('#image-selector-list > ul').prepend(elem); +        } +      }); +    } +  }; + +  // close file browser window +  this.file_browser_close = function(url) +  { +    if (url) +      $('#' + rcmail.env.file_browser_field).val(url); + +    this.editor.windowManager.close(); + +    if (rcmail.env.old_file_drop) +      rcmail.gui_objects.filedrop = rcmail.env.old_file_drop; +  }; + +  // creates file browser entry +  this.file_browser_entry = function(file_id, file) +  { +    if (!file.complete || !file.mimetype) { +      return; +    } + +    if (file.mimetype.startsWith('image/')) { +      var href = rcmail.env.comm_path+'&_id='+rcmail.env.compose_id+'&_action=display-attachment&_file='+file_id, +        img = $('<img>').attr({title: file.name, src: href + '&_thumbnail=1'}); + +      return $('<li>').data('url', href) +        .append($('<span class="img">').append(img)) +        .append($('<span class="name">').text(file.name)) +        .click(function() { ref.file_browser_close($(this).data('url')); }); +    } +  }; + +  // create smart files upload button +  this.hack_file_input = function(elem, clone_form) +  { +    var link = $(elem), +      file = $('<input>'), +      form = $('<form>').attr({method: 'post', enctype: 'multipart/form-data'}), +      offset = link.offset(); + +    // clone existing upload form +    if (clone_form) { +      file.attr('name', $('input[type="file"]', clone_form).attr('name')); +      form.attr('action', $(clone_form).attr('action')) +        .append($('<input>').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token})); +    } + +    function move_file_input(e) { +      file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); +    } + +    file.attr({type: 'file', multiple: 'multiple', size: 5, title: ''}) +      .change(function() { rcmail.upload_file(form, 'upload'); }) +      .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) +      // opacity:0 does the trick, display/visibility doesn't work +      .css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none'}) +      .appendTo(form); + +    // In FF and IE we need to move the browser file-input's button under the cursor +    // Thanks to the size attribute above we know the length of the input field +    if (navigator.userAgent.match(/Firefox|MSIE/)) +      file.css({marginLeft: '-80px'}); + +    // Note: now, I observe problem with cursor style on FF < 4 only +    link.css({overflow: 'hidden', cursor: 'pointer'}) +      .mouseenter(function() { this.__active = true; }) +      // place button under the cursor +      .mousemove(function(e) { +        if (this.__active) +          move_file_input(e); +        // move the input away if button is disabled +        else +          $(this).mouseleave(); +      }) +      .mouseleave(function() { +        file.css({top: '-10000px', left: '-10000px'}); +        this.__active = false; +      }) +      .click(function(e) { +        // forward click if mouse-enter event was missed +        if (!this.__active) { +          this.__active = true; +          move_file_input(e); +          file.trigger(e); +        } +      }) +      .mouseleave() +      .append(form); +  };  } | 
