From 2965a981b7ec22866fbdf2d567d87e2d068d3617 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Fri, 31 Jul 2015 16:04:08 -0400 Subject: [PATCH] Allow to search and import missing PGP pubkeys from keyservers using Publickey.js --- program/js/app.js | 824 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 731 insertions(+), 93 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 227ba32..fcba219 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -326,10 +326,7 @@ this.enable_command('download', 'print', true); // show printing dialog else if (this.env.action == 'print' && this.env.uid) { - if (bw.safari) - setTimeout('window.print()', 10); - else - window.print(); + this.print_dialog(); } // get unread count for each mailbox @@ -378,6 +375,8 @@ } this.http_post(postact, postdata); } + + this.check_mailvelope(this.env.action); // detect browser capabilities if (!this.is_framed() && !this.env.extwin) @@ -439,6 +438,9 @@ this.enable_command('save', true); if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search') this.init_contact_form(); + } + else if (this.env.action == 'print') { + this.print_dialog(); } break; @@ -654,7 +656,7 @@ // check input before leaving compose step if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands) < 0 && !this.env.server_error) { - if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) + if (!this.env.is_sent && this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) return false; // remove copy from local storage if compose screen is left intentionally @@ -774,7 +776,7 @@ case 'list': if (props && props != '') { - this.reset_qsearch(); + this.reset_qsearch(true); } if (this.env.action == 'compose' && this.env.extwin) { window.close(); @@ -894,7 +896,7 @@ else { // reload form if (props == 'reload') { - form.action += '?_reload=1'; + form.action += '&_reload=1'; } else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 && (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val()) @@ -1115,7 +1117,7 @@ break; case 'send': - if (!props.nocheck && !this.check_compose_input(command)) + if (!props.nocheck && !this.env.is_sent && !this.check_compose_input(command)) break; // Reset the auto-save timer @@ -1176,7 +1178,15 @@ break; case 'print': - if (this.env.action == 'get') { + if (this.task == 'addressbook') { + if (uid = this.contact_list.get_single_selection()) { + url = '&_action=print&_cid=' + uid; + if (this.env.source) + url += '&_source=' + urlencode(this.env.source); + this.open_window(this.env.comm_path + url, true, true); + } + } + else if (this.env.action == 'get') { this.gui_objects.messagepartframe.contentWindow.print(); } else if (uid = this.get_single_uid()) { @@ -1213,17 +1223,16 @@ // reset quicksearch case 'reset-search': - var n, s = this.env.search_request || this.env.qsearch, - ss = this.gui_objects.qsearchbox && this.gui_objects.qsearchbox.value != ''; + var n, s = this.env.search_request || this.env.qsearch; - this.reset_qsearch(); + this.reset_qsearch(true); this.select_all_mode = false; if (s && this.env.action == 'compose') { if (this.contact_list) this.list_contacts_clear(); } - else if (s && ss && this.env.mailbox) { + else if (s && this.env.mailbox) { this.list_mailbox(this.env.mailbox, 1); } else if (s && this.task == 'addressbook') { @@ -1433,7 +1442,7 @@ else if (delay) setTimeout(function() { ref.reload(); }, delay); else if (window.location) - location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : ''); + location.href = this.url('', {_extwin: this.env.extwin}); }; // Add variable to GET string, replace old value if exists @@ -1600,7 +1609,8 @@ this.folder_collapsed = function(node) { - var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders'; + var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders', + old = this.env[prefname]; if (node.collapsed) { this.env[prefname] = this.env[prefname] + '&'+urlencode(node.id)+'&'; @@ -1616,7 +1626,8 @@ } if (!this.drag_active) { - this.command('save-pref', { name: prefname, value: this.env[prefname] }); + if (old !== this.env[prefname]) + this.command('save-pref', { name: prefname, value: this.env[prefname] }); if (this.env.unread_counts) this.set_unread_count_display(node.id, false); @@ -2368,6 +2379,9 @@ // list messages of a specific mailbox using filter this.filter_mailbox = function(filter) { + if (this.filter_disabled) + return; + var lock = this.set_busy(true, 'searching'); this.clear_message_list(); @@ -2401,16 +2415,17 @@ if (sort) url._sort = sort; - // also send search request to get the right messages - if (this.env.search_request) - url._search = this.env.search_request; - - // set page=1 if changeing to another mailbox + // folder change, reset page, search scope, etc. if (this.env.mailbox != mbox) { page = 1; this.env.current_page = page; + this.env.search_scope = 'base'; this.select_all_mode = false; + this.reset_search_filter(); } + // also send search request to get the right messages + else if (this.env.search_request) + url._search = this.env.search_request; if (!update_only) { // unselect selected messages and clear the list and message data @@ -3242,6 +3257,530 @@ this.set_alttext('delete', label); }; + // Initialize input element for list page jump + this.init_pagejumper = function(element) + { + $(element).addClass('rcpagejumper') + .on('focus', function(e) { + // create and display popup with page selection + var i, html = ''; + + for (i = 1; i <= ref.env.pagecount; i++) + html += '<li>' + i + '</li>'; + + html = '<ul class="toolbarmenu">' + html + '</ul>'; + + if (!ref.pagejump) { + ref.pagejump = $('<div id="pagejump-selector" class="popupmenu"></div>') + .appendTo(document.body) + .on('click', 'li', function() { + if (!ref.busy) + $(element).val($(this).text()).change(); + }); + } + + if (ref.pagejump.data('count') != i) + ref.pagejump.html(html); + + ref.pagejump.attr('rel', '#' + this.id).data('count', i); + + // display page selector + ref.show_menu('pagejump-selector', true, e); + $(this).keydown(); + }) + // keyboard navigation + .on('keydown keyup click', function(e) { + var current, selector = $('#pagejump-selector'), + ul = $('ul', selector), + list = $('li', ul), + height = ul.height(), + p = parseInt(this.value); + + if (e.which != 27 && e.which != 9 && e.which != 13 && !selector.is(':visible')) + return ref.show_menu('pagejump-selector', true, e); + + if (e.type == 'keydown') { + // arrow-down + if (e.which == 40) { + if (list.length > p) + this.value = (p += 1); + } + // arrow-up + else if (e.which == 38) { + if (p > 1 && list.length > p - 1) + this.value = (p -= 1); + } + // enter + else if (e.which == 13) { + return $(this).change(); + } + // esc, tab + else if (e.which == 27 || e.which == 9) { + return $(element).val(ref.env.current_page); + } + } + + $('li.selected', ul).removeClass('selected'); + + if ((current = $(list[p - 1])).length) { + current.addClass('selected'); + $('#pagejump-selector').scrollTop(((ul.height() / list.length) * (p - 1)) - selector.height() / 2); + } + }) + .on('change', function(e) { + // go to specified page + var p = parseInt(this.value); + if (p && p != ref.env.current_page && !ref.busy) { + ref.hide_menu('pagejump-selector'); + ref.list_page(p); + } + }); + }; + + // Update page-jumper state on list updates + this.update_pagejumper = function() + { + $('input.rcpagejumper').val(this.env.current_page).prop('disabled', this.env.pagecount < 2); + }; + + // check for mailvelope API + this.check_mailvelope = function(action) + { + if (typeof window.mailvelope !== 'undefined') { + this.mailvelope_load(action); + } + else { + $(window).on('mailvelope', function() { + ref.mailvelope_load(action); + }); + } + }; + + // + this.mailvelope_load = function(action) + { + if (this.env.browser_capabilities) + this.env.browser_capabilities['pgpmime'] = 1; + + var keyring = this.get_local_storage_prefix(); + + mailvelope.getKeyring(keyring).then(function(kr) { + ref.mailvelope_keyring = kr; + ref.mailvelope_init(action, kr); + }).catch(function(err) { + // attempt to create a new keyring for this app/user + mailvelope.createKeyring(keyring).then(function(kr) { + ref.mailvelope_keyring = kr; + ref.mailvelope_init(action, kr); + }).catch(function(err) { + console.error(err); + }); + }); + }; + + // + this.mailvelope_init = function(action, keyring) + { + if (action == 'show' || action == 'preview') { + // decrypt text body + if (this.env.is_pgp_content && window.mailvelope) { + var data = $(this.env.is_pgp_content).text(); + ref.mailvelope_display_container(this.env.is_pgp_content, data, keyring); + } + // load pgp/mime message and pass it to the mailvelope display container + else if (this.env.pgp_mime_part && window.mailvelope) { + var msgid = this.display_message(this.get_label('loadingdata'), 'loading'), + selector = this.env.pgp_mime_container; + + $.ajax({ + type: 'GET', + url: this.url('get', { '_mbox': this.env.mailbox, '_uid': this.env.uid, '_part': this.env.pgp_mime_part }), + error: function(o, status, err) { + ref.http_error(o, status, err, msgid); + }, + success: function(data) { + ref.mailvelope_display_container(selector, data, keyring, msgid); + } + }); + } + } + else if (action == 'compose' && window.mailvelope) { + this.env.compose_commands.push('compose-encrypted'); + + if (this.env.pgp_mime_message) { + // fetch PGP/Mime part and open load into Mailvelope editor + var lock = this.set_busy(true, this.get_label('loadingdata')); + $.ajax({ + type: 'GET', + url: this.url('get', this.env.pgp_mime_message), + error: function(o, status, err) { + ref.http_error(o, status, err, lock); + ref.enable_command('compose-encrypted', true); + }, + success: function(data) { + ref.set_busy(false, null, lock); + ref.compose_encrypted({ quotedMail: data }); + ref.enable_command('compose-encrypted', true); + } + }); + } + else { + // enable encrypted compose toggle + this.enable_command('compose-encrypted', true); + } + } + }; + + // handler for the 'compose-encrypted' command + this.compose_encrypted = function(props) + { + var container = $('#' + this.env.composebody).parent(); + + // remove Mailvelope editor if active + if (ref.mailvelope_editor) { + ref.mailvelope_editor = null; + ref.compose_skip_unsavedcheck = false; + ref.set_button('compose-encrypted', 'act'); + + container.removeClass('mailvelope') + .find('iframe:not([aria-hidden=true])').remove(); + $('#' + ref.env.composebody).show(); + $("[name='_pgpmime']").remove(); + } + // embed Mailvelope editor container + else { + var options = { predefinedText: $('#' + this.env.composebody).val() }; + if (props.quotedMail) { + options = { quotedMail: props.quotedMail, quotedMailIndent: false }; + } + if (this.env.compose_mode == 'reply') { + options.quotedMailIndent = true; + options.quotedMailHeader = this.env.compose_reply_header; + } + + mailvelope.createEditorContainer('#' + container.attr('id'), ref.mailvelope_keyring, options).then(function(editor) { + ref.mailvelope_editor = editor; + ref.compose_skip_unsavedcheck = true; + ref.set_button('compose-encrypted', 'sel'); + + container.addClass('mailvelope'); + $('#' + ref.env.composebody).hide(); + + // notify user about loosing attachments + if (ref.env.attachments && !$.isEmptyObject(ref.env.attachments)) { + alert(ref.get_label('encryptnoattachments')); + + $.each(ref.env.attachments, function(name, attach) { + ref.remove_from_attachment_list(name); + }); + } + }).catch(function(err) { + console.error(err); + }); + } + }; + + // callback to replace the message body with the full armored + this.mailvelope_submit_messageform = function(draft, saveonly) + { + // get recipients + var recipients = []; + $.each(['to', 'cc', 'bcc'], function(i,field) { + var pos, rcpt, val = $.trim($('[name="_' + field + '"]').val()); + while (val.length && rcube_check_email(val, true)) { + rcpt = RegExp.$2; + recipients.push(rcpt); + val = val.substr(val.indexOf(rcpt) + rcpt.length + 1).replace(/^\s*,\s*/, ''); + } + }); + + // check if we have keys for all recipients + var isvalid = recipients.length > 0; + ref.mailvelope_keyring.validKeyForAddress(recipients).then(function(status) { + var missing_keys = []; + $.each(status, function(k,v) { + if (v === false) { + isvalid = false; + missing_keys.push(k); + } + }); + + // list recipients with missing keys + if (!isvalid && missing_keys.length) { + // load publickey.js + if (!$('script#publickeyjs').length) { + $('<script>') + .attr('id', 'publickeyjs') + .attr('src', ref.assets_path('program/js/publickey.js')) + .appendTo(document.body); + } + + // display dialog with missing keys + ref.show_popup_dialog( + ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')) + + '<p>' + ref.get_label('searchpubkeyservers') + '</p>', + ref.get_label('encryptedsendialog'), + [{ + text: ref.get_label('search'), + 'class': 'mainaction', + click: function() { + var $dialog = $(this); + ref.mailvelope_search_pubkeys(missing_keys, function() { + $dialog.dialog('close') + }); + } + }, + { + text: ref.get_label('cancel'), + click: function(){ + $(this).dialog('close'); + } + }] + ); + return false; + } + + if (!isvalid) { + if (!recipients.length) { + alert(ref.get_label('norecipientwarning')); + $("[name='_to']").focus(); + } + return false; + } + + // add sender identity to recipients to be able to decrypt our very own message + var senders = [], selected_sender = ref.env.identities[$("[name='_from'] option:selected").val()]; + $.each(ref.env.identities, function(k, sender) { + senders.push(sender.email); + }); + + ref.mailvelope_keyring.validKeyForAddress(senders).then(function(status) { + valid_sender = null; + $.each(status, function(k,v) { + if (v !== false) { + valid_sender = k; + if (valid_sender == selected_sender) { + return false; // break + } + } + }); + + if (!valid_sender) { + if (!confirm(ref.get_label('nopubkeyforsender'))) { + return false; + } + } + + recipients.push(valid_sender); + + ref.mailvelope_editor.encrypt(recipients).then(function(armored) { + // all checks passed, send message + var form = ref.gui_objects.messageform, + hidden = $("[name='_pgpmime']", form), + msgid = ref.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage') + + form.target = 'savetarget'; + form._draft.value = draft ? '1' : ''; + form.action = ref.add_url(form.action, '_unlock', msgid); + form.action = ref.add_url(form.action, '_framed', 1); + + if (saveonly) { + form.action = ref.add_url(form.action, '_saveonly', 1); + } + + // send pgp conent via hidden field + if (!hidden.length) { + hidden = $('<input type="hidden" name="_pgpmime">').appendTo(form); + } + hidden.val(armored); + + form.submit(); + + }).catch(function(err) { + console.log(err); + }); // mailvelope_editor.encrypt() + + }).catch(function(err) { + console.error(err); + }); // mailvelope_keyring.validKeyForAddress(senders) + + }).catch(function(err) { + console.error(err); + }); // mailvelope_keyring.validKeyForAddress(recipients) + + return false; + }; + + // wrapper for the mailvelope.createDisplayContainer API call + this.mailvelope_display_container = function(selector, data, keyring, msgid) + { + mailvelope.createDisplayContainer(selector, data, keyring, { showExternalContent: this.env.safemode }).then(function() { + $(selector).addClass('mailvelope').find('.message-part, .part-notice').hide(); + ref.hide_message(msgid); + setTimeout(function() { $(window).resize(); }, 10); + }).catch(function(err) { + console.error(err); + ref.hide_message(msgid); + ref.display_message('Message decryption failed: ' + err.message, 'error') + }); + }; + + // subroutine to query keyservers for public keys + this.mailvelope_search_pubkeys = function(emails, resolve) + { + // query with publickey.js + var deferreds = [], + pk = new PublicKey(), + lock = ref.display_message(ref.get_label('loading'), 'loading'); + + $.each(emails, function(i, email) { + var d = $.Deferred(); + pk.search(email, function(results, errorCode) { + if (errorCode !== null) { + // rejecting would make all fail + // d.reject(email); + d.resolve([email]); + } + else { + d.resolve([email].concat(results)); + } + }); + deferreds.push(d); + }); + + $.when.apply($, deferreds).then(function() { + var missing_keys = [], + key_selection = []; + + // alanyze results of all queries + $.each(arguments, function(i, result) { + var email = result.shift(); + if (!result.length) { + missing_keys.push(email); + } + else { + key_selection = key_selection.concat(result); + } + }); + + ref.hide_message(lock); + resolve(true); + + // show key import dialog + if (key_selection.length) { + ref.mailvelope_key_import_dialog(key_selection); + } + // some keys could not be found + if (missing_keys.length) { + ref.display_message(ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')), 'warning'); + } + }, function() { + console.error('Pubkey lookup failed with', arguments); + ref.hide_message(lock); + ref.display_message('pubkeysearcherror', 'error'); + resolve(false); + }); + }; + + // list the given public keys in a dialog with options to import + // them into the local Maivelope keyring + this.mailvelope_key_import_dialog = function(candidates) + { + var ul = $('<div>').addClass('listing mailvelopekeyimport'); + $.each(candidates, function(i, keyrec) { + var li = $('<div>').addClass('key'); + if (keyrec.revoked) li.addClass('revoked'); + if (keyrec.disabled) li.addClass('disabled'); + if (keyrec.expired) li.addClass('expired'); + + li.append($('<label>').addClass('keyid').text(ref.get_label('keyid'))); + li.append($('<a>').text(keyrec.keyid.substr(-8).toUpperCase()) + .attr('href', keyrec.info) + .attr('target', '_blank') + .attr('tabindex', '-1')); + + li.append($('<label>').addClass('keylen').text(ref.get_label('keylength'))); + li.append($('<span>').text(keyrec.keylen)); + + if (keyrec.expirationdate) { + li.append($('<label>').addClass('keyexpired').text(ref.get_label('keyexpired'))); + li.append($('<span>').text(new Date(keyrec.expirationdate * 1000).toDateString())); + } + + if (keyrec.revoked) { + li.append($('<span>').addClass('keyrevoked').text(ref.get_label('keyrevoked'))); + } + + var ul_ = $('<ul>').addClass('uids'); + $.each(keyrec.uids, function(j, uid) { + var li_ = $('<li>').addClass('uid'); + if (uid.revoked) li_.addClass('revoked'); + if (uid.disabled) li_.addClass('disabled'); + if (uid.expired) li_.addClass('expired'); + + ul_.append(li_.text(uid.uid)); + }); + + li.append(ul_); + li.append($('<input>') + .attr('type', 'button') + .attr('rel', keyrec.keyid) + .attr('value', ref.get_label('import')) + .addClass('button importkey') + .prop('disabled', keyrec.revoked || keyrec.disabled || keyrec.expired)); + + ul.append(li); + }); + + // display dialog with missing keys + ref.show_popup_dialog( + $('<div>') + .append($('<p>').html(ref.get_label('encryptpubkeysfound'))) + .append(ul), + ref.get_label('importpubkeys'), + [{ + text: ref.get_label('close'), + click: function(){ + $(this).dialog('close'); + } + }] + ); + + // delegate handler for import button clicks + ul.on('click', 'input.button.importkey', function() { + var btn = $(this), + keyid = btn.attr('rel'), + pk = new PublicKey(), + lock = ref.display_message(ref.get_label('loading'), 'loading'); + + // fetch from keyserver and import to Mailvelope keyring + pk.get(keyid, function(armored, errorCode) { + ref.hide_message(lock); + + if (errorCode) { + ref.display_message('Failed to get key from keyserver', 'error'); + return; + } + + // import to keyring + ref.mailvelope_keyring.importPublicKey(armored).then(function(status) { + if (status === 'REJECTED') { + // alert(ref.get_label('Key import was rejected')); + } + else { + btn.closest('.key').fadeOut(); + ref.display_message(ref.get_label('Public key $key successfully imported into your key ring') + .replace('$key', keyid.substr(-8).toUpperCase()), 'confirmation'); + } + }).catch(function(err) { + console.log(err); + }); + }); + }); + + }; + + /*********************************************************/ /********* mailbox folders methods *********/ /*********************************************************/ @@ -3336,7 +3875,7 @@ if (!this.gui_objects.messageform) return false; - var i, pos, input_from = $("[name='_from']"), + var i, elem, pos, input_from = $("[name='_from']"), input_to = $("[name='_to']"), input_subject = $("input[name='_subject']"), input_message = $("[name='_message']").get(0), @@ -3371,13 +3910,15 @@ if (!html_mode) { pos = this.env.top_posting ? 0 : input_message.value.length; - this.set_caret_pos(input_message, pos); // add signature according to selected identity - // if we have HTML editor, signature is added in callback + // if we have HTML editor, signature is added in a callback if (input_from.prop('type') == 'select-one') { this.change_identity(input_from[0]); } + + // set initial cursor position + this.set_caret_pos(input_message, pos); // scroll to the bottom of the textarea (#1490114) if (pos) { @@ -3390,11 +3931,14 @@ this.compose_restore_dialog(0, html_mode) if (input_to.val() == '') - input_to.focus(); + elem = input_to; else if (input_subject.val() == '') - input_subject.focus(); + elem = input_subject; else if (input_message) - input_message.focus(); + elem = input_message; + + // focus first empty element (need to be visible on IE8) + $(elem).filter(':visible').focus(); this.env.compose_focus_elem = document.activeElement; @@ -3481,15 +4025,40 @@ .attr({ 'autocomplete': 'off', 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'role': 'combobox' }); }; - this.submit_messageform = function(draft) + this.submit_messageform = function(draft, saveonly) { var form = this.gui_objects.messageform; if (!form) return; + // the message has been sent but not saved, ask the user what to do + if (!saveonly && this.env.is_sent) { + return this.show_popup_dialog(this.get_label('messageissent'), '', + [{ + text: this.get_label('save'), + 'class': 'mainaction', + click: function() { + ref.submit_messageform(false, true); + $(this).dialog('close'); + } + }, + { + text: this.get_label('cancel'), + click: function() { + $(this).dialog('close'); + } + }] + ); + } + + // delegate sending to Mailvelope routine + if (this.mailvelope_editor) { + return this.mailvelope_submit_messageform(draft, saveonly); + } + // all checks passed, send message - var msgid = this.set_busy(true, draft ? 'savingmessage' : 'sendingmessage'), + var msgid = this.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage'), lang = this.spellcheck_lang(), files = []; @@ -3502,6 +4071,10 @@ form.action = this.add_url(form.action, '_unlock', msgid); form.action = this.add_url(form.action, '_lang', lang); form.action = this.add_url(form.action, '_framed', 1); + + if (saveonly) { + form.action = this.add_url(form.action, '_saveonly', 1); + } // register timer to notify about connection timeout this.submit_timer = setTimeout(function(){ @@ -3801,7 +4374,7 @@ // reset history of hidden iframe used for saving draft (#1489643) // but don't do this on timer-triggered draft-autosaving (#1489789) - if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit) { + if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit && !this.mailvelope_editor) { window.frames['savetarget'].history.back(); } @@ -3870,6 +4443,11 @@ if (this.env.attachments) for (id in this.env.attachments) str += id; + + // we can't detect changes in the Mailvelope editor so assume it changed + if (this.mailvelope_editor) { + str += ';' + new Date().getTime(); + } if (save) this.cmp_hash = str; @@ -4298,14 +4876,28 @@ return url; }; + // reset search filter + this.reset_search_filter = function() + { + this.filter_disabled = true; + if (this.gui_objects.search_filter) + $(this.gui_objects.search_filter).val('ALL').change(); + this.filter_disabled = false; + }; + // reset quick-search form - this.reset_qsearch = function() + this.reset_qsearch = function(all) { if (this.gui_objects.qsearchbox) this.gui_objects.qsearchbox.value = ''; if (this.env.qsearch) this.abort_request(this.env.qsearch); + + if (all) { + this.env.search_scope = 'base'; + this.reset_search_filter(); + } this.env.qsearch = null; this.env.search_request = null; @@ -4350,13 +4942,14 @@ }; // action executed after mail is sent - this.sent_successfully = function(type, msg, folders) + this.sent_successfully = function(type, msg, folders, save_error) { this.display_message(msg, type); this.compose_skip_unsavedcheck = true; if (this.env.extwin) { - this.lock_form(this.gui_objects.messageform); + if (!save_error) + this.lock_form(this.gui_objects.messageform); var filter = {task: 'mail', action: ''}, rc = this.opener(false, filter) || this.opener(true, filter); @@ -4369,12 +4962,16 @@ } } - setTimeout(function() { window.close(); }, 1000); + if (!save_error) + setTimeout(function() { window.close(); }, 1000); } - else { + else if (!save_error) { // before redirect we need to wait some time for Chrome (#1486177) setTimeout(function() { ref.list_mailbox(); }, 500); } + + if (save_error) + this.env.is_sent = true; }; @@ -4721,6 +5318,7 @@ clearTimeout(this.preview_timer); var n, id, sid, contact, writable = false, + selected = list.selection.length, 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 @@ -4729,7 +5327,7 @@ else if (this.env.contentframe) this.show_contentframe(false); - if (list.selection.length) { + if (selected) { list.draggable = false; // no source = search result, we'll need to detect if any of @@ -4764,11 +5362,12 @@ // 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 && writable); - this.enable_command('compose', this.env.group || list.selection.length > 0); - this.enable_command('export-selected', 'copy', list.selection.length > 0); + this.enable_command('group-remove-selected', this.env.group && selected && writable); + this.enable_command('compose', this.env.group || selected); + this.enable_command('print', selected == 1); + this.enable_command('export-selected', 'copy', selected > 0); this.enable_command('edit', id && writable); - this.enable_command('delete', 'move', list.selection.length > 0 && writable); + this.enable_command('delete', 'move', selected && writable); return false; }; @@ -4881,8 +5480,8 @@ this.contact_list.data = {}; this.contact_list.clear(true); this.show_contentframe(false); - this.enable_command('delete', 'move', 'copy', false); - this.enable_command('compose', this.env.group ? true : false); + this.enable_command('delete', 'move', 'copy', 'print', false); + this.enable_command('compose', this.env.group); }; this.set_group_prop = function(prop) @@ -4922,7 +5521,7 @@ this.contact_list.clear_selection(); this.enable_command('compose', rec && rec.email); - this.enable_command('export-selected', rec && rec._type != 'group'); + this.enable_command('export-selected', 'print', rec && rec._type != 'group'); } else if (framed) return false; @@ -6610,6 +7209,8 @@ { this.enable_command('nextpage', 'lastpage', this.env.pagecount > this.env.current_page); this.enable_command('previouspage', 'firstpage', this.env.current_page > 1); + + this.update_pagejumper(); }; // mark a mailbox as selected and set environment variable @@ -6666,6 +7267,9 @@ repl, cell, col, n, len, tr; this.env.listcols = listcols; + + if (!this.env.coltypes) + this.env.coltypes = {}; // replace old column headers if (thead) { @@ -7158,7 +7762,7 @@ // compose a valid url with the given parameters this.url = function(action, query) { - var querystring = typeof query === 'string' ? '&' + query : ''; + var querystring = typeof query === 'string' ? query : ''; if (typeof action !== 'string') query = action; @@ -7170,12 +7774,12 @@ else if (this.env.action) query._action = this.env.action; - var base = this.env.comm_path, k, param = {}; + var url = this.env.comm_path, k, param = {}; // overwrite task name if (action && action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) { query._action = RegExp.$2; - base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1); + url = url.replace(/\_task=[a-z0-9_-]+/, '_task=' + RegExp.$1); } // remove undefined values @@ -7184,7 +7788,13 @@ param[k] = query[k]; } - return base + (base.indexOf('?') > -1 ? '&' : '?') + $.param(param) + querystring; + if (param = $.param(param)) + url += (url.indexOf('?') > -1 ? '&' : '?') + param; + + if (querystring) + url += (url.indexOf('?') > -1 ? '&' : '?') + querystring; + + return url; }; this.redirect = function(url, lock) @@ -7237,22 +7847,32 @@ }; // send a http request to the server - this.http_request = function(action, query, lock) + this.http_request = function(action, data, lock) { - var url = this.url(action, query); + if (typeof data !== 'object') + data = rcube_parse_query(data); + + data._remote = 1; + data._unlock = lock ? lock : 0; // trigger plugin hook - var result = this.triggerEvent('request'+action, query); + var result = this.triggerEvent('request' + action, data); - if (result !== undefined) { - // abort if one the handlers returned false - if (result === false) - return false; - else - url = this.url(action, result); + // abort if one of the handlers returned false + if (result === false) { + if (data._unlock) + this.set_busy(false, null, data._unlock); + return false; + } + else if (result !== undefined) { + data = result; + if (data._action) { + action = data._action; + delete data._action; + } } - url += '&_remote=1'; + var url = this.url(action, data); // send request this.log('HTTP GET: ' + url); @@ -7261,33 +7881,39 @@ this.start_keepalive(); return $.ajax({ - type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json', - success: function(data){ ref.http_response(data); }, + type: 'GET', url: url, dataType: 'json', + success: function(data) { ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } }); }; // send a http POST request to the server - this.http_post = function(action, postdata, lock) + this.http_post = function(action, data, lock) { - var url = this.url(action); + if (typeof data !== 'object') + data = rcube_parse_query(data); - if (postdata && typeof postdata === 'object') { - postdata._remote = 1; - postdata._unlock = (lock ? lock : 0); - } - else - postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock='+lock : ''); + data._remote = 1; + data._unlock = lock ? lock : 0; // trigger plugin hook - var result = this.triggerEvent('request'+action, postdata); - if (result !== undefined) { - // abort if one of the handlers returned false - if (result === false) - return false; - else - postdata = result; + var result = this.triggerEvent('request'+action, data); + + // abort if one of the handlers returned false + if (result === false) { + if (data._unlock) + this.set_busy(false, null, data._unlock); + return false; } + else if (result !== undefined) { + data = result; + if (data._action) { + action = data._action; + delete data._action; + } + } + + var url = this.url(action); // send request this.log('HTTP POST: ' + url); @@ -7296,7 +7922,7 @@ this.start_keepalive(); return $.ajax({ - type: 'POST', url: url, data: postdata, dataType: 'json', + type: 'POST', url: url, data: data, dataType: 'json', success: function(data){ ref.http_response(data); }, error: function(o, status, err) { ref.http_error(o, status, err, lock, action); } }); @@ -7365,7 +7991,7 @@ this.enable_command('compose', (uid && this.contact_list.rows[uid])); this.enable_command('delete', 'edit', writable); this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0)); - this.enable_command('export-selected', false); + this.enable_command('export-selected', 'print', false); } case 'move': @@ -7412,32 +8038,36 @@ this.env.qsearch = null; case 'list': if (this.task == 'mail') { - var is_multifolder = this.is_multifolder_listing(); + var is_multifolder = this.is_multifolder_listing(), + list = this.message_list, + uid = this.env.list_uid; + this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0); this.enable_command('expunge', this.env.exists && !is_multifolder); this.enable_command('purge', this.purge_mailbox_test() && !is_multifolder); this.enable_command('import-messages', !is_multifolder); this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount && !is_multifolder); - if ((response.action == 'list' || response.action == 'search') && this.message_list) { - var list = this.message_list, uid = this.env.list_uid; - - // highlight message row when we're back from message page - if (uid) { - if (!list.rows[uid]) - uid += '-' + this.env.mailbox; - if (list.rows[uid]) { - list.select(uid); + if (list) { + if (response.action == 'list' || response.action == 'search') { + // highlight message row when we're back from message page + if (uid) { + if (!list.rows[uid]) + uid += '-' + this.env.mailbox; + if (list.rows[uid]) { + list.select(uid); + } + delete this.env.list_uid; } - delete this.env.list_uid; + + this.enable_command('set-listmode', this.env.threads && !is_multifolder); + if (list.rowcount > 0) + list.focus(); + this.msglist_select(list); } - this.enable_command('set-listmode', this.env.threads && !is_multifolder); - if (list.rowcount > 0) - list.focus(); - this.msglist_select(list); - this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:list.rowcount }); - + if (response.action != 'getunread') + this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:list.rowcount }); } } else if (this.task == 'addressbook') { @@ -8198,6 +8828,14 @@ return false; } }; + + this.print_dialog = function() + { + if (bw.safari) + setTimeout('window.print()', 10); + else + window.print(); + }; } // end object rcube_webmail -- Gitblit v1.9.1