diff options
Diffstat (limited to 'program/js/app.js')
-rw-r--r-- | program/js/app.js | 1244 |
1 files changed, 677 insertions, 567 deletions
diff --git a/program/js/app.js b/program/js/app.js index 670a8e0f5..499e2a2de 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -46,11 +46,12 @@ function rcube_webmail() this.messages = {}; this.group2expand = {}; this.http_request_jobs = {}; + 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 = { @@ -197,7 +198,10 @@ function rcube_webmail() // enable general commands this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref', - 'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true); + 'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-close', 'menu-save', true); + + // set active task button + this.set_button(this.task, 'sel'); if (this.env.permaurl) this.enable_command('permaurl', 'extwin', true); @@ -232,8 +236,7 @@ function rcube_webmail() return ref.command('sort', $(this).attr('rel'), this); }); - document.onmouseup = function(e){ return ref.doc_mouse_up(e); }; - this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); }; + 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()); @@ -276,7 +279,7 @@ function rcube_webmail() this.env.address_group_stack = []; this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor', 'list-adresses', 'pushgroup', 'search', 'reset-search', 'extwin', - 'insert-response', 'save-response']; + 'insert-response', 'save-response', 'menu-open', 'menu-close']; if (this.env.drafts_mailbox) this.env.compose_commands.push('savedraft') @@ -286,31 +289,36 @@ function rcube_webmail() // add more commands (not enabled) $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']); - if (this.env.spellcheck) { - this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); }; + if (window.googie) { + this.env.editor_config.spellchecker = googie; + this.env.editor_config.spellcheck_observer = function(s) { ref.spellcheck_state(); }; + this.env.compose_commands.push('spellcheck') this.enable_command('spellcheck', true); } + // initialize HTML editor + this.editor_init(this.env.editor_config, this.env.composebody); + // init canned response functions if (this.gui_objects.responseslist) { $('a.insertresponse', this.gui_objects.responseslist) .attr('unselectable', 'on') .mousedown(function(e){ return rcube_event.cancel(e); }) - .mouseup(function(e){ - ref.command('insert-response', $(this).attr('rel')); - $(document.body).trigger('mouseup'); // hides the menu - return rcube_event.cancel(e); + .bind('mouseup keypress', function(e){ + if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) { + ref.command('insert-response', $(this).attr('rel')); + $(document.body).trigger('mouseup'); // hides the menu + return rcube_event.cancel(e); + } }); // avoid textarea loosing focus when hitting the save-response button/link - for (var i=0; this.buttons['save-response'] && i < this.buttons['save-response'].length; i++) { - $('#'+this.buttons['save-response'][i].id).mousedown(function(e){ return rcube_event.cancel(e); }) - } + $.each(this.buttons['save-response'] || [], function (i, v) { + $('#' + v.id).mousedown(function(e){ return rcube_event.cancel(e); }) + }); } - document.onmouseup = function(e){ return ref.doc_mouse_up(e); }; - // init message compose form this.init_messageform(); } @@ -334,11 +342,21 @@ function rcube_webmail() // init address book widget if (this.gui_objects.contactslist) { this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, - { multiselect:true, draggable:false, keyboard:false }); + { multiselect:true, draggable:false, keyboard:true }); 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('keypress', function(o) { + if (o.key_pressed == o.ENTER_KEY) { + if (!ref.compose_add_recipient('to')) { + // 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(); + } + } + } + }) .init(); } @@ -385,11 +403,7 @@ function rcube_webmail() .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); }; - document.onmouseup = function(e){ return ref.doc_mouse_up(e); }; $(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); }); @@ -426,6 +440,9 @@ function rcube_webmail() else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') { this.enable_command('save', 'edit', 'toggle-editor', true); this.enable_command('delete', this.env.identities_level < 2); + + // initialize HTML editor + this.editor_init(this.env.editor_config, 'rcmfd_signature'); } else if (this.env.action == 'folders') { this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); @@ -441,19 +458,22 @@ function rcube_webmail() if (this.gui_objects.identitieslist) { this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, - {multiselect:false, draggable:false, keyboard:false}); + {multiselect:false, draggable:false, keyboard:true}); this.identity_list .addEventListener('select', function(o) { ref.identity_select(o); }) + .addEventListener('keypress', function(o) { + if (o.key_pressed == o.ENTER_KEY) { + ref.identity_select(o); + } + }) .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:false}); + this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:true}); this.sections_list .addEventListener('select', function(o) { ref.section_select(o); }) + .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) ref.section_select(o); }) .init() .focus(); } @@ -461,7 +481,7 @@ function rcube_webmail() this.init_subscription_list(); } else if (this.gui_objects.responseslist) { - this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:false}); + this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:true}); this.responses_list .addEventListener('select', function(list) { var win, id = list.get_single_selection(); @@ -551,17 +571,29 @@ function rcube_webmail() .get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false); } + // catch document (and iframe) mouse clicks + var body_mouseup = function(e){ return ref.doc_mouse_up(e); }; + $(document.body) + .bind('mouseup', body_mouseup) + .bind('keydown', function(e){ return ref.doc_keypress(e); }); + + $('iframe').load(function(e) { + try { $(this.contentDocument || this.contentWindow).on('mouseup', body_mouseup); } + catch (e) {/* catch possible "Permission denied" error in IE */ } + }) + .contents().on('mouseup', body_mouseup); + // trigger init event hook this.triggerEvent('init', { task:this.task, action:this.env.action }); // execute all foreign onload scripts // @deprecated - for (var i in this.onloads) { - if (typeof this.onloads[i] === 'string') - eval(this.onloads[i]); - else if (typeof this.onloads[i] === 'function') - this.onloads[i](); - } + for (n in this.onloads) { + if (typeof this.onloads[n] === 'string') + eval(this.onloads[n]); + else if (typeof this.onloads[n] === 'function') + this.onloads[n](); + } // start keep-alive and refresh intervals this.start_refresh(); @@ -583,7 +615,7 @@ function rcube_webmail() { var ret, uid, cid, url, flag, aborted = false; - if (obj && obj.blur) + 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) @@ -626,8 +658,8 @@ function rcube_webmail() } // trigger plugin hooks - this.triggerEvent('actionbefore', {props:props, action:command}); - ret = this.triggerEvent('before'+command, props); + this.triggerEvent('actionbefore', {props:props, action:command, originalEvent:event}); + ret = this.triggerEvent('before'+command, props || event); if (ret !== undefined) { // abort if one of the handlers returned false if (ret === false) @@ -677,9 +709,6 @@ function rcube_webmail() form.target = win.name; form.submit(); } - else { - // this.display_message(this.get_label('windowopenerror'), 'error'); - } } else { this.open_window(this.env.permaurl, true); @@ -702,9 +731,15 @@ function rcube_webmail() var mimetype = this.env.attachments[props.id]; this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0); } + this.show_menu(props, props.show || undefined, event); + break; + + case 'menu-close': + this.hide_menu(props, event); + break; case 'menu-save': - this.triggerEvent(command, {props:props}); + this.triggerEvent(command, {props:props, originalEvent:event}); return false; case 'open': @@ -882,14 +917,14 @@ function rcube_webmail() case 'move': case 'moveto': // deprecated if (this.task == 'mail') - this.move_messages(props, obj); + this.move_messages(props, event); else if (this.task == 'addressbook') this.move_contacts(props); break; case 'copy': if (this.task == 'mail') - this.copy_messages(props, obj); + this.copy_messages(props, event); else if (this.task == 'addressbook') this.copy_contacts(props); break; @@ -1040,17 +1075,11 @@ function rcube_webmail() case 'spellcheck': if (this.spellcheck_state()) { - this.stop_spellchecking(); + this.editor.spellcheck_stop(); } else { - if (window.tinyMCE && tinyMCE.get(this.env.composebody)) { - tinyMCE.execCommand('mceSpellCheck', true); - } - else if (this.env.spellcheck && this.env.spellcheck.spellCheck) { - this.env.spellcheck.spellCheck(); - } + this.editor.spellcheck_start(); } - this.spellcheck_state(); break; case 'savedraft': @@ -1132,8 +1161,8 @@ function rcube_webmail() this.gui_objects.messagepartframe.contentWindow.print(); } else if (uid = this.get_single_uid()) { - this.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : ''), true, true); - if (this.printwin) { + url = '&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : ''); + if (this.open_window(this.env.comm_path + url, true, true)) { if (this.env.action != 'show') this.mark_message('read', uid); } @@ -1272,7 +1301,7 @@ function rcube_webmail() default: var func = command.replace(/-/g, '_'); if (this[func] && typeof this[func] === 'function') { - ret = this[func](props, obj); + ret = this[func](props, obj, event); } break; } @@ -1309,7 +1338,7 @@ function rcube_webmail() this.command_enabled = function(cmd) { return this.commands[cmd]; - } + }; // lock/unlock interface this.set_busy = function(a, message, id) @@ -1351,10 +1380,11 @@ function rcube_webmail() // 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') @@ -1409,10 +1439,10 @@ function rcube_webmail() 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; @@ -1451,7 +1481,8 @@ function rcube_webmail() if (menu && modkey == SHIFT_KEY && this.commands['copy']) { var pos = rcube_event.get_mouse_pos(e); this.env.drag_target = target; - $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show(); + this.show_menu(this.gui_objects.dragmenu.id, true, e); + $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}); return true; } @@ -1551,7 +1582,7 @@ function rcube_webmail() // 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 { @@ -1567,17 +1598,22 @@ function rcube_webmail() } }; + // global mouse-click handler to cleanup some UI elements this.doc_mouse_up = function(e) { - var list, id; + var list, id, target = rcube_event.get_target(e); // ignore event if jquery UI dialog is open - if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length) + if ($(target).closest('.ui-dialog, .ui-widget-overlay').length) return; - list = this.message_list || this.contact_list; - if (list && !rcube_mouse_is_over(e, list.list.parentNode)) - list.blur(); + // remove focus from list widgets + if (window.rcube_list_widget && rcube_list_widget._instances.length) { + $.each(rcube_list_widget._instances, function(i,list){ + if (list && !rcube_mouse_is_over(e, list.list.parentNode)) + list.blur(); + }); + } // reset 'pressed' buttons if (this.buttons_sel) { @@ -1586,17 +1622,90 @@ function rcube_webmail() this.button_out(this.buttons_sel[id], id); this.buttons_sel = {}; } + + // reset popup menus; delayed to have updated menu_stack data + 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]; + obj = $('#' + id); + + if (obj.is(':visible') + && target != obj.data('opener') + && target != obj.get(0) // check if scroll bar was clicked (#1489832) + && !parents.is(obj.data('opener')) + && id != skip + && (obj.attr('data-editable') != 'true' || !$(target).parents('#' + id).length) + && (obj.attr('data-sticky') != 'true' || !rcube_mouse_is_over(e, obj.get(0))) + ) { + ref.hide_menu(id, e); + } + skip = obj.data('parent'); + } + }, 10); }; + // global keypress event handler + this.doc_keypress = function(e) + { + // Helper method to move focus to the next/prev active menu item + var focus_menu_item = function(dir) { + var obj, item, mod = dir < 0 ? 'prevAll' : 'nextAll', limit = dir < 0 ? 'last' : 'first'; + if (ref.focused_menu && (obj = $('#'+ref.focused_menu))) { + item = obj.find(':focus').closest('li')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit](); + if (!item.length) + item = obj.find(':focus').closest('ul')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit](); + return item.focus().length; + } + + return 0; + }; + + var target = e.target || {}, + keyCode = rcube_event.get_keycode(e); + + // save global reference for keyboard detection on click events in IE + rcube_event._last_keyboard_event = e; + + if (e.keyCode != 27 && (!this.menu_keyboard_active || target.nodeName == 'TEXTAREA' || target.nodeName == 'SELECT')) { + return true; + } + + switch (keyCode) { + case 38: + case 40: + case 63232: // "up", in safari keypress + case 63233: // "down", in safari keypress + focus_menu_item(keyCode == 38 || keyCode == 63232 ? -1 : 1); + break; + + case 9: // tab + if (this.focused_menu) { + var mod = rcube_event.get_modifier(e); + if (!focus_menu_item(mod == SHIFT_KEY ? -1 : 1)) { + this.hide_menu(this.focused_menu, e); + } + } + return rcube_event.cancel(e); + + case 27: // esc + if (this.menu_stack.length) + this.hide_menu(this.menu_stack[this.menu_stack.length-1], e); + break; + } + + 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(); + this.message_list.focus(e); else if (this.contact_list) - this.contact_list.focus(); + this.contact_list.focus(e); return true; }; @@ -1778,6 +1887,13 @@ function rcube_webmail() +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no')); } + // detect popup blocker (#1489618) + // don't care this might not work with all browsers + if (!extwin || extwin.closed) { + this.display_message(this.get_label('windowopenerror'), 'warning'); + return; + } + // write loading... message to empty windows if (!url && extwin.document) { extwin.document.write('<html><body>' + this.get_label('loading') + '</body></html>'); @@ -1874,7 +1990,7 @@ function rcube_webmail() flags: flags.extra_flags }); - var c, n, col, html, css_class, + var c, n, col, html, css_class, label, status_class = '', status_label = '', tree = '', expando = '', list = this.message_list, rows = list.rows, @@ -1891,17 +2007,26 @@ function rcube_webmail() css_class = 'msgicon'; if (this.env.status_col === null) { css_class += ' status'; - if (flags.deleted) - css_class += ' deleted'; - else if (!flags.seen) - css_class += ' unread'; - else if (flags.unread_children > 0) - css_class += ' unreadchildren'; + if (flags.deleted) { + status_class += ' deleted'; + status_label += this.get_label('deleted') + ' '; + } + else if (!flags.seen) { + status_class += ' unread'; + status_label += this.get_label('unread') + ' '; + } + else if (flags.unread_children > 0) { + status_class += ' unreadchildren'; + } + } + if (flags.answered) { + status_class += ' replied'; + status_label += this.get_label('replied') + ' '; + } + if (flags.forwarded) { + status_class += ' forwarded'; + status_label += this.get_label('replied') + ' '; } - if (flags.answered) - css_class += ' replied'; - if (flags.forwarded) - css_class += ' forwarded'; // update selection if (message.selected && !list.in_selection(uid)) @@ -1938,15 +2063,17 @@ function rcube_webmail() row_class += ' unroot'; } - tree += '<span id="msgicn'+row.id+'" class="'+css_class+'"> </span>'; + tree += '<span id="msgicn'+row.id+'" class="'+css_class+status_class+'" title="'+status_label+'"></span>'; row.className = row_class; // build subject link if (cols.subject) { - var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show'; - var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'; - cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+urlencode(uid)+'"'+ - ' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')"><span>'+cols.subject+'</span></a>'; + var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show', + uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid', + query = { _mbox: flags.mbox }; + query[uid_param] = uid; + cols.subject = '<a href="' + this.url(action, query) + '" onclick="return rcube_event.keyboard_only(event)"' + + ' onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')" tabindex="-1"><span>'+cols.subject+'</span></a>'; } // add each submitted col @@ -1960,28 +2087,36 @@ function rcube_webmail() if (c == 'flag') { css_class = (flags.flagged ? 'flagged' : 'unflagged'); - html = '<span id="flagicn'+row.id+'" class="'+css_class+'"> </span>'; + label = this.get_label(css_class); + html = '<span id="flagicn'+row.id+'" class="'+css_class+'" title="'+label+'"></span>'; } else if (c == 'attachment') { + label = this.get_label('withattachment'); if (flags.attachmentClass) - html = '<span class="'+flags.attachmentClass+'"> </span>'; + html = '<span class="'+flags.attachmentClass+'" title="'+label+'"></span>'; else if (/application\/|multipart\/(m|signed)/.test(flags.ctype)) - html = '<span class="attachment"> </span>'; + html = '<span class="attachment" title="'+label+'"></span>'; else if (/multipart\/report/.test(flags.ctype)) - html = '<span class="report"> </span>'; - else + html = '<span class="report"></span>'; + else html = ' '; } else if (c == 'status') { - if (flags.deleted) + label = ''; + if (flags.deleted) { css_class = 'deleted'; - else if (!flags.seen) + label = this.get_label('deleted'); + } + else if (!flags.seen) { css_class = 'unread'; - else if (flags.unread_children > 0) + label = this.get_label('unread'); + } + else if (flags.unread_children > 0) { css_class = 'unreadchildren'; + } else css_class = 'msgicon'; - html = '<span id="statusicn'+row.id+'" class="'+css_class+'"> </span>'; + html = '<span id="statusicn'+row.id+'" class="'+css_class+status_class+'" title="'+label+'"></span>'; } else if (c == 'threads') html = expando; @@ -1991,8 +2126,10 @@ function rcube_webmail() html = tree + cols[c]; } else if (c == 'priority') { - if (flags.prio > 0 && flags.prio < 6) - html = '<span class="prio'+flags.prio+'"> </span>'; + if (flags.prio > 0 && flags.prio < 6) { + label = this.get_label('priority') + ' ' + flags.prio; + html = '<span class="prio'+flags.prio+'" title="'+label+'"></span>'; + } else html = ' '; } @@ -2109,20 +2246,40 @@ function rcube_webmail() this.location_href(this.env.comm_path+url, target, true); // mark as read and change mbox unread counter - if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) { + if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read > 0) { this.preview_read_timer = setTimeout(function() { - ref.set_message(id, 'unread', false); - if (ref.env.unread_counts[ref.env.mailbox]) { - ref.env.unread_counts[ref.env.mailbox] -= 1; - ref.set_unread_count(ref.env.mailbox, ref.env.unread_counts[ref.env.mailbox], ref.env.mailbox == 'INBOX'); - } - if (ref.env.preview_pane_mark_read > 0) - ref.http_post('mark', {_uid: id, _flag: 'read', _quiet: 1}); + ref.set_unread_message(id, ref.env.mailbox); + ref.http_post('mark', {_uid: id, _flag: 'read', _quiet: 1}); }, this.env.preview_pane_mark_read * 1000); } } }; + // update message status and unread counter after marking a message as read + this.set_unread_message = function(id, folder) + { + var self = this; + + // find window with messages list + if (!self.message_list) + self = self.opener(); + + if (!self && window.parent) + self = parent.rcmail; + + if (!self || !self.message_list) + return; + + // this may fail in multifolder mode + if (self.set_message(id, 'unread', false) === false) + self.set_message(id + '-' + folder, 'unread', false); + + if (self.env.unread_counts[folder] > 0) { + self.env.unread_counts[folder] -= 1; + self.set_unread_count(folder, self.env.unread_counts[folder], folder == 'INBOX' && !self.is_multifolder_listing()); + } + }; + this.show_contentframe = function(show) { var frame, win, name = this.env.contentframe; @@ -2269,6 +2426,9 @@ function rcube_webmail() url._framed = 1; } + if (this.env.uid) + url._uid = this.env.uid; + // load message list to target frame/window if (mbox) { this.set_busy(true, 'loading'); @@ -2282,7 +2442,6 @@ function rcube_webmail() this.clear_message_list = function() { this.env.messages = {}; - this.last_selected = 0; this.show_contentframe(false); if (this.message_list) @@ -2301,6 +2460,7 @@ function rcube_webmail() url._page = page; this.http_request('list', url, lock); + this.update_state({ _mbox: mbox, _page: (page && page > 1 ? page : null) }); }; // removes messages that doesn't exists from list selection array @@ -2315,7 +2475,7 @@ function rcube_webmail() selection.push(selected[i]); this.message_list.selection = selection; - } + }; // expand all threads with unread children this.expand_unread = function() @@ -2328,8 +2488,10 @@ function rcube_webmail() this.message_list.expand_all(r); this.set_unread_children(r.uid); } + new_row = new_row.nextSibling; } + return false; }; @@ -2546,8 +2708,8 @@ function rcube_webmail() } // 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; }; @@ -2574,7 +2736,7 @@ function rcube_webmail() // set message icon this.set_message_icon = function(uid) { - var css_class, + var css_class, label = '', row = this.message_list.rows[uid]; if (!row) @@ -2582,38 +2744,55 @@ function rcube_webmail() if (row.icon) { css_class = 'msgicon'; - if (row.deleted) + if (row.deleted) { css_class += ' deleted'; - else if (row.unread) + label += this.get_label('deleted') + ' '; + } + else if (row.unread) { css_class += ' unread'; + label += this.get_label('unread') + ' '; + } else if (row.unread_children) css_class += ' unreadchildren'; if (row.msgicon == row.icon) { - if (row.replied) + if (row.replied) { css_class += ' replied'; - if (row.forwarded) + label += this.get_label('replied') + ' '; + } + if (row.forwarded) { css_class += ' forwarded'; + label += this.get_label('forwarded') + ' '; + } css_class += ' status'; } - row.icon.className = css_class; + $(row.icon).attr('class', css_class).attr('title', label); } if (row.msgicon && row.msgicon != row.icon) { + label = ''; css_class = 'msgicon'; - if (!row.unread && row.unread_children) + if (!row.unread && row.unread_children) { css_class += ' unreadchildren'; - if (row.replied) + } + if (row.replied) { css_class += ' replied'; - if (row.forwarded) + label += this.get_label('replied') + ' '; + } + if (row.forwarded) { css_class += ' forwarded'; + label += this.get_label('forwarded') + ' '; + } - row.msgicon.className = css_class; + $(row.msgicon).attr('class', css_class).attr('title', label); } if (row.flagicon) { css_class = (row.flagged ? 'flagged' : 'unflagged'); - row.flagicon.className = css_class; + label = this.get_label(css_class); + $(row.flagicon).attr('class', css_class) + .attr('aria-label', label) + .attr('title', label); } }; @@ -2628,16 +2807,10 @@ function rcube_webmail() if (flag == 'unread') { if (row.unread != status) this.update_thread_root(uid, status ? 'unread' : 'read'); - row.unread = status; } - else if(flag == 'deleted') - row.deleted = status; - else if (flag == 'replied') - row.replied = status; - else if (flag == 'forwarded') - row.forwarded = status; - else if (flag == 'flagged') - row.flagged = status; + + if ($.inArray(flag, ['unread', 'deleted', 'replied', 'forwarded', 'flagged']) > -1) + row[flag] = status; }; // set message row status, class and icon @@ -2651,22 +2824,8 @@ function rcube_webmail() if (flag) this.set_message_status(uid, flag, status); - var rowobj = $(row.obj); - - if (row.unread && !rowobj.hasClass('unread')) - rowobj.addClass('unread'); - else if (!row.unread && rowobj.hasClass('unread')) - rowobj.removeClass('unread'); - - if (row.deleted && !rowobj.hasClass('deleted')) - rowobj.addClass('deleted'); - else if (!row.deleted && rowobj.hasClass('deleted')) - rowobj.removeClass('deleted'); - - if (row.flagged && !rowobj.hasClass('flagged')) - rowobj.addClass('flagged'); - else if (!row.flagged && rowobj.hasClass('flagged')) - rowobj.removeClass('flagged'); + if ($.inArray(flag, ['unread', 'deleted', 'flagged']) > -1) + $(row.obj)[row[flag] ? 'addClass' : 'removeClass'](flag); this.set_unread_children(uid); this.set_message_icon(uid); @@ -2687,12 +2846,12 @@ function rcube_webmail() }; // copy selected messages to the specified mailbox - this.copy_messages = function(mbox, obj) + this.copy_messages = function(mbox, event) { if (mbox && typeof mbox === 'object') mbox = mbox.id; else if (!mbox) - return this.folder_selector(obj, function(folder) { ref.command('copy', folder); }); + return this.folder_selector(event, function(folder) { ref.command('copy', folder); }); // exit if current or no mailbox specified if (!mbox || mbox == this.env.mailbox) @@ -2709,12 +2868,12 @@ function rcube_webmail() }; // move selected messages to the specified mailbox - this.move_messages = function(mbox, obj) + this.move_messages = function(mbox, event) { if (mbox && typeof mbox === 'object') mbox = mbox.id; else if (!mbox) - return this.folder_selector(obj, function(folder) { ref.command('move', folder); }); + return this.folder_selector(event, function(folder) { ref.command('move', folder); }); // exit if current or no mailbox specified if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing())) @@ -3011,12 +3170,12 @@ function rcube_webmail() this.message_list.clear_selection(); if (count < 0) post_data._count = (count*-1); - else if (count > 0) + else if (count > 0) // remove threads from the end of the list this.delete_excessive_thread_rows(); } - // ?? + // set of messages to mark as seen if (r_uids.length) post_data._ruid = this.uids_to_list(r_uids); @@ -3030,7 +3189,7 @@ function rcube_webmail() // argument should be a coma-separated list of uids this.flag_deleted_as_read = function(uids) { - var icn_src, uid, i, len, + var uid, i, len, rows = this.message_list ? this.message_list.rows : {}; if (typeof uids == 'string') @@ -3158,7 +3317,7 @@ function rcube_webmail() if (!this.gui_objects.messageform) return false; - var input_from = $("[name='_from']"), + var i, input_from = $("[name='_from']"), input_to = $("[name='_to']"), input_subject = $("input[name='_subject']"), input_message = $("[name='_message']").get(0), @@ -3187,7 +3346,7 @@ function rcube_webmail() // init live search events this.init_address_input_events(input_to, ac_props); - for (var i in ac_fields) { + for (i in ac_fields) { this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props); } @@ -3202,10 +3361,11 @@ function rcube_webmail() // check for locally stored compose data if (window.localStorage) { - var index = this.local_storage_get_item('compose.index', []); + var key, formdata, index = this.local_storage_get_item('compose.index', []); - for (var key, i = 0; i < index.length; i++) { - key = index[i], formdata = this.local_storage_get_item('compose.' + key, null, true); + for (i = 0; i < index.length; i++) { + key = index[i]; + formdata = this.local_storage_get_item('compose.' + key, null, true); if (!formdata) { continue; } @@ -3279,7 +3439,7 @@ function rcube_webmail() this.env.recipients_delimiter = this.env.recipients_separator + ' '; obj.keydown(function(e) { return ref.ksearch_keydown(e, this, props); }) - .attr('autocomplete', 'off'); + .attr({ 'autocomplete': 'off', 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'role': 'combobox' }); }; this.submit_messageform = function(draft) @@ -3350,18 +3510,19 @@ function rcube_webmail() input.val(oldval + recipients.join(delim + ' ') + delim + ' '); this.triggerEvent('add-recipient', { field:field, recipients:recipients }); } + + return recipients.length; }; // checks the input fields before sending a message this.check_compose_input = function(cmd) { // check input fields - var ed, input_to = $("[name='_to']"), + var input_to = $("[name='_to']"), input_cc = $("[name='_cc']"), input_bcc = $("[name='_bcc']"), input_from = $("[name='_from']"), - input_subject = $("[name='_subject']"), - input_message = $("[name='_message']"); + input_subject = $("[name='_subject']"); // check sender (if have no identities) if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) { @@ -3388,10 +3549,12 @@ function rcube_webmail() // display localized warning for missing subject if (input_subject.val() == '') { - var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body); - var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject')); + var buttons = {}, + 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); - var buttons = {}; buttons[this.get_label('cancel')] = function(){ input_subject.focus(); $(this).dialog('close'); @@ -3408,100 +3571,36 @@ function rcube_webmail() buttons: buttons, close: function(event, ui) { $(this).remove() } }); + prompt_value.select(); return false; } - // Apply spellcheck changes if spell checker is active - this.stop_spellchecking(); - - if (window.tinyMCE) - ed = tinyMCE.get(this.env.composebody); - // check for empty body - if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) { - input_message.focus(); + if (!this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) { + this.editor.focus(); return false; } - else if (ed) { - if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) { - ed.focus(); - return false; - } - // move body from html editor to textarea (just to be sure, #1485860) - tinyMCE.triggerSave(); - } + + // move body from html editor to textarea (just to be sure, #1485860) + this.editor.save(); return true; }; - this.toggle_editor = function(props) + this.toggle_editor = function(props, obj, e) { - this.stop_spellchecking(); + // @todo: this should work also with many editors on page + var result = this.editor.toggle(props.html); - var ed, curr, content, result, - // these non-printable chars are not removed on text2html and html2text - // we can use them as temp signature replacement - sig_mark = "\u0002\u0003", - input = $('#' + props.id), - signature = this.env.identity ? this.env.signatures[this.env.identity] : null, - is_sig = signature && signature.text && signature.text.length > 1; - - if (props.mode == 'html') { - content = input.val(); - - // replace current text signature with temp mark - if (is_sig) - content = content.replace(signature.text, sig_mark); - - // convert to html - result = this.plain2html(content, function(data) { - // replace signature mark with html version of the signature - if (is_sig) - data = data.replace(sig_mark, '<div id="_rc_sig">' + signature.html + '</div>'); - - input.val(data); - tinyMCE.execCommand('mceAddControl', false, props.id); - - if (ref.env.default_font) - setTimeout(function() { - $(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font); - }, 500); - }); + if (!result && e) { + // fix selector value if operation failed + $(e.target).filter('select').val(props.html ? 'plain' : 'html'); } - else { - ed = tinyMCE.get(props.id); - - if (is_sig) { - // get current version of signature, we'll need it in - // case of html2text conversion abort - if (curr = ed.dom.get('_rc_sig')) - curr = curr.innerHTML; - - // replace current signature with some non-printable characters - // we use non-printable characters, because this replacement - // is visible to the user - // doing this after getContent() would be hard - ed.dom.setHTML('_rc_sig', sig_mark); - } - - // get html content - content = ed.getContent(); - - // convert html to text - result = this.html2plain(content, function(data) { - tinyMCE.execCommand('mceRemoveControl', false, props.id); - // replace signture mark with text version of the signature - if (is_sig) - data = data.replace(sig_mark, "\n" + signature.text); - - input.val(data); - }); - - // bring back current signature - if (!result && curr) - ed.dom.setHTML('_rc_sig', curr); + if (result) { + // update internal format flag + $("input[name='_is_html']").val(props.html ? 1 : 0); } return result; @@ -3510,30 +3609,11 @@ function rcube_webmail() this.insert_response = function(key) { var insert = this.env.textresponses[key] ? this.env.textresponses[key].text : null; + if (!insert) return false; - // insert into tinyMCE editor - if ($("input[name='_is_html']").val() == '1') { - var editor = tinyMCE.get(this.env.composebody); - editor.getWin().focus(); // correct focus in IE & Chrome - editor.selection.setContent(this.quote_html(insert).replace(/\r?\n/g, '<br/>'), { format:'text' }); - } - // replace selection in compose textarea - else { - var textarea = rcube_find_object(this.env.composebody), - selection = $(textarea).is(':focus') ? this.get_input_selection(textarea) : { start:0, end:0 }, - inp_value = textarea.value; - pre = inp_value.substring(0, selection.start), - end = inp_value.substring(selection.end, inp_value.length); - - // insert response text - textarea.value = pre + insert + end; - - // set caret after inserted text - this.set_caret_pos(textarea, selection.start + insert.length); - textarea.focus(); - } + this.editor.replace(insert); }; /** @@ -3541,42 +3621,8 @@ function rcube_webmail() */ this.save_response = function() { - var sigstart, text = '', strip = false; - - // get selected text from tinyMCE editor - if ($("input[name='_is_html']").val() == '1') { - var editor = tinyMCE.get(this.env.composebody); - editor.getWin().focus(); // correct focus in IE & Chrome - text = editor.selection.getContent({ format:'text' }); - - if (!text) { - text = editor.getContent({ format:'text' }); - strip = true; - } - } - // get selected text from compose textarea - else { - var textarea = rcube_find_object(this.env.composebody), sigstart; - if (textarea && $(textarea).is(':focus')) { - text = this.get_input_selection(textarea).text; - } - - if (!text && textarea) { - text = textarea.value; - strip = true; - } - } - - // strip off signature - if (strip) { - sigstart = text.indexOf('-- \n'); - if (sigstart > 0) { - text = text.substring(0, sigstart); - } - } - // show dialog to enter a name and to modify the text to be saved - var buttons = {}, + var buttons = {}, text = this.editor.get_content(true, true), html = '<form class="propform">' + '<div class="prop block"><label>' + this.get_label('responsename') + '</label>' + '<input type="text" name="name" id="ffresponsename" size="40" /></div>' + @@ -3621,15 +3667,18 @@ function rcube_webmail() $('<a>').addClass('insertresponse active') .attr('href', '#') .attr('rel', key) + .attr('tabindex', '0') .html(this.quote_html(response.name)) .appendTo(li) .mousedown(function(e){ return rcube_event.cancel(e); }) - .mouseup(function(e){ - ref.command('insert-response', key); - $(document.body).trigger('mouseup'); // hides the menu - return rcube_event.cancel(e); + .bind('mouseup keypress', function(e){ + if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) { + ref.command('insert-response', $(this).attr('rel')); + $(document.body).trigger('mouseup'); // hides the menu + return rcube_event.cancel(e); + } }); } }; @@ -3655,33 +3704,14 @@ function rcube_webmail() return false; }; - this.stop_spellchecking = function() - { - var ed; - - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) { - if (ed.plugins && ed.plugins.spellchecker && ed.plugins.spellchecker.active) - ed.execCommand('mceSpellCheck'); - } - else if (ed = this.env.spellcheck) { - if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found') - $(ed.spell_span).trigger('click'); - } - - this.spellcheck_state(); - }; - + // updates spellchecker buttons on state change this.spellcheck_state = function() { - var ed, active; + var active = this.editor.spellcheck_state(); - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker) - active = ed.plugins.spellchecker.active; - else if ((ed = this.env.spellcheck) && ed.state) - active = ed.state != 'ready' && ed.state != 'no_error_found'; - - 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; }; @@ -3689,43 +3719,19 @@ function rcube_webmail() // get selected language this.spellcheck_lang = function() { - var ed; - - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker) - return ed.plugins.spellchecker.selectedLang; - else if (this.env.spellcheck) - return GOOGIE_CUR_LANG; + return this.editor.get_language(); }; this.spellcheck_lang_set = function(lang) { - var ed; - - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins) - ed.plugins.spellchecker.selectedLang = lang; - else if (this.env.spellcheck) - this.env.spellcheck.setCurrentLanguage(lang); + this.editor.set_language(lang); }; // resume spellchecking, highlight provided mispellings without new ajax request - this.spellcheck_resume = function(ishtml, data) + this.spellcheck_resume = function(data) { - if (ishtml) { - var ed = tinyMCE.get(this.env.composebody); - sp = ed.plugins.spellchecker; - - sp.active = 1; - sp._markWords(data); - ed.nodeChanged(); - } - else { - var sp = this.env.spellcheck; - sp.prepare(false, true); - sp.processData(data); - } - - this.spellcheck_state(); - } + this.editor.spellcheck_resume(data); + }; this.set_draft_id = function(id) { @@ -3785,16 +3791,13 @@ function rcube_webmail() this.compose_field_hash = function(save) { // check input fields - var ed, i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject']; + var i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject']; for (i=0; i<hash_fields.length; i++) if (val = $('[name="_' + hash_fields[i] + '"]').val()) str += val + ':'; - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) - str += ed.getContent(); - else - str += $("[name='_message']").val(); + str += this.editor.get_content(); if (this.env.attachments) for (id in this.env.attachments) @@ -3813,9 +3816,7 @@ function rcube_webmail() ed, empty = true; // get fresh content from editor - if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) { - tinyMCE.triggerSave(); - } + this.editor.save(); if (this.env.draft_id) { formdata.draft_id = this.env.draft_id; @@ -3878,15 +3879,8 @@ function rcube_webmail() }); // initialize HTML editor - if (formdata._is_html == '1') { - if (!html_mode) { - tinyMCE.execCommand('mceAddControl', false, this.env.composebody); - this.triggerEvent('aftertoggle-editor', { mode:'html' }); - } - } - else if (html_mode) { - tinyMCE.execCommand('mceRemoveControl', false, this.env.composebody); - this.triggerEvent('aftertoggle-editor', { mode:'plain' }); + if ((formdata._is_html == '1' && !html_mode) || (formdata._is_html != '1' && html_mode)) { + this.command('toggle-editor', {id: this.env.composebody, html: !html_mode}); } } }; @@ -3915,7 +3909,7 @@ function rcube_webmail() } this.local_storage_remove_item('compose.index'); } - } + }; this.change_identity = function(obj, show_sig) @@ -3935,11 +3929,8 @@ function rcube_webmail() return; } - var i, rx, cursor_pos, p = -1, + var i, rx, 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, delim = this.env.recipients_separator, rx_delim = RegExp.escape(delim), @@ -3960,7 +3951,7 @@ function rcube_webmail() // cleanup rx = new RegExp(rx_delim + '\\s*' + rx_delim, 'g'); - input_val = input_val.replace(rx, delim); + input_val = String(input_val).replace(rx, delim); rx = new RegExp('^[\\s' + rx_delim + ']+'); input_val = input_val.replace(rx, ''); @@ -3986,92 +3977,7 @@ function rcube_webmail() else this.enable_command('insert-sig', false); - if (!is_html) { - // remove the 'old' signature - if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) { - sig = this.env.signatures[sig].text; - sig = sig.replace(/\r\n/g, '\n'); - - 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); - } - // add the new signature string - if (show_sig && this.env.signatures && this.env.signatures[id]) { - sig = this.env.signatures[id].text; - sig = sig.replace(/\r\n/g, '\n'); - - 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; - } - else if (!message) { // empty message - cursor_pos = 0; - message = '\n\n' + sig; - } - else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position - message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length); - cursor_pos = pos; - } - else { // on top - cursor_pos = 0; - message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, ''); - } - } - else { - message = message.replace(/[\r\n]+$/, ''); - cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0; - message += '\n\n' + sig; - } - } - else - cursor_pos = this.env.top_posting ? 0 : message.length; - - input_message.val(message); - - // move cursor before the signature - this.set_caret_pos(input_message.get(0), cursor_pos); - } - else if (show_sig && this.env.signatures) { // html - var editor = tinyMCE.get(this.env.composebody), - sigElem = editor.dom.get('_rc_sig'); - - // Append the signature as a div within the body - if (!sigElem) { - var body = editor.getBody(), - doc = editor.getDoc(); - - sigElem = doc.createElement('div'); - sigElem.setAttribute('id', '_rc_sig'); - - if (this.env.top_posting) { - // if no existing sig and top posting then insert at caret pos - editor.getWin().focus(); // correct focus in IE & Chrome - - var node = editor.selection.getNode(); - if (node.nodeName == 'BODY') { - // no real focus, insert at start - body.insertBefore(sigElem, body.firstChild); - body.insertBefore(doc.createElement('br'), body.firstChild); - } - else { - body.insertBefore(sigElem, node.nextSibling); - body.insertBefore(doc.createElement('br'), node.nextSibling); - } - } - else { - if (bw.ie) // add empty line before signature on IE - body.appendChild(doc.createElement('br')); - - body.appendChild(sigElem); - } - } - - if (this.env.signatures[id]) - sigElem.innerHTML = this.env.signatures[id].html; - } - + this.editor.change_signature(id, show_sig); this.env.identity = id; this.triggerEvent('change_identity'); return true; @@ -4147,6 +4053,9 @@ function rcube_webmail() // called from upload page this.add2attachment_list = function(name, att, upload_id) { + if (upload_id) + this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id}); + if (!this.gui_objects.attachmentlist) return false; @@ -4214,7 +4123,7 @@ function rcube_webmail() this.upload_progress_update = function(param) { - var elem = $('#'+param.name + '> span'); + var elem = $('#'+param.name + ' > span'); if (!elem.length || !param.text) return; @@ -4400,8 +4309,7 @@ function rcube_webmail() if (this.ksearch_timer) clearTimeout(this.ksearch_timer); - var highlight, - key = rcube_event.get_keycode(e), + var key = rcube_event.get_keycode(e), mod = rcube_event.get_modifier(e); switch (key) { @@ -4410,9 +4318,9 @@ function rcube_webmail() if (!this.ksearch_visible()) return; - var dir = key==38 ? 1 : 0; + var dir = key == 38 ? 1 : 0, + highlight = document.getElementById('rcmkSearchItem' + this.ksearch_selected); - highlight = document.getElementById('rcmksearchSelected'); if (!highlight) highlight = this.ksearch_pane.__ul.firstChild; @@ -4460,14 +4368,14 @@ function rcube_webmail() this.ksearch_select = function(node) { - var current = $('#rcmksearchSelected'); - if (current[0] && node) { - current.removeAttr('id').removeClass('selected'); + if (this.ksearch_pane && node) { + this.ksearch_pane.find('li.selected').removeClass('selected').removeAttr('aria-selected'); } if (node) { - $(node).attr('id', 'rcmksearchSelected').addClass('selected'); + $(node).addClass('selected').attr('aria-selected', 'true'); this.ksearch_selected = node._rcm_id; + $(this.ksearch_input).attr('aria-activedescendant', 'rcmkSearchItem' + this.ksearch_selected); } }; @@ -4596,16 +4504,20 @@ function rcube_webmail() return; // display search results - var i, len, ul, li, text, type, init, + var i, id, len, ul, text, type, init, value = this.ksearch_value, maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15; // create results pane if not present if (!this.ksearch_pane) { ul = $('<ul>'); - this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane') + 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('mouseup', 'li', function(e){ ref.ksearch_click(e.target); }) } ul = this.ksearch_pane.__ul; @@ -4630,23 +4542,29 @@ function rcube_webmail() for (i=0; i < len && maxlen > 0; i++) { text = typeof results[i] === 'object' ? results[i].name : results[i]; type = typeof results[i] === 'object' ? results[i].type : ''; - li = document.createElement('LI'); - li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '<').replace(/>/g, '>').replace(/##([^%]+)%%/g, '<b>$1</b>'); - li.onmouseover = function(){ ref.ksearch_select(this); }; - li.onmouseup = function(){ ref.ksearch_click(this) }; - li._rcm_id = this.env.contacts.length + i; - if (type) li.className = type; - ul.appendChild(li); + id = i + this.env.contacts.length; + $('<li>').attr('id', 'rcmkSearchItem' + id) + .attr('role', 'option') + .html(this.quote_html(text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>')) + .addClass(type || '') + .appendTo(ul) + .get(0)._rcm_id = id; maxlen -= 1; } } if (ul.childNodes.length) { + // set the right aria-* attributes to the input field + $(this.ksearch_input) + .attr('aria-haspopup', 'true') + .attr('aria-expanded', 'true') + .attr('aria-owns', 'rcmKSearchpane'); + this.ksearch_pane.show(); + // select the first if (!this.env.contacts.length) { - $('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected'); - this.ksearch_selected = 0; + this.ksearch_select($('li:first', ul).get(0)); } } @@ -4683,6 +4601,12 @@ function rcube_webmail() if (this.ksearch_pane) this.ksearch_pane.hide(); + $(this.ksearch_input) + .attr('aria-haspopup', 'false') + .attr('aria-expanded', 'false') + .removeAttr('aria-activedescendant') + .removeAttr('aria-owns'); + this.ksearch_destroy(); }; @@ -4887,6 +4811,7 @@ function rcube_webmail() // add link to pop back to parent group if (this.env.address_group_stack.length > 1) { $('<a href="#list">...</a>') + .attr('title', this.gettext('uponelevel')) .addClass('poplink') .appendTo(boxtitle) .click(function(e){ return ref.command('popgroup','',this); }); @@ -4920,7 +4845,7 @@ function rcube_webmail() else if (framed) return false; - if (action && (cid || action=='add') && !this.drag_active) { + if (action && (cid || action == 'add') && !this.drag_active) { if (this.env.group) url._gid = this.env.group; @@ -4937,7 +4862,9 @@ function rcube_webmail() // add/delete member to/from the group this.group_member_change = function(what, cid, source, gid) { - what = what == 'add' ? 'add' : 'del'; + if (what != 'add') + what = 'del'; + var label = this.get_label(what == 'add' ? 'addingmember' : 'removingmember'), lock = this.display_message(label, 'loading'), post_data = {_cid: cid, _source: source, _gid: gid}; @@ -4974,7 +4901,7 @@ function rcube_webmail() // 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, + var dest = to.type == 'group' ? to.source : to.id, source = this.env.source, group = this.env.group ? this.env.group : '', cid = this.contact_list.get_selection().join(','); @@ -5045,13 +4972,14 @@ function rcube_webmail() { 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; 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 { @@ -5089,15 +5017,15 @@ function rcube_webmail() // update a contact record in the list this.update_contact_row = function(cid, cols_arr, newcid, source, data) { - var c, row, list = this.contact_list; + var list = this.contact_list; cid = this.html_identifier(cid); // when in searching mode, concat cid with the source name if (!list.rows[cid]) { - cid = cid+'-'+source; + cid = cid + '-' + source; if (newcid) - newcid = newcid+'-'+source; + newcid = newcid + '-' + source; } list.update_row(cid, cols_arr, newcid, true); @@ -5113,7 +5041,7 @@ function rcube_webmail() var c, col, list = this.contact_list, row = { cols:[] }; - row.id = 'rcmrow'+this.html_identifier(cid); + row.id = 'rcmrow' + this.html_identifier(cid); row.className = 'contact ' + (classes || ''); if (list.in_selection(cid)) @@ -5149,7 +5077,7 @@ function rcube_webmail() return false; }); - $('select.addfieldmenu').change(function(e) { + $('select.addfieldmenu').change(function() { ref.insert_edit_field($(this).val(), $(this).attr('rel'), this); this.selectedIndex = 0; }); @@ -5211,6 +5139,7 @@ function rcube_webmail() this.remove_group_item = function(prop) { var key = 'G'+prop.source+prop.id; + if (this.treelist.remove(key)) { this.triggerEvent('group_delete', { source:prop.source, id:prop.id }); delete this.env.contactfolders[key]; @@ -5328,6 +5257,7 @@ function rcube_webmail() this.reset_add_input(); prop.type = 'group'; + var key = 'G'+prop.source+prop.id, link = $('<a>').attr('href', '#') .attr('rel', prop.source+':'+prop.id) @@ -5383,9 +5313,11 @@ function rcube_webmail() this.update_group_commands = function() { - var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null; - this.enable_command('group-create', (source && source.groups && !source.readonly)); - this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly)); + var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null, + supported = source && source.groups && !source.readonly; + + this.enable_command('group-create', supported); + this.enable_command('group-rename', 'group-delete', supported && this.env.group); }; this.init_edit_field = function(col, elem) @@ -5423,6 +5355,7 @@ function rcube_webmail() if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') { var input, colprop = this.env.coltypes[col], + input_id = 'ff_' + col + (colprop.count || 0), row = $('<div>').addClass('row'), cell = $('<div>').addClass('contactfieldcontent data'), label = $('<div>').addClass('contactfieldlabel label'); @@ -5430,13 +5363,14 @@ function rcube_webmail() if (colprop.subtypes_select) label.html(colprop.subtypes_select); else - label.html(colprop.label); + label.html('<label for="' + input_id + '">' + colprop.label + '</label>'); var name_suffix = colprop.limit != 1 ? '[]' : ''; + if (colprop.type == 'text' || colprop.type == 'date') { input = $('<input>') .addClass('ff_'+col) - .attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size}) + .attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size, id: input_id}) .appendTo(cell); this.init_edit_field(col, input); @@ -5447,18 +5381,19 @@ function rcube_webmail() else if (colprop.type == 'textarea') { input = $('<textarea>') .addClass('ff_'+col) - .attr({ name: '_'+col+name_suffix, cols:colprop.size, rows:colprop.rows }) + .attr({ name: '_'+col+name_suffix, cols:colprop.size, rows:colprop.rows, id: input_id }) .appendTo(cell); this.init_edit_field(col, input); } else if (colprop.type == 'composite') { - var childcol, cp, first, templ, cols = [], suffices = []; + var i, childcol, cp, first, templ, cols = [], suffices = []; + // read template for composite field order if ((templ = this.env[col+'_template'])) { - for (var j=0; j < templ.length; j++) { - cols.push(templ[j][1]); - suffices.push(templ[j][2]); + for (i=0; i < templ.length; i++) { + cols.push(templ[i][1]); + suffices.push(templ[i][2]); } } else { // list fields according to appearance in colprop @@ -5466,7 +5401,7 @@ function rcube_webmail() cols.push(childcol); } - for (var i=0; i < cols.length; i++) { + for (i=0; i < cols.length; i++) { childcol = cols[i]; cp = colprop.childs[childcol]; input = $('<input>') @@ -5482,7 +5417,7 @@ function rcube_webmail() else if (colprop.type == 'select') { input = $('<select>') .addClass('ff_'+col) - .attr('name', '_'+col+name_suffix) + .attr({ 'name': '_'+col+name_suffix, id: input_id }) .appendTo(cell); var options = input.attr('options'); @@ -5651,7 +5586,7 @@ function rcube_webmail() this.listsearch = function(id) { - var folder, lock = this.set_busy(true, 'searching'); + var lock = this.set_busy(true, 'searching'); if (this.contact_list) { this.list_contacts_clear(); @@ -5789,7 +5724,7 @@ function rcube_webmail() this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$'); this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, - {multiselect:false, draggable:true, keyboard:false, toggleselect:true}); + {multiselect:false, draggable:true, keyboard:true, toggleselect:true}); this.subscription_list .addEventListener('select', function(o){ ref.subscription_select(o); }) .addEventListener('dragstart', function(o){ ref.drag_active = true; }) @@ -5798,7 +5733,8 @@ function rcube_webmail() row.obj.onmouseover = function() { ref.focus_subscription(row.id); }; row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); }; }) - .init(); + .init() + .focus(); $('#mailboxroot') .mouseover(function(){ ref.focus_subscription(this.id); }) @@ -5830,7 +5766,7 @@ function rcube_webmail() this.env.dstfolder = null; - if (this.env.subscriptionrows[id] && row.length) + if (row.length && this.env.subscriptionrows[id]) row.removeClass('droptarget'); else $(this.subscription_list.frame).removeClass('droptarget'); @@ -5898,7 +5834,8 @@ function rcube_webmail() if (!this.gui_objects.subscriptionlist) return false; - var row, n, i, tmp, tmp_name, rowid, folders = [], list = [], slist = [], + 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()); @@ -5925,24 +5862,32 @@ function rcube_webmail() // add to folder/row-ID map this.env.subscriptionrows[id] = [name, display_name, false]; - // sort folders (to find a place where to insert the row) - // replace delimiter with \0 character to fix sorting - // issue where 'Abc Abc' would be placed before 'Abc/def' - var replace_from = RegExp(RegExp.escape(this.env.delimiter), 'g'), - replace_to = String.fromCharCode(0); - - $.each(this.env.subscriptionrows, function(k,v) { - if (v.length < 4) { - var n = v[0]; - n = n.replace(replace_from, replace_to); - v.push(n); - } - folders.push(v); - }); + // copy folders data to an array for sorting + $.each(this.env.subscriptionrows, function(k, v) { folders.push(v); }); + try { + // use collator if supported (FF29, IE11, Opera15, Chrome24) + collator = new Intl.Collator(this.env.locale.replace('_', '-')); + } + catch (e) {}; + + // sort folders folders.sort(function(a, b) { - var len = a.length - 1; n1 = a[len], n2 = b[len]; - return n1 < n2 ? -1 : 1; + var i, f1, f2, + path1 = a[0].split(ref.env.delimiter), + path2 = b[0].split(ref.env.delimiter); + + for (i=0; i<path1.length; i++) { + f1 = path1[i]; + f2 = path2[i]; + + if (f1 !== f2) { + if (collator) + return collator.compare(f1, f2); + else + return f1 < f2 ? -1 : 1; + } + } }); for (n in folders) { @@ -6084,7 +6029,7 @@ function rcube_webmail() this.subscription_list.remove_row(id.replace(/^rcmrow/, '')); $('#'+id).remove(); delete this.env.subscriptionrows[id]; - } + }; this.get_subfolders = function(folder) { @@ -6104,7 +6049,7 @@ function rcube_webmail() } return list; - } + }; this.subscribe = function(folder) { @@ -6128,9 +6073,7 @@ function rcube_webmail() var id, folders = this.env.subscriptionrows; for (id in folders) if (folders[id] && folders[id][0] == folder) - break; - - return id; + return id; }; // when user select a folder in manager @@ -6216,15 +6159,12 @@ function rcube_webmail() init_button(cmd, this.buttons[cmd][i]); } } - - // set active task button - this.set_button(this.task, 'sel'); }; // set button to a specific state this.set_button = function(command, state) { - var n, button, obj, a_buttons = this.buttons[command], + var n, button, obj, $obj, a_buttons = this.buttons[command], len = a_buttons ? a_buttons.length : 0; for (n=0; n<len; n++) { @@ -6259,8 +6199,15 @@ function rcube_webmail() obj.disabled = state == 'pas'; } else if (button.type == 'uibutton') { + button.status = state; $(obj).button('option', 'disabled', state == 'pas'); } + else { + $obj = $(obj); + $obj + .attr('tabindex', state == 'pas' || state == 'sel' ? '-1' : ($obj.attr('data-tabindex') || '0')) + .attr('aria-disabled', state == 'pas' || state == 'sel' ? 'true' : 'false'); + } } }; @@ -6384,7 +6331,8 @@ function rcube_webmail() this.messages[key].labels = [{'id': id, 'msg': msg}]; } else { - obj.click(function() { return ref.hide_message(obj); }); + obj.click(function() { return ref.hide_message(obj); }) + .attr('role', 'alert'); } this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj }); @@ -6514,10 +6462,8 @@ function rcube_webmail() this.treelist.select(name); } else if (this.gui_objects.folderlist) { - $('li.selected', this.gui_objects.folderlist) - .removeClass('selected').addClass('unfocused'); - $(this.get_folder_li(name, prefix, encode)) - .removeClass('unfocused').addClass('selected'); + $('li.selected', this.gui_objects.folderlist).removeClass('selected'); + $(this.get_folder_li(name, prefix, encode)).addClass('selected'); // trigger event hook this.triggerEvent('selectfolder', { folder:name, prefix:prefix }); @@ -6567,7 +6513,7 @@ function rcube_webmail() tr = document.createElement('tr'); for (c=0, len=repl.length; c < len; c++) { - cell = document.createElement('td'); + cell = document.createElement('th'); cell.innerHTML = repl[c].html || ''; if (repl[c].id) cell.id = repl[c].id; if (repl[c].className) cell.className = repl[c].className; @@ -6751,17 +6697,15 @@ function rcube_webmail() }; // create folder selector popup, position and display it - this.folder_selector = function(obj, callback) + this.folder_selector = function(event, callback) { var container = this.folder_selector_element; if (!container) { var rows = [], delim = this.env.delimiter, - ul = $('<ul class="toolbarmenu iconized">'), - li = document.createElement('li'), - link = document.createElement('a'), - span = document.createElement('span'); + ul = $('<ul class="toolbarmenu">'), + link = document.createElement('a'); container = $('<div id="folder-selector" class="popupmenu"></div>'); link.href = '#'; @@ -6769,33 +6713,30 @@ function rcube_webmail() // loop over sorted folders list $.each(this.env.mailboxes_list, function() { - var tmp, n = 0, s = 0, + var n = 0, s = 0, folder = ref.env.mailboxes[this], id = folder.id, - a = link.cloneNode(false), row = li.cloneNode(false); + a = $(link.cloneNode(false)), + row = $('<li>'); if (folder.virtual) - a.className += ' virtual'; - else { - a.className += ' active'; - a.onclick = function() { container.hide().data('callback')(folder.id); }; - } + a.addClass('virtual').attr('aria-disabled', 'true').attr('tabindex', '-1'); + else + a.addClass('active').data('id', folder.id); if (folder['class']) - a.className += ' ' + folder['class']; + a.addClass(folder['class']); // calculate/set indentation level while ((s = id.indexOf(delim, s)) >= 0) { n++; s++; } - a.style.paddingLeft = n ? (n * 16) + 'px' : 0; + a.css('padding-left', n ? (n * 16) + 'px' : 0); // add folder name element - tmp = span.cloneNode(false); - $(tmp).text(folder.name); - a.appendChild(tmp); + a.append($('<span>').text(folder.name)); - row.appendChild(a); + row.append(a); rows.push(row); }); @@ -6807,23 +6748,157 @@ function rcube_webmail() // set max-height if the list is long if (rows.length > 10) - container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9) + container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9); - // hide selector on click out of selector element - var fn = function(e) { if (e.target != container.get(0)) container.hide(); }; - $(document.body).on('mouseup', fn); - $('iframe').contents().on('mouseup', fn) - .load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; }); + // register delegate event handler for folder item clicks + container.on('click', 'a.active', function(e){ + container.data('callback')($(this).data('id')); + return false; + }); this.folder_selector_element = container; } + container.data('callback', callback); + // position menu on the screen - this.element_position(container, obj); + this.show_menu('folder-selector', true, event); + }; + + + /***********************************************/ + /********* popup menu functions *********/ + /***********************************************/ + + // Show/hide a specific popup menu + this.show_menu = function(prop, show, event) + { + var name = typeof prop == 'object' ? prop.menu : prop, + obj = $('#'+name), + ref = event && event.target ? $(event.target) : $(obj.attr('rel') || '#'+name+'link'), + keyboard = rcube_event.is_keyboard(event), + align = obj.attr('data-align') || '', + stack = false; + + // find "real" button element + if (ref.get(0).tagName != 'A' && ref.closest('a').length) + ref = ref.closest('a'); + + if (typeof prop == 'string') + prop = { menu:name }; + + // let plugins or skins provide the menu element + if (!obj.length) { + obj = this.triggerEvent('menu-get', { name:name, props:prop, originalEvent:event }); + } + + if (!obj || !obj.length) { + // just delegate the action to subscribers + return this.triggerEvent(show === false ? 'menu-close' : 'menu-open', { name:name, props:prop, originalEvent:event }); + } + + // move element to top for proper absolute positioning + obj.appendTo(document.body); + + if (typeof show == 'undefined') + show = obj.is(':visible') ? false : true; + + if (show && ref.length) { + var win = $(window), + pos = ref.offset(), + above = align.indexOf('bottom') >= 0; + + stack = ref.attr('role') == 'menuitem' || ref.closest('[role=menuitem]').length > 0; + + ref.offsetWidth = ref.outerWidth(); + ref.offsetHeight = ref.outerHeight(); + if (!above && pos.top + ref.offsetHeight + obj.height() > win.height()) { + above = true; + } + if (align.indexOf('right') >= 0) { + pos.left = pos.left + ref.outerWidth() - obj.width(); + } + else if (stack) { + pos.left = pos.left + ref.offsetWidth - 5; + pos.top -= ref.offsetHeight; + } + if (pos.left + obj.width() > win.width()) { + pos.left = win.width() - obj.width() - 12; + } + pos.top = Math.max(0, pos.top + (above ? -obj.height() : ref.offsetHeight)); + obj.css({ left:pos.left+'px', top:pos.top+'px' }); + } + + // add menu to stack + if (show) { + // truncate stack down to the one containing the ref link + for (var i = this.menu_stack.length - 1; stack && i >= 0; i--) { + if (!$(ref).parents('#'+this.menu_stack[i]).length) + this.hide_menu(this.menu_stack[i]); + } + if (stack && this.menu_stack.length) { + obj.data('parent', $.last(this.menu_stack)); + obj.css('z-index', ($('#'+$.last(this.menu_stack)).css('z-index') || 0) + 1); + } + else if (!stack && this.menu_stack.length) { + this.hide_menu(this.menu_stack[0], event); + } + + obj.show().attr('aria-hidden', 'false').data('opener', ref.attr('aria-expanded', 'true').get(0)); + this.triggerEvent('menu-open', { name:name, obj:obj, props:prop, originalEvent:event }); + this.menu_stack.push(name); + + this.menu_keyboard_active = show && keyboard; + if (this.menu_keyboard_active) { + this.focused_menu = name; + obj.find('a,input:not(:disabled)').not('[aria-disabled=true]').first().focus(); + } + } + else { // close menu + this.hide_menu(name, event); + } - container.show().data('callback', callback); + return show; }; + // hide the given popup menu (and it's childs) + this.hide_menu = function(name, event) + { + if (!this.menu_stack.length) { + // delegate to subscribers + this.triggerEvent('menu-close', { name:name, props:{ menu:name }, originalEvent:event }); + return; + } + + var obj, keyboard = rcube_event.is_keyboard(event); + for (var j=this.menu_stack.length-1; j >= 0; j--) { + obj = $('#' + this.menu_stack[j]).hide().attr('aria-hidden', 'true').data('parent', false); + this.triggerEvent('menu-close', { name:this.menu_stack[j], obj:obj, props:{ menu:this.menu_stack[j] }, originalEvent:event }); + if (this.menu_stack[j] == name) { + j = -1; // stop loop + if (obj.data('opener')) { + $(obj.data('opener')).attr('aria-expanded', 'false'); + if (keyboard) + obj.data('opener').focus(); + } + } + this.menu_stack.pop(); + } + + // focus previous menu in stack + if (this.menu_stack.length && keyboard) { + this.menu_keyboard_active = true; + this.focused_menu = $.last(this.menu_stack); + if (!obj || !obj.data('opener')) + $('#'+this.focused_menu).find('a,input:not(:disabled)').not('[aria-disabled=true]').first().focus(); + } + else { + this.focused_menu = null; + this.menu_keyboard_active = false; + } + } + + // position a menu element on the screen in relation to other object this.element_position = function(element, obj) { @@ -6857,6 +6932,12 @@ function rcube_webmail() element.css({left: left + 'px', top: top + 'px'}); }; + // initialize HTML editor + this.editor_init = function(config, id) + { + this.editor = new rcube_text_editor(config, id); + }; + /********************************************************/ /********* html to text conversion functions *********/ @@ -6987,6 +7068,13 @@ function rcube_webmail() this.start_keepalive(); }; + // update browser location to remember current view + this.update_state = function(query) + { + if (window.history.replaceState) + window.history.replaceState({}, document.title, rcmail.url('', query)); + }; + // send a http request to the server this.http_request = function(action, query, lock) { @@ -7171,9 +7259,24 @@ function rcube_webmail() 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); + } + delete this.env.list_uid; + } + this.enable_command('set-listmode', this.env.threads && !is_multifolder); - this.msglist_select(this.message_list); - this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); + if (list.rowcount > 0) + list.focus(); + this.msglist_select(list); + this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:list.rowcount }); + } } else if (this.task == 'addressbook') { @@ -7183,10 +7286,18 @@ function rcube_webmail() this.enable_command('search-create', this.env.source == ''); this.enable_command('search-delete', this.env.search_id); this.update_group_commands(); + if (this.contact_list.rowcount > 0) + this.contact_list.focus(); this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount }); } } break; + + case 'list-contacts': + case 'search-contacts': + if (this.contact_list && this.contact_list.rowcount > 0) + this.contact_list.focus(); + break; } if (response.unlock) @@ -7883,7 +7994,6 @@ function rcube_webmail() { return localStorage.removeItem(this.get_local_storage_prefix() + key); }; - } // end object rcube_webmail |