From 8799df8ccdb09114b14742e0493a8ba401688fdf Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Thu, 08 Aug 2013 08:15:30 -0400 Subject: [PATCH] Fix redundant SQL query on contact photo request after photo upload. The query was also invalid in case of uploading photo in contact create form. Move contact photo handling from show.inc into a separate file. --- program/js/app.js | 671 ++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 466 insertions(+), 205 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 55c71d7..6f5bfbc 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -44,7 +44,9 @@ comm_path: './', blankpage: 'program/resources/blank.gif', recipients_separator: ',', - recipients_delimiter: ', ' + recipients_delimiter: ', ', + popup_width: 1150, + popup_width_small: 900 }; // create protected reference to myself @@ -189,7 +191,7 @@ case 'mail': // enable mail commands - this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true); + this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', 'import-messages', true); if (this.gui_objects.messagelist) { this.message_list = new rcube_list_widget(this.gui_objects.messagelist, { @@ -227,9 +229,9 @@ this.set_button_titles(); this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', - 'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', + 'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download', - 'forward', 'forward-inline', 'forward-attachment']; + 'forward', 'forward-inline', 'forward-attachment', 'change-format']; if (this.env.action == 'show' || this.env.action == 'preview') { this.enable_command(this.env.message_commands, this.env.uid); @@ -253,7 +255,8 @@ } } else if (this.env.action == 'compose') { - this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'search', 'reset-search', 'extwin']; + this.env.address_group_stack = []; + this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'pushgroup', 'search', 'reset-search', 'extwin']; if (this.env.drafts_mailbox) this.env.compose_commands.push('savedraft') @@ -274,12 +277,15 @@ // init message compose form this.init_messageform(); } + else if (this.env.action == 'get') + this.enable_command('download', 'print', true); // show printing dialog - else if (this.env.action == 'print' && this.env.uid) + else if (this.env.action == 'print' && this.env.uid) { if (bw.safari) setTimeout('window.print()', 10); else window.print(); + } // get unread count for each mailbox if (this.gui_objects.mailboxlist) { @@ -314,17 +320,19 @@ } // detect browser capabilities - if (!this.is_framed()) + if (!this.is_framed() && !this.env.extwin) this.browser_capabilities_check(); break; case 'addressbook': + this.env.address_group_stack = []; + if (this.gui_objects.folderlist) this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups); this.enable_command('add', 'import', this.env.writable_source); - this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true); + this.enable_command('list', 'listgroup', 'pushgroup', 'popgroup', 'listsearch', 'advanced-search', true); if (this.gui_objects.contactslist) { this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, @@ -368,7 +376,7 @@ } if (this.gui_objects.qsearchbox) - this.enable_command('search', 'reset-search', 'moveto', true); + this.enable_command('search', 'reset-search', true); break; @@ -596,16 +604,28 @@ case 'extwin': if (this.env.action == 'compose') { - var form = this.gui_objects.messageform; + var form = this.gui_objects.messageform, + win = this.open_window(''); $("input[name='_action']", form).val('compose'); form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 }); - form.target = this.open_window('', 1100, 900); + form.target = win.name; form.submit(); } else { - this.open_window(this.env.permaurl, 900, 900); + this.open_window(this.env.permaurl, true); } + break; + + case 'change-format': + url = this.env.permaurl + '&_format=' + props; + + if (this.env.action == 'preview') + url = url.replace(/_action=show/, '_action=preview') + '&_framed=1'; + if (this.env.extwin) + url += '&_extwin=1'; + + location.href = url; break; case 'menu-open': @@ -775,16 +795,18 @@ // mail task commands case 'move': - case 'moveto': + case 'moveto': // deprecated if (this.task == 'mail') this.move_messages(props); else if (this.task == 'addressbook') - this.copy_contact(null, props); + this.move_contacts(props); break; case 'copy': if (this.task == 'mail') this.copy_messages(props); + else if (this.task == 'addressbook') + this.copy_contacts(props); break; case 'mark': @@ -846,11 +868,8 @@ // open attachment in frame if it's of a supported mimetype if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) { - var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'+this.env.uid+props); - if (attachment_win) { - setTimeout(function(){ attachment_win.focus(); }, 10); + if (this.open_window(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1')) break; - } } this.goto_url('get', qstring+'&_download=1', false); @@ -925,16 +944,13 @@ url._to = props; } else { - // use contact_id passed as command parameter - var n, len, a_cids = []; + var a_cids = []; + // use contact id passed as command parameter if (props) a_cids.push(props); // get selected contacts - else if (this.contact_list) { - var selection = this.contact_list.get_selection(); - for (n=0, len=selection.length; n<len; n++) - a_cids.push(selection[n]); - } + else if (this.contact_list) + a_cids = this.contact_list.get_selection(); if (a_cids.length) this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true); @@ -969,8 +985,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; } @@ -992,7 +1008,7 @@ // Reset the auto-save timer clearTimeout(this.save_timer); - this.upload_file(props || this.gui_objects.uploadform); + this.upload_file(props || this.gui_objects.uploadform, 'upload'); break; case 'insert-sig': @@ -1036,10 +1052,12 @@ break; case 'print': - if (uid = this.get_single_uid()) { - ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : '')); + if (this.env.action == 'get') { + this.gui_objects.messagepartframe.contentWindow.print(); + } + else if (uid = this.get_single_uid()) { + ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''), true, true); if (this.printwin) { - setTimeout(function(){ ref.printwin.focus(); }, 20); if (this.env.action != 'show') this.mark_message('read', uid); } @@ -1047,15 +1065,15 @@ break; case 'viewsource': - if (uid = this.get_single_uid()) { - ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)); - if (this.sourcewin) - setTimeout(function(){ ref.sourcewin.focus(); }, 20); - } + if (uid = this.get_single_uid()) + this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true); break; case 'download': - if (uid = this.get_single_uid()) + if (this.env.action == 'get') { + location.href = location.href.replace(/_frame=/, '_download='); + } + else if (uid = this.get_single_uid()) this.goto_url('viewsource', { _uid: uid, _mbox: this.env.mailbox, _save: 1 }); break; @@ -1092,9 +1110,29 @@ } break; + case 'pushgroup': + // add group ID to stack + this.env.address_group_stack.push(props.id); + if (obj && event) + rcube_event.cancel(event); + case 'listgroup': this.reset_qsearch(); this.list_contacts(props.source, props.id); + break; + + case 'popgroup': + if (this.env.address_group_stack.length > 1) { + this.env.address_group_stack.pop(); + this.reset_qsearch(); + this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1]); + } + break; + + case 'import-messages': + var form = props || this.gui_objects.importform; + $('input[name="_unlock"]', form).val(this.set_busy(true, 'importwait')); + this.upload_file(form, 'import'); break; case 'import': @@ -1171,6 +1209,7 @@ if (typeof cmd === 'string') { this.commands[cmd] = enable; this.set_button(cmd, (enable ? 'act' : 'pas')); + this.triggerEvent('enable-command', {command: cmd, status: enable}); } // push array elements into commands array else { @@ -1235,7 +1274,7 @@ if (!url) url = this.env.comm_path; - return url.replace(/_task=[a-z]+/, '_task='+task); + return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task); }; this.reload = function(delay) @@ -1313,7 +1352,7 @@ this.drag_menu = function(e, target) { var modkey = rcube_event.get_modifier(e), - menu = this.gui_objects.message_dragmenu; + menu = this.gui_objects.dragmenu; if (menu && modkey == SHIFT_KEY && this.commands['copy']) { var pos = rcube_event.get_mouse_pos(e); @@ -1327,7 +1366,7 @@ this.drag_menu_action = function(action) { - var menu = this.gui_objects.message_dragmenu; + var menu = this.gui_objects.dragmenu; if (menu) { $(menu).hide(); } @@ -1442,8 +1481,12 @@ list.draglayer.hide(); this.drag_end(e); - if (!this.drag_menu(e, target)) - this.command('moveto', target); + if (this.contact_list) { + if (!this.contacts_drag_menu(e, target)) + this.command('move', target); + } + else if (!this.drag_menu(e, target)) + this.command('move', target); } // reset 'pressed' buttons @@ -1490,7 +1533,7 @@ } } // Multi-message commands - this.enable_command('delete', 'moveto', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0); + this.enable_command('delete', 'move', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0); // reset all-pages-selection if (selected || (list.selection.length && list.selection.length != list.rowcount)) @@ -1498,7 +1541,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); }; @@ -1514,12 +1557,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); } }; @@ -1527,11 +1571,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) @@ -1571,7 +1615,7 @@ this.msglist_set_coltypes = function(list) { - var i, found, name, cols = list.list.tHead.rows[0].cells; + var i, found, name, cols = list.thead.rows[0].cells; this.env.coltypes = []; @@ -1592,42 +1636,55 @@ this.check_droptarget = function(id) { - if (this.task == 'mail') - return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual) ? 1 : 0; + switch (this.task) { + case 'mail': + 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; + case '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; - 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; + case 'addressbook': + var target; + if (id != this.env.source && (target = this.env.contactfolders[id])) { + // droptarget is a group + if (target.type == 'group') { + if (target.id != this.env.group && !this.env.contactfolders[target.source].readonly) { + var is_other = this.env.selection_sources.length > 1 || $.inArray(target.source, this.env.selection_sources) == -1; + return !is_other || this.commands.move ? 1 : 2; + } + } + // droptarget is a (writable) addressbook and it's not the source + else if (!target.readonly && (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1)) { + return this.commands.move ? 1 : 2; } } - // 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 0; }; - this.open_window = function(url, width, height) + // open popup window + this.open_window = function(url, small, toolbar) { - 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), - 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,location=no'); + var wname = 'rcmextwin' + new Date().getTime(); + + url += (url.match(/\?/) ? '&' : '?') + '_extwin=1'; + + if (this.env.standard_windows) + extwin = window.open(url, wname); + else { + 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(small ? this.env.popup_width_small : this.env.popup_width, page_width), + h = page_height, // always use same height + l = (win.screenLeft || win.screenX) + 20, + t = (win.screenTop || win.screenY) + 20, + extwin = window.open(url, wname, + 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,location=no,scrollbars=yes' + +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no')); + } // write loading... message to empty windows if (!url && extwin.document) { @@ -1636,10 +1693,8 @@ // focus window, delayed to bring to front window.setTimeout(function() { extwin.focus(); }, 10); - // position window with setTimeout for Chrome (#1488931) - window.setTimeout(function() { extwin.moveTo(l,t); }, bw.chrome ? 100 : 10); - return wname; + return extwin; }; @@ -1723,10 +1778,7 @@ + (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'); - - row.id = 'rcmrow'+uid; + row = { cols:[], style:{}, id:'rcmrow'+uid }; // message status icons css_class = 'msgicon'; @@ -1790,8 +1842,7 @@ // add each submitted col for (n in this.env.coltypes) { c = this.env.coltypes[n]; - col = document.createElement('td'); - col.className = String(c).toLowerCase(); + col = { className: String(c).toLowerCase() }; if (c == 'flag') { css_class = (flags.flagged ? 'flagged' : 'unflagged'); @@ -1836,8 +1887,7 @@ html = cols[c]; col.innerHTML = html; - - row.appendChild(col); + row.cols.push(col); } list.insert_row(row, attop); @@ -1938,7 +1988,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, true); else this.location_href(this.env.comm_path+url, target, true); @@ -1964,14 +2014,18 @@ if (name && (frame = this.get_frame_element(name))) { if (!show && (win = this.get_frame_window(name))) { - if (win.location && win.location.href.indexOf(this.env.blankpage)<0) - win.location.href = this.env.blankpage; + if (win.stop) + win.stop(); + else // IE + win.document.execCommand('Stop'); + + win.location.href = this.env.blankpage; } else if (!bw.safari && !bw.konq) $(frame)[show ? 'show' : 'hide'](); } - if (!show && this.busy) + if (!show && this.env.frame_lock) this.set_busy(false, null, this.env.frame_lock); }; @@ -2099,12 +2153,12 @@ this.clear_message_list = function() { - this.env.messages = {}; - this.last_selected = 0; + this.env.messages = {}; + this.last_selected = 0; - this.show_contentframe(false); - if (this.message_list) - this.message_list.clear(true); + this.show_contentframe(false); + if (this.message_list) + this.message_list.clear(true); }; // send remote request to load message list @@ -2200,7 +2254,7 @@ if (root) row = rows[root] ? rows[root].obj : null; else - row = this.message_list.list.tBodies[0].firstChild; + row = this.message_list.tbody.firstChild; while (row) { if (row.nodeType == 1 && (r = rows[row.uid])) { @@ -2376,7 +2430,7 @@ this.delete_excessive_thread_rows = function() { var rows = this.message_list.rows, - tbody = this.message_list.list.tBodies[0], + tbody = this.message_list.tbody, row = tbody.firstChild, cnt = this.env.pagesize + 1; @@ -2549,7 +2603,7 @@ // Hide message command buttons until a message is selected this.enable_command(this.env.message_commands, false); - this._with_selected_messages('moveto', post_data, lock); + this._with_selected_messages('move', post_data, lock); }; // delete selected messages from the current mailbox @@ -2608,7 +2662,7 @@ this._with_selected_messages('delete', post_data); }; - // Send a specifc moveto/delete request with UIDs of all selected messages + // Send a specifc move/delete request with UIDs of all selected messages // @private this._with_selected_messages = function(action, post_data, lock) { @@ -2650,7 +2704,7 @@ this.delete_excessive_thread_rows(); if (!lock) { - msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage'; + msg = action == 'move' ? 'movingmessage' : 'deletingmessage'; lock = this.display_message(this.get_label(msg), 'loading'); } @@ -2964,11 +3018,12 @@ // open new compose window if (this.env.compose_extwin && !this.env.extwin) { - this.open_window(url, 1150, 900); + this.open_window(url); } else { this.redirect(url); - window.resizeTo(Math.max(1150, $(window).width()), Math.max(900, $(window).height())); + if (this.env.extwin) + window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24); } }; @@ -2984,10 +3039,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 && !window.opener.closed && 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; } @@ -3010,7 +3065,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]); } } @@ -3071,12 +3126,18 @@ this.compose_recipient_select = function(list) { - this.enable_command('add-recipient', list.selection.length > 0); + var id, n, recipients = 0; + for (n=0; n < list.selection.length; n++) { + id = list.selection[n]; + if (this.env.contactdata[id]) + recipients++; + } + this.enable_command('add-recipient', recipients); }; 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++) { @@ -3095,8 +3156,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 }); } }; @@ -3286,6 +3349,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); }; @@ -3330,12 +3402,54 @@ if (!show_sig) show_sig = this.env.show_sig; - var cursor_pos, p = -1, + // 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 i, rx, 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 = this.env.identity, + delim = this.env.recipients_delimiter, + headers = ['replyto', 'bcc']; + + // update reply-to/bcc fields with addresses defined in identities + for (i in headers) { + var key = headers[i], + old_val = sig && this.env.identities[sig] ? this.env.identities[sig][key] : '', + new_val = id && this.env.identities[id] ? this.env.identities[id][key] : '', + input = $('[name="_'+key+'"]'), input_val = input.val(); + + // remove old address(es) + if (old_val && input_val) { + rx = new RegExp('\\s*' + RegExp.escape(old_val) + '\\s*'); + input_val = input_val.replace(rx, ''); + } + + // cleanup + rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g'); + input_val = input_val.replace(rx, delim) + rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$'); + input_val = input_val.replace(rx, '') + + // add new address(es) + if (new_val) { + rx = new RegExp(RegExp.escape(delim) + '\\s*$'); + if (input_val && !rx.test(input_val)) + input_val += delim + ' '; + input_val += new_val + delim + ' '; + } + + if (old_val || new_val) + input.val(input_val).change(); + } // enable manual signature insert if (this.env.signatures && this.env.signatures[id]) { @@ -3351,7 +3465,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); } @@ -3360,7 +3474,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; @@ -3404,7 +3518,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 @@ -3435,8 +3549,8 @@ return true; }; - // upload attachment file - this.upload_file = function(form) + // upload (attachment) file + this.upload_file = function(form, action) { if (!form) return false; @@ -3463,7 +3577,7 @@ return; } - var frame_name = this.async_upload_form(form, 'upload', function(e) { + var frame_name = this.async_upload_form(form, action || 'upload', function(e) { var d, content = ''; try { if (this.contentDocument) { @@ -3661,14 +3775,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) { + var rc = this.opener(); this.lock_form(this.gui_objects.messageform); - if (window.opener && !window.opener.closed && opener.rcmail) - 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 { @@ -4040,43 +4159,55 @@ if (this.preview_timer) clearTimeout(this.preview_timer); - var n, id, sid, ref = this, writable = false, + var n, id, sid, contact, 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); if (list.selection.length) { + list.draggable = false; + // 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) { + + if (source) { + this.env.selection_sources.push(this.env.source); + } + + for (n in list.selection) { + contact = list.data[list.selection[n]]; + if (!source) { sid = String(list.selection[n]).replace(/^[^-]+-/, ''); if (sid && this.env.address_sources[sid]) { - writable = writable || !this.env.address_sources[sid].readonly; + writable = writable || (!this.env.address_sources[sid].readonly && !contact.readonly); this.env.selection_sources.push(sid); } } - this.env.selection_sources = $.unique(this.env.selection_sources); + else { + writable = writable || (!source.readonly && !contact.readonly); + } + + if (contact._type != 'group') + list.draggable = true; } - else { - this.env.selection_sources.push(this.env.source); - writable = !source.readonly; - } + + this.env.selection_sources = $.unique(this.env.selection_sources); } // if a group is currently selected, and there is at least one contact selected // thend we can enable the group-remove-selected command - this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0); + this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0 && writable); this.enable_command('compose', this.env.group || list.selection.length > 0); - this.enable_command('export-selected', list.selection.length > 0); + this.enable_command('export-selected', 'copy', list.selection.length > 0); this.enable_command('edit', id && writable); - this.enable_command('delete', list.selection.length && writable); + this.enable_command('delete', 'move', list.selection.length > 0 && writable); return false; }; @@ -4104,10 +4235,28 @@ else if (!this.env.search_request) folder = group ? 'G'+src+group : src; - this.select_folder(folder, '', true); - this.env.source = src; this.env.group = group; + + // truncate groups listing stack + var index = $.inArray(this.env.group, this.env.address_group_stack); + if (index < 0) + this.env.address_group_stack = []; + else + this.env.address_group_stack = this.env.address_group_stack.slice(0,index); + + // make sure the current group is on top of the stack + if (this.env.group) { + this.env.address_group_stack.push(this.env.group); + + // mark the first group on the stack as selected in the directory list + folder = 'G'+src+this.env.address_group_stack[0]; + } + else if (this.gui_objects.addresslist_title) { + $(this.gui_objects.addresslist_title).html(this.get_label('contacts')); + } + + this.select_folder(folder, '', true); // load contacts remotely if (this.gui_objects.contactslist) { @@ -4163,16 +4312,38 @@ this.list_contacts_clear = function() { + this.contact_list.data = {}; this.contact_list.clear(true); this.show_contentframe(false); - this.enable_command('delete', false); + this.enable_command('delete', 'move', 'copy', false); this.enable_command('compose', this.env.group ? true : false); + }; + + this.set_group_prop = function(prop) + { + if (this.gui_objects.addresslist_title) { + var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents + + // add link to pop back to parent group + if (this.env.address_group_stack.length > 1) { + $('<a href="#list">...</a>') + .addClass('poplink') + .appendTo(boxtitle) + .click(function(e){ return ref.command('popgroup','',this); }); + boxtitle.append(' » '); + } + + boxtitle.append($('<span>'+prop.name+'</span>')); + } + + this.triggerEvent('groupupdate', prop); }; // load contact record this.load_contact = function(cid, action, framed) { - var win, url = {}, target = window; + var win, url = {}, target = window, + rec = this.contact_list ? this.contact_list.data[cid] : null; if (win = this.get_frame_window(this.env.contentframe)) { url._framed = 1; @@ -4182,7 +4353,9 @@ // load dummy content, unselect selected row(s) if (!cid) this.contact_list.clear_selection(); - this.enable_command('delete', 'compose', 'export-selected', cid); + + this.enable_command('compose', rec && rec.email); + this.enable_command('export-selected', rec && rec._type != 'group'); } else if (framed) return false; @@ -4212,14 +4385,38 @@ this.http_post('group-'+what+'members', post_data, lock); }; - // copy a contact to the specified target (group or directory) - this.copy_contact = function(cid, to) + this.contacts_drag_menu = function(e, to) + { + var dest = to.type == 'group' ? to.source : to.id, + source = this.env.source; + + if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly) + return true; + + // 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]; + + if (to.type == 'group' && dest == source) { + var cid = this.contact_list.get_selection().join(','); + this.group_member_change('add', cid, dest, to.id); + return true; + } + // move action is not possible, "redirect" to copy if menu wasn't requested + else if (!this.commands.move && rcube_event.get_modifier(e) != SHIFT_KEY) { + this.copy_contacts(to); + return true; + } + + return this.drag_menu(e, to); + }; + + // copy contact(s) to the specified target (group or directory) + this.copy_contacts = function(to) { var n, dest = to.type == 'group' ? to.source : to.id, source = this.env.source, - group = this.env.group ? this.env.group : ''; - - if (!cid) + group = this.env.group ? this.env.group : '', cid = this.contact_list.get_selection().join(','); if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly) @@ -4232,13 +4429,12 @@ // 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}; + return; - this.http_post('copy', post_data, lock); - } + 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); } // target is an addressbook else if (to.id != source) { @@ -4249,19 +4445,53 @@ } }; - this.delete_contacts = function() + // move contact(s) to the specified target (group or directory) + this.move_contacts = function(to) { - var selection = this.contact_list.get_selection(), - undelete = this.env.source && this.env.address_sources[this.env.source].undelete; + var dest = to.type == 'group' ? to.source : to.id, + source = this.env.source, + group = this.env.group ? this.env.group : ''; - // exit if no mailbox specified or if selection is empty - if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm')))) + if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly) return; - var id, n, a_cids = [], - post_data = {_source: this.env.source, _from: (this.env.action ? this.env.action : '')}, - lock = this.display_message(this.get_label('contactdeleting'), 'loading'); + // 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]; + if (to.type == 'group') { + if (dest == source) + return; + + this._with_selected_contacts('move', {_to: dest, _togid: to.id}); + } + // target is an addressbook + else if (to.id != source) + this._with_selected_contacts('move', {_to: to.id}); + }; + + // delete contact(s) + this.delete_contacts = function() + { + var undelete = this.env.source && this.env.address_sources[this.env.source].undelete; + + if (!undelete && !confirm(this.get_label('deletecontactconfirm'))) + return; + + return this._with_selected_contacts('delete'); + }; + + this._with_selected_contacts = function(action, post_data) + { + var selection = this.contact_list ? this.contact_list.get_selection() : []; + + // exit if no mailbox specified or if selection is empty + if (!selection.length && !this.env.cid) + return; + + var n, a_cids = [], + label = action == 'delete' ? 'contactdeleting' : 'movingcontact', + lock = this.display_message(this.get_label(label), 'loading'); if (this.env.cid) a_cids.push(this.env.cid); else { @@ -4276,6 +4506,11 @@ this.show_contentframe(false); } + if (!post_data) + post_data = {}; + + post_data._source = this.env.source; + post_data._from = this.env.action; post_data._cid = a_cids.join(','); if (this.env.group) @@ -4286,13 +4521,13 @@ post_data._search = this.env.search_request; // send request to server - this.http_post('delete', post_data, lock) + this.http_post(action, post_data, lock) return true; }; // update a contact record in the list - this.update_contact_row = function(cid, cols_arr, newcid, source) + this.update_contact_row = function(cid, cols_arr, newcid, source, data) { var c, row, list = this.contact_list; @@ -4305,31 +4540,18 @@ newcid = newcid+'-'+source; } - if (list.rows[cid] && (row = list.rows[cid].obj)) { - for (c=0; c<cols_arr.length; c++) - if (row.cells[c]) - $(row.cells[c]).html(cols_arr[c]); - - // cid change - if (newcid) { - newcid = this.html_identifier(newcid); - row.id = 'rcmrow' + newcid; - list.remove_row(cid); - list.init_row(row); - list.selection[0] = newcid; - row.style.display = ''; - } - } + list.update_row(cid, cols_arr, newcid, true); + list.data[cid] = data; }; // add row to contacts list - this.add_contact_row = function(cid, cols, classes) + this.add_contact_row = function(cid, cols, classes, data) { if (!this.gui_objects.contactslist) return false; var c, col, list = this.contact_list, - row = document.createElement('tr'); + row = { cols:[] }; row.id = 'rcmrow'+this.html_identifier(cid); row.className = 'contact ' + (classes || ''); @@ -4339,12 +4561,14 @@ // add each submitted col for (c in cols) { - col = document.createElement('td'); + col = {}; col.className = String(c).toLowerCase(); col.innerHTML = cols[c]; - row.appendChild(col); + row.cols.push(col); } + // store data in list member + list.data[cid] = data; list.insert_row(row); this.enable_command('export', list.rowcount > 0); @@ -4448,11 +4672,22 @@ this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); }); this.name_input_li = $('<li>').addClass(type).append(this.name_input); - var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : $('ul.groups li:last', this.get_folder_li(this.env.source,'',true)); + var ul, li; + + // find list (UL) element + if (type == 'contactsearch') + ul = this.gui_objects.folderlist; + else + ul = $('ul.groups', this.get_folder_li(this.env.source,'',true)); + + // append to the list + li = $('li:last', ul); if (li.length) this.name_input_li.insertAfter(li); - else - this.name_input_li.appendTo(type == 'contactsearch' ? this.gui_objects.folderlist : $('ul.groups', this.get_folder_li(this.env.source,'',true))); + else { + this.name_input_li.appendTo(ul); + ul.show(); // make sure the list is visible + } } this.name_input.select().focus(); @@ -4509,11 +4744,13 @@ this.reset_add_input = function() { if (this.name_input) { + var li = this.name_input.parent(); if (this.env.group_renaming) { - var li = this.name_input.parent(); li.children().last().show(); this.env.group_renaming = false; } + else if ($('li', li.parent()).length == 1) + li.parent().hide(); this.name_input.remove(); @@ -4758,7 +4995,7 @@ this.replace_contact_photo = function(id) { var img_src = id == '-del-' ? this.env.photo_placeholder : - this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id; + this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + (this.env.cid || 0) + '&_photo=' + id; this.set_photo_actions(id); $(this.gui_objects.contactphoto).children('img').attr('src', img_src); @@ -4941,17 +5178,15 @@ this.update_identity_row = function(id, name, add) { - var row, col, list = this.identity_list, + var list = this.identity_list, rid = this.html_identifier(id); - if (list.rows[rid] && (row = list.rows[rid].obj)) { - $(row.cells[0]).html(name); - } - else if (add) { - row = $('<tr>').attr('id', 'rcmrow'+rid).get(0); - col = $('<td>').addClass('mail').html(name).appendTo(row); - list.insert_row(row); + if (add) { + list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] }); list.select(rid); + } + else { + list.update_row(rid, [ name ]); } }; @@ -5643,11 +5878,11 @@ }; // 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()) { - parent.rcmail.show_popup_dialog(html, title); + parent.rcmail.show_popup_dialog(html, title, buttons); return; } @@ -5655,17 +5890,21 @@ .html(html) .dialog({ title: title, + buttons: buttons, modal: true, resizable: true, - width: 580, + width: 500, close: function(event, ui) { $(this).remove() } }); - // 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) }) - .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?) + // 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 + 75 + (buttons ? 50 : 0)), + width: Math.min(w - 20, width + 20) + }); }; // enable/disable buttons for page shifting @@ -5727,7 +5966,7 @@ this.set_message_coltypes = function(coltypes, repl, smart_col) { var list = this.message_list, - thead = list ? list.list.tHead : null, + thead = list ? list.thead : null, cell, col, n, len, th, tr; this.env.coltypes = coltypes; @@ -5740,14 +5979,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++) { @@ -5969,9 +6208,9 @@ var base = this.env.comm_path, k, param = {}; // overwrite task name - if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) { + if (query._action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) { query._action = RegExp.$2; - base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); + base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1); } // remove undefined values @@ -6157,7 +6396,7 @@ this.enable_command('export-selected', false); } - case 'moveto': + case 'move': if (this.env.action == 'show') { // re-enable commands on move/delete error this.enable_command(this.env.message_commands, true); @@ -6198,6 +6437,7 @@ if ((response.action == 'list' || response.action == 'search') && this.message_list) { this.msglist_select(this.message_list); + this.message_list.resize(); this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); } } @@ -6208,6 +6448,7 @@ this.enable_command('search-create', this.env.source == ''); this.enable_command('search-delete', this.env.search_id); this.update_group_commands(); + this.contact_list.resize(); this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount }); } } @@ -6247,6 +6488,14 @@ var location_url = request.getResponseHeader("Location"); 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') @@ -6361,9 +6610,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'); } }); @@ -6403,7 +6653,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 @@ -6500,6 +6750,17 @@ /********* helper methods *********/ /********************************************************/ + // 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() -- Gitblit v1.9.1