From 3ebac0167bf20104fb7a2a55934765117760264c Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Mon, 18 Aug 2014 03:53:18 -0400 Subject: [PATCH] Only add zen-mode text labels in compose step --- program/js/app.js | 1098 ++++++++++++++++++++++++++++++-------------------------- 1 files changed, 587 insertions(+), 511 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index 9c2acfc..a8e66e2 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -46,12 +46,12 @@ this.messages = {}; this.group2expand = {}; this.http_request_jobs = {}; - this.menu_stack = new Array(); + this.menu_stack = []; // webmail client settings this.dblclick_time = 500; this.message_time = 5000; - this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); + this.identifier_expr = /[^0-9a-z_-]/gi; // environment defaults this.env = { @@ -236,8 +236,6 @@ return ref.command('sort', $(this).attr('rel'), this); }); - this.gui_objects.messagelist.parentNode.onclick = function(e){ return ref.click_on_list(e || window.event); }; - this.enable_command('toggle_status', 'toggle_flag', 'sort', true); this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing()); @@ -314,9 +312,9 @@ }); // 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); }) - } + $.each(this.buttons['save-response'] || [], function (i, v) { + $('#' + v.id).mousedown(function(e){ return rcube_event.cancel(e); }) + }); } // init message compose form @@ -346,10 +344,10 @@ this.contact_list .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); }) .addEventListener('select', function(o) { ref.compose_recipient_select(o); }) - .addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); }) + .addEventListener('dblclick', function(o) { ref.compose_add_recipient(); }) .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) { - if (!ref.compose_add_recipient('to')) { + if (!ref.compose_add_recipient()) { // execute link action on <enter> if not a recipient entry if (o.last_selected && String(o.last_selected).charAt(0) == 'G') { $(o.rows[o.last_selected].obj).find('a').first().click(); @@ -358,6 +356,9 @@ } }) .init(); + + // remember last focused address field + $('#_to,#_cc,#_bcc').focus(function() { ref.env.focused_field = this; }); } if (this.gui_objects.addressbookslist) { @@ -403,15 +404,21 @@ .addEventListener('dragend', function(e) { ref.drag_end(e); }) .init(); - if (this.env.cid) - this.contact_list.highlight_row(this.env.cid); - - this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); }; - $(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); }); this.update_group_commands(); this.command('list'); + } + + if (this.gui_objects.savedsearchlist) { + this.savedsearchlist = new rcube_treelist_widget(this.gui_objects.savedsearchlist, { + id_prefix: 'rcmli', + id_encode: this.html_identifier_encode, + id_decode: this.html_identifier_decode + }); + + this.savedsearchlist.addEventListener('select', function(node) { + ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }); }); } this.set_page_buttons(); @@ -471,9 +478,6 @@ }) .init() .focus(); - - if (this.env.iid) - this.identity_list.highlight_row(this.env.iid); } else if (this.gui_objects.sectionslist) { this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:true}); @@ -536,7 +540,7 @@ // select first input field in an edit form if (this.gui_objects.editform) $("input,select,textarea", this.gui_objects.editform) - .not(':hidden').not(':disabled').first().select(); + .not(':hidden').not(':disabled').first().select().focus(); // unset contentframe variable if preview_pane is enabled if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible')) @@ -557,6 +561,7 @@ // init treelist widget if (this.gui_objects.folderlist && window.rcube_treelist_widget) { this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, { + selectable: true, id_prefix: 'rcmli', id_encode: this.html_identifier_encode, id_decode: this.html_identifier_decode, @@ -621,7 +626,7 @@ { var ret, uid, cid, url, flag, aborted = false; - if (obj && obj.blur && !(event || rcube_event.is_keyboard(event))) + if (obj && obj.blur && !(event && rcube_event.is_keyboard(event))) obj.blur(); // do nothing if interface is locked by other command (with exception for searching reset) @@ -649,17 +654,18 @@ // remove copy from local storage if compose screen is left intentionally this.remove_compose_data(this.env.compose_id); + this.compose_skip_unsavedcheck = true; } this.last_command = command; // process external commands if (typeof this.command_handlers[command] === 'function') { - ret = this.command_handlers[command](props, obj); + ret = this.command_handlers[command](props, obj, event); return ret !== undefined ? ret : (obj ? false : true); } else if (typeof this.command_handlers[command] === 'string') { - ret = window[this.command_handlers[command]](props, obj); + ret = window[this.command_handlers[command]](props, obj, event); return ret !== undefined ? ret : (obj ? false : true); } @@ -710,6 +716,7 @@ if (win) { this.save_compose_form_local(); + this.compose_skip_unsavedcheck = true; $("input[name='_action']", form).val('compose'); form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 }); form.target = win.name; @@ -1045,7 +1052,7 @@ if (this.task == 'mail') { url._mbox = this.env.mailbox; if (props) - url._to = props; + url._to = props; // also send search request so we can go back to search result after message is sent if (this.env.search_request) url._search = this.env.search_request; @@ -1073,8 +1080,12 @@ break; } } - else if (props) + else if (props && typeof props == 'string') { url._to = props; + } + else if (props && typeof props == 'object') { + $.extend(url, props); + } this.open_compose_step(url); break; @@ -1344,7 +1355,7 @@ this.command_enabled = function(cmd) { return this.commands[cmd]; - } + }; // lock/unlock interface this.set_busy = function(a, message, id) @@ -1386,13 +1397,14 @@ // switch to another application task this.switch_task = function(task) { - if (this.task===task && task!='mail') + if (this.task === task && task != 'mail') return; var url = this.get_task_url(task); + if (task == 'mail') url += '&_mbox=INBOX'; - else if (task == 'logout') + else if (task == 'logout' && !this.env.server_error) this.clear_compose_data(); this.redirect(url); @@ -1439,15 +1451,15 @@ this.is_framed = function() { - return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command); + return this.env.framed && parent.rcmail && parent.rcmail != this && typeof parent.rcmail.command == 'function'; }; this.save_pref = function(prop) { - var request = {'_name': prop.name, '_value': prop.value}; + var request = {_name: prop.name, _value: prop.value}; if (prop.session) - request['_session'] = prop.session; + request._session = prop.session; if (prop.env) this.env[prop.env] = prop.value; @@ -1587,7 +1599,7 @@ // select the folder if one of its childs is currently selected // don't select if it's virtual (#1488346) - if (this.env.mailbox && this.env.mailbox.startsWith(name + this.env.delimiter) && !node.virtual) + if (!node.virtual && this.env.mailbox && this.env.mailbox.startsWith(name + this.env.delimiter)) this.command('list', name); } else { @@ -1629,7 +1641,7 @@ } // reset popup menus; delayed to have updated menu_stack data - window.setTimeout(function(e){ + setTimeout(function(e){ var obj, skip, config, id, i, parents = $(target).parents(); for (i = ref.menu_stack.length - 1; i >= 0; i--) { id = ref.menu_stack[i]; @@ -1681,8 +1693,8 @@ case 40: case 63232: // "up", in safari keypress case 63233: // "down", in safari keypress - focus_menu_item(mod = keyCode == 38 || keyCode == 63232 ? -1 : 1); - break; + focus_menu_item(keyCode == 38 || keyCode == 63232 ? -1 : 1); + return rcube_event.cancel(e); case 9: // tab if (this.focused_menu) { @@ -1701,19 +1713,6 @@ return true; } - - this.click_on_list = function(e) - { - if (this.gui_objects.qsearchbox) - this.gui_objects.qsearchbox.blur(); - - if (this.message_list) - this.message_list.focus(e); - else if (this.contact_list) - this.contact_list.focus(e); - - return true; - }; this.msglist_select = function(list) { @@ -1845,9 +1844,6 @@ return (this.env.mailboxes[id] && !this.env.mailboxes[id].virtual && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0; - - case 'settings': - return id != this.env.mailbox ? 1 : 0; case 'addressbook': var target; @@ -2480,7 +2476,7 @@ selection.push(selected[i]); this.message_list.selection = selection; - } + }; // expand all threads with unread children this.expand_unread = function() @@ -2493,8 +2489,10 @@ this.message_list.expand_all(r); this.set_unread_children(r.uid); } + new_row = new_row.nextSibling; } + return false; }; @@ -2711,8 +2709,8 @@ } // update unread_children for roots - for (var i=0; i<roots.length; i++) - this.set_unread_children(roots[i].uid); + for (r=0; r<roots.length; r++) + this.set_unread_children(roots[r].uid); return count; }; @@ -3363,63 +3361,7 @@ } // check for locally stored compose data - if (window.localStorage) { - var key, formdata, index = this.local_storage_get_item('compose.index', []); - - for (i = 0; i < index.length; i++) { - key = index[i]; - formdata = this.local_storage_get_item('compose.' + key, null, true); - if (!formdata) { - continue; - } - // restore saved copy of current compose_id - if (formdata.changed && key == this.env.compose_id) { - this.restore_compose_form(key, html_mode); - break; - } - // skip records from 'other' drafts - if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) { - continue; - } - // skip records on reply - if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) { - continue; - } - // show dialog asking to restore the message - if (formdata.changed && formdata.session != this.env.session_id) { - this.show_popup_dialog( - this.get_label('restoresavedcomposedata') - .replace('$date', new Date(formdata.changed).toLocaleString()) - .replace('$subject', formdata._subject) - .replace(/\n/g, '<br/>'), - this.get_label('restoremessage'), - [{ - text: this.get_label('restore'), - click: function(){ - ref.restore_compose_form(key, html_mode); - ref.remove_compose_data(key); // remove old copy - ref.save_compose_form_local(); // save under current compose_id - $(this).dialog('close'); - } - }, - { - text: this.get_label('delete'), - click: function(){ - ref.remove_compose_data(key); - $(this).dialog('close'); - } - }, - { - text: this.get_label('ignore'), - click: function(){ - $(this).dialog('close'); - } - }] - ); - break; - } - } - } + this.compose_restore_dialog(0, html_mode) if (input_to.val() == '') input_to.focus(); @@ -3436,6 +3378,72 @@ // start the auto-save timer this.auto_save_start(); }; + + this.compose_restore_dialog = function(j, html_mode) + { + var i, key, formdata, index = this.local_storage_get_item('compose.index', []); + + var show_next = function(i) { + if (++i < index.length) + ref.compose_restore_dialog(i, html_mode) + } + + for (i = j || 0; i < index.length; i++) { + key = index[i]; + formdata = this.local_storage_get_item('compose.' + key, null, true); + if (!formdata) { + continue; + } + // restore saved copy of current compose_id + if (formdata.changed && key == this.env.compose_id) { + this.restore_compose_form(key, html_mode); + break; + } + // skip records from 'other' drafts + if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) { + continue; + } + // skip records on reply + if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) { + continue; + } + // show dialog asking to restore the message + if (formdata.changed && formdata.session != this.env.session_id) { + this.show_popup_dialog( + this.get_label('restoresavedcomposedata') + .replace('$date', new Date(formdata.changed).toLocaleString()) + .replace('$subject', formdata._subject) + .replace(/\n/g, '<br/>'), + this.get_label('restoremessage'), + [{ + text: this.get_label('restore'), + click: function(){ + ref.restore_compose_form(key, html_mode); + ref.remove_compose_data(key); // remove old copy + ref.save_compose_form_local(); // save under current compose_id + $(this).dialog('close'); + } + }, + { + text: this.get_label('delete'), + click: function(){ + ref.remove_compose_data(key); + $(this).dialog('close'); + show_next(i); + } + }, + { + text: this.get_label('ignore'), + click: function(){ + $(this).dialog('close'); + show_next(i); + } + }] + ); + break; + } + } + } this.init_address_input_events = function(obj, props) { @@ -3465,6 +3473,7 @@ form._draft.value = draft ? '1' : ''; 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); // register timer to notify about connection timeout this.submit_timer = setTimeout(function(){ @@ -3488,6 +3497,12 @@ this.compose_add_recipient = function(field) { + // find last focused field name + if (!field) { + field = $(this.env.focused_field).filter(':visible'); + field = field.length ? field.attr('id').replace('_', '') : 'to'; + } + var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter; if (this.contact_list && this.contact_list.selection.length) { @@ -3556,26 +3571,32 @@ myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>') .appendTo(document.body), prompt_value = $('<input>').attr({type: 'text', size: 30}).val(this.get_label('nosubject')) - .appendTo(myprompt); + .appendTo(myprompt), + save_func = function() { + input_subject.val(prompt_value.val()); + myprompt.dialog('close'); + ref.command(cmd, { nocheck:true }); // repeat command which triggered this + }; - buttons[this.get_label('cancel')] = function(){ + buttons[this.get_label('sendmessage')] = function() { + save_func($(this)); + }; + buttons[this.get_label('cancel')] = function() { input_subject.focus(); $(this).dialog('close'); - }; - buttons[this.get_label('sendmessage')] = function(){ - input_subject.val(prompt_value.val()); - $(this).dialog('close'); - ref.command(cmd, { nocheck:true }); // repeat command which triggered this }; myprompt.dialog({ modal: true, resizable: false, buttons: buttons, - close: function(event, ui) { $(this).remove() } + close: function(event, ui) { $(this).remove(); } }); - prompt_value.select(); + prompt_value.select().keydown(function(e) { + if (e.which == 13) save_func(); + }); + return false; } @@ -3653,7 +3674,7 @@ $(this).dialog('close'); }; - this.show_popup_dialog(html, this.gettext('savenewresponse'), buttons); + this.show_popup_dialog(html, this.gettext('newresponse'), buttons); $('#ffresponsetext').val(text); $('#ffresponsename').select(); @@ -3701,10 +3722,7 @@ // submit delete request if (key && confirm(this.get_label('deleteresponseconfirm'))) { this.http_post('settings/delete-response', { _key: key }, false); - return true; } - - return false; }; // updates spellchecker buttons on state change @@ -3712,8 +3730,9 @@ { var active = this.editor.spellcheck_state(); - if (this.buttons.spellcheck) - $('#'+this.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected'); + $.each(this.buttons.spellcheck || [], function(i, v) { + $('#' + v.id)[active ? 'addClass' : 'removeClass']('selected'); + }); return active; }; @@ -3760,6 +3779,7 @@ // always remove local copy upon saving as draft this.remove_compose_data(this.env.compose_id); + this.compose_skip_unsavedcheck = false; }; this.auto_save_start = function() @@ -3784,6 +3804,21 @@ ref.compose_type_activity_last = ref.compose_type_activity; } }, 5000); + + $(window).unload(function() { + // remove copy from local storage if compose screen is left after warning + if (!ref.env.server_error) + ref.remove_compose_data(ref.env.compose_id); + }); + } + + // check for unsaved changes before leaving the compose page + if (!window.onbeforeunload) { + window.onbeforeunload = function() { + if (!ref.compose_skip_unsavedcheck && ref.cmp_hash != ref.compose_field_hash()) { + return ref.get_label('notsentwarning'); + } + }; } // Unlock interface now that saving is complete @@ -3850,15 +3885,16 @@ } }); - if (window.localStorage && !empty) { + if (!empty) { var index = this.local_storage_get_item('compose.index', []), key = this.env.compose_id; - if ($.inArray(key, index) < 0) { - index.push(key); - } - this.local_storage_set_item('compose.' + key, formdata, true); - this.local_storage_set_item('compose.index', index); + if ($.inArray(key, index) < 0) { + index.push(key); + } + + this.local_storage_set_item('compose.' + key, formdata, true); + this.local_storage_set_item('compose.index', index); } }; @@ -3890,28 +3926,25 @@ // remove stored compose data from localStorage this.remove_compose_data = function(key) { - if (window.localStorage) { - var index = this.local_storage_get_item('compose.index', []); + var index = this.local_storage_get_item('compose.index', []); - if ($.inArray(key, index) >= 0) { - this.local_storage_remove_item('compose.' + key); - this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; })); - } + if ($.inArray(key, index) >= 0) { + this.local_storage_remove_item('compose.' + key); + this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; })); } }; // clear all stored compose data of this user this.clear_compose_data = function() { - if (window.localStorage) { - var i, index = this.local_storage_get_item('compose.index', []); + var i, index = this.local_storage_get_item('compose.index', []); - for (i=0; i < index.length; i++) { - this.local_storage_remove_item('compose.' + index[i]); - } - this.local_storage_remove_item('compose.index'); + for (i=0; i < index.length; i++) { + this.local_storage_remove_item('compose.' + index[i]); } - } + + this.local_storage_remove_item('compose.index'); + }; this.change_identity = function(obj, show_sig) @@ -3931,18 +3964,16 @@ return; } - var i, rx, - id = obj.options[obj.selectedIndex].value, + var id = obj.options[obj.selectedIndex].value, sig = this.env.identity, delim = this.env.recipients_separator, - rx_delim = RegExp.escape(delim), - headers = ['replyto', 'bcc']; + rx_delim = RegExp.escape(delim); // 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] : '', + $.each(['replyto', 'bcc'], function() { + var rx, key = this, + old_val = sig && ref.env.identities[sig] ? ref.env.identities[sig][key] : '', + new_val = id && ref.env.identities[id] ? ref.env.identities[id][key] : '', input = $('[name="_'+key+'"]'), input_val = input.val(); // remove old address(es) @@ -3969,7 +4000,7 @@ if (old_val || new_val) input.val(input_val).change(); - } + }); // enable manual signature insert if (this.env.signatures && this.env.signatures[id]) { @@ -4058,6 +4089,14 @@ if (upload_id) this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id}); + if (!this.env.attachments) + this.env.attachments = {}; + + if (upload_id && this.env.attachments[upload_id]) + delete this.env.attachments[upload_id]; + + this.env.attachments[name] = att; + if (!this.gui_objects.attachmentlist) return false; @@ -4066,7 +4105,7 @@ if (!att.complete && att.frame) 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; + + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="'+this.get_label('cancel')+'" />' : this.get_label('cancel')) + '</a>' + att.html; var indicator, li = $('<li>'); @@ -4083,10 +4122,9 @@ li.appendTo(this.gui_objects.attachmentlist); } - if (upload_id && this.env.attachments[upload_id]) - delete this.env.attachments[upload_id]; - - this.env.attachments[name] = att; + // set tabindex attribute + var tabindex = $(this.gui_objects.attachmentlist).attr('data-tabindex') || '0'; + li.find('a').attr('tabindex', tabindex); return true; }; @@ -4125,7 +4163,7 @@ this.upload_progress_update = function(param) { - var elem = $('#'+param.name + '> span'); + var elem = $('#'+param.name + ' > span'); if (!elem.length || !param.text) return; @@ -4280,23 +4318,25 @@ this.sent_successfully = function(type, msg, folders) { this.display_message(msg, type); + this.compose_skip_unsavedcheck = true; if (this.env.extwin) { - var rc = this.opener(); this.lock_form(this.gui_objects.messageform); + + var rc = this.opener(); if (rc) { rc.display_message(msg, type); // refresh the folder where sent message was saved or replied message comes from if (folders && rc.env.task == 'mail' && rc.env.action == '' && $.inArray(rc.env.mailbox, folders) >= 0) { - // @TODO: try with 'checkmail' here when #1485186 is fixed. See also #1489249. - rc.command('list'); + rc.command('checkmail'); } } - setTimeout(function(){ window.close() }, 1000); + + setTimeout(function() { window.close(); }, 1000); } else { // before redirect we need to wait some time for Chrome (#1486177) - setTimeout(function(){ ref.list_mailbox(); }, 500); + setTimeout(function() { ref.list_mailbox(); }, 500); } }; @@ -4516,10 +4556,6 @@ this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').attr('role', 'listbox') .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body); this.ksearch_pane.__ul = ul[0]; - - // register (delegate) event handlers - ul.on('mouseover', 'li', function(e){ ref.ksearch_select(e.target); }) - .on('onmouseup', 'li', function(e){ ref.ksearch_click(e.target); }) } ul = this.ksearch_pane.__ul; @@ -4542,7 +4578,7 @@ // add each result line to list if (results && (len = results.length)) { for (i=0; i < len && maxlen > 0; i++) { - text = typeof results[i] === 'object' ? results[i].name : results[i]; + text = typeof results[i] === 'object' ? (results[i].display || results[i].name) : results[i]; type = typeof results[i] === 'object' ? results[i].type : ''; id = i + this.env.contacts.length; $('<li>').attr('id', 'rcmkSearchItem' + id) @@ -4550,6 +4586,8 @@ .html(this.quote_html(text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>')) .addClass(type || '') .appendTo(ul) + .mouseover(function() { ref.ksearch_select(this); }) + .mouseup(function() { ref.ksearch_click(this); }) .get(0)._rcm_id = id; maxlen -= 1; } @@ -4701,6 +4739,7 @@ this.list_contacts = function(src, group, page) { var win, folder, url = {}, + refresh = src === undefined && group === undefined && page === undefined, target = window; if (!src) @@ -4713,7 +4752,7 @@ page = this.env.current_page = 1; this.reset_qsearch(); } - else if (group != this.env.group) + else if (!refresh && group != this.env.group) page = this.env.current_page = 1; if (this.env.search_id) @@ -4742,7 +4781,8 @@ $(this.gui_objects.addresslist_title).html(this.get_label('contacts')); } - this.select_folder(folder, '', true); + if (!this.env.search_id) + this.select_folder(folder, '', true); // load contacts remotely if (this.gui_objects.contactslist) { @@ -4850,6 +4890,9 @@ if (action && (cid || action == 'add') && !this.drag_active) { if (this.env.group) url._gid = this.env.group; + + if (this.env.search_request) + url._search = this.env.search_request; url._action = action; url._source = this.env.source; @@ -4974,7 +5017,7 @@ { var selection = this.contact_list ? this.contact_list.get_selection() : []; - // exit if no mailbox specified or if selection is empty + // exit if no contact specified or if selection is empty if (!selection.length && !this.env.cid) return; @@ -5104,29 +5147,55 @@ .submit(function() { $('input.mainaction').click(); return false; }); }; + // group creation dialog this.group_create = function() { - this.add_input_row('contactgroup'); + var input = $('<input>').attr('type', 'text'), + content = $('<label>').text(this.get_label('namex')).append(input); + + this.show_popup_dialog(content, this.get_label('newgroup'), + [{ + text: this.get_label('save'), + click: function() { + var name; + + if (name = input.val()) { + ref.http_post('group-create', {_source: ref.env.source, _name: name}, + ref.set_busy(true, 'loading')); + } + + $(this).dialog('close'); + } + }] + ); }; + // group rename dialog this.group_rename = function() { - if (!this.env.group || !this.gui_objects.folderlist) + if (!this.env.group) return; - if (!this.name_input) { - this.enable_command('list', 'listgroup', false); - this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name); - this.name_input.bind('keydown', function(e) { return ref.add_input_keydown(e); }); - this.env.group_renaming = true; + var group_name = this.env.contactgroups['G' + this.env.source + this.env.group].name, + input = $('<input>').attr('type', 'text').val(group_name), + content = $('<label>').text(this.get_label('namex')).append(input); - var link, li = this.get_folder_li('G'+this.env.source+this.env.group,'',true); - if (li && (link = li.firstChild)) { - $(link).hide().before(this.name_input); - } - } + this.show_popup_dialog(content, this.get_label('grouprename'), + [{ + text: this.get_label('save'), + click: function() { + var name; - this.name_input.select().focus(); + if ((name = input.val()) && name != group_name) { + ref.http_post('group-rename', {_source: ref.env.source, _gid: ref.env.group, _name: name}, + ref.set_busy(true, 'loading')); + } + + $(this).dialog('close'); + } + }], + {open: function() { input.select(); }} + ); }; this.group_delete = function() @@ -5151,38 +5220,6 @@ this.list_contacts(prop.source, 0); }; - // @TODO: maybe it would be better to use popup instead of inserting input to the list? - this.add_input_row = function(type) - { - if (!this.gui_objects.folderlist) - return; - - if (!this.name_input) { - this.name_input = $('<input>').attr('type', 'text').data('tt', type); - this.name_input.bind('keydown', function(e) { return ref.add_input_keydown(e); }); - this.name_input_li = $('<li>').addClass(type).append(this.name_input); - - 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(ul); - ul.show(); // make sure the list is visible - } - } - - this.name_input.select().focus(); - }; - //remove selected contacts from current active group this.group_remove_selected = function() { @@ -5202,62 +5239,9 @@ } }; - // handler for keyboard events on the input field - this.add_input_keydown = function(e) - { - var key = rcube_event.get_keycode(e), - input = $(e.target), itype = input.data('tt'); - - // enter - if (key == 13) { - var newname = input.val(); - - if (newname) { - var lock = this.set_busy(true, 'loading'); - - if (itype == 'contactsearch') - this.http_post('search-create', {_search: this.env.search_request, _name: newname}, lock); - else if (this.env.group_renaming) - this.http_post('group-rename', {_source: this.env.source, _gid: this.env.group, _name: newname}, lock); - else - this.http_post('group-create', {_source: this.env.source, _name: newname}, lock); - } - return false; - } - // escape - else if (key == 27) - this.reset_add_input(); - - return true; - }; - - this.reset_add_input = function() - { - if (this.name_input) { - var li = this.name_input.parent(); - if (this.env.group_renaming) { - li.children().last().show(); - this.env.group_renaming = false; - } - else if ($('li', li.parent()).length == 1) - li.parent().hide(); - - this.name_input.remove(); - - if (this.name_input_li) - this.name_input_li.remove(); - - this.name_input = this.name_input_li = null; - } - - this.enable_command('list', 'listgroup', true); - }; - // callback for creating a new contact group this.insert_contact_group = function(prop) { - this.reset_add_input(); - prop.type = 'group'; var key = 'G'+prop.source+prop.id, @@ -5267,7 +5251,7 @@ .html(prop.name); this.env.contactfolders[key] = this.env.contactgroups[key] = prop; - this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, true); + this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, 'contactgroup'); this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) }); }; @@ -5275,8 +5259,6 @@ // callback for renaming a contact group this.update_contact_group = function(prop) { - this.reset_add_input(); - var key = 'G'+prop.source+prop.id, newnode = {}; @@ -5540,8 +5522,6 @@ // callback for creating a new saved search record this.insert_saved_search = function(name, id) { - this.reset_add_input(); - var key = 'S'+id, link = $('<a>').attr('href', '#') .attr('rel', id) @@ -5549,7 +5529,7 @@ .html(name), prop = { name:name, id:id }; - this.treelist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch'); + this.savedsearchlist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch'); this.select_folder(key,'',true); this.enable_command('search-delete', true); this.env.search_id = id; @@ -5557,10 +5537,27 @@ this.triggerEvent('abook_search_insert', prop); }; - // creates an input for saved search name + // creates a dialog for saved search this.search_create = function() { - this.add_input_row('contactsearch'); + var input = $('<input>').attr('type', 'text'), + content = $('<label>').text(this.get_label('namex')).append(input); + + this.show_popup_dialog(content, this.get_label('searchsave'), + [{ + text: this.get_label('save'), + click: function() { + var name; + + if (name = input.val()) { + ref.http_post('search-create', {_search: ref.env.search_request, _name: name}, + ref.set_busy(true, 'loading')); + } + + $(this).dialog('close'); + } + }] + ); }; this.search_delete = function() @@ -5575,7 +5572,7 @@ this.remove_search_item = function(id) { var li, key = 'S'+id; - if (this.treelist.remove(key)) { + if (this.savedsearchlist.remove(key)) { this.triggerEvent('search_delete', { id:id, li:li }); } @@ -5595,7 +5592,13 @@ } this.reset_qsearch(); - this.select_folder('S'+id, '', true); + + if (this.savedsearchlist) { + this.treelist.select(''); + this.savedsearchlist.select('S'+id); + } + else + this.select_folder('S'+id, '', true); // reset vars this.env.current_page = 1; @@ -5665,10 +5668,8 @@ id = this.env.iid ? this.env.iid : selection[0]; // submit request with appended token - if (confirm(this.get_label('deleteidentityconfirm'))) - this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true); - - return true; + if (id && confirm(this.get_label('deleteidentityconfirm'))) + this.http_post('settings/delete-identity', { _iid: id }, true); }; this.update_identity_row = function(id, name, add) @@ -5712,6 +5713,23 @@ frame.location.href = this.env.blankpage; } } + + this.enable_command('delete', false); + }; + + this.remove_identity = function(id) + { + var frame, list = this.identity_list, + rid = this.html_identifier(id); + + if (list && id) { + list.remove_row(rid); + if (this.env.contentframe && (frame = this.get_frame_window(this.env.contentframe))) { + frame.location.href = this.env.blankpage; + } + } + + this.enable_command('delete', false); }; @@ -5725,64 +5743,56 @@ this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$'); - this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, - {multiselect:false, draggable:true, keyboard:true, toggleselect:true}); + this.subscription_list = new rcube_treelist_widget(this.gui_objects.subscriptionlist, { + selectable: true, + tabexit: false, + id_prefix: 'rcmli', + id_encode: this.html_identifier_encode, + id_decode: this.html_identifier_decode, + searchbox: '#foldersearch' + }); + this.subscription_list - .addEventListener('select', function(o){ ref.subscription_select(o); }) - .addEventListener('dragstart', function(o){ ref.drag_active = true; }) - .addEventListener('dragend', function(o){ ref.subscription_move_folder(o); }) - .addEventListener('initrow', function (row) { - row.obj.onmouseover = function() { ref.focus_subscription(row.id); }; - row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); }; - }) - .init() - .focus(); + .addEventListener('select', function(node) { ref.subscription_select(node.id); }) + .addEventListener('collapse', function(node) { ref.folder_collapsed(node) }) + .addEventListener('expand', function(node) { ref.folder_collapsed(node) }) + .addEventListener('search', function(p) { if (p.query) ref.subscription_select(); }) + .draggable({cancel: 'li.mailbox.root'}) + .droppable({ + // @todo: find better way, accept callback is executed for every folder + // on the list when dragging starts (and stops), this is slow, but + // I didn't find a method to check droptarget on over event + accept: function(node) { + var source_folder = ref.folder_id2name($(node).attr('id')), + dest_folder = ref.folder_id2name(this.id), + source = ref.env.subscriptionrows[source_folder], + dest = ref.env.subscriptionrows[dest_folder]; - $('#mailboxroot') - .mouseover(function(){ ref.focus_subscription(this.id); }) - .mouseout(function(){ ref.unfocus_subscription(this.id); }) - }; + return source && !source[2] + && dest_folder != source_folder.replace(ref.last_sub_rx, '') + && !dest_folder.startsWith(source_folder + ref.env.delimiter); + }, + drop: function(e, ui) { + var source = ref.folder_id2name(ui.draggable.attr('id')), + dest = ref.folder_id2name(this.id); - this.focus_subscription = function(id) - { - var row, folder; - - if (this.drag_active && this.env.mailbox && (row = document.getElementById(id))) - if (this.env.subscriptionrows[id] && - (folder = this.env.subscriptionrows[id][0]) !== null - ) { - if (this.check_droptarget(folder) && - !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] && - folder != this.env.mailbox.replace(this.last_sub_rx, '') && - !folder.startsWith(this.env.mailbox + this.env.delimiter) - ) { - this.env.dstfolder = folder; - $(row).addClass('droptarget'); + ref.subscription_move_folder(source, dest); } - } + }); }; - this.unfocus_subscription = function(id) + this.folder_id2name = function(id) { - var row = $('#'+id); - - this.env.dstfolder = null; - - if (row.length && this.env.subscriptionrows[id]) - row.removeClass('droptarget'); - else - $(this.subscription_list.frame).removeClass('droptarget'); + return ref.html_identifier_decode(id.replace(/^rcmli/, '')); }; - this.subscription_select = function(list) + this.subscription_select = function(id) { - var id, folder; + var folder; - if (list && (id = list.get_single_selection()) && - (folder = this.env.subscriptionrows['rcmrow'+id]) - ) { - this.env.mailbox = folder[0]; - this.show_folder(folder[0]); + if (id && id != '*' && (folder = this.env.subscriptionrows[id])) { + this.env.mailbox = id; + this.show_folder(id); this.enable_command('delete-folder', !folder[2]); } else { @@ -5792,24 +5802,18 @@ } }; - this.subscription_move_folder = function(list) + this.subscription_move_folder = function(from, to) { - if (this.env.mailbox && this.env.dstfolder !== null && - this.env.dstfolder != this.env.mailbox && - this.env.dstfolder != this.env.mailbox.replace(this.last_sub_rx, '') - ) { - var path = this.env.mailbox.split(this.env.delimiter), + if (from && to !== null && from != to && to != from.replace(this.last_sub_rx, '')) { + var path = from.split(this.env.delimiter), basename = path.pop(), - newname = this.env.dstfolder === '' ? basename : this.env.dstfolder + this.env.delimiter + basename; + newname = to === '' || to === '*' ? basename : to + this.env.delimiter + basename; - if (newname != this.env.mailbox) { - this.http_post('rename-folder', {_folder_oldname: this.env.mailbox, _folder_newname: newname}, this.set_busy(true, 'foldermoving')); - this.subscription_list.draglayer.hide(); + if (newname != from) { + this.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname}, + this.set_busy(true, 'foldermoving')); } } - - this.drag_active = false; - this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder)); }; // tell server to create and subscribe a new mailbox @@ -5821,51 +5825,61 @@ // delete a specific mailbox with all its messages this.delete_folder = function(name) { - var id = this.get_folder_row_id(name ? name : this.env.mailbox), - folder = this.env.subscriptionrows[id][0]; + if (!name) + name = this.env.mailbox; - if (folder && confirm(this.get_label('deletefolderconfirm'))) { - var lock = this.set_busy(true, 'folderdeleting'); - this.http_post('delete-folder', {_mbox: folder}, lock); + if (name && confirm(this.get_label('deletefolderconfirm'))) { + this.http_post('delete-folder', {_mbox: name}, this.set_busy(true, 'folderdeleting')); } }; // Add folder row to the table and initialize it - this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name) + this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders) { if (!this.gui_objects.subscriptionlist) return false; - var row, n, tmp, tmp_name, rowid, collator, - folders = [], list = [], slist = [], - tbody = this.gui_objects.subscriptionlist.tBodies[0], - refrow = $('tr', tbody).get(1), - id = 'rcmrow'+((new Date).getTime()); + // reset searching + if (this.subscription_list.is_search()) { + this.subscription_select(); + this.subscription_list.reset_search(); + } - if (!refrow) { + // disable drag-n-drop temporarily + this.subscription_list.draggable('destroy').droppable('destroy'); + + var row, n, tmp, tmp_name, rowid, collator, pos, p, parent = '', + folders = [], list = [], slist = [], + list_element = $(this.gui_objects.subscriptionlist); + row = refrow ? refrow : $($('li', list_element).get(1)).clone(true); + + if (!row.length) { // Refresh page if we don't have a table row to clone this.goto_url('folders'); return false; } - // clone a table row if there are existing rows - row = $(refrow).clone(true); - // set ID, reset css class - row.attr({id: id, 'class': class_name}); + row.attr({id: 'rcmli' + this.html_identifier_encode(id), 'class': class_name}); + + if (!refrow || !refrow.length) { + // remove old data, subfolders and toggle + $('ul,div.treetoggle', row).remove(); + row.removeData('filtered'); + } // set folder name - row.find('td:first').html(display_name); + $('a:first', row).text(display_name); // update subscription checkbox - $('input[name="_subscribed[]"]', row).val(name) + $('input[name="_subscribed[]"]:first', row).val(id) .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false}); // add to folder/row-ID map this.env.subscriptionrows[id] = [name, display_name, false]; // copy folders data to an array for sorting - $.each(this.env.subscriptionrows, function(k, v) { folders.push(v); }); + $.each(this.env.subscriptionrows, function(k, v) { v[3] = k; folders.push(v); }); try { // use collator if supported (FF29, IE11, Opera15, Chrome24) @@ -5877,65 +5891,108 @@ folders.sort(function(a, b) { var i, f1, f2, path1 = a[0].split(ref.env.delimiter), - path2 = b[0].split(ref.env.delimiter); + path2 = b[0].split(ref.env.delimiter), + len = path1.length; - for (i=0; i<path1.length; i++) { + for (i=0; i<len; i++) { f1 = path1[i]; f2 = path2[i]; if (f1 !== f2) { + if (f2 === undefined) + return 1; if (collator) return collator.compare(f1, f2); else return f1 < f2 ? -1 : 1; } + else if (i == len-1) { + return -1 + } } }); for (n in folders) { + p = folders[n][3]; // protected folder if (folders[n][2]) { - tmp_name = folders[n][0] + this.env.delimiter; + tmp_name = p + this.env.delimiter; // prefix namespace cannot have subfolders (#1488349) if (tmp_name == this.env.prefix_ns) continue; - slist.push(folders[n][0]); + slist.push(p); tmp = tmp_name; } // protected folder's child - else if (tmp && folders[n][0].startsWith(tmp)) - slist.push(folders[n][0]); + else if (tmp && p.startsWith(tmp)) + slist.push(p); // other else { - list.push(folders[n][0]); + list.push(p); tmp = null; } } // check if subfolder of a protected folder for (n=0; n<slist.length; n++) { - if (name.startsWith(slist[n] + this.env.delimiter)) - rowid = this.get_folder_row_id(slist[n]); + if (id.startsWith(slist[n] + this.env.delimiter)) + rowid = slist[n]; } // find folder position after sorting for (n=0; !rowid && n<list.length; n++) { - if (n && list[n] == name) - rowid = this.get_folder_row_id(list[n-1]); + if (n && list[n] == id) + rowid = list[n-1]; } // add row to the table - if (rowid) - $('#'+rowid).after(row); - else - row.appendTo(tbody); + if (rowid && (n = this.subscription_list.get_item(rowid, true))) { + // find parent folder + if (pos = id.lastIndexOf(this.env.delimiter)) { + parent = id.substring(0, pos); + parent = this.subscription_list.get_item(parent, true); + + // add required tree elements to the parent if not already there + if (!$('div.treetoggle', parent).length) { + $('<div> </div>').addClass('treetoggle collapsed').appendTo(parent); + } + if (!$('ul', parent).length) { + $('<ul>').css('display', 'none').appendTo(parent); + } + } + + if (parent && n == parent) { + $('ul:first', parent).append(row); + } + else { + while (p = $(n).parent().parent().get(0)) { + if (parent && p == parent) + break; + if (!$(p).is('li.mailbox')) + break; + n = p; + } + + $(n).after(row); + } + } + else { + list_element.append(row); + } + + // add subfolders + $.extend(this.env.subscriptionrows, subfolders || {}); // update list widget - this.subscription_list.clear_selection(); - if (!skip_init) - this.init_subscription_list(); + this.subscription_list.reset(true); + this.subscription_select(); - row = row.get(0); + // expand parent + if (parent) { + this.subscription_list.expand(this.folder_id2name(parent.id)); + } + + row = row.show().get(0); if (row.scrollIntoView) row.scrollIntoView(); @@ -5943,114 +6000,85 @@ }; // replace an existing table row with a new folder line (with subfolders) - this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name) + this.replace_folder_row = function(oldid, id, name, display_name, is_protected, class_name) { if (!this.gui_objects.subscriptionlist) { - if (this.is_framed) - return parent.rcmail.replace_folder_row(oldfolder, newfolder, display_name, is_protected, class_name); + if (this.is_framed()) { + // @FIXME: for some reason this 'parent' variable need to be prefixed with 'window.' + return window.parent.rcmail.replace_folder_row(oldid, id, name, display_name, is_protected, class_name); + } + return false; } - var i, n, len, name, dispname, oldrow, tmprow, row, level, - tbody = this.gui_objects.subscriptionlist.tBodies[0], - folders = this.env.subscriptionrows, - id = this.get_folder_row_id(oldfolder), - prefix_len = oldfolder.length, - subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'), - // find subfolders of renamed folder - list = this.get_subfolders(oldfolder); + // reset searching + if (this.subscription_list.is_search()) { + this.subscription_select(); + this.subscription_list.reset_search(); + } + + var subfolders = {}, + row = this.subscription_list.get_item(oldid, true), + parent = $(row).parent(), + old_folder = this.env.subscriptionrows[oldid], + prefix_len_id = oldid.length, + prefix_len_name = old_folder[0].length, + subscribed = $('input[name="_subscribed[]"]:first', row).prop('checked'); // no renaming, only update class_name - if (oldfolder == newfolder) { - $('#'+id).attr('class', class_name || ''); - this.subscription_list.focus(); + if (oldid == id) { + $(row).attr('class', class_name || ''); return; } - // replace an existing table row - this._remove_folder_row(id); - row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name)); + // update subfolders + $('li', row).each(function() { + var fname = ref.folder_id2name(this.id), + folder = ref.env.subscriptionrows[fname], + newid = id + fname.slice(prefix_len_id); - // detect tree depth change - if (len = list.length) { - level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length; + this.id = 'rcmli' + ref.html_identifier_encode(newid); + $('input[name="_subscribed[]"]:first', this).val(newid); + folder[0] = name + folder[0].slice(prefix_len_name); + + subfolders[newid] = folder; + delete ref.env.subscriptionrows[fname]; + }); + + // get row off the list + row = $(row).detach(); + + delete this.env.subscriptionrows[oldid]; + + // remove parent list/toggle elements if not needed + if (parent.get(0) != this.gui_objects.subscriptionlist && !$('li', parent).length) { + $('ul,div.treetoggle', parent.parent()).remove(); } - // move subfolders to the new branch - for (n=0; n<len; n++) { - id = list[n]; - name = this.env.subscriptionrows[id][0]; - dispname = this.env.subscriptionrows[id][1]; - oldrow = $('#'+id); - tmprow = oldrow.clone(true); - oldrow.remove(); - row.after(tmprow); - row = tmprow; - // update folder index - name = newfolder + name.slice(prefix_len); - $('input[name="_subscribed[]"]', row).val(name); - this.env.subscriptionrows[id][0] = name; - // update the name if level is changed - if (level != 0) { - if (level > 0) { - for (i=level; i>0; i--) - dispname = dispname.replace(/^ /, ''); - } - else { - for (i=level; i<0; i++) - dispname = ' ' + dispname; - } - row.find('td:first').html(dispname); - this.env.subscriptionrows[id][1] = dispname; - } - } - - // update list widget - this.init_subscription_list(); + // move the existing table row + this.add_folder_row(id, name, display_name, is_protected, subscribed, class_name, row, subfolders); }; // remove the table row of a specific mailbox from the table - this.remove_folder_row = function(folder, subs) + this.remove_folder_row = function(folder) { - var n, len, list = [], id = this.get_folder_row_id(folder); - - // get subfolders if any - if (subs) - list = this.get_subfolders(folder); - - // remove old row - this._remove_folder_row(id); - - // remove subfolders - for (n=0, len=list.length; n<len; n++) - this._remove_folder_row(list[n]); - }; - - this._remove_folder_row = function(id) - { - this.subscription_list.remove_row(id.replace(/^rcmrow/, '')); - $('#'+id).remove(); - delete this.env.subscriptionrows[id]; - }; - - this.get_subfolders = function(folder) - { - var name, list = [], - prefix = folder + this.env.delimiter, - row = $('#'+this.get_folder_row_id(folder)).get(0); - - while (row = row.nextSibling) { - if (row.id) { - name = this.env.subscriptionrows[row.id][0]; - if (name && name.startsWith(prefix)) { - list.push(row.id); - } - else - break; - } + // reset searching + if (this.subscription_list.is_search()) { + this.subscription_select(); + this.subscription_list.reset_search(); } - return list; + var list = [], row = this.subscription_list.get_item(folder, true); + + // get subfolders if any + $('li', row).each(function() { list.push(ref.folder_id2name(this.id)); }); + + // remove folder row (and subfolders) + this.subscription_list.remove(folder); + + // update local list variable + list.push(folder); + $.each(list, function(i, v) { delete ref.env.subscriptionrows[v]; }); }; this.subscribe = function(folder) @@ -6067,15 +6095,6 @@ var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading'); this.http_post('unsubscribe', {_mbox: folder}, lock); } - }; - - // helper method to find a specific mailbox row ID - this.get_folder_row_id = function(folder) - { - var id, folders = this.env.subscriptionrows; - for (id in folders) - if (folders[id] && folders[id][0] == folder) - return id; }; // when user select a folder in manager @@ -6101,9 +6120,9 @@ // disables subscription checkbox (for protected folder) this.disable_subscription = function(folder) { - var id = this.get_folder_row_id(folder); - if (id) - $('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true); + var row = this.subscription_list.get_item(folder, true); + if (row) + $('input[name="_subscribed[]"]:first', row).prop('disabled', true); }; this.folder_size = function(folder) @@ -6117,6 +6136,37 @@ $('#folder-size').replaceWith(size); }; + // filter folders by namespace + this.folder_filter = function(prefix) + { + this.subscription_list.reset_search(); + + this.subscription_list.container.children('li').each(function() { + var i, folder = ref.folder_id2name(this.id); + // show all folders + if (prefix == '---') { + } + // got namespace prefix + else if (prefix) { + if (folder !== prefix) { + $(this).data('filtered', true).hide(); + return + } + } + // no namespace prefix, filter out all other namespaces + else { + // first get all namespace roots + for (i in ref.env.ns_roots) { + if (folder === ref.env.ns_roots[i]) { + $(this).data('filtered', true).hide(); + return; + } + } + } + + $(this).removeData('filtered').show(); + }); + }; /*********************************************************/ /********* GUI functionality *********/ @@ -6420,22 +6470,27 @@ }; // open a jquery UI dialog with the given content - this.show_popup_dialog = function(html, title, buttons, options) + this.show_popup_dialog = function(content, title, buttons, options) { // forward call to parent window if (this.is_framed()) { - return parent.rcmail.show_popup_dialog(html, title, buttons, options); + return parent.rcmail.show_popup_dialog(content, title, buttons, options); } - var popup = $('<div class="popup">') - .html(html) - .dialog($.extend({ + var popup = $('<div class="popup">'); + + if (typeof content == 'object') + popup.append(content); + else + popup.html(content); + + popup.dialog($.extend({ title: title, buttons: buttons, modal: true, resizable: true, width: 500, - close: function(event, ui) { $(this).remove() } + close: function(event, ui) { $(this).remove(); } }, options || {})); // resize and center popup @@ -6460,6 +6515,10 @@ // mark a mailbox as selected and set environment variable this.select_folder = function(name, prefix, encode) { + if (this.savedsearchlist) { + this.savedsearchlist.select(''); + } + if (this.treelist) { this.treelist.select(name); } @@ -7357,6 +7416,7 @@ // save message in local storage and do not redirect if (this.env.action == 'compose') { this.save_compose_form_local(); + this.compose_skip_unsavedcheck = true; } else if (redirect_url) { setTimeout(function(){ ref.redirect(redirect_url, true); }, 2000); @@ -7499,8 +7559,10 @@ // post the given form to a hidden iframe this.async_upload_form = function(form, action, onload) { - var frame, ts = new Date().getTime(), - frame_name = 'rcmupload'+ts; + // create hidden iframe + var ts = new Date().getTime(), + frame_name = 'rcmupload' + ts, + frame = this.async_upload_form_frame(frame_name); // upload progress support if (this.env.upload_progress_name) { @@ -7515,31 +7577,24 @@ field.val(ts); } - // have to do it this way for IE - // otherwise the form will be posted to a new window - if (document.all) { - document.body.insertAdjacentHTML('BeforeEnd', '<iframe name="'+frame_name+'"' - + ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>'); - frame = $('iframe[name="'+frame_name+'"]'); - } - // for standards-compliant browsers - else { - frame = $('<iframe>').attr('name', frame_name) - .css({border: 'none', width: 0, height: 0, visibility: 'hidden'}) - .appendTo(document.body); - } - - // handle upload errors, parsing iframe content in onload + // handle upload errors by parsing iframe content in onload frame.bind('load', {ts:ts}, onload); $(form).attr({ target: frame_name, - action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }), + action: this.url(action, {_id: this.env.compose_id || '', _uploadid: ts, _from: this.env.action}), method: 'POST'}) .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data') .submit(); return frame_name; + }; + + // create iframe element for files upload + this.async_upload_form_frame = function(name) + { + return $('<iframe>').attr({name: name, style: 'border: none; width: 0; height: 0; visibility: hidden'}) + .appendTo(document.body); }; // html5 file-drop API @@ -7590,7 +7645,7 @@ $.ajax({ type: 'POST', dataType: 'json', - url: ref.url(ref.env.filedrop.action||'upload', { _id:ref.env.compose_id||ref.env.cid||'', _uploadid:ts, _remote:1 }), + url: ref.url(ref.env.filedrop.action || 'upload', {_id: ref.env.compose_id||ref.env.cid||'', _uploadid: ts, _remote: 1, _from: ref.env.action}), contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary, processData: false, timeout: 0, // disable default timeout set in ajaxSetup() @@ -7979,22 +8034,43 @@ // wrapper for localStorage.getItem(key) this.local_storage_get_item = function(key, deflt, encrypted) { + var item; + // TODO: add encryption - var item = localStorage.getItem(this.get_local_storage_prefix() + key); + try { + item = localStorage.getItem(this.get_local_storage_prefix() + key); + } + catch (e) { } + return item !== null ? JSON.parse(item) : (deflt || null); }; // wrapper for localStorage.setItem(key, data) this.local_storage_set_item = function(key, data, encrypted) { - // TODO: add encryption - return localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data)); + // try/catch to handle no localStorage support, but also error + // in Safari-in-private-browsing-mode where localStorage exists + // but can't be used (#1489996) + try { + // TODO: add encryption + localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data)); + return true; + } + catch (e) { + return false; + } }; // wrapper for localStorage.removeItem(key) this.local_storage_remove_item = function(key) { - return localStorage.removeItem(this.get_local_storage_prefix() + key); + try { + localStorage.removeItem(this.get_local_storage_prefix() + key); + return true; + } + catch (e) { + return false; + } }; } // end object rcube_webmail -- Gitblit v1.9.1