From a3b85d7b8560cdc1057fcaffa3acbd247b4b5b7a Mon Sep 17 00:00:00 2001 From: Thomas B. <thomas@roundcube.net> Date: Mon, 07 Oct 2013 13:19:03 -0400 Subject: [PATCH] Merge pull request #133 from cwickert/release-0.9-canned-responses --- program/js/app.js | 824 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 610 insertions(+), 214 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 955c77f..1f128d4 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3,8 +3,8 @@ | Roundcube Webmail Client Script | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2012, The Roundcube Dev Team | - | Copyright (C) 2011, Kolab Systems AG | + | Copyright (C) 2005-2013, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -219,14 +219,15 @@ if (this.gui_objects.qsearchbox) { if (this.env.search_text != null) this.gui_objects.qsearchbox.value = this.env.search_text; - $(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list.blur(); }); + $(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list && rcmail.message_list.blur(); }); } this.set_button_titles(); - this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', 'forward', - 'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'download', - 'print', 'load-attachment', 'show-headers', 'hide-headers', 'forward-attachment']; + this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', + 'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', + 'print', 'load-attachment', 'show-headers', 'hide-headers', 'download', + 'forward', 'forward-inline', 'forward-attachment']; if (this.env.action == 'show' || this.env.action == 'preview') { this.enable_command(this.env.message_commands, this.env.uid); @@ -250,12 +251,14 @@ } } else if (this.env.action == 'compose') { - this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'extwin']; + this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', + 'toggle-editor', 'list-adresses', 'search', 'reset-search', 'extwin', + 'insert-response', 'save-response']; if (this.env.drafts_mailbox) this.env.compose_commands.push('savedraft') - this.enable_command(this.env.compose_commands, 'identities', true); + this.enable_command(this.env.compose_commands, 'identities', 'responses', true); // add more commands (not enabled) $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']); @@ -264,6 +267,23 @@ this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); }; this.env.compose_commands.push('spellcheck') this.enable_command('spellcheck', true); + } + + // init canned response functions + if (this.gui_objects.responseslist) { + $('a.insertresponse', this.gui_objects.responseslist) + .attr('unselectable', 'on') + .mousedown(function(e){ return rcube_event.cancel(e); }) + .mouseup(function(e){ + ref.command('insert-response', $(this).attr('rel')); + $(document.body).trigger('mouseup'); // hides the menu + return rcube_event.cancel(e); + }); + + // avoid textarea loosing focus when hitting the save-response button/link + for (var i=0; this.buttons['save-response'] && i < this.buttons['save-response'].length; i++) { + $('#'+this.buttons['save-response'][i].id).mousedown(function(e){ return rcube_event.cancel(e); }) + } } document.onmouseup = function(e){ return p.doc_mouse_up(e); }; @@ -311,7 +331,7 @@ } // detect browser capabilities - if (!this.is_framed()) + if (!this.is_framed() && !this.env.extwin) this.browser_capabilities_check(); break; @@ -360,7 +380,7 @@ if (this.gui_objects.editform) { this.enable_command('save', true); - if (this.env.action == 'add' || this.env.action == 'edit') + if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search') this.init_contact_form(); } @@ -370,7 +390,7 @@ break; case 'settings': - this.enable_command('preferences', 'identities', 'save', 'folders', true); + this.enable_command('preferences', 'identities', 'responses', 'save', 'folders', true); if (this.env.action == 'identities') { this.enable_command('add', this.env.identities_level < 2); @@ -387,9 +407,12 @@ } else if (this.env.action == 'edit-folder' && this.gui_objects.editform) { this.enable_command('save', 'folder-size', true); - parent.rcmail.env.messagecount = this.env.messagecount; + parent.rcmail.env.exists = this.env.messagecount; parent.rcmail.enable_command('purge', this.env.messagecount); $("input[type='text']").first().select(); + } + else if (this.env.action == 'responses') { + this.enable_command('add', true); } if (this.gui_objects.identitieslist) { @@ -407,8 +430,22 @@ this.sections_list.init(); this.sections_list.focus(); } - else if (this.gui_objects.subscriptionlist) + else if (this.gui_objects.subscriptionlist) { this.init_subscription_list(); + } + else if (this.gui_objects.responseslist) { + this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:false}); + this.responses_list.addEventListener('select', function(list){ + var win, id = list.get_single_selection(); + p.enable_command('delete', !!id && $.inArray(id, p.env.readonly_responses) < 0); + if (id && (win = p.get_frame_window(p.env.contentframe))) { + p.set_busy(true); + p.location_href({ _action:'edit-response', _key:id, _framed:1 }, win); + } + }); + this.responses_list.init(); + this.responses_list.focus(); + } break; @@ -440,10 +477,11 @@ this.enable_command('login', true); break; + } - default: - break; - } + // unset contentframe variable if preview_pane is enabled + if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible')) + this.env.contentframe = null; // prevent from form submit with Enter key in file input fields if (bw.ie) @@ -507,7 +545,7 @@ return false; // let the browser handle this click (shift/ctrl usually opens the link in a new window/tab) - if ((obj && obj.href && String(obj.href).indexOf(location.href) < 0) && rcube_event.get_modifier(event)) { + if ((obj && obj.href && String(obj.href).indexOf('#') < 0) && rcube_event.get_modifier(event)) { return true; } @@ -581,11 +619,11 @@ var prevstate = this.env.compose_extwin; $("input[name='_action']", this.gui_objects.messageform).val('compose'); this.gui_objects.messageform.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 }); - this.gui_objects.messageform.target = this.open_window('', 1150, 900); + this.gui_objects.messageform.target = this.open_window('', 1100); this.gui_objects.messageform.submit(); } else { - this.open_window(this.env.permaurl, 1000, 1200); + this.open_window(this.env.permaurl, 900); } break; @@ -650,13 +688,13 @@ break; case 'expunge': - if (this.env.messagecount) + if (this.env.exists) this.expunge_mailbox(this.env.mailbox); break; case 'purge': case 'empty-mailbox': - if (this.env.messagecount) + if (this.env.exists) this.purge_mailbox(this.env.mailbox); break; @@ -681,6 +719,13 @@ case 'add': if (this.task == 'addressbook') this.load_contact(0, 'add'); + else if (this.task == 'settings' && this.env.action == 'responses') { + var frame; + if ((frame = this.get_frame_window(this.env.contentframe))) { + this.set_busy(true); + this.location_href({ _action:'add-response', _framed:1 }, frame); + } + } else if (this.task == 'settings') { this.identity_list.clear_selection(); this.load_identity(0, 'add-identity'); @@ -744,7 +789,10 @@ // addressbook task else if (this.task == 'addressbook') this.delete_contacts(); - // user settings task + // settings: canned response + else if (this.task == 'settings' && this.env.action == 'responses') + this.delete_response(); + // settings: user identities else if (this.task == 'settings') this.delete_identity(); break; @@ -818,12 +866,10 @@ var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part; // open attachment in frame if it's of a supported mimetype - if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, $.map(this.env.mimetypes, function(v,k){ return v })) >= 0) { - if (props.mimetype == 'text/html') - qstring += '&_safe=1'; - this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'); - if (this.attachment_win) { - setTimeout(function(){ ref.attachment_win.focus(); }, 10); + if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes) >= 0) { + var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', this.html_identifier('rcubemailattachment'+this.env.uid+props.part)); + if (attachment_win) { + setTimeout(function(){ attachment_win.focus(); }, 10); break; } } @@ -944,8 +990,8 @@ // Reset the auto-save timer clearTimeout(this.save_timer); - // compose form did not change - if (this.cmp_hash == this.compose_field_hash()) { + // compose form did not change (and draft wasn't saved already) + if (this.env.draft_id && this.cmp_hash == this.compose_field_hash()) { this.auto_save_start(); break; } @@ -989,7 +1035,7 @@ if (uid = this.get_single_uid()) { 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 + // do reply-list, when list is detected and popup menu wasn't used url._all = (!props && this.commands['reply-list'] ? 'list' : 'all'); else if (command == 'reply-list') url._all = 'list'; @@ -999,10 +1045,12 @@ break; case 'forward-attachment': + case 'forward-inline': case 'forward': - if (uid = this.get_single_uid()) { - url = { _forward_uid: uid, _mbox: this.env.mailbox }; - if (command == 'forward-attachment' || (!props && this.env.forward_attachment)) + var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []); + if (uids.length) { + url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox }; + if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1) url._attachment = 1; this.open_compose_step(url); } @@ -1048,8 +1096,13 @@ this.reset_qsearch(); this.select_all_mode = false; - if (s && this.env.mailbox) + if (s && this.env.action == 'compose') { + if (this.contact_list) + this.list_contacts_clear(); + } + else if (s && this.env.mailbox) { this.list_mailbox(this.env.mailbox, 1); + } else if (s && this.task == 'addressbook') { if (this.env.source == '') { for (n in this.env.address_sources) break; @@ -1097,6 +1150,7 @@ // user settings commands case 'preferences': case 'identities': + case 'responses': case 'folders': this.goto_url('settings/' + command); break; @@ -1383,8 +1437,8 @@ // over the folders for (k in this.env.folder_coords) { pos = this.env.folder_coords[k]; - if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2){ - if ((check = this.check_droptarget(k))) { + if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2) { + if (check = this.check_droptarget(k)) { li = this.get_folder_li(k); div = $(li.getElementsByTagName('div')[0]); @@ -1398,7 +1452,8 @@ rcmail.command('collapse-folder', rcmail.folder_auto_expand); rcmail.drag_start(null); }, 1000); - } else if (this.folder_auto_timer) { + } + else if (this.folder_auto_timer) { clearTimeout(this.folder_auto_timer); this.folder_auto_timer = null; this.folder_auto_expand = null; @@ -1408,9 +1463,10 @@ this.env.folder_coords[k].on = 1; this.env.last_folder_target = k; layerclass = 'draglayer' + (check > 1 ? 'copy' : 'normal'); - } else { // Clear target, otherwise drag end will trigger move into last valid droptarget - this.env.last_folder_target = null; } + // Clear target, otherwise drag end will trigger move into last valid droptarget + else + this.env.last_folder_target = null; } else if (pos.on) { $(this.get_folder_li(k)).removeClass('droptarget'); @@ -1520,22 +1576,22 @@ if (this.preview_read_timer) clearTimeout(this.preview_read_timer); - var selected = list.get_single_selection() != null; + var selected = list.get_single_selection(); - this.enable_command(this.env.message_commands, selected); + this.enable_command(this.env.message_commands, selected != null); if (selected) { // Hide certain command buttons when Drafts folder is selected if (this.env.mailbox == this.env.drafts_mailbox) - this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', false); + this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', 'forward-inline', false); // Disable reply-list when List-Post header is not set else { - var msg = this.env.messages[list.get_single_selection()]; + var msg = this.env.messages[selected]; if (!msg.ml) this.enable_command('reply-list', false); } } // Multi-message commands - this.enable_command('delete', 'moveto', 'copy', 'mark', (list.selection.length > 0 ? true : false)); + this.enable_command('delete', 'moveto', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0); // reset all-pages-selection if (selected || (list.selection.length && list.selection.length != list.rowcount)) @@ -1543,7 +1599,7 @@ // start timer for message preview (wait for double click) if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select) - this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); + this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time); else if (this.env.contentframe) this.show_contentframe(false); }; @@ -1559,12 +1615,13 @@ var win = this.get_frame_window(this.env.contentframe); - if (win && win.location.href.indexOf(this.env.blankpage)>=0) { + if (win && win.location.href.indexOf(this.env.blankpage) >= 0) { if (this.preview_timer) clearTimeout(this.preview_timer); if (this.preview_read_timer) clearTimeout(this.preview_read_timer); - this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200); + + this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time); } }; @@ -1572,11 +1629,11 @@ { if (this.preview_timer) clearTimeout(this.preview_timer); - if (this.preview_read_timer) clearTimeout(this.preview_read_timer); var uid = list.get_single_selection(); + if (uid && this.env.mailbox == this.env.drafts_mailbox) this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox }); else if (uid) @@ -1637,39 +1694,46 @@ this.check_droptarget = function(id) { - var allow = false, copy = false; - if (this.task == 'mail') - allow = (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual); - else if (this.task == 'settings') - allow = (id != this.env.mailbox); - else if (this.task == 'addressbook') { + return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual) ? 1 : 0; + + if (this.task == 'settings') + return id != this.env.mailbox ? 1 : 0; + + if (this.task == 'addressbook') { if (id != this.env.source && this.env.contactfolders[id]) { + // droptarget is a group - contact add to group action if (this.env.contactfolders[id].type == 'group') { var target_abook = this.env.contactfolders[id].source; - allow = this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly; - copy = target_abook != this.env.source; + if (this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly) { + // search result may contain contacts from many sources + return (this.env.selection_sources.length > 1 || $.inArray(target_abook, this.env.selection_sources) == -1) ? 2 : 1; + } } - else { - allow = !this.env.contactfolders[id].readonly; - copy = true; + // droptarget is a (writable) addressbook - contact copy action + else if (!this.env.contactfolders[id].readonly) { + // search result may contain contacts from many sources + return (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1) ? 2 : 0; } } } - return allow ? (copy ? 2 : 1) : 0; + return 0; }; - this.open_window = function(url, width, height) + this.open_window = function(url, width) { - var w = Math.min(width, screen.width - 10), - h = Math.min(height, screen.height - 100), - l = (screen.width - w) / 2 + (screen.left || 0), - t = Math.max(0, (screen.height - h) / 2 + (screen.top || 0) - 20); - - var wname = 'rcmextwin' + new Date().getTime(), - extwin = window.open(url + '&_extwin=1', wname, 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no'); - extwin.moveTo(l,t); + var win = this.is_framed() ? parent.window : window, + page = $(win), + page_width = page.width(), + page_height = bw.mz ? $('body', win).height() : page.height(), + w = Math.min(width, page_width), + h = page_height, // always use same height + l = (win.screenLeft || win.screenX) + 20, + t = (win.screenTop || win.screenY) + 20, + wname = 'rcmextwin' + new Date().getTime(), + extwin = window.open(url + (url.match(/\?/) ? '&' : '?') + '_extwin=1', wname, + 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no,location=no'); // write loading... message to empty windows if (!url && extwin.document) { @@ -1677,7 +1741,7 @@ } // focus window, delayed to bring to front - window.setTimeout(function(){ extwin.focus(); }, 10); + window.setTimeout(function() { extwin.focus(); }, 10); return wname; }; @@ -1716,6 +1780,14 @@ if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) { row.expando = expando; expando.onmousedown = function(e) { return self.expand_message_row(e, uid); }; + if (bw.touch) { + expando.addEventListener('touchend', function(e) { + if (e.changedTouches.length == 1) { + self.expand_message_row(e, uid); + return rcube_event.cancel(e); + } + }, false); + } } this.triggerEvent('insertrow', { uid:uid, row:row }); @@ -1761,7 +1833,6 @@ + (!flags.seen ? ' unread' : '') + (flags.deleted ? ' deleted' : '') + (flags.flagged ? ' flagged' : '') - + (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '') + (message.selected ? ' selected' : ''), // for performance use DOM instead of jQuery here row = document.createElement('tr'); @@ -1814,6 +1885,9 @@ expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>'; row_class += ' thread' + (message.expanded? ' expanded' : ''); } + + if (flags.unread_children && flags.seen && !message.expanded) + row_class += ' unroot'; } tree += '<span id="msgicn'+uid+'" class="'+css_class+'"> </span>'; @@ -1860,7 +1934,7 @@ html = expando; else if (c == 'subject') { if (bw.ie) { - col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); }; + col.onmouseover = function() { rcube_webmail.long_subject_title_ex(this, message.depth+1); }; if (bw.ie8) tree = '<span></span>' + tree; // #1487821 } @@ -1875,7 +1949,8 @@ else html = cols[c]; - col.innerHTML = html; + if (html) + col.innerHTML = html; row.appendChild(col); } @@ -1978,7 +2053,7 @@ } else { if (!preview && this.env.message_extwin && !this.env.extwin) - this.open_window(this.env.comm_path+url, 1000, 1200); + this.open_window(this.env.comm_path+url, 1000); else this.location_href(this.env.comm_path+url, target, true); @@ -2550,27 +2625,18 @@ if (mbox && typeof mbox === 'object') mbox = mbox.id; - // exit if current or no mailbox specified or if selection is empty - if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))) + // exit if current or no mailbox specified + if (!mbox || mbox == this.env.mailbox) return; - var a_uids = [], n, selection, - lock = this.display_message(this.get_label('copyingmessage'), 'loading'), - post_data = {_mbox: this.env.mailbox, _target_mbox: mbox, _from: (this.env.action ? this.env.action : '')}; + var post_data = this.selection_post_data({_target_mbox: mbox}); - if (this.env.uid) - a_uids[0] = this.env.uid; - else { - selection = this.message_list.get_selection(); - for (n in selection) { - a_uids.push(selection[n]); - } - } - - post_data._uid = this.uids_to_list(a_uids); + // exit if selection is empty + if (!post_data._uid) + return; // send request to server - this.http_post('copy', post_data, lock); + this.http_post('copy', post_data, this.display_message(this.get_label('copyingmessage'), 'loading')); }; // move selected messages to the specified mailbox @@ -2579,12 +2645,15 @@ if (mbox && typeof mbox === 'object') mbox = mbox.id; - // exit if current or no mailbox specified or if selection is empty - if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))) + // exit if current or no mailbox specified + if (!mbox || mbox == this.env.mailbox) return; - var lock = false, - add_post = {_target_mbox: mbox, _from: (this.env.action ? this.env.action : '')}; + var lock = false, post_data = this.selection_post_data({_target_mbox: mbox}); + + // exit if selection is empty + if (!post_data._uid) + return; // show wait message if (this.env.action == 'show') @@ -2595,7 +2664,7 @@ // Hide message command buttons until a message is selected this.enable_command(this.env.message_commands, false); - this._with_selected_messages('moveto', lock, add_post); + this._with_selected_messages('moveto', post_data, lock); }; // delete selected messages from the current mailbox @@ -2603,7 +2672,7 @@ { var uid, i, len, trash = this.env.trash_mailbox, list = this.message_list, - selection = list ? $.merge([], list.get_selection()) : []; + selection = list ? list.get_selection() : []; // exit if no mailbox specified or if selection is empty if (!this.env.uid && !selection.length) @@ -2622,7 +2691,6 @@ return false; } // if there isn't a defined trash mailbox or we are in it - // @TODO: we should check if defined trash mailbox exists else if (!trash || this.env.mailbox == trash) this.permanently_remove_messages(); // we're in Junk folder and delete_junk is enabled @@ -2645,32 +2713,29 @@ // delete the selected messages permanently this.permanently_remove_messages = function() { - // exit if no mailbox specified or if selection is empty - if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)) + var post_data = this.selection_post_data(); + + // exit if selection is empty + if (!post_data._uid) return; this.show_contentframe(false); - this._with_selected_messages('delete', false, {_from: this.env.action ? this.env.action : ''}); + this._with_selected_messages('delete', post_data); }; // Send a specifc moveto/delete request with UIDs of all selected messages // @private - this._with_selected_messages = function(action, lock, post_data) + this._with_selected_messages = function(action, post_data, lock) { - var a_uids = [], count = 0, msg, lock; + var count = 0, msg; - if (typeof(post_data) != 'object') - post_data = {}; - - if (this.env.uid) - a_uids[0] = this.env.uid; - else { + // update the list (remove rows, clear selection) + if (this.message_list) { var n, id, root, roots = [], selection = this.message_list.get_selection(); for (n=0, len=selection.length; n<len; n++) { id = selection[n]; - a_uids.push(id); if (this.env.threading) { count += this.update_thread(id); @@ -2690,10 +2755,6 @@ } } - // also send search request to get the right messages - if (this.env.search_request) - post_data._search = this.env.search_request; - if (this.env.display_next && this.env.next_uid) post_data._next_uid = this.env.next_uid; @@ -2702,9 +2763,6 @@ // remove threads from the end of the list else if (count > 0) this.delete_excessive_thread_rows(); - - post_data._uid = this.uids_to_list(a_uids); - post_data._mbox = this.env.mailbox; if (!lock) { msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage'; @@ -2715,22 +2773,41 @@ this.http_post(action, post_data, lock); }; + // build post data for message delete/move/copy/flag requests + this.selection_post_data = function(data) + { + if (typeof(data) != 'object') + data = {}; + + data._mbox = this.env.mailbox; + + if (!data._uid) { + var uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection(); + data._uid = this.uids_to_list(uids); + } + + if (this.env.action) + data._from = this.env.action; + + // also send search request to get the right messages + if (this.env.search_request) + data._search = this.env.search_request; + + return data; + }; + // set a specific flag to one or more messages this.mark_message = function(flag, uid) { - var a_uids = [], r_uids = [], len, n, id, selection, + var a_uids = [], r_uids = [], len, n, id, list = this.message_list; if (uid) a_uids[0] = uid; else if (this.env.uid) a_uids[0] = this.env.uid; - else if (list) { - selection = list.get_selection(); - for (n=0, len=selection.length; n<len; n++) { - a_uids.push(selection[n]); - } - } + else if (list) + a_uids = list.get_selection(); if (!list) r_uids = a_uids; @@ -2738,12 +2815,12 @@ list.focus(); for (n=0, len=a_uids.length; n<len; n++) { id = a_uids[n]; - if ((flag=='read' && list.rows[id].unread) - || (flag=='unread' && !list.rows[id].unread) - || (flag=='delete' && !list.rows[id].deleted) - || (flag=='undelete' && list.rows[id].deleted) - || (flag=='flagged' && !list.rows[id].flagged) - || (flag=='unflagged' && list.rows[id].flagged)) + if ((flag == 'read' && list.rows[id].unread) + || (flag == 'unread' && !list.rows[id].unread) + || (flag == 'delete' && !list.rows[id].deleted) + || (flag == 'undelete' && list.rows[id].deleted) + || (flag == 'flagged' && !list.rows[id].flagged) + || (flag == 'unflagged' && list.rows[id].flagged)) { r_uids.push(id); } @@ -2774,16 +2851,12 @@ this.toggle_read_status = function(flag, a_uids) { var i, len = a_uids.length, - post_data = {_uid: this.uids_to_list(a_uids), _flag: flag}, + post_data = this.selection_post_data({_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) - post_data._search = this.env.search_request; + this.set_message(a_uids[i], 'unread', (flag == 'unread' ? true : false)); this.http_post('mark', post_data, lock); @@ -2795,16 +2868,12 @@ this.toggle_flagged_status = function(flag, a_uids) { var i, len = a_uids.length, - post_data = {_uid: this.uids_to_list(a_uids), _flag: flag}, + post_data = this.selection_post_data({_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) - post_data._search = this.env.search_request; + this.set_message(a_uids[i], 'flagged', (flag == 'flagged' ? true : false)); this.http_post('mark', post_data, lock); }; @@ -2843,25 +2912,20 @@ this.flag_as_undeleted = function(a_uids) { - var i, len=a_uids.length, - post_data = {_uid: this.uids_to_list(a_uids), _flag: 'undelete'}, + var i, len = a_uids.length, + post_data = this.selection_post_data({_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) - post_data._search = this.env.search_request; - this.http_post('mark', post_data, lock); - return true; }; this.flag_as_deleted = function(a_uids) { var r_uids = [], - post_data = {_uid: this.uids_to_list(a_uids), _flag: 'delete'}, + post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'delete'}), lock = this.display_message(this.get_label('markingmessage'), 'loading'), rows = this.message_list ? this.message_list.rows : [], count = 0; @@ -2892,9 +2956,6 @@ this.delete_excessive_thread_rows(); } - if (this.env.action) - post_data._from = this.env.action; - // ?? if (r_uids.length) post_data._ruid = this.uids_to_list(r_uids); @@ -2902,12 +2963,7 @@ if (this.env.skip_deleted && this.env.display_next && this.env.next_uid) post_data._next_uid = this.env.next_uid; - // also send search request to get the right messages - if (this.env.search_request) - post_data._search = this.env.search_request; - this.http_post('mark', post_data, lock); - return true; }; // flag as read without mark request (called from backend) @@ -2987,7 +3043,7 @@ // 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 + return (this.env.exists && (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)))); }; @@ -3023,11 +3079,12 @@ // open new compose window if (this.env.compose_extwin && !this.env.extwin) { - this.open_window(url, 1150, 900); + this.open_window(url, 1150); } else { this.redirect(url); - window.resizeTo(Math.max(1150, $(window).width()), Math.max(900, $(window).height())); + if (this.env.extwin) + window.resizeTo(Math.max(1150, $(window).width()), $(window).height()+24); } }; @@ -3043,10 +3100,10 @@ input_message = $("[name='_message']").get(0), html_mode = $("input[name='_is_html']").val() == '1', ac_fields = ['cc', 'bcc', 'replyto', 'followupto'], - ac_props; + ac_props, opener_rc = this.opener(); // close compose step in opener - if (window.opener && opener.rcmail && opener.rcmail.env.action == 'compose') { + if (opener_rc && opener_rc.env.action == 'compose') { setTimeout(function(){ opener.history.back(); }, 100); this.env.opened_extwin = true; } @@ -3069,7 +3126,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' && !this.env.opened_extwin) { + if (input_from.prop('type') == 'select-one') { this.change_identity(input_from[0]); } } @@ -3118,6 +3175,13 @@ form._draft.value = draft ? '1' : ''; form.action = this.add_url(form.action, '_unlock', msgid); form.action = this.add_url(form.action, '_lang', lang); + + // register timer to notify about connection timeout + this.submit_timer = setTimeout(function(){ + ref.set_busy(false, null, msgid); + ref.display_message(ref.get_label('requesttimedout'), 'error'); + }, this.env.request_timeout * 1000); + form.submit(); }; @@ -3128,7 +3192,7 @@ this.compose_add_recipient = function(field) { - var recipients = [], input = $('#_'+field); + var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter; if (this.contact_list && this.contact_list.selection.length) { for (var id, n=0; n < this.contact_list.selection.length; n++) { @@ -3147,8 +3211,10 @@ } if (recipients.length && input.length) { - var oldval = input.val(); - input.val((oldval ? oldval + this.env.recipients_delimiter : '') + recipients.join(this.env.recipients_delimiter)); + var oldval = input.val(), rx = new RegExp(RegExp.escape(delim) + '\\s*$'); + if (oldval && !rx.test(oldval)) + oldval += delim + ' '; + input.val(oldval + recipients.join(delim + ' ') + delim + ' '); this.triggerEvent('add-recipient', { field:field, recipients:recipients }); } }; @@ -3264,6 +3330,154 @@ return true; }; + 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(insert, { 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(); + } + }; + + /** + * Open the dialog to save a new canned response + */ + 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 = {}, + html = '<form class="propform">' + + '<div class="prop block"><label>' + this.get_label('responsename') + '</label>' + + '<input type="text" name="name" id="ffresponsename" size="40" /></div>' + + '<div class="prop block"><label>' + this.get_label('responsetext') + '</label>' + + '<textarea name="text" id="ffresponsetext" cols="40" rows="8"></textarea></div>' + + '</form>'; + + buttons[this.gettext('save')] = function(e) { + var name = $('#ffresponsename').val(), + text = $('#ffresponsetext').val(); + + if (!text) { + $('#ffresponsetext').select(); + return false; + } + if (!name) + name = text.substring(0,40); + + var lock = ref.display_message(ref.get_label('savingresponse'), 'loading'); + ref.http_post('settings/responses', { _insert:1, _name:name, _text:text }, lock); + $(this).dialog('close'); + }; + + buttons[this.gettext('cancel')] = function() { + $(this).dialog('close'); + }; + + this.show_popup_dialog(html, this.gettext('savenewresponse'), buttons); + + $('#ffresponsetext').val(text); + $('#ffresponsename').select(); + }; + + this.add_response_item = function(response) + { + var key = response.key; + this.env.textresponses[key] = response; + + // append to responses list + if (this.gui_objects.responseslist) { + var li = $('<li>').appendTo(this.gui_objects.responseslist); + $('<a>').addClass('insertresponse active') + .attr('href', '#') + .attr('rel', key) + .html(this.quote_html(response.name)) + .appendTo(li) + .mousedown(function(e){ + return rcube_event.cancel(e); + }) + .mouseup(function(e){ + ref.command('insert-response', key); + $(document.body).trigger('mouseup'); // hides the menu + return rcube_event.cancel(e); + }); + } + }; + + this.edit_responses = function() + { + // TODO: implement inline editing of responses + }; + + this.delete_response = function(key) + { + if (!key && this.responses_list) { + var selection = this.responses_list.get_selection(); + key = selection[0]; + } + + // submit delete request + if (key && confirm(this.get_label('deleteresponseconfirm'))) { + this.http_post('settings/delete-response', { _key: key }, false); + return true; + } + + return false; + }; + this.stop_spellchecking = function() { var ed; @@ -3338,6 +3552,15 @@ this.set_draft_id = function(id) { + var rc; + + if (!this.env.draft_id && id && (rc = this.opener())) { + // refresh the drafts folder in opener window + if (rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == this.env.drafts_mailbox) + rc.command('checkmail'); + } + + this.env.draft_id = id; $("input[name='_draft_saveid']").val(id); }; @@ -3382,6 +3605,15 @@ if (!show_sig) show_sig = this.env.show_sig; + // first function execution + if (!this.env.identities_initialized) { + this.env.identities_initialized = true; + if (this.env.show_sig_later) + this.env.show_sig = true; + if (this.env.opened_extwin) + return; + } + var cursor_pos, p = -1, id = obj.options[obj.selectedIndex].value, input_message = $("[name='_message']"), @@ -3403,7 +3635,7 @@ sig = this.env.signatures[sig].text; sig = sig.replace(/\r\n/g, '\n'); - p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig); + 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); } @@ -3412,7 +3644,7 @@ sig = this.env.signatures[id].text; sig = sig.replace(/\r\n/g, '\n'); - if (this.env.sig_above) { + 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; @@ -3456,7 +3688,7 @@ sigElem = doc.createElement('div'); sigElem.setAttribute('id', '_rc_sig'); - if (this.env.sig_above) { + 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 @@ -3567,7 +3799,12 @@ 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); + var indicator, li = $('<li>'); + + li.attr('id', name) + .addClass(att.classname) + .html(att.html) + .on('mouseover', function() { rcube_webmail.long_subject_title_ex(this, 0); }); // replace indicator's li if (upload_id && (indicator = document.getElementById(upload_id))) { @@ -3657,7 +3894,8 @@ // reset vars this.env.current_page = 1; - r = this.http_request('search', url, lock); + var action = this.env.action == 'compose' && this.contact_list ? 'search-contacts' : 'search'; + r = this.http_request(action, url, lock); this.env.qsearch = {lock: lock, request: r}; } @@ -3712,13 +3950,19 @@ this.env.search_id = null; }; - this.sent_successfully = function(type, msg) + this.sent_successfully = function(type, msg, target) { this.display_message(msg, type); - if (this.env.extwin && window.opener && opener.rcmail) { + if (this.env.extwin) { + var rc = this.opener(); this.lock_form(this.gui_objects.messageform); - opener.rcmail.display_message(msg, type); + if (rc) { + rc.display_message(msg, type); + // refresh the folder where sent message was saved + if (target && rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == target) + rc.command('checkmail'); + } setTimeout(function(){ window.close() }, 1000); } else { @@ -4093,24 +4337,30 @@ var n, id, sid, ref = this, writable = false, source = this.env.source ? this.env.address_sources[this.env.source] : null; + // we don't have dblclick handler here, so use 200 instead of this.dblclick_time if (id = list.get_single_selection()) this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, 200); else if (this.env.contentframe) this.show_contentframe(false); - // no source = search result, we'll need to detect if any of - // selected contacts are in writable addressbook to enable edit/delete if (list.selection.length) { + // no source = search result, we'll need to detect if any of + // selected contacts are in writable addressbook to enable edit/delete + // we'll also need to know sources used in selection for copy + // and group-addmember operations (drag&drop) + this.env.selection_sources = []; if (!source) { for (n in list.selection) { sid = String(list.selection[n]).replace(/^[^-]+-/, ''); - if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) { - writable = true; - break; + if (sid && this.env.address_sources[sid]) { + writable = writable || !this.env.address_sources[sid].readonly; + this.env.selection_sources.push(sid); } } + this.env.selection_sources = $.unique(this.env.selection_sources); } else { + this.env.selection_sources.push(this.env.source); writable = !source.readonly; } } @@ -4145,7 +4395,7 @@ if (this.env.search_id) folder = 'S'+this.env.search_id; - else + else if (!this.env.search_request) folder = group ? 'G'+src+group : src; this.select_folder(folder); @@ -4198,7 +4448,7 @@ this.env.source = src; this.env.group = group; - // also send search request to get the right messages + // also send search request to get the right records if (this.env.search_request) url._search = this.env.search_request; @@ -4261,22 +4511,35 @@ // copy a contact to the specified target (group or directory) this.copy_contact = function(cid, to) { + var n, dest = to.type == 'group' ? to.source : to.id, + source = this.env.source, + group = this.env.group ? this.env.group : ''; + if (!cid) cid = this.contact_list.get_selection().join(','); - if (to.type == 'group' && to.source == this.env.source) - this.group_member_change('add', cid, to.source, to.id); - else if (to.type == 'group' && !this.env.address_sources[to.source].readonly) { - var lock = this.display_message(this.get_label('copyingcontact'), 'loading'), - post_data = {_cid: cid, _source: this.env.source, _to: to.source, _togid: to.id, - _gid: (this.env.group ? this.env.group : '')}; + if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly) + return; - this.http_post('copy', post_data, lock); + // search result may contain contacts from many sources, but if there is only one... + if (source == '' && this.env.selection_sources.length == 1) + source = this.env.selection_sources[0]; + + // tagret is a group + if (to.type == 'group') { + if (dest == source) + this.group_member_change('add', cid, dest, to.id); + else { + var lock = this.display_message(this.get_label('copyingcontact'), 'loading'), + post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group}; + + this.http_post('copy', post_data, lock); + } } - else if (to.id != this.env.source && cid && this.env.address_sources[to.id] && !this.env.address_sources[to.id].readonly) { + // target is an addressbook + else if (to.id != source) { var lock = this.display_message(this.get_label('copyingcontact'), 'loading'), - post_data = {_cid: cid, _source: this.env.source, _to: to.id, - _gid: (this.env.group ? this.env.group : '')}; + post_data = {_cid: cid, _source: this.env.source, _to: to.id, _gid: group}; this.http_post('copy', post_data, lock); } @@ -4374,7 +4637,8 @@ for (c in cols) { col = document.createElement('td'); col.className = String(c).toLowerCase(); - col.innerHTML = cols[c]; + if (cols[c]) + col.innerHTML = cols[c]; row.appendChild(col); } @@ -4387,10 +4651,11 @@ { var ref = this, col; - this.set_photo_actions($('#ff_photo').val()); - - for (col in this.env.coltypes) - this.init_edit_field(col, null); + if (this.env.coltypes) { + this.set_photo_actions($('#ff_photo').val()); + for (col in this.env.coltypes) + this.init_edit_field(col, null); + } $('.contactfieldgroup .row a.deletebutton').click(function() { ref.delete_edit_field(this); @@ -4417,6 +4682,11 @@ } $("input[type='text']:visible").first().focus(); + + // Submit search form on Enter + if (this.env.action == 'search') + $(this.gui_objects.editform).append($('<input type="submit">').hide()) + .submit(function() { $('input.mainaction').click(); return false; }); }; this.group_create = function() @@ -5039,6 +5309,42 @@ col = $('<td>').addClass('mail').html(name).appendTo(row); list.insert_row(row); list.select(rid); + } + }; + + this.update_response_row = function(response, oldkey) + { + var row, col, list = this.responses_list; + + if (list && oldkey && list.rows[oldkey] && (row = list.rows[oldkey].obj)) { + $(row.cells[0]).html(response.name); + // update references because the key likely changed + row.id = 'rcmrow'+response.key; + list.init_row(row); + list.select(response.key); + delete list.rows[oldkey]; + } + else if (list) { + row = $('<tr>').attr('id', 'rcmrow'+response.key).get(0); + col = $('<td>').addClass('name').html(response.name).appendTo(row); + list.insert_row(row); + list.select(response.key); + } + }; + + this.remove_response = function(key) + { + var frame; + + if (this.env.textresponses) { + delete this.env.textresponses[key]; + } + + if (this.responses_list) { + this.responses_list.remove_row(key); + if (this.env.contentframe && (frame = this.get_frame_window(this.env.contentframe))) { + frame.location.href = this.env.blankpage; + } } }; @@ -5720,7 +6026,7 @@ }; // open a jquery UI dialog with the given content - this.show_popup_dialog = function(html, title) + this.show_popup_dialog = function(html, title, buttons) { // forward call to parent window if (this.is_framed()) { @@ -5732,6 +6038,7 @@ .html(html) .dialog({ title: title, + buttons: buttons, modal: true, resizable: true, width: 580, @@ -5741,7 +6048,7 @@ // resize and center popup var win = $(window), w = win.width(), h = win.height(), width = popup.width(), height = popup.height(); - popup.dialog('option', { height: Math.min(h-40, height+50), width: Math.min(w-20, width+50) }) + popup.dialog('option', { height: Math.min(h-40, height+75 + (buttons ? 50 : 0)), width: Math.min(w-20, width+50) }) .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?) }; @@ -5814,14 +6121,14 @@ for (c=0, len=repl.length; c < len; c++) { cell = document.createElement('td'); - cell.innerHTML = repl[c].html; + cell.innerHTML = repl[c].html || ''; if (repl[c].id) cell.id = repl[c].id; if (repl[c].className) cell.className = repl[c].className; tr.appendChild(cell); } th.appendChild(tr); thead.parentNode.replaceChild(th, thead); - thead = th; + list.thead = thead = th; } for (n=0, len=this.env.coltypes.length; n<len; n++) { @@ -6120,14 +6427,14 @@ // send request this.log('HTTP GET: ' + url); + // reset keep-alive interval + this.start_keepalive(); + return $.ajax({ type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json', success: function(data){ ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } }); - - // reset keep-alive interval - this.start_keepalive(); }; // send a http POST request to the server @@ -6155,14 +6462,14 @@ // send request this.log('HTTP POST: ' + url); + // reset keep-alive interval + this.start_keepalive(); + return $.ajax({ type: 'POST', url: url, data: postdata, dataType: 'json', success: function(data){ ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } }); - - // reset keep-alive interval - this.start_keepalive(); }; // aborts ajax request @@ -6244,7 +6551,7 @@ case 'purge': case 'expunge': if (this.task == 'mail') { - if (!this.env.messagecount) { + if (!this.env.exists) { // clear preview pane content if (this.env.contentframe) this.show_contentframe(false); @@ -6264,7 +6571,8 @@ this.env.qsearch = null; case 'list': if (this.task == 'mail') { - this.enable_command('show', 'expunge', 'select-all', 'select-none', (this.env.messagecount > 0)); + this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0); + this.enable_command('expunge', this.env.exists); this.enable_command('purge', this.purge_mailbox_test()); this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount); @@ -6317,12 +6625,29 @@ // redirect to url specified in location header if not empty var location_url = request.getResponseHeader("Location"); - if (location_url) + if (location_url && this.env.action != 'compose') // don't redirect on compose screen, contents might get lost (#1488926) this.redirect(location_url); + + // 403 Forbidden response (CSRF prevention) - reload the page. + // In case there's a new valid session it will be used, otherwise + // login form will be presented (#1488960). + if (request.status == 403) { + (this.is_framed() ? parent : window).location.reload(); + return; + } // re-send keep-alive requests after 30 seconds if (action == 'keep-alive') setTimeout(function(){ ref.keep_alive(); ref.start_keepalive(); }, 30000); + }; + + // callback when an iframe finished loading + this.iframe_loaded = function(unlock) + { + this.set_busy(false, null, unlock); + + if (this.submit_timer) + clearTimeout(this.submit_timer); }; // post the given form to a hidden iframe @@ -6424,9 +6749,10 @@ 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, + timeout: 0, // disable default timeout set in ajaxSetup() data: formdata || multipart, headers: {'X-Roundcube-Request': ref.env.request_token}, - beforeSend: function(xhr, s) { if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; }, + xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; return xhr; }, success: function(data){ ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); } }); @@ -6466,7 +6792,7 @@ 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 += reader.result + crlf; multipart += dashdash + boundary + crlf; if (j == last) // we're done, submit the data @@ -6563,6 +6889,25 @@ /********* helper methods *********/ /********************************************************/ + /** + * Quote html entities + */ + this.quote_html = function(str) + { + return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); + }; + + // get window.opener.rcmail if available + this.opener = function() + { + // catch Error: Permission denied to access property rcmail + try { + if (window.opener && !opener.closed && opener.rcmail) + return opener.rcmail; + } + catch (e) {} + }; + // check if we're in show mode or if we have a unique selection // and return the message uid this.get_single_uid = function() @@ -6614,6 +6959,57 @@ range.moveStart('character', pos); range.select(); } + }; + + // 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; + + 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) }; }; // disable/enable all fields of a form @@ -6776,11 +7172,11 @@ if (!elem.title) { var $elem = $(elem); if ($elem.width() + indent * 15 > $elem.parent().width()) - elem.title = $elem.html(); + elem.title = $elem.text(); } }; -rcube_webmail.long_subject_title_ie = function(elem, indent) +rcube_webmail.long_subject_title_ex = function(elem, indent) { if (!elem.title) { var $elem = $(elem), -- Gitblit v1.9.1