+ }
+ };
+
+ // set class to read/unread
+ this.toggle_read_status = function(flag, a_uids)
+ {
+ var i, len = a_uids.length,
+ url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
+ lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+ // mark all message rows as read/unread
+ for (i=0; i<len; i++)
+ this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
+
+ // also send search request to get the right messages
+ if (this.env.search_request)
+ url += '&_search='+this.env.search_request;
+
+ this.http_post('mark', url, lock);
+
+ for (i=0; i<len; i++)
+ this.update_thread_root(a_uids[i], flag);
+ };
+
+ // set image to flagged or unflagged
+ this.toggle_flagged_status = function(flag, a_uids)
+ {
+ var i, len = a_uids.length,
+ url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
+ lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+ // mark all message rows as flagged/unflagged
+ for (i=0; i<len; i++)
+ this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
+
+ // also send search request to get the right messages
+ if (this.env.search_request)
+ url += '&_search='+this.env.search_request;
+
+ this.http_post('mark', url, lock);
+ };
+
+ // mark all message rows as deleted/undeleted
+ this.toggle_delete_status = function(a_uids)
+ {
+ var len = a_uids.length,
+ i, uid, all_deleted = true,
+ rows = this.message_list ? this.message_list.rows : [];
+
+ if (len == 1) {
+ if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
+ this.flag_as_deleted(a_uids);
+ else
+ this.flag_as_undeleted(a_uids);
+
+ return true;
+ }
+
+ for (i=0; i<len; i++) {
+ uid = a_uids[i];
+ if (rows[uid] && !rows[uid].deleted) {
+ all_deleted = false;
+ break;
+ }
+ }
+
+ if (all_deleted)
+ this.flag_as_undeleted(a_uids);
+ else
+ this.flag_as_deleted(a_uids);
+
+ return true;
+ };
+
+ this.flag_as_undeleted = function(a_uids)
+ {
+ var i, len=a_uids.length,
+ url = '_uid='+this.uids_to_list(a_uids)+'&_flag=undelete',
+ lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+ for (i=0; i<len; i++)
+ this.set_message(a_uids[i], 'deleted', false);
+
+ // also send search request to get the right messages
+ if (this.env.search_request)
+ url += '&_search='+this.env.search_request;
+
+ this.http_post('mark', url, lock);
+ return true;
+ };
+
+ this.flag_as_deleted = function(a_uids)
+ {
+ var add_url = '',
+ r_uids = [],
+ rows = this.message_list ? this.message_list.rows : [],
+ count = 0;
+
+ for (var i=0, len=a_uids.length; i<len; i++) {
+ uid = a_uids[i];
+ if (rows[uid]) {
+ if (rows[uid].unread)
+ r_uids[r_uids.length] = uid;
+
+ if (this.env.skip_deleted) {
+ count += this.update_thread(uid);
+ this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
+ }
+ else
+ this.set_message(uid, 'deleted', true);
+ }
+ }
+
+ // make sure there are no selected rows
+ if (this.env.skip_deleted && this.message_list) {
+ if(!this.env.display_next)
+ this.message_list.clear_selection();
+ if (count < 0)
+ add_url += '&_count='+(count*-1);
+ else if (count > 0)
+ // remove threads from the end of the list
+ this.delete_excessive_thread_rows();
+ }
+
+ add_url = '&_from='+(this.env.action ? this.env.action : ''),
+ lock = this.display_message(this.get_label('markingmessage'), 'loading');
+
+ // ??
+ if (r_uids.length)
+ add_url += '&_ruid='+this.uids_to_list(r_uids);
+
+ if (this.env.skip_deleted) {
+ if (this.env.display_next && this.env.next_uid)
+ add_url += '&_next_uid='+this.env.next_uid;
+ }
+
+ // also send search request to get the right messages
+ if (this.env.search_request)
+ add_url += '&_search='+this.env.search_request;
+
+ this.http_post('mark', '_uid='+this.uids_to_list(a_uids)+'&_flag=delete'+add_url, lock);
+ return true;
+ };
+
+ // flag as read without mark request (called from backend)
+ // argument should be a coma-separated list of uids
+ this.flag_deleted_as_read = function(uids)
+ {
+ var icn_src, uid, i, len,
+ rows = this.message_list ? this.message_list.rows : [];
+
+ uids = String(uids).split(',');
+
+ for (i=0, len=uids.length; i<len; i++) {
+ uid = uids[i];
+ if (rows[uid])
+ this.set_message(uid, 'unread', false);
+ }
+ };
+
+ // Converts array of message UIDs to comma-separated list for use in URL
+ // with select_all mode checking
+ this.uids_to_list = function(uids)
+ {
+ return this.select_all_mode ? '*' : uids.join(',');
+ };
+
+
+ /*********************************************************/
+ /********* mailbox folders methods *********/
+ /*********************************************************/
+
+ this.expunge_mailbox = function(mbox)
+ {
+ var lock, url = '_mbox='+urlencode(mbox);
+
+ // lock interface if it's the active mailbox
+ if (mbox == this.env.mailbox) {
+ lock = this.set_busy(true, 'loading');
+ url += '&_reload=1';
+ if (this.env.search_request)
+ url += '&_search='+this.env.search_request;
+ }
+
+ // send request to server
+ this.http_post('expunge', url, lock);
+ };
+
+ this.purge_mailbox = function(mbox)
+ {
+ var lock = false,
+ url = '_mbox='+urlencode(mbox);
+
+ if (!confirm(this.get_label('purgefolderconfirm')))
+ return false;
+
+ // lock interface if it's the active mailbox
+ if (mbox == this.env.mailbox) {
+ lock = this.set_busy(true, 'loading');
+ url += '&_reload=1';
+ }
+
+ // send request to server
+ this.http_post('purge', url, lock);
+ };
+
+ // test if purge command is allowed
+ this.purge_mailbox_test = function()
+ {
+ return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
+ || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
+ || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
+ };
+
+
+ /*********************************************************/
+ /********* login form methods *********/
+ /*********************************************************/
+
+ // handler for keyboard events on the _user field
+ this.login_user_keyup = function(e)
+ {
+ var key = rcube_event.get_keycode(e);
+ var passwd = $('#rcmloginpwd');
+
+ // enter
+ if (key == 13 && passwd.length && !passwd.val()) {
+ passwd.focus();
+ return rcube_event.cancel(e);
+ }
+
+ return true;
+ };
+
+
+ /*********************************************************/
+ /********* message compose methods *********/
+ /*********************************************************/
+
+ // init message compose form: set focus and eventhandlers
+ this.init_messageform = function()
+ {
+ if (!this.gui_objects.messageform)
+ return false;
+
+ var input_from = $("[name='_from']"),
+ input_to = $("[name='_to']"),
+ input_subject = $("input[name='_subject']"),
+ input_message = $("[name='_message']").get(0),
+ html_mode = $("input[name='_is_html']").val() == '1',
+ ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
+ ac_props;
+
+ // configure parallel autocompletion
+ if (this.env.autocomplete_threads > 0) {
+ ac_props = {
+ threads: this.env.autocomplete_threads,
+ sources: this.env.autocomplete_sources
+ };
+ }
+
+ // init live search events
+ this.init_address_input_events(input_to, ac_props);
+ for (var i in ac_fields) {
+ this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
+ }
+
+ if (!html_mode) {
+ 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() == '') {
+ this.change_identity(input_from[0]);
+ }
+ }
+
+ if (input_to.val() == '')
+ input_to.focus();
+ else if (input_subject.val() == '')
+ input_subject.focus();
+ else if (input_message)
+ input_message.focus();
+
+ this.env.compose_focus_elem = document.activeElement;
+
+ // get summary of all field values
+ this.compose_field_hash(true);
+
+ // start the auto-save timer
+ this.auto_save_start();
+ };
+
+ this.init_address_input_events = function(obj, props)
+ {
+ this.env.recipients_delimiter = this.env.recipients_separator + ' ';
+
+ obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
+ .attr('autocomplete', 'off');
+ };
+
+ // checks the input fields before sending a message
+ this.check_compose_input = function(cmd)
+ {
+ // check input fields
+ var ed, input_to = $("[name='_to']"),
+ input_cc = $("[name='_cc']"),
+ input_bcc = $("[name='_bcc']"),
+ input_from = $("[name='_from']"),
+ input_subject = $("[name='_subject']"),
+ input_message = $("[name='_message']");
+
+ // check sender (if have no identities)
+ if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
+ alert(this.get_label('nosenderwarning'));
+ input_from.focus();
+ return false;
+ }
+
+ // check for empty recipient
+ var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
+ if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true)) {
+ alert(this.get_label('norecipientwarning'));
+ input_to.focus();
+ return false;
+ }
+
+ // check if all files has been uploaded
+ for (var key in this.env.attachments) {
+ if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
+ alert(this.get_label('notuploadedwarning'));
+ return false;
+ }
+ }
+
+ // 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 = {};
+ buttons[this.get_label('cancel')] = function(){
+ input_subject.focus();
+ $(this).dialog('close');
+ };
+ buttons[this.get_label('sendmessage')] = function(){
+ input_subject.val(prompt_value.val());
+ $(this).dialog('close');
+ ref.command(cmd, { nocheck:true }); // repeat command which triggered this
+ };
+
+ myprompt.dialog({
+ modal: true,
+ resizable: false,
+ 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();
+ 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();
+ }
+
+ return true;
+ };
+
+ this.toggle_editor = function(props)
+ {
+ if (props.mode == 'html') {
+ this.display_spellcheck_controls(false);
+ this.plain2html($('#'+props.id).val(), props.id);
+ tinyMCE.execCommand('mceAddControl', false, props.id);
+ }
+ else {
+ var thisMCE = tinyMCE.get(props.id), existingHtml;
+ if (thisMCE.plugins.spellchecker && thisMCE.plugins.spellchecker.active)
+ thisMCE.execCommand('mceSpellCheck', false);
+
+ if (existingHtml = thisMCE.getContent()) {
+ if (!confirm(this.get_label('editorwarning'))) {
+ return false;
+ }
+ this.html2plain(existingHtml, props.id);
+ }
+ tinyMCE.execCommand('mceRemoveControl', false, props.id);
+ this.display_spellcheck_controls(true);
+ }
+
+ return true;
+ };
+
+ this.stop_spellchecking = function()
+ {
+ var ed;
+ if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
+ if (ed.plugins.spellchecker && ed.plugins.spellchecker.active)
+ ed.execCommand('mceSpellCheck');
+ }
+ else if ((ed = this.env.spellcheck) && !this.spellcheck_ready) {
+ $(ed.spell_span).trigger('click');
+ this.set_spellcheck_state('ready');
+ }
+ };
+
+ this.display_spellcheck_controls = function(vis)
+ {
+ if (this.env.spellcheck) {
+ // stop spellchecking process
+ if (!vis)
+ this.stop_spellchecking();
+
+ $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden');
+ }
+ };
+
+ this.set_spellcheck_state = function(s)
+ {
+ this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
+ this.enable_command('spellcheck', this.spellcheck_ready);
+ };
+
+ // get selected language
+ this.spellcheck_lang = function()
+ {
+ var ed;
+ if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins.spellchecker) {
+ return ed.plugins.spellchecker.selectedLang;
+ }
+ else if (this.env.spellcheck) {
+ return GOOGIE_CUR_LANG;
+ }
+ };
+
+ // resume spellchecking, highlight provided mispellings without new ajax request
+ this.spellcheck_resume = function(ishtml, data)
+ {
+ if (ishtml) {
+ var ed = tinyMCE.get(this.env.composebody);
+ sp = ed.plugins.spellchecker;
+
+ sp.active = 1;
+ sp._markWords(data);
+ ed.nodeChanged();
+ }
+ else {
+ var sp = this.env.spellcheck;
+ sp.prepare(false, true);
+ sp.processData(data);
+ }
+ }
+
+ this.set_draft_id = function(id)
+ {
+ $("input[name='_draft_saveid']").val(id);
+ };
+
+ this.auto_save_start = function()
+ {
+ if (this.env.draft_autosave)
+ this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
+
+ // Unlock interface now that saving is complete
+ this.busy = false;
+ };
+
+ this.compose_field_hash = function(save)
+ {
+ // check input fields
+ var ed, str = '',
+ value_to = $("[name='_to']").val(),
+ value_cc = $("[name='_cc']").val(),
+ value_bcc = $("[name='_bcc']").val(),
+ value_subject = $("[name='_subject']").val();
+
+ if (value_to)
+ str += value_to+':';
+ if (value_cc)
+ str += value_cc+':';
+ if (value_bcc)
+ str += value_bcc+':';
+ if (value_subject)
+ str += value_subject+':';
+
+ if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
+ str += ed.getContent();
+ else
+ str += $("[name='_message']").val();
+
+ if (this.env.attachments)
+ for (var upload_id in this.env.attachments)
+ str += upload_id;
+
+ if (save)
+ this.cmp_hash = str;
+
+ return str;
+ };
+
+ this.change_identity = function(obj, show_sig)
+ {
+ if (!obj || !obj.options)
+ return false;
+
+ if (!show_sig)
+ show_sig = this.env.show_sig;
+
+ var cursor_pos, p = -1,
+ 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,
+ sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
+
+ // enable manual signature insert
+ if (this.env.signatures && this.env.signatures[id]) {
+ this.enable_command('insert-sig', true);
+ this.env.compose_commands.push('insert-sig');
+ }
+ 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].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
+ sig = sig.replace(/\r\n/g, '\n');
+
+ if (!sig.match(/^--[ -]\n/m))
+ sig = sig_separator + '\n' + sig;
+
+ p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
+ if (p >= 0)
+ 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]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
+ sig = sig.replace(/\r\n/g, '\n');
+
+ if (!sig.match(/^--[ -]\n/m))
+ sig = sig_separator + '\n' + sig;
+
+ if (this.env.sig_above) {
+ if (p >= 0) { // in place of removed signature
+ message = message.substring(0, p) + sig + message.substring(p, message.length);
+ cursor_pos = p - 1;
+ }
+ 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.sig_above) {
+ // 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 {
+ if (bw.ie) // add empty line before signature on IE
+ body.appendChild(doc.createElement('br'));
+
+ body.appendChild(sigElem);
+ }
+ }
+
+ if (this.env.signatures[id]) {
+ if (this.env.signatures[id].is_html) {
+ sig = this.env.signatures[id].text;
+ if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
+ sig = sig_separator + '<br />' + sig;
+ }
+ else {
+ sig = this.env.signatures[id].text;
+ if (!sig.match(/^--[ -]\r?\n/m))
+ sig = sig_separator + '\n' + sig;
+ sig = '<pre>' + sig + '</pre>';
+ }
+
+ sigElem.innerHTML = sig;
+ }
+ }
+
+ this.env.identity = id;
+ return true;
+ };
+
+ // upload attachment file
+ this.upload_file = function(form)
+ {
+ if (!form)
+ return false;
+
+ // get file input field, count files on capable browser
+ var i, size = 0, field = $('input[type=file]', form).get(0),
+ files = field.files ? field.files.length : field.value ? 1 : 0;
+
+ // create hidden iframe and post upload form
+ if (files) {
+ // check file size
+ if (field.files && this.env.max_filesize && this.env.filesizeerror) {
+ for (i=0; i<files; i++)
+ size += field.files[i].size;
+ if (size && size > this.env.max_filesize) {
+ this.display_message(this.env.filesizeerror, 'error');
+ return;
+ }
+ }
+
+ var frame_name = this.async_upload_form(form, 'upload', function(e) {
+ var d, content = '';
+ try {
+ if (this.contentDocument) {
+ d = this.contentDocument;
+ } else if (this.contentWindow) {
+ d = this.contentWindow.document;
+ }
+ content = d.childNodes[0].innerHTML;
+ } catch (err) {}
+
+ if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
+ if (!content.match(/display_message/))
+ rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
+ rcmail.remove_from_attachment_list(e.data.ts);
+ }
+ // Opera hack: handle double onload
+ if (bw.opera)
+ rcmail.env.uploadframe = e.data.ts;
+ });
+
+ // display upload indicator and cancel button
+ 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="" />'+content;
+ 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
+ this.gui_objects.attachmentform = form;
+ return true;
+ };
+
+ // add file name to attachment list
+ // called from upload page
+ this.add2attachment_list = function(name, att, upload_id)
+ {
+ if (!this.gui_objects.attachmentlist)
+ return false;
+
+ var indicator, li = $('<li>').attr('id', name).html(att.html);
+
+ // replace indicator's li
+ if (upload_id && (indicator = document.getElementById(upload_id))) {
+ li.replaceAll(indicator);
+ }
+ else { // add new li
+ li.appendTo(this.gui_objects.attachmentlist);
+ }
+
+ if (upload_id && this.env.attachments[upload_id])
+ delete this.env.attachments[upload_id];
+
+ this.env.attachments[name] = att;
+
+ return true;
+ };
+
+ this.remove_from_attachment_list = function(name)
+ {
+ delete this.env.attachments[name];
+ $('#'+name).remove();
+ };
+
+ this.remove_attachment = function(name)
+ {
+ if (name && this.env.attachments[name])
+ this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
+
+ return true;
+ };
+
+ this.cancel_attachment_upload = function(name, frame_name)
+ {
+ if (!name || !frame_name)
+ return false;
+
+ 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
+ this.add_contact = function(value)
+ {
+ if (value)
+ this.http_post('addcontact', '_address='+value);
+
+ return true;
+ };
+
+ // send remote request to search mail or contacts
+ this.qsearch = function(value)
+ {
+ if (value != '') {
+ var n, lock = this.set_busy(true, 'searching');
+
+ if (this.message_list)
+ this.clear_message_list();
+ else if (this.contact_list)
+ this.list_contacts_clear();
+
+ // reset vars
+ this.env.current_page = 1;
+ r = this.http_request('search', this.search_params(value)
+ + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
+ + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
+
+ this.env.qsearch = {lock: lock, request: r};
+ }
+ };
+
+ // build URL params for search
+ this.search_params = function(search, filter)
+ {
+ var n, url = [], mods_arr = [],
+ mods = this.env.search_mods,
+ mbox = this.env.mailbox;
+
+ if (!filter && this.gui_objects.search_filter)
+ filter = this.gui_objects.search_filter.value;
+
+ if (!search && this.gui_objects.qsearchbox)
+ search = this.gui_objects.qsearchbox.value;
+
+ if (filter)
+ url.push('_filter=' + urlencode(filter));
+
+ if (search) {
+ url.push('_q='+urlencode(search));
+
+ if (mods && this.message_list)
+ mods = mods[mbox] ? mods[mbox] : mods['*'];
+
+ if (mods) {
+ for (n in mods)
+ mods_arr.push(n);
+ url.push('_headers='+mods_arr.join(','));
+ }
+ }
+
+ if (mbox)
+ url.push('_mbox='+urlencode(mbox));
+
+ return url.join('&');
+ };
+
+ // reset quick-search form
+ this.reset_qsearch = function()
+ {
+ if (this.gui_objects.qsearchbox)
+ this.gui_objects.qsearchbox.value = '';
+
+ if (this.env.qsearch)
+ this.abort_request(this.env.qsearch);
+
+ this.env.qsearch = null;
+ this.env.search_request = null;
+ this.env.search_id = null;
+ };
+
+ this.sent_successfully = function(type, msg)
+ {
+ this.display_message(msg, type);
+ // before redirect we need to wait some time for Chrome (#1486177)
+ window.setTimeout(function(){ ref.list_mailbox(); }, 500);
+ };
+
+
+ /*********************************************************/
+ /********* keyboard live-search methods *********/
+ /*********************************************************/
+
+ // handler for keyboard events on address-fields
+ this.ksearch_keydown = function(e, obj, props)
+ {
+ if (this.ksearch_timer)
+ clearTimeout(this.ksearch_timer);
+
+ var highlight,
+ key = rcube_event.get_keycode(e),
+ mod = rcube_event.get_modifier(e);
+
+ switch (key) {
+ case 38: // key up
+ case 40: // key down
+ if (!this.ksearch_pane)
+ break;
+
+ var dir = key==38 ? 1 : 0;
+
+ highlight = document.getElementById('rcmksearchSelected');
+ if (!highlight)
+ highlight = this.ksearch_pane.__ul.firstChild;
+
+ if (highlight)
+ this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
+
+ return rcube_event.cancel(e);
+
+ case 9: // tab
+ if (mod == SHIFT_KEY || !this.ksearch_visible()) {
+ this.ksearch_hide();
+ return;
+ }
+
+ case 13: // enter
+ if (!this.ksearch_visible())
+ return false;
+
+ // insert selected address and hide ksearch pane
+ this.insert_recipient(this.ksearch_selected);
+ this.ksearch_hide();
+
+ return rcube_event.cancel(e);
+
+ case 27: // escape
+ this.ksearch_hide();
+ return;
+
+ case 37: // left
+ case 39: // right
+ if (mod != SHIFT_KEY)
+ return;
+ }
+
+ // start timer
+ this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
+ this.ksearch_input = obj;
+
+ return true;
+ };
+
+ this.ksearch_visible = function()
+ {
+ return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
+ };
+
+ this.ksearch_select = function(node)
+ {
+ var current = $('#rcmksearchSelected');
+ if (current[0] && node) {
+ current.removeAttr('id').removeClass('selected');
+ }
+
+ if (node) {
+ $(node).attr('id', 'rcmksearchSelected').addClass('selected');
+ this.ksearch_selected = node._rcm_id;
+ }
+ };
+
+ this.insert_recipient = function(id)
+ {
+ if (id === null || !this.env.contacts[id] || !this.ksearch_input)
+ return;
+
+ // get cursor pos
+ var inp_value = this.ksearch_input.value,
+ cpos = this.get_caret_pos(this.ksearch_input),
+ p = inp_value.lastIndexOf(this.ksearch_value, cpos),
+ trigger = false,
+ insert = '',
+ // replace search string with full address
+ pre = inp_value.substring(0, p),
+ end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
+
+ this.ksearch_destroy();
+
+ // insert all members of a group
+ if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) {
+ insert += this.env.contacts[id].name + this.env.recipients_delimiter;
+ this.group2expand = $.extend({}, this.env.contacts[id]);
+ this.group2expand.input = this.ksearch_input;
+ this.http_request('mail/group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false);
+ }
+ else if (typeof this.env.contacts[id] === 'string') {
+ insert = this.env.contacts[id] + this.env.recipients_delimiter;
+ trigger = true;
+ }
+
+ 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);
+
+ if (trigger)
+ this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert });
+ };
+
+ this.replace_group_recipients = function(id, recipients)
+ {
+ if (this.group2expand && this.group2expand.id == id) {
+ this.group2expand.input.value = this.group2expand.input.value.replace(this.group2expand.name, recipients);
+ this.triggerEvent('autocomplete_insert', { field:this.group2expand.input, insert:recipients });
+ this.group2expand = null;
+ }
+ };
+
+ // address search processor
+ this.ksearch_get_results = function(props)
+ {
+ var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
+
+ if (inp_value === null)
+ return;
+
+ if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
+ this.ksearch_pane.hide();
+
+ // get string from current cursor pos to last comma
+ var cpos = this.get_caret_pos(this.ksearch_input),
+ p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1),
+ q = inp_value.substring(p+1, cpos),
+ min = this.env.autocomplete_min_length,
+ ac = this.ksearch_data;
+
+ // trim query string
+ q = $.trim(q);
+
+ // Don't (re-)search if the last results are still active
+ if (q == this.ksearch_value)
+ return;
+
+ this.ksearch_destroy();
+
+ if (q.length && q.length < min) {
+ if (!this.ksearch_info) {
+ this.ksearch_info = this.display_message(
+ this.get_label('autocompletechars').replace('$min', min));