diff options
author | Thomas Bruederli <thomas@roundcube.net> | 2014-06-05 09:18:07 +0200 |
---|---|---|
committer | Thomas Bruederli <thomas@roundcube.net> | 2014-06-05 09:18:07 +0200 |
commit | 99cdca46b7bcc46fe6affd9e9f9f60a546b2e5b8 (patch) | |
tree | e3d0bec8e981825e98681fb4d5ec1ec73ee65c40 | |
parent | 17a76c3fd7665e92d2160f2178e31b3821a98e1e (diff) | |
parent | 3412e50b54e3daac8745234e21ab6e72be0ed165 (diff) |
Merge branch 'dev-accessibility'
Conflicts:
program/include/rcmail_output_html.php
program/js/app.js
program/js/treelist.js
program/lib/Roundcube/html.php
skins/larry/styles.css
skins/larry/templates/compose.html
49 files changed, 1886 insertions, 1027 deletions
diff --git a/plugins/legacy_browser/skins/larry/ie7hacks.css b/plugins/legacy_browser/skins/larry/ie7hacks.css index 2a174001e..85ebaf239 100644 --- a/plugins/legacy_browser/skins/larry/ie7hacks.css +++ b/plugins/legacy_browser/skins/larry/ie7hacks.css @@ -37,6 +37,7 @@ input.button { a.iconbutton, a.deletebutton, .boxpagenav a.icon, +a.button span.icon, .pagenav a.button span.inner, .boxfooter .listbutton .inner, .attachmentslist li a.delete, diff --git a/plugins/managesieve/skins/larry/managesieve.css b/plugins/managesieve/skins/larry/managesieve.css index 1f954caf2..2172c60b2 100644 --- a/plugins/managesieve/skins/larry/managesieve.css +++ b/plugins/managesieve/skins/larry/managesieve.css @@ -417,11 +417,13 @@ body.iframe.mail #filter-form /* vacation form */ -#settings-sections span.vacation a { - background: url(images/vacation_icons.png) no-repeat 7px 1px; +#settings-sections .vacation a { + background-image: url(images/vacation_icons.png); + background-repeat: no-repeat; + background-position: 7px 1px; } -#settings-sections span.vacation.selected a { +#settings-sections .vacation.selected a { background-position: 7px -23px; } diff --git a/plugins/zipdownload/zipdownload.js b/plugins/zipdownload/zipdownload.js index 644c1e030..af9136c1d 100644 --- a/plugins/zipdownload/zipdownload.js +++ b/plugins/zipdownload/zipdownload.js @@ -43,21 +43,10 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { link.html('').append(span); } - span.addClass('folder-selector-link').text(rcmail.gettext('zipdownload.download')); - + span.text(rcmail.gettext('zipdownload.download')); rcmail.env.download_link = link; }); - - // hide menu on click out of menu element - var fn = function(e) { - var menu = $('#zipdownload-menu'); - if (e.target != menu.get(0)) - menu.hide(); - }; - $(document.body).on('mouseup', fn); - $('iframe').contents().on('mouseup', fn) - .load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; }); -}); + }); function rcmail_zipdownload(mode) @@ -100,14 +89,10 @@ function rcmail_zipdownload(mode) } // display download options menu -function rcmail_zipdownload_menu() +function rcmail_zipdownload_menu(e) { - // fix menu style and display menu - var z_index = rcmail.env.download_link.parents('.popupmenu').css('z-index'), - menu = $('#zipdownload-menu').css({'max-height': 'none', 'z-index': z_index + 1}).show(); - - // position menu on the screen - rcmail.element_position(menu, rcmail.env.download_link); + // show (sub)menu for download selection + rcmail.command('menu-open', 'zipdownload-menu', e && e.target ? e.target : rcmail.env.download_link, e); // abort default download action return false; diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index 90a314437..edb8188cc 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -96,7 +96,10 @@ class zipdownload extends rcube_plugin $rcmail = rcmail::get_instance(); $menu = array(); - $ul_attr = $rcmail->config->get('skin') == 'classic' ? null : array('class' => 'toolbarmenu'); + $ul_attr = array('role' => 'menu', 'aria-labelledby' => 'aria-label-zipdownloadmenu'); + if ($rcmail->config->get('skin') != 'classic') { + $ul_attr['class'] = 'toolbarmenu'; + } foreach (array('eml', 'mbox', 'maildir') as $type) { $menu[] = html::tag('li', null, $rcmail->output->button(array( @@ -106,7 +109,8 @@ class zipdownload extends rcube_plugin ))); } - $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu'), + $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu', 'aria-hidden' => 'true'), + html::tag('h2', array('class' => 'voice', 'id' => 'aria-label-zipdownloadmenu'), "Message Download Options Menu") . html::tag('ul', $ul_attr, implode('', $menu)))); } diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a9e717b86..1a227927e 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1077,14 +1077,17 @@ class rcmail extends rcube } else { foreach ($table_data as $row_data) { - $class = !empty($row_data['class']) ? $row_data['class'] : ''; + $class = !empty($row_data['class']) ? $row_data['class'] : null; + if (!empty($attrib['rowclass'])) + $class = trim($class . ' ' . $attrib['rowclass']); $rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]); $table->add_row(array('id' => $rowid, 'class' => $class)); // format each col foreach ($a_show_cols as $col) { - $table->add($col, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); + $val = is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]; + $table->add($col, empty($attrib['ishtml']) ? $this->Q($val) : $val); } } } @@ -1490,7 +1493,7 @@ class rcmail extends rcube $html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : ''); $link_attrib = $folder['virtual'] ? array() : array( 'href' => $this->url(array('_mbox' => $folder['id'])), - 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name), + 'onclick' => sprintf("return %s.command('list','%s',this,event)", rcmail_output::JS_OBJECT_NAME, $js_name), 'rel' => $folder['id'], 'title' => $title, ); diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 43d73a6b4..6594209f6 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -893,6 +893,14 @@ EOF; return ''; } + // localize title and summary attributes + if ($command != 'button' && !empty($attrib['title']) && $this->app->text_exists($attrib['title'])) { + $attrib['title'] = $this->app->gettext($attrib['title']); + } + if ($command != 'button' && !empty($attrib['summary']) && $this->app->text_exists($attrib['summary'])) { + $attrib['summary'] = $this->app->gettext($attrib['summary']); + } + // execute command switch ($command) { // return a button @@ -1165,6 +1173,17 @@ EOF; $attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain'])); } + // set accessibility attributes + if (!$attrib['role']) { + $attrib['role'] = 'button'; + } + if (!empty($attrib['class']) && !empty($attrib['classact']) || !empty($attrib['imagepas']) && !empty($attrib['imageact'])) { + if (array_key_exists('tabindex', $attrib)) + $attrib['data-tabindex'] = $attrib['tabindex']; + $attrib['tabindex'] = '-1'; // disable button by default + $attrib['aria-disabled'] = 'true'; + } + // set title to alt attribute for IE browsers if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) { $attrib['title'] = $attrib['alt']; @@ -1353,6 +1372,20 @@ EOF; $is_empty = true; } + // set default page title + if (empty($this->pagetitle)) { + $this->pagetitle = 'Roundcube Mail'; + } + + // declare page language + if (!empty($_SESSION['language'])) { + $lang = substr($_SESSION['language'], 0, 2); + $output = preg_replace('/<html/', '<html lang="' . html::quote($lang) . '"', $output, 1); + if (!headers_sent()) { + header('Content-Language: ' . $lang); + } + } + // replace specialchars in content $page_title = html::quote($this->pagetitle); $page_header = ''; diff --git a/program/js/app.js b/program/js/app.js index 11204ffb2..d12dd81ca 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -46,6 +46,7 @@ function rcube_webmail() this.messages = {}; this.group2expand = {}; this.http_request_jobs = {}; + this.menu_stack = new Array(); // webmail client settings this.dblclick_time = 500; @@ -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') @@ -302,10 +305,12 @@ function rcube_webmail() $('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 @@ -314,8 +319,6 @@ function rcube_webmail() } } - document.onmouseup = function(e){ return ref.doc_mouse_up(e); }; - // init message compose form this.init_messageform(); } @@ -339,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(); } @@ -394,7 +407,6 @@ function rcube_webmail() 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(); }); @@ -449,9 +461,14 @@ 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(); @@ -459,9 +476,10 @@ function rcube_webmail() 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(); } @@ -469,7 +487,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(); @@ -559,6 +577,18 @@ 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 }); @@ -591,7 +621,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) @@ -634,8 +664,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) @@ -707,9 +737,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': @@ -887,14 +923,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; @@ -1450,7 +1486,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; } @@ -1566,17 +1603,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) { @@ -1585,17 +1627,87 @@ function rcube_webmail() this.button_out(this.buttons_sel[id], id); this.buttons_sel = {}; } + + // reset popup menus; delayed to have updated menu_stack data + window.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); + + 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(mod = 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; }; @@ -1880,7 +1992,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, @@ -1897,17 +2009,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)) @@ -1944,15 +2065,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 @@ -1966,28 +2089,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; @@ -1997,8 +2128,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 = ' '; } @@ -2308,7 +2441,6 @@ function rcube_webmail() this.clear_message_list = function() { this.env.messages = {}; - this.last_selected = 0; this.show_contentframe(false); if (this.message_list) @@ -2327,6 +2459,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 @@ -2600,7 +2733,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) @@ -2608,38 +2741,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); } }; @@ -2693,12 +2843,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) @@ -2715,12 +2865,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())) @@ -3286,7 +3436,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) @@ -3357,6 +3507,8 @@ 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 @@ -3507,15 +3659,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); + } }); } }; @@ -3787,7 +3942,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, ''); @@ -4155,7 +4310,7 @@ function rcube_webmail() return; var dir = key == 38 ? 1 : 0, - highlight = document.getElementById('rcmksearchSelected'); + highlight = document.getElementById('rcmkSearchItem' + this.ksearch_selected); if (!highlight) highlight = this.ksearch_pane.__ul.firstChild; @@ -4204,14 +4359,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); } }; @@ -4340,16 +4495,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('onmouseup', 'li', function(e){ ref.ksearch_click(e.target); }) } ul = this.ksearch_pane.__ul; @@ -4374,23 +4533,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)); } } @@ -4427,6 +4592,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(); }; @@ -4631,6 +4802,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); }); @@ -5174,6 +5346,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'); @@ -5181,14 +5354,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); @@ -5199,7 +5372,7 @@ 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); @@ -5235,7 +5408,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'); @@ -5542,7 +5715,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; }) @@ -5551,7 +5724,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); }) @@ -5976,15 +6150,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++) { @@ -6019,8 +6190,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'); + } } }; @@ -6144,7 +6322,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 }); @@ -6274,10 +6453,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 }); @@ -6327,7 +6504,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; @@ -6511,17 +6688,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 = '#'; @@ -6529,33 +6704,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); }); @@ -6567,23 +6739,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); + }; + - container.show().data('callback', callback); + /***********************************************/ + /********* 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); + } + + 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) { @@ -6753,6 +7059,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) { @@ -6938,6 +7251,8 @@ function rcube_webmail() if ((response.action == 'list' || response.action == 'search') && this.message_list) { this.enable_command('set-listmode', this.env.threads && !is_multifolder); + if (this.message_list.rowcount > 0) + this.message_list.focus(); this.msglist_select(this.message_list); this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount }); } @@ -6949,10 +7264,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) diff --git a/program/js/common.js b/program/js/common.js index 48e85558f..5ac4febce 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -281,6 +281,25 @@ cancel: function(evt) return false; }, +/** + * Determine whether the given event was trigered from keyboard + */ +is_keyboard: function(e) +{ + return e && ( + (e.mozInputSource && e.mozInputSource == e.MOZ_SOURCE_KEYBOARD) || + (!e.pageX && (e.pageY || 0) <= 0 && !e.clientX && (e.clientY || 0) <= 0) + ); +}, + +/** + * Accept event if triggered from keyboard action (e.g. <Enter>) + */ +keyboard_only: function(e) +{ + return rcube_event.is_keyboard(e) ? true : rcube_event.cancel(e); +}, + touchevent: function(e) { return { pageX:e.pageX, pageY:e.pageY, offsetX:e.pageX - e.target.offsetLeft, offsetY:e.pageY - e.target.offsetTop, target:e.target, istouch:true }; @@ -593,6 +612,11 @@ if (!String.prototype.startsWith) { }; } +// array utility function +jQuery.last = function(arr) { + return arr && arr.length ? arr[arr.length-1] : undefined; +} + // jQuery plugin to emulate HTML5 placeholder attributes on input elements jQuery.fn.placeholder = function(text) { return this.each(function() { diff --git a/program/js/list.js b/program/js/list.js index 04aec1c99..5492c0ad4 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -51,7 +51,7 @@ function rcube_list_widget(list, p) this.rowcount = 0; this.colcount = 0; - this.subject_col = -1; + this.subject_col = 0; this.modkey = 0; this.multiselect = false; this.multiexpand = false; @@ -60,6 +60,7 @@ function rcube_list_widget(list, p) this.column_movable = false; this.keyboard = false; this.toggleselect = false; + this.aria_listbox = false; this.drag_active = false; this.col_drag_active = false; @@ -75,6 +76,9 @@ function rcube_list_widget(list, p) if (p && typeof p === 'object') for (var n in p) this[n] = p[n]; + + // register this instance + rcube_list_widget._instances.push(this); }; @@ -94,11 +98,17 @@ init: function() this.tbody = this.list; } + if ($(this.list).attr('role') == 'listbox') { + this.aria_listbox = true; + if (this.multiselect) + $(this.list).attr('aria-multiselectable', 'true'); + } + if (this.tbody) { this.rows = {}; this.rowcount = 0; - var r, len, rows = this.tbody.childNodes; + var r, len, rows = this.tbody.childNodes, me = this; for (r=0, len=rows.length; r<len; r++) { this.rowcount += this.init_row(rows[r]) ? 1 : 0; @@ -108,8 +118,13 @@ init: function() this.frame = this.list.parentNode; // set body events - if (this.keyboard) + if (this.keyboard) { rcube_event.add_listener({event:'keydown', object:this, method:'key_press'}); + + // allow the table element to receive focus. + $(this.list).attr('tabindex', '0') + .on('focus', function(e){ me.focus(e); }); + } } return this; @@ -154,6 +169,15 @@ init_row: function(row) }, false); } + // label the list row with the subject col as descriptive label + if (this.aria_listbox) { + var lbl_id = 'l:' + row.id; + $(row) + .attr('role', 'option') + .attr('aria-labelledby', lbl_id) + .find(this.col_tagname()).eq(this.subject_col).attr('id', lbl_id); + } + if (document.all) row.onselectstart = function() { return false; }; @@ -175,7 +199,7 @@ init_header: function() if (this.fixed_header) { // copy (modified) fixed header back to the actual table $(this.list.tHead).replaceWith($(this.fixed_header).find('thead').clone()); - $(this.list.tHead).find('tr td').attr('style', ''); // remove fixed widths + $(this.list.tHead).find('th,td').attr('style', '').find('a').attr('tabindex', '-1'); // remove fixed widths } else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0) { this.init_fixed_header(); @@ -202,6 +226,7 @@ init_fixed_header: function() if (!this.fixed_header) { this.fixed_header = $('<table>') .attr('class', this.list.className + ' fixedcopy') + .attr('role', 'presentation') .css({ position:'fixed' }) .append(clone) .append('<tbody></tbody>'); @@ -220,6 +245,12 @@ init_fixed_header: function() $(this.fixed_header).find('thead').replaceWith(clone); } + // avoid scrolling header links being focused + $(this.list.tHead).find('a.sortcol').attr('tabindex', '-1'); + + // set tabindex to fixed header sort links + clone.find('a.sortcol').attr('tabindex', '0'); + this.thead = clone.get(0); this.resize(); }, @@ -262,6 +293,7 @@ clear: function(sel) this.rows = {}; this.rowcount = 0; + this.last_selected = 0; if (sel) this.clear_selection(); @@ -370,41 +402,68 @@ update_row: function(id, cols, newid, select) */ focus: function(e) { - var n, id; + if (this.focused) + return; + this.focused = true; - for (n in this.selection) { - id = this.selection[n]; - if (this.rows[id] && this.rows[id].obj) { - $(this.rows[id].obj).addClass('selected').removeClass('unfocused'); - } + if (e) + rcube_event.cancel(e); + + var focus_elem = null; + + if (this.last_selected && this.rows[this.last_selected]) { + focus_elem = $(this.rows[this.last_selected].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0'); } // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) - // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) - $('iframe,:focus:not(body)').blur(); - window.focus(); + if (focus_elem && focus_elem.length) { + // We now fix this by explicitly assigning focus to a dedicated link element + this.focus_noscroll(focus_elem); + } + else { + // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) + $('iframe,:focus:not(body)').blur(); + window.focus(); + } - if (e || (e = window.event)) - rcube_event.cancel(e); + $(this.list).addClass('focus').removeAttr('tabindex'); + + // set internal focus pointer to first row + if (!this.last_selected) + this.select_first(CONTROL_KEY); }, /** * remove focus from the list */ -blur: function() +blur: function(e) { - var n, id; this.focused = false; - for (n in this.selection) { - id = this.selection[n]; - if (this.rows[id] && this.rows[id].obj) { - $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused'); - } + + // avoid the table getting focus right again + var me = this; + setTimeout(function(){ + $(me.list).removeClass('focus').attr('tabindex', '0'); + }, 20); + + if (this.last_selected && this.rows[this.last_selected]) { + $(this.rows[this.last_selected].obj) + .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); } }, +/** + * Focus the given element without scrolling the list container + */ +focus_noscroll: function(elem) +{ + var y = this.frame.scrollTop || this.frame.scrollY; + elem.focus(); + this.frame.scrollTop = y; +}, + /** * Set/unset the given column as hidden @@ -522,6 +581,8 @@ click_row: function(e, id) } this.rows[id].clicked = now; + this.focus(); + return false; }, @@ -794,9 +855,9 @@ get_prev_row: function() get_first_row: function() { if (this.rowcount) { - var i, len, uid, rows = this.tbody.childNodes; + var i, uid, rows = this.tbody.childNodes; - for (i=0, len=rows.length-1; i<len; i++) + for (i=0; i<rows.length; i++) if (rows[i].id && (uid = this.get_row_uid(rows[i]))) return uid; } @@ -839,9 +900,10 @@ get_cell: function(row, index) */ select_row: function(id, mod_key, with_mouse) { - var select_before = this.selection.join(','); + var select_before = this.selection.join(','), + in_selection_before = this.in_selection(id); - if (!this.multiselect) + if (!this.multiselect && with_mouse) mod_key = 0; if (!this.shift_start) @@ -877,20 +939,26 @@ select_row: function(id, mod_key, with_mouse) this.multi_selecting = true; } - // trigger event if selection changed - if (this.selection.join(',') != select_before) - this.triggerEvent('select'); - - if (this.last_selected != 0 && this.rows[this.last_selected]) - $(this.rows[this.last_selected].obj).removeClass('focused'); + if (this.last_selected != 0 && this.rows[this.last_selected]) { + $(this.rows[this.last_selected].obj).removeClass('focused') + .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); + } // unselect if toggleselect is active and the same row was clicked again - if (this.toggleselect && this.last_selected == id) { + if (this.toggleselect && in_selection_before) { this.clear_selection(); - id = null; } - else + // trigger event if selection changed + else if (this.selection.join(',') != select_before) { + this.triggerEvent('select'); + } + + if (this.rows[id]) { $(this.rows[id].obj).addClass('focused'); + // set cursor focus to link inside selected row + if (this.focused) + this.focus_noscroll($(this.rows[id].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0')); + } if (!this.selection.length) this.shift_start = null; @@ -1038,7 +1106,7 @@ select_all: function(filter) this.highlight_row(n, true, true); } else { - $(this.rows[n].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[n].obj).removeClass('selected').removeAttr('aria-selected'); } } @@ -1095,14 +1163,16 @@ clear_selection: function(id, no_event) else { for (n in this.selection) if (this.rows[this.selection[n]]) { - $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[this.selection[n]].obj).removeClass('selected').removeAttr('aria-selected'); } this.selection = []; } - if (num_select && !this.selection.length && !no_event) + if (num_select && !this.selection.length && !no_event) { this.triggerEvent('select'); + this.last_selected = 0; + } }, @@ -1156,13 +1226,13 @@ highlight_row: function(id, multiple, norecur) if (this.selection.length > 1 || !this.in_selection(id)) { this.clear_selection(null, true); this.selection[0] = id; - $(this.rows[id].obj).addClass('selected'); + $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); } } else { if (!this.in_selection(id)) { // select row this.selection.push(id); - $(this.rows[id].obj).addClass('selected'); + $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); if (!norecur && !this.rows[id].expanded) this.highlight_children(id, true); } @@ -1172,7 +1242,7 @@ highlight_row: function(id, multiple, norecur) a_post = this.selection.slice(p+1, this.selection.length); this.selection = a_pre.concat(a_post); - $(this.rows[id].obj).removeClass('selected').removeClass('unfocused'); + $(this.rows[id].obj).removeClass('selected').removeAttr('aria-selected'); if (!norecur && !this.rows[id].expanded) this.highlight_children(id, false); } @@ -1252,6 +1322,14 @@ key_press: function(e) return rcube_event.cancel(e); + case 9: // Tab + this.blur(); + break; + + case 13: // Enter + if (!this.selection.length) + this.select_row(this.last_selected, mod_key, false); + default: this.key_pressed = keyCode; this.modkey = mod_key; @@ -1311,9 +1389,17 @@ use_arrow_key: function(keyCode, mod_key) } if (new_row) { + // simulate ctr-key if no rows are selected + if (!mod_key && !this.selection.length) + mod_key = CONTROL_KEY; + this.select_row(new_row.uid, mod_key, false); this.scrollto(new_row.uid); } + else if (!new_row && !selected_row) { + // select the first row if none selected yet + this.select_first(CONTROL_KEY); + } return false; }, @@ -1708,3 +1794,6 @@ column_replace: function(from, to) rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; + +// static +rcube_list_widget._instances = []; diff --git a/program/js/treelist.js b/program/js/treelist.js index 90eeeeae7..65f5fd4f4 100644 --- a/program/js/treelist.js +++ b/program/js/treelist.js @@ -55,6 +55,7 @@ function rcube_treelist_widget(node, p) drag_active = false, search_active = false, last_search = '', + has_focus = false, box_coords = {}, item_coords = [], autoexpand_timer, @@ -151,6 +152,19 @@ function rcube_treelist_widget(node, p) }) } + container.on('focusin', function(e){ + // TODO: only accept focus on virtual nodes from keyboard events + has_focus = true; + }) + .on('focusout', function(e){ + has_focus = false; + }); + + container.attr('role', 'tree'); + + $(document.body) + .bind('keydown', keypress); + /////// private methods @@ -201,13 +215,13 @@ function rcube_treelist_widget(node, p) function select(id) { if (selection) { - id2dom(selection).removeClass('selected'); + id2dom(selection).removeClass('selected').removeAttr('aria-selected'); selection = null; } var li = id2dom(id); if (li.length) { - li.addClass('selected'); + li.addClass('selected').attr('aria-selected', 'true'); selection = id; // TODO: expand all parent nodes if collapsed scroll_to_node(li); @@ -262,6 +276,7 @@ function rcube_treelist_widget(node, p) // insert as child of an existing node if (parent_node) { + node.level = parent_node.level + 1; if (!parent_node.children) parent_node.children = []; @@ -296,6 +311,7 @@ function rcube_treelist_widget(node, p) } // insert at top level else { + node.level = 0; data.push(node); li = render_node(node, container); } @@ -392,7 +408,7 @@ function rcube_treelist_widget(node, p) */ function update_data() { - data = walk_list(container); + data = walk_list(container, 0); } /** @@ -401,6 +417,7 @@ function rcube_treelist_widget(node, p) function update_dom(node) { var li = id2dom(node.id); + li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); li.children('ul').first()[(node.collapsed ? 'hide' : 'show')](); li.children('div.treetoggle').removeClass('collapsed expanded').addClass(node.collapsed ? 'collapsed' : 'expanded'); me.triggerEvent('toggle', node); @@ -507,6 +524,7 @@ function rcube_treelist_widget(node, p) // render child nodes for (var i=0; i < data.length; i++) { + data[i].level = 0; render_node(data[i], container); } @@ -523,6 +541,7 @@ function rcube_treelist_widget(node, p) var li = $('<li>') .attr('id', p.id_prefix + (p.id_encode ? p.id_encode(node.id) : node.id)) + .attr('role', 'treeitem') .addClass((node.classes || []).join(' ')) .data('id', node.id); @@ -546,12 +565,14 @@ function rcube_treelist_widget(node, p) // add child list and toggle icon if (node.children && node.children.length) { + li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); $('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li); - var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass); + var ul = $('<ul>').appendTo(li).attr('class', node.childlistclass).attr('role', 'group'); if (node.collapsed) ul.hide(); for (var i=0; i < node.children.length; i++) { + node.children[i].level = node.level + 1; render_node(node.children[i], ul); } } @@ -563,7 +584,7 @@ function rcube_treelist_widget(node, p) * Recursively walk the DOM tree and build an internal data structure * representing the skeleton of this tree list. */ - function walk_list(ul) + function walk_list(ul, level) { var result = []; ul.children('li').each(function(i,e){ @@ -572,9 +593,10 @@ function rcube_treelist_widget(node, p) id: dom2id(li), classes: String(li.attr('class')).split(' '), virtual: li.hasClass('virtual'), + level: level, html: li.children().first().get(0).outerHTML, text: li.children().first().text(), - children: walk_list(sublist) + children: walk_list(sublist, level+1) } if (sublist.length) { @@ -592,15 +614,29 @@ function rcube_treelist_widget(node, p) if (!li.children('div.treetoggle').length) $('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li); + + li.attr('aria-expanded', node.collapsed ? 'false' : 'true'); } if (li.hasClass('selected')) { + li.attr('aria-selected', 'true'); selection = node.id; } li.data('id', node.id); + + // declare list item as treeitem + li.attr('role', 'treeitem').attr('aria-level', node.level+1); + + // allow virtual nodes to receive focus + if (node.virtual) { + li.children('a:first').attr('tabindex', '0'); + } + result.push(node); indexbyid[node.id] = node; - }) + }); + + ul.attr('role', level == 0 ? 'tree' : 'group'); return result; } @@ -683,6 +719,70 @@ function rcube_treelist_widget(node, p) return undefined; } + /** + * Handler for keyboard events on treelist + */ + function keypress(e) + { + var target = e.target || {}, + keyCode = rcube_event.get_keycode(e); + + if (!has_focus || target.nodeName == 'INPUT' || 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 + var li = container.find(':focus').closest('li'); + if (li.length) { + focus_next(li, (mod = keyCode == 38 || keyCode == 63232 ? -1 : 1)); + } + break; + + case 37: // Left arrow key + case 39: // Right arrow key + var id, node, li = container.find(':focus').closest('li'); + if (li.length) { + id = dom2id(li); + node = indexbyid[id]; + if (node && node.children.length) + toggle(id, rcube_event.get_modifier(e) == SHIFT_KEY); // toggle subtree + } + return false; + } + + return true; + } + + function focus_next(li, dir, from_child) + { + var mod = dir < 0 ? 'prev' : 'next', + next = li[mod](), limit, parent; + + if (dir > 0 && !from_child && li.children('ul[role=group]:visible').length) { + li.children('ul').children('li:first').children('a:first').focus(); + } + else if (dir < 0 && !from_child && next.children('ul[role=group]:visible').length) { + next.children('ul').children('li:last').children('a:last').focus(); + } + else if (next.length && next.children('a:first')) { + next.children('a:first').focus(); + } + else { + parent = li.parent().closest('li[role=treeitem]'); + if (parent.length) + if (dir < 0) { + parent.children('a:first').focus(); + } + else { + focus_next(parent, dir, true); + } + } +>>>>>>> dev-accessibility + } + ///// drag & drop support diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index a88570d75..0209d1bf2 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -32,7 +32,7 @@ class html public static $doctype = 'xhtml'; public static $lc_tags = true; - public static $common_attrib = array('id','class','style','title','align','unselectable'); + public static $common_attrib = array('id','class','style','title','align','unselectable','tabindex','role'); public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script'); @@ -285,7 +285,9 @@ class html // ignore not allowed attributes, except data-* if (!empty($allowed)) { - if (!isset($allowed_f[$key]) && @substr_compare($key, 'data-', 0, 5) !== 0) { + $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0; + $is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0; + if (!$is_aria_attr && !isset($allowed_f[$key]) && (!$is_data_attr || !isset($allowed_f['data-*']))) { continue; } } @@ -835,7 +837,7 @@ class html_table extends html if (!empty($this->header)) { $rowcontent = ''; foreach ($this->header as $c => $col) { - $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content); + $rowcontent .= self::tag($this->_head_tagname(), $col->attrib, $col->content); } $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) : self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib); @@ -888,7 +890,16 @@ class html_table extends html private function _row_tagname() { static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div'); - return $row_tagnames[$this->tagname] ? $row_tagnames[$this->tagname] : $row_tagnames['*']; + return $row_tagnames[$this->tagname] ?: $row_tagnames['*']; + } + + /** + * Getter for the corresponding tag name for table row elements + */ + private function _head_tagname() + { + static $head_tagnames = array('table' => 'th', '*' => 'span'); + return $head_tagnames[$this->tagname] ?: $head_tagnames['*']; } /** @@ -897,7 +908,7 @@ class html_table extends html private function _col_tagname() { static $col_tagnames = array('table' => 'td', '*' => 'span'); - return $col_tagnames[$this->tagname] ? $col_tagnames[$this->tagname] : $col_tagnames['*']; + return $col_tagnames[$this->tagname] ?: $col_tagnames['*']; } } diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 2da99105f..e7d385fe6 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -157,16 +157,24 @@ $labels['more'] = 'More'; $labels['back'] = 'Back'; $labels['options'] = 'Options'; +$labels['first'] = 'First'; +$labels['last'] = 'Last'; +$labels['previous'] = 'Previous'; +$labels['next'] = 'Next'; $labels['select'] = 'Select'; $labels['all'] = 'All'; $labels['none'] = 'None'; $labels['currpage'] = 'Current page'; +$labels['isread'] = 'Read'; $labels['unread'] = 'Unread'; $labels['flagged'] = 'Flagged'; +$labels['unflagged'] = 'Not Flagged'; $labels['unanswered'] = 'Unanswered'; $labels['withattachment'] = 'With attachment'; $labels['deleted'] = 'Deleted'; $labels['undeleted'] = 'Not deleted'; +$labels['replied'] = 'Replied'; +$labels['forwarded'] = 'Forwarded'; $labels['invert'] = 'Invert'; $labels['filter'] = 'Filter'; $labels['list'] = 'List'; @@ -259,6 +267,7 @@ $labels['upload'] = 'Upload'; $labels['uploadprogress'] = '$percent ($current from $total)'; $labels['close'] = 'Close'; $labels['messageoptions'] = 'Message options...'; +$labels['togglecomposeoptions'] = 'Toggle composition options'; $labels['low'] = 'Low'; $labels['lowest'] = 'Lowest'; @@ -348,7 +357,9 @@ $labels['addcontact'] = 'Add new contact'; $labels['editcontact'] = 'Edit contact'; $labels['contacts'] = 'Contacts'; $labels['contactproperties'] = 'Contact properties'; +$labels['contactnameandorg'] = 'Name and Organization'; $labels['personalinfo'] = 'Personal information'; +$labels['contactphoto'] = 'Contact photo'; $labels['edit'] = 'Edit'; $labels['cancel'] = 'Cancel'; @@ -372,6 +383,7 @@ $labels['newcontactgroup'] = 'Create new contact group'; $labels['grouprename'] = 'Rename group'; $labels['groupdelete'] = 'Delete group'; $labels['groupremoveselected'] = 'Remove selected contacts from group'; +$labels['uponelevel'] = 'Up one level'; $labels['previouspage'] = 'Show previous page'; $labels['firstpage'] = 'Show first page'; @@ -468,6 +480,7 @@ $labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)'; $labels['2047folding'] = 'Full RFC 2047 (other)'; $labels['force7bit'] = 'Use MIME encoding for 8-bit characters'; $labels['advancedoptions'] = 'Advanced options'; +$labels['toggleadvancedoptions'] = 'Toggle advanced options'; $labels['focusonnewmessage'] = 'Focus browser window on new message'; $labels['checkallfolders'] = 'Check all folders for new messages'; $labels['displaynext'] = 'After message delete/move display the next message'; @@ -569,4 +582,48 @@ $labels['japanese'] = 'Japanese'; $labels['korean'] = 'Korean'; $labels['chinese'] = 'Chinese'; +// accessibility (voice-only) headings and descriptions +$labels['arialabeltopnav'] = 'Window control'; +$labels['arialabeltasknav'] = 'Application tasks'; +$labels['arialabeltoolbar'] = 'Application toolbar'; +$labels['arialabelmessagessearchfilter'] = 'Email listing filter'; +$labels['arialabelmailsearchform'] = 'Email message search form'; +$labels['arialabelcontactsearchform'] = 'Contacts search form'; +$labels['arialabelmailquicksearchbox'] = 'Email search input'; +$labels['arialabelquicksearchbox'] = 'Search input'; +$labels['arialabelfolderlist'] = 'Email folder selection'; +$labels['arialabelmessagelist'] = 'Email Messages Listing'; +$labels['arialabelmailpreviewframe'] = 'Message preview'; +$labels['arialabelmailboxmenu'] = 'Folder actions menu'; +$labels['arialabellistselectmenu'] = 'List selection menu'; +$labels['arialabelthreadselectmenu'] = 'Threads listing menu'; +$labels['arialabelmessagelistoptions'] = 'Message list display and sorting options'; +$labels['arialabelmailimportdialog'] = 'Message import dialog'; +$labels['arialabelmessagenav'] = 'Message navigation'; +$labels['arialabelmessagebody'] = 'Message Body'; +$labels['arialabelmessageactions'] = 'Message actions'; +$labels['arialabelcontactquicksearch'] = 'Contacts search form'; +$labels['arialabelcontactsearchbox'] = 'Contact search input'; +$labels['arialabelmessageheaders'] = 'Message headers'; +$labels['arialabelcomposeoptions'] = 'Composition options'; +$labels['arialabelresponsesmenu'] = 'Canned responses menu'; +$labels['arialabelattachmentuploadform'] = 'Attachment upload form'; +$labels['arialabelattachmentpreview'] = 'Attachment preview'; +$labels['ariasummarycomposecontacts'] = 'List of contacts and groups to select as recipients'; +$labels['arialabelcontactexportoptions'] = 'Contact export options'; +$labels['arialabelabookgroupoptions'] = 'Addressbook/group options'; +$labels['arialabelpreferencesform'] = 'Preferences form'; +$labels['arialabelidentityeditfrom'] = 'Identity edit form'; +$labels['arialabelresonseeditfrom'] = 'Response edit form'; + +$labels['helplistnavigation'] = 'List keyboard navigation'; +$labels['helplistkeyboardnavigation'] = "Arrows up/down: Move row focus/selection. +Space: Select focused row. +Shift + up/down: Select additional row above/below. +Ctrl + Space: Add focused row to selection/remove from selection."; +$labels['helplistkeyboardnavmessages'] = "Arrows right/left: expand/collapse message thread (in threads mode only). +Enter: Open the selected/focused message. +Delete: Move selected messages to Trash."; +$labels['helplistkeyboardnavcontacts'] = "Enter: Open the selected/focused contact."; + ?> diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc index 3bbbfccdf..0fc753056 100644 --- a/program/steps/addressbook/edit.inc +++ b/program/steps/addressbook/edit.inc @@ -98,12 +98,15 @@ function rcmail_get_edit_record() function rcmail_contact_edithead($attrib) { + global $RCMAIL; + // check if we have a valid result $record = rcmail_get_edit_record(); $i_size = !empty($attrib['size']) ? $attrib['size'] : 20; $form = array( 'head' => array( + 'name' => $RCMAIL->gettext('contactnameandorg'), 'content' => array( 'prefix' => array('size' => $i_size), 'firstname' => array('size' => $i_size, 'visible' => true), diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index be0dd2a33..8955488bd 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -395,7 +395,7 @@ function rcmail_js_contacts_list($result, $prefix='') ), '»'); } else - $val = ' '; + $val = ''; break; default: @@ -422,7 +422,7 @@ function rcmail_contacts_list_title($attrib) unset($attrib['name']); $OUTPUT->add_gui_object('addresslist_title', $attrib['id']); - $OUTPUT->add_label('contacts'); + $OUTPUT->add_label('contacts','uponelevel'); return html::tag($attrib['tag'], $attrib, $RCMAIL->gettext($attrib['label']), html::$common_attrib); } @@ -518,7 +518,7 @@ function rcmail_contact_form($form, $record, $attrib = null) foreach ($coltypes as $col => $prop) { if ($prop['subtypes']) { $subtype_names = array_map('rcmail_get_type_label', $prop['subtypes']); - $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); + $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype', 'title' => $prop['label'] . ' ' . $RCMAIL->gettext('type'))); $select_subtype->add($subtype_names, $prop['subtypes']); $coltypes[$col]['subtypes_select'] = $select_subtype->show(); } @@ -607,7 +607,7 @@ function rcmail_contact_form($form, $record, $attrib = null) // prepare subtype selector in edit mode if ($edit_mode && is_array($colprop['subtypes'])) { $subtype_names = array_map('rcmail_get_type_label', $colprop['subtypes']); - $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); + $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype', 'title' => $colprop['label'] . ' ' . $RCMAIL->gettext('type'))); $select_subtype->add($subtype_names, $colprop['subtypes']); } else @@ -649,6 +649,8 @@ function rcmail_contact_form($form, $record, $attrib = null) if ($subtypes[$i]) $subtype = $subtypes[$i]; + $colprop['id'] = 'ff_' . $col . intval($coltypes[$field]['count']); + // render composite field if ($colprop['type'] == 'composite') { $composite = array(); $j = 0; @@ -714,7 +716,7 @@ function rcmail_contact_form($form, $record, $attrib = null) // display row with label if ($label) { $rows .= html::div('row', - html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : rcube::Q($label)) . + html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : html::label($colprop['id'], rcube::Q($label))) . html::div('contactfieldcontent '.$colprop['type'], $val)); } else // row without label @@ -803,7 +805,7 @@ function rcmail_contact_photo($attrib) else $ff_value = '-del-'; // will disable delete-photo action - $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => '')); + $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => $RCMAIL->gettext('contactphoto'))); $content = html::div($attrib, $img); if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) { diff --git a/program/steps/addressbook/show.inc b/program/steps/addressbook/show.inc index f4224a3e2..4471ea658 100644 --- a/program/steps/addressbook/show.inc +++ b/program/steps/addressbook/show.inc @@ -60,6 +60,7 @@ function rcmail_contact_head($attrib) $form = array( 'head' => array( // section 'head' is magic! + 'name' => $RCMAIL->gettext('contactnameandorg'), 'content' => array( 'prefix' => array('type' => 'text'), 'firstname' => array('type' => 'text'), diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 6baf6e79a..0257f3187 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -969,7 +969,7 @@ function rcmail_compose_body($attrib) $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set)); } - $out .= "\n".'<iframe name="savetarget" src="program/resources/blank.gif" style="width:0;height:0;border:none;visibility:hidden;"></iframe>'; + $out .= "\n".'<iframe name="savetarget" src="program/resources/blank.gif" style="width:0;height:0;border:none;visibility:hidden;" aria-hidden="true"></iframe>'; return $out; } @@ -1859,9 +1859,10 @@ function rcmail_compose_responses_list($attrib) foreach ($RCMAIL->get_compose_responses(true) as $response) { $key = $response['key']; $item = html::a(array( - 'href '=> '#'.urlencode($response['name']), + 'href' => '#'.urlencode($response['name']), 'class' => rtrim('insertresponse ' . $attrib['itemclass']), 'unselectable' => 'on', + 'tabindex' => '0', 'rel' => $key, ), rcube::Q($response['name'])); diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 50b1e8292..0dba3c125 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -111,7 +111,9 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { if (!$OUTPUT->ajax_call) { $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', 'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage', - 'copy', 'move', 'quota', 'replyall', 'replylist', 'stillsearching'); + 'copy', 'move', 'quota', 'replyall', 'replylist', 'stillsearching', + 'flagged', 'unflagged', 'unread', 'deleted', 'replied', 'forwarded', + 'priority', 'withattachment'); } $pagetitle = $RCMAIL->localize_foldername($mbox_name, true); @@ -531,14 +533,19 @@ function rcmail_message_list_head($attrib, $a_show_cols) $a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc'); if (!empty($attrib['optionsmenuicon'])) { - $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')"; - if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true') - $list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu', - 'id' => 'listmenulink', 'title' => $RCMAIL->gettext('listoptions'))); - else - $list_menu = html::a(array('href' => '#', 'onclick' => $onclick), - html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], - 'id' => 'listmenulink', 'title' => $RCMAIL->gettext('listoptions')))); + $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu', this, event)"; + $inner = $RCMAIL->gettext('listoptions'); + if (is_string($attrib['optionsmenuicon']) && $attrib['optionsmenuicon'] != 'true') { + $inner = html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'alt' => $RCMAIL->gettext('listoptions'))); + } + $list_menu = html::a(array( + 'href' => '#list-options', + 'onclick' => $onclick, + 'class' => 'listmenu', + 'id' => 'listmenulink', + 'title' => $RCMAIL->gettext('listoptions'), + 'tabindex' => '0', + ), $inner); } else { $list_menu = ''; @@ -558,12 +565,14 @@ function rcmail_message_list_head($attrib, $a_show_cols) // get column name switch ($col) { case 'flag': - $col_name = html::span('flagged', ' '); + $col_name = html::span('flagged', $RCMAIL->gettext('flagged')); break; case 'attachment': case 'priority': + $col_name = html::span($col, $RCMAIL->gettext($col)); + break; case 'status': - $col_name = html::span($col, ' '); + $col_name = html::span($col, $RCMAIL->gettext('readstatus')); break; case 'threads': $col_name = $list_menu; diff --git a/program/steps/mail/list_contacts.inc b/program/steps/mail/list_contacts.inc index 0ee81135b..4f17beffd 100644 --- a/program/steps/mail/list_contacts.inc +++ b/program/steps/mail/list_contacts.inc @@ -110,7 +110,7 @@ else if (!empty($result) && $result->count > 0) { $keyname = $row['_type'] == 'group' ? 'contactgroup' : 'contact'; $OUTPUT->command('add_contact_row', $row_id, array( - $keyname => html::span(array('title' => $email), rcube::Q($name ? $name : $email) . + $keyname => html::a(array('title' => $email), rcube::Q($name ? $name : $email) . ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '') )), $classname); } diff --git a/program/steps/mail/search_contacts.inc b/program/steps/mail/search_contacts.inc index d56581695..ccef32dd2 100644 --- a/program/steps/mail/search_contacts.inc +++ b/program/steps/mail/search_contacts.inc @@ -87,7 +87,7 @@ if (!empty($result) && $result->count > 0) { $row_id = $row['ID'].'-'.$i; $jsresult[$row_id] = format_email_recipient($email, $name); $OUTPUT->command('add_contact_row', $row_id, array( - 'contact' => html::span(array('title' => $email), rcube::Q($name ? $name : $email) . + 'contact' => html::a(array('title' => $email), rcube::Q($name ? $name : $email) . ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '') )), 'person'); } diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index f1c10da3d..0ebdd6277 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -199,6 +199,7 @@ function rcmail_message_attachments($attrib) if (sizeof($MESSAGE->attachments)) { foreach ($MESSAGE->attachments as $attach_prop) { $filename = rcmail_attachment_name($attach_prop, true); + $size = ''; if ($PRINT_MODE) { $size = $RCMAIL->message_part_size($attach_prop); @@ -213,6 +214,10 @@ function rcmail_message_attachments($attrib) $title = ''; } + if ($attach_prop->size) { + $size = ' ' . html::span('attachment-size', '(' . $RCMAIL->show_bytes($attach_prop->size) . ')'); + } + $mimetype = rcmail_fix_mimetype($attach_prop->mimetype); $class = rcube_utils::file2class($mimetype, $filename); $id = 'attach' . $attach_prop->mime_id; @@ -222,7 +227,7 @@ function rcmail_message_attachments($attrib) rcmail_output::JS_OBJECT_NAME, $attach_prop->mime_id), 'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)', 'title' => rcube::Q($title), - ), rcube::Q($filename)); + ), rcube::Q($filename) . $size); $ol .= html::tag('li', array('class' => $class, 'id' => $id), $link); diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc index 6b7bd08d2..c61ac6da9 100644 --- a/program/steps/settings/edit_folder.inc +++ b/program/steps/settings/edit_folder.inc @@ -132,6 +132,7 @@ function rcmail_folder_form($attrib) } $select = $RCMAIL->folder_selector(array( + 'id' => '_parent', 'name' => '_parent', 'noselection' => '---', 'realnames' => false, @@ -155,7 +156,7 @@ function rcmail_folder_form($attrib) // Settings: threading if ($threading_supported && ($mbox_imap == 'INBOX' || (!$options['noselect'] && !$options['is_root']))) { - $select = new html_select(array('name' => '_viewmode', 'id' => '_listmode')); + $select = new html_select(array('name' => '_viewmode', 'id' => '_viewmode')); $select->add($RCMAIL->gettext('list'), 0); $select->add($RCMAIL->gettext('threads'), 1); diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 4b4575f10..8f9cf090f 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -343,7 +343,7 @@ function rcmail_user_prefs($current = null) if (is_array($meta) && $meta['name']) { $skinname = $meta['name']; $author_link = $meta['url'] ? html::a(array('href' => $meta['url'], 'target' => '_blank'), rcube::Q($meta['author'])) : rcube::Q($meta['author']); - $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); + $license_link = $meta['license-url'] ? html::a(array('href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'), rcube::Q($meta['license'])) : rcube::Q($meta['license']); } $skinnames[] = mb_strtolower($skinname); diff --git a/program/steps/settings/responses.inc b/program/steps/settings/responses.inc index 06093b3b8..ddd1924fe 100644 --- a/program/steps/settings/responses.inc +++ b/program/steps/settings/responses.inc @@ -95,7 +95,7 @@ function rcmail_responses_list($attrib) { global $RCMAIL, $OUTPUT; - $attrib += array('id' => 'rcmresponseslist', 'tagname' => 'table', 'cols' => 1); + $attrib += array('id' => 'rcmresponseslist', 'tagname' => 'table'); $plugin = $RCMAIL->plugins->exec_hook('responses_list', array( 'list' => $RCMAIL->get_compose_responses(true), diff --git a/skins/classic/addressbook.css b/skins/classic/addressbook.css index ebf9ab9d9..30052585a 100644 --- a/skins/classic/addressbook.css +++ b/skins/classic/addressbook.css @@ -279,6 +279,11 @@ body.iframe, padding: 0; } +#contacthead > legend +{ + display: none; +} + #contacthead .names span.namefield, #contacthead .names input { diff --git a/skins/classic/common.css b/skins/classic/common.css index 813df9ed3..cc1f3e60a 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -609,6 +609,7 @@ img.uploading /***** common table settings ******/ +table.records-table thead tr th, table.records-table thead tr td { height: 20px; @@ -619,6 +620,7 @@ table.records-table thead tr td background: url(images/listheader.gif) top left repeat-x #CCC; font-size: 11px; font-weight: bold; + text-align: left; } table.records-table tbody tr td @@ -629,7 +631,8 @@ table.records-table tbody tr td white-space: nowrap; border-bottom: 1px solid #EBEBEB; overflow: hidden; - text-align: left; + text-align: left; + outline: none; } table.records-table tr diff --git a/skins/classic/mail.css b/skins/classic/mail.css index a0d1f17cd..4c4e5d968 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -719,6 +719,7 @@ table.messagelist.fixedcopy z-index: 2; } +.messagelist thead tr th, .messagelist thead tr td { height: 20px; @@ -729,25 +730,26 @@ table.messagelist.fixedcopy background: url(images/listheader.gif) top left repeat-x #CCC; font-size: 11px; font-weight: bold; + text-align: left; } -.messagelist thead tr td.sortedASC, -.messagelist thead tr td.sortedDESC +.messagelist thead tr > .sortedASC, +.messagelist thead tr > .sortedDESC { background-position: 0 -26px; } -.messagelist thead tr td.sortedASC a +.messagelist thead tr > .sortedASC a { background: url(images/icons/sort.gif) right 0 no-repeat; } -.messagelist thead tr td.sortedDESC a +.messagelist thead tr > .sortedDESC a { background: url(images/icons/sort.gif) right -14px no-repeat; } -.messagelist thead tr td a +.messagelist thead tr a { display: block; width: auto !important; @@ -756,18 +758,19 @@ table.messagelist.fixedcopy text-decoration: none; } -.messagelist thead tr td.size.sortedASC a, -.messagelist thead tr td.size.sortedDESC a +.messagelist thead tr > .size.sortedASC a, +.messagelist thead tr > .size.sortedDESC a { padding-right: 18px; } -.messagelist thead tr td.subject +.messagelist thead tr > .subject { padding-left: 18px; width: 99%; } +.messagelist tbody tr th, .messagelist tbody tr td { height: 20px; @@ -780,6 +783,7 @@ table.messagelist.fixedcopy -o-text-overflow: ellipsis; border-bottom: 1px solid #EBEBEB; cursor: default; + outline: none; } .messagelist tbody tr td a @@ -803,40 +807,42 @@ table.messagelist.fixedcopy cursor: pointer; } -.messagelist tr td.flag span, -.messagelist tr td.status span, -.messagelist tr td.attachment span, -.messagelist tr td.priority span +.messagelist tr > .flag span, +.messagelist tr > .status span, +.messagelist tr > .attachment span, +.messagelist tr > .priority span { display: block; width: 15px; + text-indent: -5000px; + overflow: hidden; } .messagelist tr td div.collapsed, .messagelist tr td div.expanded, -.messagelist tr td.threads div.listmenu, -.messagelist tr td.attachment span.attachment, -.messagelist tr td.attachment span.report, -.messagelist tr td.priority span.priority, -.messagelist tr td.priority span.prio1, -.messagelist tr td.priority span.prio2, -.messagelist tr td.priority span.prio3, -.messagelist tr td.priority span.prio4, -.messagelist tr td.priority span.prio5, -.messagelist tr td.flag span.flagged, -.messagelist tr td.flag span.unflagged, -.messagelist tr td.flag span.unflagged:hover, -.messagelist tr td.status span.status, -.messagelist tr td.status span.msgicon, -.messagelist tr td.status span.deleted, -.messagelist tr td.status span.unread, -.messagelist tr td.status span.unreadchildren, -.messagelist tr td.subject span.msgicon, -.messagelist tr td.subject span.deleted, -.messagelist tr td.subject span.unread, -.messagelist tr td.subject span.replied, -.messagelist tr td.subject span.forwarded, -.messagelist tr td.subject span.unreadchildren +.messagelist tr > .threads .listmenu, +.messagelist tr > .attachment span.attachment, +.messagelist tr > .attachment span.report, +.messagelist tr > .priority span.priority, +.messagelist tr > .priority span.prio1, +.messagelist tr > .priority span.prio2, +.messagelist tr > .priority span.prio3, +.messagelist tr > .priority span.prio4, +.messagelist tr > .priority span.prio5, +.messagelist tr > .flag span.flagged, +.messagelist tr > .flag span.unflagged, +.messagelist tr > .flag span.unflagged:hover, +.messagelist tr > .status span.status, +.messagelist tr > .status span.msgicon, +.messagelist tr > .status span.deleted, +.messagelist tr > .status span.unread, +.messagelist tr > .status span.unreadchildren, +.messagelist tr > .subject span.msgicon, +.messagelist tr > .subject span.deleted, +.messagelist tr > .subject span.unread, +.messagelist tr > .subject span.replied, +.messagelist tr > .subject span.forwarded, +.messagelist tr > .subject span.unreadchildren { display: inline-block; vertical-align: middle; @@ -845,99 +851,99 @@ table.messagelist.fixedcopy background: url(images/messageicons.png) center no-repeat; } -.messagelist tr td.attachment span.attachment +.messagelist tr > .attachment span.attachment { background-position: 0 -170px; } -.messagelist tr td.attachment span.report +.messagelist tr > .attachment span.report { background-position: 0 -255px; } -.messagelist tr td.priority span.priority +.messagelist tr > .priority span.priority { background-position: 0 -309px; } -.messagelist tr td.priority span.prio5 +.messagelist tr > .priority span.prio5 { background-position: 0 -358px; } -.messagelist tr td.priority span.prio4 +.messagelist tr > .priority span.prio4 { background-position: 0 -340px; } -.messagelist tr td.priority span.prio3 +.messagelist tr > .priority span.prio3 { background-position: 0 -324px; } -.messagelist tr td.priority span.prio2 +.messagelist tr > .priority span.prio2 { background-position: 0 -309px; } -.messagelist tr td.priority span.prio1 +.messagelist tr > .priority span.prio1 { background-position: 0 -290px; } -.messagelist tr td.flag span.flagged +.messagelist tr > .flag span.flagged { background-position: 0 -153px; } -.messagelist tr td.flag span.unflagged:hover +.messagelist tr > .flag span.unflagged:hover { background-position: 0 -136px; } -.messagelist tr td.subject span.msgicon, -.messagelist tr td.subject span.unreadchildren +.messagelist tr > .subject span.msgicon, +.messagelist tr > .subject span.unreadchildren { background-position: 0 -51px; margin: 0 2px; } -.messagelist tr td.subject span.replied +.messagelist tr > .subject span.replied { background-position: 0 -85px; } -.messagelist tr td.subject span.forwarded +.messagelist tr > .subject span.forwarded { background-position: 0 -68px; } -.messagelist tr td.subject span.replied.forwarded +.messagelist tr > .subject span.replied.forwarded { background-position: 0 -102px; } -.messagelist tr td.status span.msgicon, -.messagelist tr td.flag span.unflagged, -.messagelist tr td.status span.unreadchildren +.messagelist tr > .status span.msgicon, +.messagelist tr > .flag span.unflagged, +.messagelist tr > .status span.unreadchildren { background-position: 0 17px; /* no icon */ } -.messagelist tr td.status span.msgicon:hover +.messagelist tr > .status span.msgicon:hover { background-position: 0 -272px; } -.messagelist tr td.status span.deleted, -.messagelist tr td.subject span.deleted +.messagelist tr > .status span.deleted, +.messagelist tr > .subject span.deleted { background-position: 0 -187px; } -.messagelist tr td.status span.status, -.messagelist tr td.status span.unread, -.messagelist tr td.subject span.unread +.messagelist tr > .status span.status, +.messagelist tr > .status span.unread, +.messagelist tr > .subject span.unread { background-position: 0 -119px; } @@ -954,10 +960,12 @@ table.messagelist.fixedcopy cursor: pointer; } -.messagelist tr td.threads div.listmenu +.messagelist tr > .threads .listmenu { background-position: 0 -238px; cursor: pointer; + overflow: hidden; + text-indent: -5000px; } .messagelist tbody tr td.subject @@ -977,45 +985,45 @@ table.messagelist.fixedcopy text-decoration: underline; } -.messagelist tr td.attachment, -.messagelist tr td.threads, -.messagelist tr td.status, -.messagelist tr td.flag, -.messagelist tr td.priority +.messagelist tr > .attachment, +.messagelist tr > .threads, +.messagelist tr > .status, +.messagelist tr > .flag, +.messagelist tr > .priority { width: 17px; padding: 0 0 0 2px; } -.messagelist tr td.size +.messagelist tr > .size { width: 60px; text-align: right; padding: 0 2px; } -.messagelist tr td.fromto, -.messagelist tr td.from, -.messagelist tr td.to, -.messagelist tr td.cc, -.messagelist tr td.replyto +.messagelist tr > .fromto, +.messagelist tr > .from, +.messagelist tr > .to, +.messagelist tr > .cc, +.messagelist tr > .replyto { width: 180px; padding: 0 2px; } -.messagelist tr td.date +.messagelist tr > .date { width: 135px; padding: 0 2px; } -.messagelist tr td.folder +.messagelist tr > .folder { width: 135px; } -.messagelist tr td.hidden +.messagelist tr > .hidden { display: none; } @@ -1038,6 +1046,7 @@ table.messagelist.fixedcopy } /* This padding-left minus the focused padding left should be half of the focused border-left */ +.messagelist thead tr th:first-child, .messagelist thead tr td:first-child, .messagelist tbody tr td:first-child { border-left: 0; @@ -1058,21 +1067,15 @@ table.messagelist.fixedcopy .messagelist tr.selected td { color: #FFFFFF; - background-color: #CC3333; -} - -.messagelist tr.unfocused td -{ - color: #FFFFFF; background-color: #929292; } -.messagelist tr.selected td a +.messagelist.focus tr.selected td { - color: #FFFFFF; + background-color: #CC3333; } -.messagelist tr.unfocused td a +.messagelist tr.selected td a { color: #FFFFFF; } diff --git a/skins/larry/addressbook.css b/skins/larry/addressbook.css index bfdd68127..0583ce06e 100644 --- a/skins/larry/addressbook.css +++ b/skins/larry/addressbook.css @@ -138,56 +138,25 @@ background: url(images/listicons.png) -2px -1180px no-repeat; } -/* This padding-left should be equal to the focused border-left + the focused padding-left */ -#contacts-table thead tr td:first-child, -#contacts-table tbody tr td:first-child { - border-left: 0; - padding-left: 36px; -} - -/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */ -#contacts-table tbody tr.focused > td:first-child { - border-left: 2px solid #b0ccd7; - padding-left: 34px; -} - -#contacts-table tbody tr.selected.focused > td:first-child { - border-left-color: #9ec2d0; -} - #contacts-table .contact td.name { - background-position: 6px -1603px; -} - -#contacts-table .contact.focused td.name { background-position: 4px -1603px; } -#contacts-table .contact.selected td.name, -#contacts-table .contact.unfocused td.name { - background-position: 6px -1627px; - font-weight: bold; -} - -#contacts-table .contact.selected.focused td.name { +#contacts-table .contact.selected td.name { background-position: 4px -1627px; + font-weight: bold; } #contacts-table .group td.name { - background-position: 6px -1555px; -} - -#contacts-table .group.focused td.name { background-position: 4px -1555px; } -#contacts-table .group.selected td.name, -#contacts-table .group.unfocused td.name { - background-position: 6px -1579px; +#contacts-table .group.selected td.name { + background-position: 4px -1579px; font-weight: bold; } -#contacts-table .group.selected.focused td.name { +#contacts-table.focus .group.selected.focused td.name { background-position: 4px -1579px; } @@ -232,6 +201,8 @@ float: left; margin: 0 18px 20px 0; width: 112px; + border: 0; + padding: 0; } #contactpic { @@ -267,6 +238,10 @@ opacity: 0.05; } +#contactphoto .formlinks a[aria-disabled='true'] { + visibility: hidden; +} + #contacthead { border: 0; margin: 0 16em 1em 0; @@ -275,6 +250,10 @@ font-size: 12px; } +#contacthead > legend { + display: none; +} + form #contacthead { margin-right: 0; } diff --git a/skins/larry/includes/footer.html b/skins/larry/includes/footer.html index f421ec5b0..6cd3e62d1 100644 --- a/skins/larry/includes/footer.html +++ b/skins/larry/includes/footer.html @@ -6,6 +6,7 @@ var UI = new rcube_mail_ui(); $(document).ready(function(){ UI.set('errortitle', '<roundcube:label name="errortitle" quoting="javascript" />'); + UI.set('toggleoptions', '<roundcube:label name="toggleadvancedoptions" quoting="javascript" />'); UI.init(); }); diff --git a/skins/larry/includes/header.html b/skins/larry/includes/header.html index 69e8b8aa6..179b86002 100644 --- a/skins/larry/includes/header.html +++ b/skins/larry/includes/header.html @@ -1,5 +1,6 @@ <div id="header"> -<div id="topline"> +<div id="topline" role="banner" aria-labelledby="aria-label-topnav"> + <h2 id="aria-label-topnav" class="voice"><roundcube:label name="arialabeltopnav" /></h2> <div class="topleft"> <roundcube:container name="topline-left" id="topline-left" /> <roundcube:button name="about" type="link" label="about" class="about-link" onclick="UI.show_about(this);return false" condition="!env:extwin" /> @@ -21,13 +22,14 @@ <roundcube:if condition="!env:extwin && !env:framed" /> <div id="topnav"> - <div id="taskbar" class="topright"> - <roundcube:button command="mail" label="mail" class="button-mail" classSel="button-mail button-selected" innerClass="button-inner" /> - <roundcube:button command="addressbook" label="addressbook" class="button-addressbook" classSel="button-addressbook button-selected" innerClass="button-inner" /> - <roundcube:container name="taskbar" id="taskbar" /> - <roundcube:button command="settings" label="settings" class="button-settings" classSel="button-settings button-selected" innerClass="button-inner" /> - <roundcube:button command="logout" label="logout" class="button-logout" classSel="button-logout" innerClass="button-inner" /> - <span class="minmodetoggle"></span> + <h2 id="aria-label-tasknav" class="voice"><roundcube:label name="arialabeltasknav" /></h2> + <div id="taskbar" class="topright" role="navigation" aria-labelledby="aria-label-tasknav"> + <roundcube:button command="mail" label="mail" class="button-mail" classSel="button-mail button-selected" innerClass="button-inner" /> + <roundcube:button command="addressbook" label="addressbook" class="button-addressbook" classSel="button-addressbook button-selected" innerClass="button-inner" /> + <roundcube:container name="taskbar" id="taskbar" /> + <roundcube:button command="settings" label="settings" class="button-settings" classSel="button-settings button-selected" innerClass="button-inner" /> + <roundcube:button command="logout" label="logout" class="button-logout" classSel="button-logout" innerClass="button-inner" /> + <span class="minmodetoggle" role="presentation"></span> </div> <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" alt="Logo" onclick="if(window.rcmail)rcmail.command('switch-task','mail')" /> </div> diff --git a/skins/larry/includes/mailtoolbar.html b/skins/larry/includes/mailtoolbar.html index ac08a3200..691345bb4 100644 --- a/skins/larry/includes/mailtoolbar.html +++ b/skins/larry/includes/mailtoolbar.html @@ -3,11 +3,11 @@ <roundcube:button command="reply" type="link" class="button reply disabled" classAct="button reply" classSel="button reply pressed" label="reply" title="replytomessage" /> <span class="dropbutton"> <roundcube:button command="reply-all" type="link" class="button reply-all disabled" classAct="button reply-all" classSel="button reply-all pressed" label="replyall" title="replytoallmessage" /> - <span class="dropbuttontip" id="replyallmenulink" onclick="UI.show_popup('replyallmenu');return false"></span> + <a href="#reply-all" class="dropbuttontip" id="replyallmenulink" onclick="UI.toggle_popup('replyallmenu',event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="replyallmenu-menu" tabindex="0">Reply-all options</a> </span> <span class="dropbutton"> <roundcube:button command="forward" type="link" class="button forward disabled" classAct="button forward" classSel="button forward pressed" label="forward" title="forwardmessage" /> - <span class="dropbuttontip" id="forwardmenulink" onclick="UI.show_popup('forwardmenu');return false"></span> + <a href="#forward" class="dropbuttontip" id="forwardmenulink" onclick="UI.toggle_popup('forwardmenu',event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="forwardmenu-menu" tabindex="0">Forwarding options</a> </span> <roundcube:button command="delete" type="link" class="button delete disabled" classAct="button delete" classSel="button delete pressed" label="delete" title="deletemessage" /> <roundcube:if condition="template:name == 'message'" /> @@ -15,44 +15,48 @@ <roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="printmessage" /> <roundcube:endif /> <roundcube:container name="toolbar" id="mailtoolbar" /> -<roundcube:button name="markmenulink" id="markmessagemenulink" type="link" class="button markmessage" label="mark" title="markmessages" onclick="UI.show_popup('markmessagemenu');return false" /> -<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button more" label="more" title="moreactions" onclick="UI.show_popup('messagemenu');return false" /> +<roundcube:button name="markmenulink" id="markmessagemenulink" type="link" class="button markmessage" label="mark" title="markmessages" onclick="UI.toggle_popup('markmessagemenu',event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="markmessagemenu-menu" /> +<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button more" label="more" title="moreactions" onclick="UI.toggle_popup('messagemenu',event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="messagemenu-menu" /> -<div id="forwardmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="forward-inline" label="forwardinline" prop="sub" classAct="forwardlink active" class="forwardlink" /></li> - <li><roundcube:button command="forward-attachment" label="forwardattachment" prop="sub" classAct="forwardattachmentlink active" class="forwardattachmentlink" /></li> +<div id="forwardmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-forwardmenu" class="voice">Forwarding options</h3> + <ul id="forwardmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-forwardmenu"> + <li role="menuitem"><roundcube:button command="forward-inline" label="forwardinline" prop="sub" classAct="forwardlink active" class="forwardlink" /></li> + <li role="menuitem"><roundcube:button command="forward-attachment" label="forwardattachment" prop="sub" classAct="forwardattachmentlink active" class="forwardattachmentlink" /></li> <roundcube:container name="forwardmenu" id="forwardmenu" /> </ul> </div> -<div id="replyallmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="reply-all" label="replyall" prop="sub" class="replyalllink" classAct="replyalllink active" /></li> - <li><roundcube:button command="reply-list" label="replylist" prop="sub" class="replylistlink" classAct="replylistlink active" /></li> +<div id="replyallmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-replyallmenu" class="voice">Reply-all options</h3> + <ul id="replyallmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-replyallmenu"> + <li role="menuitem"><roundcube:button command="reply-all" label="replyall" prop="sub" class="replyalllink" classAct="replyalllink active" /></li> + <li role="menuitem"><roundcube:button command="reply-list" label="replylist" prop="sub" class="replylistlink" classAct="replylistlink active" /></li> <roundcube:container name="replyallmenu" id="replyallmenu" /> </ul> </div> -<div id="messagemenu" class="popupmenu"> - <ul class="toolbarmenu iconized"> - <li><roundcube:button command="print" label="printmessage" class="icon" classAct="icon active" innerclass="icon print" /></li> - <li><roundcube:button command="download" label="emlsave" class="icon" classAct="icon active" innerclass="icon download" /></li> - <li><roundcube:button command="edit" prop="new" label="editasnew" class="icon" classAct="icon active" innerclass="icon edit" /></li> - <li><roundcube:button command="viewsource" label="viewsource" class="icon" classAct="icon active" innerclass="icon viewsource" /></li> - <li><roundcube:button command="move" label="moveto" class="icon" classAct="icon active" innerclass="icon move folder-selector-link" /></li> - <li><roundcube:button command="copy" label="copyto" class="icon" classAct="icon active" innerclass="icon copy folder-selector-link" /></li> - <li><roundcube:button command="open" label="openinextwin" target="_blank" class="icon" classAct="icon active" innerclass="icon extwin" /></li> +<div id="messagemenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-messagemenu" class="voice">More message toolbar actions</h3> + <ul id="messagemenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-messagemenu"> + <li role="menuitem"><roundcube:button command="print" label="printmessage" class="icon" classAct="icon active" innerclass="icon print" /></li> + <li role="menuitem"><roundcube:button command="download" label="emlsave" class="icon" classAct="icon active" innerclass="icon download" /></li> + <li role="menuitem"><roundcube:button command="edit" prop="new" label="editasnew" class="icon" classAct="icon active" innerclass="icon edit" /></li> + <li role="menuitem"><roundcube:button command="viewsource" label="viewsource" class="icon" classAct="icon active" innerclass="icon viewsource" /></li> + <li role="menuitem"><roundcube:button command="move" label="moveto" class="icon" classAct="icon active" innerclass="icon move folder-selector-link" /></li> + <li role="menuitem"><roundcube:button command="copy" label="copyto" class="icon" classAct="icon active" innerclass="icon copy folder-selector-link" /></li> + <li role="menuitem"><roundcube:button command="open" label="openinextwin" target="_blank" class="icon" classAct="icon active" innerclass="icon extwin" /></li> <roundcube:container name="messagemenu" id="messagemenu" /> </ul> </div> -<div id="markmessagemenu" class="popupmenu"> - <ul class="toolbarmenu iconized"> - <li><roundcube:button command="mark" prop="read" label="markread" classAct="icon active" class="icon" innerclass="icon read" /></li> - <li><roundcube:button command="mark" prop="unread" label="markunread" classAct="icon active" class="icon" innerclass="icon unread" /></li> - <li><roundcube:button command="mark" prop="flagged" label="markflagged" classAct="icon active" class="icon" innerclass="icon flagged" /></li> - <li><roundcube:button command="mark" prop="unflagged" label="markunflagged" classAct="icon active" class="icon" innerclass="icon unflagged" /></li> +<div id="markmessagemenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-markmessagemenu" class="voice">Mark selected messages as...</h3> + <ul id="markmessagemenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-markmessagemenu"> + <li role="menuitem"><roundcube:button command="mark" prop="read" label="markread" classAct="icon active" class="icon" innerclass="icon read" /></li> + <li role="menuitem"><roundcube:button command="mark" prop="unread" label="markunread" classAct="icon active" class="icon" innerclass="icon unread" /></li> + <li role="menuitem"><roundcube:button command="mark" prop="flagged" label="markflagged" classAct="icon active" class="icon" innerclass="icon flagged" /></li> + <li role="menuitem"><roundcube:button command="mark" prop="unflagged" label="markunflagged" classAct="icon active" class="icon" innerclass="icon unflagged" /></li> <roundcube:container name="markmenu" id="markmessagemenu" /> </ul> </div> diff --git a/skins/larry/includes/settingstabs.html b/skins/larry/includes/settingstabs.html index e62695848..d43e8f075 100644 --- a/skins/larry/includes/settingstabs.html +++ b/skins/larry/includes/settingstabs.html @@ -1,7 +1,9 @@ -<div id="settings-sections" class="uibox listbox"> -<h2 class="boxtitle"><roundcube:label name="settings" /></h2> +<div id="settings-sections" class="uibox listbox" role="navigation" aria-labelledby="aria-label-settingstabs"> +<h2 class="boxtitle" id="aria-label-settingstabs"><roundcube:label name="settings" /></h2> <div id="settings-tabs" class="scroller"> - <roundcube:object name="settingstabs" class="listitem" /> + <ul class="listing iconized"> + <roundcube:object name="settingstabs" class="listitem" tagname="li" /> + </ul> <roundcube:container name="tabs" id="settings-tabs" /> </div> </div> diff --git a/skins/larry/mail.css b/skins/larry/mail.css index e258cad88..b82fb790e 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -162,6 +162,7 @@ a.iconbutton.threadmode.selected { padding-right: 36px; } +#mailboxlist li.mailbox > a:focus, #mailboxlist li.mailbox.selected > a { background-position: 6px -21px; } @@ -170,6 +171,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -189px; } +#mailboxlist li.mailbox.inbox > a:focus, #mailboxlist li.mailbox.inbox.selected > a { background-position: 6px -213px; } @@ -178,6 +180,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -238px; } +#mailboxlist li.mailbox.drafts > a:focus, #mailboxlist li.mailbox.drafts.selected > a { background-position: 6px -262px; } @@ -186,6 +189,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -286px; } +#mailboxlist li.mailbox.sent > a:focus, #mailboxlist li.mailbox.sent.selected > a { background-position: 6px -310px; } @@ -194,6 +198,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -334px; } +#mailboxlist li.mailbox.junk > a:focus, #mailboxlist li.mailbox.junk.selected > a { background-position: 6px -358px; } @@ -202,6 +207,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -382px; } +#mailboxlist li.mailbox.trash > a:focus, #mailboxlist li.mailbox.trash.selected > a { background-position: 6px -406px; } @@ -210,6 +216,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -1924px; } +#mailboxlist li.mailbox.trash.empty > a:focus, #mailboxlist li.mailbox.trash.empty.selected > a { background-position: 6px -1948px; } @@ -218,6 +225,7 @@ a.iconbutton.threadmode.selected { background-position: 6px -1699px; } +#mailboxlist li.mailbox.archive > a:focus, #mailboxlist li.mailbox.archive.selected > a { background-position: 6px -1723px; } @@ -226,6 +234,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -238px; } +#mailboxlist li.mailbox ul li.drafts > a:focus, #mailboxlist li.mailbox ul li.drafts.selected > a { background-position: 23px -262px; } @@ -234,6 +243,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -286px; } +#mailboxlist li.mailbox ul li.sent > a:focus, #mailboxlist li.mailbox ul li.sent.selected > a { background-position: 23px -310px; } @@ -242,6 +252,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -334px; } +#mailboxlist li.mailbox ul li.junk > a:focus, #mailboxlist li.mailbox ul li.junk.selected > a { background-position: 23px -358px; } @@ -250,6 +261,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -382px; } +#mailboxlist li.mailbox ul li.trash > a:focus, #mailboxlist li.mailbox ul li.trash.selected > a { background-position: 23px -406px; } @@ -258,6 +270,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -1924px; } +#mailboxlist li.mailbox ul li.trash.empty > a:focus, #mailboxlist li.mailbox ul li.trash.empty.selected > a { background-position: 23px -1948px; } @@ -266,6 +279,7 @@ a.iconbutton.threadmode.selected { background-position: 23px -1699px; } +#mailboxlist li.mailbox ul li.archive > a:focus, #mailboxlist li.mailbox ul li.archive.selected > a { background-position: 23px -1723px; } @@ -304,6 +318,7 @@ a.iconbutton.threadmode.selected { padding-left: 52px; /* 36 + 1 x 16 */ background-position: 22px -95px; /* 6 + 1 x 16 */ } +#mailboxlist li.mailbox ul li > a:focus, #mailboxlist li.mailbox ul li.selected > a { background-position: 22px -119px; } @@ -316,6 +331,7 @@ a.iconbutton.threadmode.selected { padding-left: 68px; /* 2x */ background-position: 38px -95px; } +#mailboxlist li.mailbox ul ul li > a:focus, #mailboxlist li.mailbox ul ul li.selected > a { background-position: 38px -119px; } @@ -327,6 +343,7 @@ a.iconbutton.threadmode.selected { padding-left: 84px; /* 3x */ background-position: 54px -95px; } +#mailboxlist li.mailbox ul ul ul li > a:focus, #mailboxlist li.mailbox ul ul ul li.selected > a { background-position: 54px -119px; } @@ -338,6 +355,7 @@ a.iconbutton.threadmode.selected { padding-left: 100px; /* 4x */ background-position: 70px -95px; } +#mailboxlist li.mailbox ul ul ul ul li > a:focus, #mailboxlist li.mailbox ul ul ul ul li.selected > a { background-position: 70px -119px; } @@ -464,66 +482,66 @@ table.messagelist.fixedcopy { z-index: 2; } -.messagelist thead td:first-child { +.messagelist thead th:first-child { border-radius: 4px 0 0 0; /* for Chrome */ } -.messagelist tr td.attachment, -.messagelist tr td.threads, -.messagelist tr td.status, -.messagelist tr td.flag, -.messagelist tr td.priority { +.messagelist tr > .attachment, +.messagelist tr > .threads, +.messagelist tr > .status, +.messagelist tr > .flag, +.messagelist tr > .priority { width: 20px; padding: 2px 3px; } -.webkit .messagelist tr td.attachment, -.webkit .messagelist tr td.threads, -.webkit .messagelist tr td.status, -.webkit .messagelist tr td.flag, -.webkit .messagelist tr td.priority { +.webkit .messagelist tr > .attachment, +.webkit .messagelist tr > .threads, +.webkit .messagelist tr > .status, +.webkit .messagelist tr > .flag, +.webkit .messagelist tr > .priority { width: 26px; } -.messagelist tr td.threads { +.messagelist tr > .threads { width: 26px; } -.webkit .messagelist tr td.threads { +.webkit .messagelist tr > .threads { width: 30px; } -.messagelist tr td.threads, -.messagelist tr td.threads + td { +.messagelist tr > .threads, +.messagelist tr > .threads + td { border-left: 0; } -.messagelist tr td.size { +.messagelist tr > .size { width: 60px; text-align: right; } -.messagelist thead tr td.size { +.messagelist thead tr th.size { text-align: left; } -.messagelist tr td.fromto, -.messagelist tr td.from, -.messagelist tr td.to, -.messagelist tr td.cc, -.messagelist tr td.replyto { +.messagelist tr > .fromto, +.messagelist tr > .from, +.messagelist tr > .to, +.messagelist tr > .cc, +.messagelist tr > .replyto { width: 200px; } -.messagelist tr td.date { +.messagelist tr > .date { width: 155px; } -.messagelist tr td.folder { +.messagelist tr > .folder { width: 135px; } -.messagelist tr td.hidden { +.messagelist tr > .hidden { display: none; } @@ -540,19 +558,22 @@ table.messagelist.fixedcopy { /* background-color: #fff; */ } +.messagelist tr.flagged th, .messagelist tr.flagged td, .messagelist tr.flagged td a { color: #f30; } -.messagelist thead tr td.sortedASC a, -.messagelist thead tr td.sortedDESC a { +.messagelist thead tr th.sortedASC a, +.messagelist thead tr th.sortedDESC a { color: #004458; text-decoration: underline; - background: url(images/listicons.png) right -912px no-repeat; + background-image: url(images/listicons.png); + background-repeat: no-repeat; + background-position: right -912px; } -.messagelist thead tr td.sortedASC a { +.messagelist thead tr th.sortedASC a { background-position: right -944px; } @@ -574,39 +595,41 @@ table.messagelist.fixedcopy { cursor: pointer; } -.messagelist tr td.flag span, -.messagelist tr td.status span, -.messagelist tr td.attachment span, -.messagelist tr td.priority span { +.messagelist tr > .flag span, +.messagelist tr > .status span, +.messagelist tr > .attachment span, +.messagelist tr > .priority span { display: block; width: 20px; + text-indent: -5000px; + overflow: hidden; } .messagelist tr td div.collapsed, .messagelist tr td div.expanded, -.messagelist tr td.threads div.listmenu, -.messagelist tr td.attachment span.attachment, -.messagelist tr td.attachment span.report, -.messagelist tr td.priority span.priority, -.messagelist tr td.priority span.prio1, -.messagelist tr td.priority span.prio2, -.messagelist tr td.priority span.prio3, -.messagelist tr td.priority span.prio4, -.messagelist tr td.priority span.prio5, -.messagelist tr td.flag span.flagged, -.messagelist tr td.flag span.unflagged, -.messagelist tr td.flag span.unflagged:hover, -.messagelist tr td.status span.status, -.messagelist tr td.status span.msgicon, -.messagelist tr td.status span.deleted, -.messagelist tr td.status span.unread, -.messagelist tr td.status span.unreadchildren, -.messagelist tr td.subject span.msgicon, -.messagelist tr td.subject span.deleted, -.messagelist tr td.subject span.unread, -.messagelist tr td.subject span.replied, -.messagelist tr td.subject span.forwarded, -.messagelist tr td.subject span.unreadchildren { +.messagelist tr > .threads .listmenu, +.messagelist tr > .attachment span.attachment, +.messagelist tr > .attachment span.report, +.messagelist tr > .priority span.priority, +.messagelist tr > .priority span.prio1, +.messagelist tr > .priority span.prio2, +.messagelist tr > .priority span.prio3, +.messagelist tr > .priority span.prio4, +.messagelist tr > .priority span.prio5, +.messagelist tr > .flag span.flagged, +.messagelist tr > .flag span.unflagged, +.messagelist tr > .flag span.unflagged:hover, +.messagelist tr > .status span.status, +.messagelist tr > .status span.msgicon, +.messagelist tr > .status span.deleted, +.messagelist tr > .status span.unread, +.messagelist tr > .status span.unreadchildren, +.messagelist tr > .subject span.msgicon, +.messagelist tr > .subject span.deleted, +.messagelist tr > .subject span.unread, +.messagelist tr > .subject span.replied, +.messagelist tr > .subject span.forwarded, +.messagelist tr > .subject span.unreadchildren { display: inline-block; vertical-align: middle; height: 18px; @@ -619,7 +642,7 @@ table.messagelist.fixedcopy { background-position: 0 -996px; } -.messagelist thead tr td.attachment span.attachment { +.messagelist thead tr th.attachment span.attachment { background-position: -24px -997px; } @@ -627,7 +650,7 @@ table.messagelist.fixedcopy { background-position: -24px -1116px; } -.messagelist thead tr td.priority span.priority { +.messagelist thead tr th.priority span.priority { background-position: -24px -1845px; } @@ -651,15 +674,15 @@ table.messagelist.fixedcopy { background-position: 0 -1036px; } -.messagelist thead tr td.flag span.flagged { +.messagelist thead tr th.flag span.flagged { background-position: -22px -1036px; } -.messagelist tr td.status span.msgicon:hover { - background-position: -23px -1056px; +.messagelist tr:hover td.status span.msgicon { + background-position: -23px -1057px; } -.messagelist tr td.flag span.unflagged:hover { +.messagelist tr:hover td.flag span.unflagged { background-position: -23px -1076px; } @@ -702,10 +725,10 @@ table.messagelist.fixedcopy { .messagelist tr td.status span.unread, .messagelist tr td.subject span.unread, .messagelist tr td.status span.unread:hover { - background-position: 0 -1016px; + background-position: 0 -1017px; } -.messagelist thead tr td.status span.status { +.messagelist thead tr th.status span.status { background-position: -23px -1017px; } @@ -719,13 +742,23 @@ table.messagelist.fixedcopy { cursor: pointer; } -.messagelist tr td.threads div.listmenu { - background-position: 0 -976px; +.messagelist tr th.threads .listmenu { + background-position: 4px -972px; cursor: pointer; - width: 26px; + width: 24px; + height: 21px; + overflow: hidden; + text-indent: -5000px; + margin: -3px -5px -2px -6px; + padding: 3px 5px 2px 6px; +} + +.messagelist tr th.threads .listmenu:focus { + background-color: rgba(73,180,210,0.7); + outline: none; } -.messagelist thead tr td.subject, +.messagelist thead tr th.subject, .messagelist tbody tr td.subject { width: 99%; white-space: nowrap; @@ -909,6 +942,16 @@ h3.subject { border-radius: 3px 0 0 0; /* for Opera */ } +.moreheaderstoggle:focus { + background: #f2f2f2; + background: -moz-linear-gradient(left, #66bcd9 0, #49b3d2 100%); + background: -webkit-gradient(linear, left top, right top, color-stop(0,#66bcd9), color-stop(100%,#49b3d2)); + background: -o-linear-gradient(left, #66bcd9 0, #49b3d2 100%); + background: -ms-linear-gradient(left, #66bcd9 0, #49b3d2 100%); + background: linear-gradient(left, #66bcd9 0, #49b3d2 100%); + border-right-color: #149cc5; +} + .moreheaderstoggle .iconlink { display: inline-block; position: absolute; @@ -1303,13 +1346,19 @@ div.message-partheaders .headers-table td.header { margin-left: 0.5em; } -#compose-contacts li a, #contacts-table td { - background: url(images/listicons.png) -100px 0 no-repeat; +#compose-contacts li a, +#contacts-table td { + background-image: url(images/listicons.png); + background-position: -100px 0; + background-repeat: no-repeat; overflow: hidden; - padding-left: 36px; text-overflow: ellipsis; } +#compose-contacts li a { + padding-left: 36px; +} + #contacts-table td.contactgroup a { color: #376572; text-decoration: none; @@ -1331,6 +1380,7 @@ div.message-partheaders .headers-table td.header { background-position: 6px -766px; } +#compose-contacts li.addressbook a:focus, #compose-contacts li.addressbook.selected a { background-position: 6px -791px; } @@ -1339,20 +1389,36 @@ div.message-partheaders .headers-table td.header { background-position: 6px -1555px; } +#contacts-table.focus tr.focused td.contactgroup { + background-position: 4px -1555px; +} + #contacts-table tr.unfocused td.contactgroup, #contacts-table tr.selected td.contactgroup { background-position: 6px -1579px; } +#contacts-table.focus tr.selected.focused td.contactgroup { + background-position: 4px -1579px; +} + #contacts-table td.contact { background-position: 6px -1603px; } +#contacts-table.focus tr.focused td.contact { + background-position: 4px -1603px; +} + #contacts-table tr.unfocused td.contact, #contacts-table tr.selected td.contact { background-position: 6px -1627px; } +#contacts-table.focus tr.selected.focused td.contact { + background-position: 4px -1627px; +} + #compose-content { position: absolute; top: 0; diff --git a/skins/larry/settings.css b/skins/larry/settings.css index 6d4d13ca4..1ac62cd31 100644 --- a/skins/larry/settings.css +++ b/skins/larry/settings.css @@ -70,7 +70,7 @@ width: 20px; height: 18px; background: url('images/listicons.png') 0 -1157px no-repeat; - text-indent: 1000px; + text-indent: -5000px; overflow: hidden; } @@ -78,9 +78,10 @@ background-position: -24px -1137px; } -#sections-table tbody td.section, -#settings-sections span.listitem a, -#settings-sections span.tablink a { +#sections-table tbody td, +#sections-table .listitem span, +#settings-sections .listitem a, +#settings-sections .tablink a { padding-left: 36px; background-image: url(images/listicons.png); background-position: -100px 0; @@ -88,120 +89,120 @@ } /* note: support span.tablink because this is used by plugins */ -#settings-sections span.listitem a, -#settings-sections span.tablink a { +#settings-sections .listitem a, +#settings-sections .tablink a { background-position: 6px -862px; } -#settings-sections span.selected a, -#settings-sections span.tablink.selected a { +#settings-sections .selected a, +#settings-sections .tablink.selected a { background-position: 6px -887px; } -#settings-sections span.preferences a { +#settings-sections .preferences a { background-position: 6px -431px; } -#settings-sections span.preferences.selected a { +#settings-sections .preferences.selected a { background-position: 6px -455px; } -#settings-sections span.folders a, -#sections-table #rcmrowfolders td.section { +#settings-sections .folders a, +#sections-table #rcmrowfolders .section { background-position: 6px 2px; } -#settings-sections span.folders.selected a, -#sections-table #rcmrowfolders.selected td.section { +#settings-sections .folders.selected a, +#sections-table #rcmrowfolders.selected .section { background-position: 6px -22px; } -#settings-sections span.identities a { +#settings-sections .identities a { background-position: 6px -478px; } -#settings-sections span.identities.selected a { +#settings-sections .identities.selected a { background-position: 6px -502px; } -#settings-sections span.filter a { +#settings-sections .filter a { background-position: 6px -1746px; } -#settings-sections span.filter.selected a { +#settings-sections .filter.selected a { background-position: 6px -1770px; } -#settings-sections span.password a { +#settings-sections .password a { background-position: 6px -1795px; } -#settings-sections span.password.selected a { +#settings-sections .password.selected a { background-position: 6px -1819px; } -#settings-sections span.responses a { +#settings-sections .responses a { background-position: 6px -1972px; } -#settings-sections span.responses.selected a { +#settings-sections .responses.selected a { background-position: 6px -1996px; } -#sections-table #rcmrowgeneral td.section { - background-position: 6px -573px; +#sections-table #rcmrowgeneral .section { + background-position: 4px -573px; } -#sections-table #rcmrowgeneral.selected td.section { - background-position: 6px -598px; +#sections-table #rcmrowgeneral.selected .section { + background-position: 4px -598px; } -#sections-table #rcmrowmailbox td.section { - background-position: 6px -621px; +#sections-table #rcmrowmailbox .section { + background-position: 4px -621px; } -#sections-table #rcmrowmailbox.selected td.section { - background-position: 6px -646px; +#sections-table #rcmrowmailbox.selected .section { + background-position: 4px -646px; } -#sections-table #rcmrowcompose td.section { - background-position: 6px -670px; +#sections-table #rcmrowcompose .section { + background-position: 4px -670px; } -#sections-table #rcmrowcompose.selected td.section { - background-position: 6px -695px; +#sections-table #rcmrowcompose.selected .section { + background-position: 4px -695px; } -#sections-table #rcmrowmailview td.section { - background-position: 6px -718px; +#sections-table #rcmrowmailview .section { + background-position: 4px -718px; } -#sections-table #rcmrowmailview.selected td.section { - background-position: 6px -742px; +#sections-table #rcmrowmailview.selected .section { + background-position: 4px -742px; } -#sections-table #rcmrowaddressbook td.section { - background-position: 6px -766px; +#sections-table #rcmrowaddressbook .section { + background-position: 4px -766px; } -#sections-table #rcmrowaddressbook.selected td.section { - background-position: 6px -791px; +#sections-table #rcmrowaddressbook.selected .section { + background-position: 4px -791px; } -#sections-table #rcmrowserver td.section { - background-position: 6px -814px; +#sections-table #rcmrowserver .section { + background-position: 4px -814px; } -#sections-table #rcmrowserver.selected td.section { - background-position: 6px -838px; +#sections-table #rcmrowserver.selected .section { + background-position: 4px -838px; } -#sections-table #rcmrowcalendar td.section { - background-position: 6px -526px; +#sections-table #rcmrowcalendar .section { + background-position: 4px -526px; } -#sections-table #rcmrowcalendar.selected td.section { - background-position: 6px -550px; +#sections-table #rcmrowcalendar.selected .section { + background-position: 4px -550px; } #folderslist, diff --git a/skins/larry/styles.css b/skins/larry/styles.css index d1e45012b..17543c875 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -35,6 +35,10 @@ img { border: 0; } +.voice { + display: none; +} + input[type="text"], input[type="password"], textarea { @@ -312,6 +316,16 @@ input.button:active { border-left-color: #555; } +.buttongroup a.button:focus, +.buttongroup a.button.selected:focus { + background: #f2f2f2; + background: -moz-linear-gradient(top, #49b3d2 0, #66bcd9 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0,#49b3d2), color-stop(100%,#66bcd9)); + background: -o-linear-gradient(top, #49b3d2 0, #66bcd9 100%); + background: -ms-linear-gradient(top, #49b3d2 0, #66bcd9 100%); + background: linear-gradient(top, #49b3d2 0, #66bcd9 100%); +} + .pagenav a.button { padding: 1px 3px; height: 16px; @@ -950,6 +964,13 @@ a.iconlink.upload { background: url(images/buttons.png) -1000px 0 no-repeat; } +#taskbar a:focus { + color: #fff; + text-shadow: 0px 1px 1px #666; + background-color: rgba(73,180,210,0.7); + outline: none; +} + #taskbar a.button-selected { color: #3cf; background-color: #2c2c2c; @@ -1115,6 +1136,7 @@ a.iconlink.upload { } .boxtitle, +.uibox .listing thead th, .uibox .listing thead td { font-size: 12px; font-weight: bold; @@ -1126,7 +1148,14 @@ a.iconlink.upload { white-space: nowrap; } +.uibox .listing thead th, +.uibox .listing thead td { + padding-bottom: 8px; + height: auto; +} + .uibox .boxtitle, +.uibox .listing thead th, .uibox .listing thead td { background: #b0ccd7; color: #004458; @@ -1145,6 +1174,7 @@ a.iconlink.upload { } .listbox .listitem a, +.listbox .listitem span, .listbox .tablink a, .listing tbody td, .listing li a { @@ -1162,17 +1192,40 @@ a.iconlink.upload { display: table-cell; height: auto; min-height: 14px; + outline: none; +} + +.listing tbody td a { + color: #376572; + text-shadow: 0px 1px 1px #fff; + text-decoration: none; } .webkit .listing tbody td { height: 14px; } +/* This padding-left minus the focused padding left should be half of the focused border-left */ +.listing thead tr td:first-child, +.listing tbody tr td:first-child { + border-left: 2px solid transparent; + padding-left: 6px; +} + +.listing.iconized thead tr td:first-child, +.listing.iconized tbody tr td:first-child { + padding-left: 34px; +} + +/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */ +.listing.focus tbody tr.focused > td:first-child { + border-left: 2px solid #739da8; +} + .listbox .listitem.selected, .listbox .tablink.selected, .listbox .listitem.selected > a, .listbox .tablink.selected > a, -.listing tbody tr.unfocused td, .listing tbody tr.selected td, .listing li.selected, .listing li.selected > a { @@ -1239,6 +1292,16 @@ ul.treelist li a { text-overflow: ellipsis; } +ul.treelist li a:focus, +ul.listing .listitem a:focus, +ul.listing .listitem span:focus, +ul.listing.focus .listitem.focused span { + color: #fff !important; + background-color: rgba(73,180,210,0.6); + text-shadow: 0px 1px 1px #666; + outline: none; +} + ul.treelist ul li a { padding-left: 38px; } @@ -1319,6 +1382,13 @@ ul.treelist li.selected > div.expanded { margin-top: 1px; } +.boxfooter a.listbutton:focus { + color: #fff; + background-color: rgba(73,180,210,0.6); + text-shadow: 0px 1px 1px #666; + outline: none; +} + .uibox .boxfooter .listbutton:first-child { border-radius: 0 0 0 4px; } @@ -1328,7 +1398,9 @@ ul.treelist li.selected > div.expanded { width: 48px; height: 35px; text-indent: -5000px; - background: url(images/buttons.png) -1000px 0 no-repeat; + background-image: url(images/buttons.png); + background-position: -1000px 0; + background-repeat: no-repeat; } .boxfooter .listbutton.add .inner { @@ -1442,6 +1514,7 @@ table.records-table { border: 0; } +.records-table thead th, .records-table thead td { color: #69939e; font-size: 11px; @@ -1456,13 +1529,17 @@ table.records-table { padding: 8px 7px; overflow: hidden; text-overflow: ellipsis; + text-align: left; } +.records-table.sortheader thead th, .records-table.sortheader thead td { padding: 0; } +.records-table thead th a, .records-table thead td a, +.records-table thead th span, .records-table thead td span { display: block; padding: 7px 7px; @@ -1472,6 +1549,14 @@ table.records-table { text-overflow: ellipsis; } +.records-table thead th a:focus, +.records-table thead td a:focus { + color: #fff; + background-color: rgba(73,180,210,0.7); + text-shadow: 0px 1px 1px #666; + outline: none; +} + .records-table tbody td { padding: 2px 7px; border-bottom: 1px solid #ddd; @@ -1481,27 +1566,28 @@ table.records-table { overflow: hidden; text-overflow: ellipsis; background-color: #fff; + outline: none; } /* This padding-left minus the focused padding left should be half of the focused border-left */ +.records-table thead tr th:first-child, .records-table thead tr td:first-child, .records-table tbody tr td:first-child { - border-left: 0; - padding-left: 6px; -} - -/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */ -.records-table tbody tr.focused > td:first-child { - border-left: 2px solid #b0ccd7; + border-left: 2px solid transparent; padding-left: 4px; } -.records-table tbody tr.selected.focused > td:first-child { - border-left-color: #49b3d2; +/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */ +.records-table.focus tbody tr.focused > td:first-child { + border-left: 2px solid #49b3d2; } .records-table tr.selected td { color: #fff !important; + background-color: #4db0d2 !important; +} + +.records-table.focus tr.selected td { background: #019bc6; background: -moz-linear-gradient(top, #019bc6 0%, #017cb4 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#019bc6), color-stop(100%,#017cb4)); @@ -1515,16 +1601,6 @@ table.records-table { color: #fff !important; } -.records-table tr.unfocused td { - color: #fff !important; - background-color: #4db0d2 !important; -} - -.records-table tr.unfocused td a, -.records-table tr.unfocused td span { - color: #fff !important; -} - .records-table tr.deleted td, .records-table tr.deleted td a { color: #ccc !important; @@ -1921,6 +1997,14 @@ ul.proplist li { border-radius: 0; } +.dropbutton .dropbuttontip:focus, +.toolbar a.button:focus { + color: #fff; + text-shadow: 0px 1px 1px #666; + background-color: rgba(30,150,192, 0.5); + border-radius: 3px; +} + .toolbar a.button.disabled { opacity: 0.4; filter: alpha(opacity=40); @@ -1936,12 +2020,16 @@ ul.proplist li { position: absolute; right: 0; top: 0; - height: 42px; + height: 41px; width: 18px; + overflow: hidden; + text-indent: -5000px; background: url(images/buttons.png) 0 -1255px no-repeat; cursor: pointer; + outline: none; } +.dropbutton .dropbuttontip:focus, .dropbutton .dropbuttontip:hover { background-position: -26px -1255px; } @@ -2132,6 +2220,19 @@ select.decorated option { } +a.menuselector:focus, +a.menuselector.focus, +a.iconbutton:focus, +.pagenav a.button:focus { + border-color: #4fadd5; + -webkit-box-shadow: 0 0 4px 2px rgba(71,135,177, 0.8); + -moz-box-shadow: 0 0 4px 2px rgba(71,135,177, 0.8); + -o-box-shadow: 0 0 4px 2px rgba(71,135,177, 0.8); + box-shadow: 0 0 4px 2px rgba(71,135,177, 0.8); + outline: none; +} + + /*** quota indicator ***/ #quotadisplay { @@ -2224,6 +2325,7 @@ ul.toolbarmenu li a.active { .googie_list td.googie_list_onhover, ul.toolbarmenu li a.active:hover, +ul.toolbarmenu li a.active:focus, #rcmKSearchpane ul li.selected, select.decorated option:hover, select.decorated option[selected='selected'] { @@ -2233,6 +2335,7 @@ select.decorated option[selected='selected'] { background: -o-linear-gradient(top, #00aad6 0%, #008fc9 100%); background: -ms-linear-gradient(top, #00aad6 0%, #008fc9 100%); background: linear-gradient(top, #00aad6 0%, #008fc9 100%); + outline: none; } ul.toolbarmenu.iconized li a, @@ -2620,6 +2723,7 @@ ul.toolbarmenu li span.copy { overflow: hidden; text-overflow: ellipsis; line-height: 20px; + outline: none; } .attachmentslist li a.drop { @@ -2631,6 +2735,15 @@ ul.toolbarmenu li span.copy { right: 0; top: 0; padding: 0; + overflow: hidden; + text-indent: -5000px; + outline: none; +} + +.attachmentslist li a:focus, +.attachmentslist li a.drop:focus { + background-color: rgba(30,150,192, 0.5); + border-radius: 2px; } #compose-attachments ul li { @@ -2665,26 +2778,22 @@ ul.toolbarmenu li span.copy { /*** fieldset tabs ***/ -.tabsbar { - margin-bottom: 12px; - padding-top: 15px; - height: 27px; - white-space: nowrap; +.tabbed.ui-tabs { + padding: 0; + border: 0 !important; + background: none; } -.ui-dialog-content .tabsbar { - margin-bottom: 0; +.boxcontent.tabbed.ui-tabs { + padding: 10px; } -.tabsbar .tablink { - padding: 15px 1px 15px 0; - background: #f8f8f8; - background: -moz-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(50%,#d3d3d3), color-stop(100%,#f8f8f8)); - background: -webkit-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%); - background: -o-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%); - background: linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%); +.ui-tabs .tabsbar.ui-tabs-nav { + margin-bottom: 10px; +} + +.ui-dialog-content .tabsbar { + margin-bottom: 0; } .tabsbar .tablink:last-child { @@ -2695,30 +2804,15 @@ ul.toolbarmenu li span.copy { border-right: 0; } -.tabsbar .tablink a { - padding: 15px; - color: #999; - font-size: 12px; - font-weight: bold; - text-decoration: none; +.ui-tabs .ui-tabs-nav li.tablink a { background: #fff; - border-right: 1px solid #fafafa; -} - -.tabsbar .tablink.selected a { - color: #004458; - background: #f6f6f6; - background: -moz-linear-gradient(top, #fff 40%, #efefef 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(40%,#fff), color-stop(100%,#efefef)); - background: -o-linear-gradient(top, #fff 40%, #efefef 100%); - background: -ms-linear-gradient(top, #fff 40%, #efefef 100%); - background: linear-gradient(top, #fff 40%, #efefef 100%); } -fieldset.tab { +.ui-tabs fieldset.ui-tabs-panel { border: 0; padding: 0; margin-left: 0; + background: none; } #image-selector-form.droptarget { diff --git a/skins/larry/templates/addressbook.html b/skins/larry/templates/addressbook.html index 97efdc6f3..e1101e648 100644 --- a/skins/larry/templates/addressbook.html +++ b/skins/larry/templates/addressbook.html @@ -10,25 +10,50 @@ <div id="mainscreen"> +<h1 class="voice"><roundcube:label name="addressbook" /></h1> + <!-- toolbar --> -<div id="addressbooktoolbar" class="toolbar"> +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="addressbooktoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar"> <roundcube:button command="import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="importcontacts" /> <span class="dropbutton"> <roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="export" title="exportvcards" /> - <span class="dropbuttontip" id="exportmenulink" onclick="UI.show_popup('exportmenu');return false"></span> + <a href="#export" class="dropbuttontip" id="exportmenulink" onclick="return UI.toggle_popup('exportmenu',event)" aria-haspopup="true" aria-expanded="false" aria-owns="exportmenu-menu" tabindex="0"><roundcube:label name="arialabelcontactexportoptions" /></a> </span> <span class="spacer"></span> <roundcube:button command="compose" type="link" class="button compose disabled" classAct="button compose" classSel="button compose pressed" label="compose" title="writenewmessage" /> <roundcube:button command="advanced-search" type="link" class="button search disabled" classAct="button search" classSel="button search pressed" label="advanced" title="advsearch" /> <roundcube:container name="toolbar" id="addressbooktoolbar" /> + + <div id="exportmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-exportmenu" class="voice"><roundcube:label name="arialabelcontactexportoptions" /></h3> + <ul id="exportmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-exportmenu"> + <li role="menuitem"><roundcube:button command="export" label="exportall" prop="sub" class="exportalllink" classAct="exportalllink active" /></li> + <li role="menuitem"><roundcube:button command="export-selected" label="exportsel" prop="sub" class="exportsellink" classAct="exportsellink active" /></li> + </ul> + </div> + </div> <!-- search box --> -<div id="quicksearchbar" class="searchbox"> +<div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform"> +<h2 id="aria-label-searchform" class="voice"><roundcube:label name="arialabelcontactsearchform" /></h2> +<label for="quicksearchbox" class="voice"><roundcube:label name="arialabelquicksearchbox" /></label> <roundcube:object name="searchform" id="quicksearchbox" /> -<roundcube:button name="searchmenulink" id="searchmenulink" class="iconbutton searchoptions" onclick="UI.show_popup('searchmenu');return false" title="searchmod" content=" " /> -<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " /> +<roundcube:button command="menu-open" prop="searchmenu" id="searchmenulink" class="iconbutton searchoptions" title="searchmod" label="options" aria-haspopup="true" aria-expanded="false" aria-owns="searchmenu-menu" /> +<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" /> + +<div id="searchmenu" class="popupmenu" data-editable="true"> + <h3 id="aria-label-searchmenu" class="voice"><roundcube:label name="searchmod" /></h3> + <ul class="toolbarmenu" id="searchmenu-menu" role="menu" aria-labelledby="aria-label-searchmenu"> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="name" id="s_mod_name" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="name" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="firstname" id="s_mod_firstname" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="firstname" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="surname" id="s_mod_surname" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="surname" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="email" id="s_mod_email" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="email" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="*" id="s_mod_all" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="allfields" /></span></label></li> + </ul> +</div> </div> <div id="mainscreencontent"> @@ -36,42 +61,64 @@ <div id="addressview-left"> <!-- sources/groups list --> -<div id="directorylistbox" class="uibox listbox"> +<div id="directorylistbox" class="uibox listbox" role="navigation" aria-labelledby="directorylist-header"> <h2 id="directorylist-header" class="boxtitle"><roundcube:label name="groups" /></h2> <div id="directorylist-content" class="scroller withfooter"> <roundcube:object name="directorylist" id="directorylist" class="treelist listing iconized" /> </div> <div id="directorylist-footer" class="boxfooter"> - <roundcube:button command="group-create" type="link" title="newcontactgroup" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="groupoptions" id="groupoptionslink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('groupoptions');return false" innerClass="inner" content="⚙" /> + <roundcube:button command="group-create" type="link" title="newcontactgroup" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="newcontactgroup" /><roundcube:button name="groupoptions" id="groupoptionslink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('groupoptions',event)" innerClass="inner" label="arialabelabookgroupoptions" aria-haspopup="true" aria-expanded="false" aria-owns="groupoptionsmenu" /> </div> </div> +<div id="groupoptions" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-groupoptions" class="voice"><roundcube:label name="arialabelabookgroupoptions" /></h3> + <ul id="groupoptionsmenu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-groupoptions"> + <li role="menuitem"><roundcube:button command="group-rename" label="grouprename" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="group-delete" label="groupdelete" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="search-create" label="searchsave" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="search-delete" label="searchdelete" classAct="active" /></li> + <roundcube:container name="groupoptions" id="groupoptionsmenu" /> + </ul> +</div> + </div><!-- end addressview-left --> -<div id="addressview-right"> +<div id="addressview-right" role="main" aria-labelledby="aria-label-contactslist"> <!-- contacts list --> <div id="addresslist" class="uibox listbox"> -<roundcube:object name="addresslisttitle" label="contacts" tag="h2" class="boxtitle" /> +<roundcube:object name="addresslisttitle" label="contacts" tag="h2" class="boxtitle" id="aria-label-contactslist" /> <div class="scroller withfooter"> -<roundcube:object name="addresslist" id="contacts-table" class="listing" noheader="true" /> +<roundcube:object name="addresslist" id="contacts-table" class="listing iconized" noheader="true" role="listbox" /> +</div> +<div class="boxpagenav"> + <roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" label="first" /> + <roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" label="previous" /> + <roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" label="next" /> + <roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" label="last" /> </div> <div class="boxfooter"> - <roundcube:button command="add" type="link" title="newcontact" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button command="delete" type="link" title="deletecontact" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" content="x" /><roundcube:button command="group-remove-selected" type="link" title="groupremoveselected" class="listbutton removegroup disabled" classAct="listbutton removegroup" innerClass="inner" content="-" /> - <roundcube:object name="recordsCountDisplay" class="countdisplay" label="fromtoshort" /> + <roundcube:button command="add" type="link" title="newcontact" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="newcontact" /><roundcube:button command="delete" type="link" title="deletecontact" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" label="deletecontact" /><roundcube:button command="group-remove-selected" type="link" title="groupremoveselected" class="listbutton removegroup disabled" classAct="listbutton removegroup" innerClass="inner" label="groupremoveselected" /> + <span class="countdisplay" aria-live="polite" aria-relevant="text"> + <span class="voice"><roundcube:label name="contacts" /></span> + <roundcube:object name="recordsCountDisplay" label="fromtoshort" /> + </span> </div> -<div class="boxpagenav"> - <roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" content="|&lt;" /> - <roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" content="&lt;" /> - <roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" content="&gt;" /> - <roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" content="&gt;|" /> </div> + +<div class="voice" role="note"> +<h3><roundcube:label name="helplistnavigation" /></h3> +<pre> +<roundcube:label name="helplistkeyboardnavigation" /> +<roundcube:label name="helplistkeyboardnavcontacts" /> +</pre> </div> <div id="contacts-box" class="uibox"> <div class="iframebox"> - <roundcube:object name="addressframe" id="contact-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" /> + <roundcube:object name="addressframe" id="contact-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="contactproperties" /> </div> </div> @@ -81,37 +128,10 @@ </div><!-- end mainscreen --> -<div id="exportmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="export" label="exportall" prop="sub" class="exportalllink" classAct="exportalllink active" /></li> - <li><roundcube:button command="export-selected" label="exportsel" prop="sub" class="exportsellink" classAct="exportsellink active" /></li> - </ul> -</div> - -<div id="searchmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><label><input type="checkbox" name="s_mods[]" value="name" id="s_mod_name" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="name" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="firstname" id="s_mod_firstname" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="firstname" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="surname" id="s_mod_surname" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="surname" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="email" id="s_mod_email" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="email" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="*" id="s_mod_all" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="allfields" /></span></label></li> - </ul> -</div> - -<div id="groupoptions" class="popupmenu"> - <ul id="groupoptionsmenu" class="toolbarmenu"> - <li><roundcube:button command="group-rename" label="grouprename" classAct="active" /></li> - <li><roundcube:button command="group-delete" label="groupdelete" classAct="active" /></li> - <li><roundcube:button command="search-create" label="searchsave" classAct="active" /></li> - <li><roundcube:button command="search-delete" label="searchdelete" classAct="active" /></li> - <roundcube:container name="groupoptions" id="groupoptionsmenu" /> - </ul> -</div> - -<div id="dragcontactmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li> - <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li> +<div id="dragcontactmenu" class="popupmenu" aria-hidden="true"> + <ul class="toolbarmenu" role="menu"> + <li role="menuitem"><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li> </ul> </div> diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html index 90df4f3a8..2812d3706 100644 --- a/skins/larry/templates/compose.html +++ b/skins/larry/templates/compose.html @@ -13,66 +13,71 @@ <div id="mainscreen"> +<h1 class="voice"><roundcube:object name="pagetitle" /></h1> + <!-- toolbar --> -<div id="messagetoolbar" class="fullwidth"> -<div id="mailtoolbar" class="toolbar"> - <roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="cancel" condition="!env:extwin" /> - <roundcube:button command="close" type="link" class="button close disabled" classAct="button close" classSel="button close pressed" label="cancel" condition="env:extwin" /> +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar"> + <roundcube:button command="list" type="link" class="button back disabled" classAct="button back" label="cancel" condition="!env:extwin" tabindex="2" /> + <roundcube:button command="close" type="link" class="button close disabled" classAct="button close" label="cancel" condition="env:extwin" tabindex="2" /> <span class="spacer"></span> - <roundcube:button command="send" type="link" class="button send" classAct="button send" classSel="button send pressed" label="send" title="sendmessage" /> - <roundcube:button command="savedraft" type="link" class="button savedraft" classAct="button savedraft" classSel="button savedraft pressed" label="save" title="savemessage" /> + <roundcube:button command="send" type="link" class="button send disabled" classAct="button send" label="send" title="sendmessage" tabindex="2" /> + <roundcube:button command="savedraft" type="link" class="button savedraft disabled" classAct="button savedraft" label="save" title="savemessage" tabindex="2" /> <span class="spacer"></span> <roundcube:if condition="config:enable_spellcheck" /> <span class="dropbutton"> - <roundcube:button command="spellcheck" type="link" class="button spellcheck disabled" classAct="button spellcheck" classSel="button spellcheck pressed" label="spellcheck" title="checkspelling" /> - <span class="dropbuttontip" id="spellmenulink" onclick="UI.show_popup('spellmenu');return false"></span> + <roundcube:button command="spellcheck" type="link" class="button spellcheck disabled" classAct="button spellcheck" classSel="button spellcheck pressed" label="spellcheck" title="checkspelling" tabindex="2" /> + <a href="#languages" class="dropbuttontip" id="spellmenulink" onclick="UI.toggle_popup('spellmenu',event);return false" aria-haspopup="true" aria-expanded="false"tabindex="2">Select Spell Language</a> </span> <roundcube:endif /> - <roundcube:button name="addattachment" type="link" class="button attach" classAct="button attach" classSel="button attach pressed" label="attach" title="addattachment" onclick="UI.show_uploadform();return false" /> - <roundcube:button command="insert-sig" type="link" class="button insertsig disabled" classAct="button insertsig" classSel="button insertsig pressed" label="signature" title="insertsignature" /> - <a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="UI.show_popup('responsesmenu');return false"><roundcube:label name="responses" /></a> + <roundcube:button name="addattachment" type="link" class="button attach" label="attach" title="addattachment" onclick="UI.show_uploadform(event);return false" aria-haspopup="true" aria-expanded="false"tabindex="2" /> + <roundcube:button command="insert-sig" type="link" class="button insertsig disabled" classAct="button insertsig" label="signature" title="insertsignature" tabindex="2" /> + <a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="UI.toggle_popup('responsesmenu',event);return false" tabindex="2" aria-haspopup="true" aria-expanded="false" aria-owns="textresponsesmenu"><roundcube:label name="responses" /></a> <roundcube:container name="toolbar" id="compose-toolbar" /> </div> -</div> <div id="mainscreencontent"> <div id="composeview-left"> <!-- inline address book --> -<div id="compose-contacts" class="uibox listbox"> -<h2 class="boxtitle"><roundcube:label name="contacts" /></h2> - <div class="listsearchbox"> +<div id="compose-contacts" class="uibox listbox" role="region" aria-labelledby="aria-label-composecontacts"> +<h2 id="aria-label-composecontacts" class="boxtitle"><roundcube:label name="contacts" /></h2> + <div class="listsearchbox" role="search" aria-labelledby="aria-label-composequicksearch"> + <h3 id="aria-label-composequicksearch" class="voice"><roundcube:label name="arialabelcontactquicksearch" /></h3> <div class="searchbox"> + <label for="contactsearchbox" class="voice"><roundcube:label name="arialabelcontactsearchbox" /></label> <roundcube:object name="searchform" id="contactsearchbox" /> <a id="searchmenulink" class="iconbutton searchicon"> </a> <roundcube:button command="reset-search" class="iconbutton reset" title="resetsearch" content=" " /> </div> </div> - <roundcube:object name="addressbooks" id="directorylist" class="listing" /> - <div class="scroller withfooter"> - <roundcube:object name="addresslist" id="contacts-table" class="listing" noheader="true" /> + <roundcube:object name="addressbooks" id="directorylist" class="treelist listing" summary="ariasummarycomposecontacts" /> + <div class="scroller withfooter" tabindex="-1"> + <roundcube:object name="addresslist" id="contacts-table" class="listing iconized" noheader="true" role="listbox" /> </div> <div class="boxfooter"> <roundcube:button command="add-recipient" prop="to" type="link" title="to" class="listbutton addto disabled" classAct="listbutton addto" innerClass="inner" content="To+" /><roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="listbutton addcc disabled" classAct="listbutton addcc" innerClass="inner" content="Cc+" /><roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="listbutton addbcc disabled" classAct="listbutton addbcc" innerClass="inner" content="Bcc+" /> </div> <div class="boxpagenav"> - <roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" content="|&lt;" /> - <roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" content="&lt;" /> - <roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" content="&gt;" /> - <roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" content="&gt;|" /> + <roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" label="first" /> + <roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" label="previous" /> + <roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" label="next" /> + <roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" label="last" /> </div> </div> </div> -<div id="composeview-right"> +<div id="composeview-right" role="main"> <roundcube:form name="form" method="post" id="compose-content" class="uibox"> <!-- message headers --> -<div id="composeheaders"> -<a href="#options" id="composeoptionstoggle" class="moreheaderstoggle"><span class="iconlink" title="<roundcube:label name='options' />"></span></a> +<div id="composeheaders" role="region" aria-labelledby="aria-label-composeheaders"> +<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2> + +<a href="#options" id="composeoptionstoggle" class="moreheaderstoggle" title="<roundcube:label name='togglecomposeoptions' />" aria-exapnded="false"><span class="iconlink"></span></a> <table class="headers-table compose-headers"> <tbody> @@ -80,77 +85,78 @@ <td class="title"><label for="_from"><roundcube:label name="from" /></label></td> <td class="editfield"> <roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" /> - <a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit"><roundcube:label name="editidents" /></a> + <a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit" tabindex="0"><roundcube:label name="editidents" /></a> </td> </tr><tr> <td class="title top"><label for="_to"><roundcube:label name="to" /></label></td> - <td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="2" /></td> + <td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" aria-required="true" /></td> </tr><tr id="compose-cc"> <td class="title top"> <label for="_cc"><roundcube:label name="cc" /></label> - <a href="#cc" onclick="return UI.hide_header_row('cc');" class="iconbutton cancel" title="<roundcube:label name='delete' />">x</a> + <a href="#cc" onclick="return UI.hide_header_row('cc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="cc" /></a> </td> - <td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="3" /></td> + <td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td> </tr><tr id="compose-bcc"> <td class="title top"> <label for="_bcc"><roundcube:label name="bcc" /></label> - <a href="#bcc" onclick="return UI.hide_header_row('bcc');" class="iconbutton cancel" title="<roundcube:label name='delete' />">x</a> + <a href="#bcc" onclick="return UI.hide_header_row('bcc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="bcc" /></a> </td> - <td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="4" /></td> + <td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td> </tr><tr id="compose-replyto"> <td class="title top"> <label for="_replyto"><roundcube:label name="replyto" /></label> - <a href="#replyto" onclick="return UI.hide_header_row('replyto');" class="iconbutton cancel" title="<roundcube:label name='delete' />">x</a> + <a href="#replyto" onclick="return UI.hide_header_row('replyto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="replyto" /></a> </td> - <td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="5" /></td> + <td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="1" /></td> </tr><tr id="compose-followupto"> <td class="title top"> <label for="_followupto"><roundcube:label name="followupto" /></label> - <a href="#followupto" onclick="return UI.hide_header_row('followupto');" class="iconbutton cancel" title="<roundcube:label name='delete' />">x</a> + <a href="#followupto" onclick="return UI.hide_header_row('followupto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" tabindex="3"><roundcube:label name="delete" /> <roundcube:label name="followupto" /></a> </td> - <td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="7" /></td> + <td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="1" /></td> </tr><tr> <td></td> <td class="formlinks"> - <a href="#cc" onclick="return UI.show_header_row('cc')" id="cc-link" class="iconlink add"><roundcube:label name="addcc" /></a> - <a href="#bcc" onclick="return UI.show_header_row('bcc')" id="bcc-link" class="iconlink add"><roundcube:label name="addbcc" /></a> - <a href="#reply-to" onclick="return UI.show_header_row('replyto')" id="replyto-link" class="iconlink add"><roundcube:label name="addreplyto" /></a> - <a href="#followup-to" onclick="return UI.show_header_row('followupto')" id="followupto-link" class="iconlink add"><roundcube:label name="addfollowupto" /></a> + <a href="#cc" onclick="return UI.show_header_row('cc')" id="cc-link" class="iconlink add" tabindex="3"><roundcube:label name="addcc" /></a> + <a href="#bcc" onclick="return UI.show_header_row('bcc')" id="bcc-link" class="iconlink add" tabindex="3"><roundcube:label name="addbcc" /></a> + <a href="#reply-to" onclick="return UI.show_header_row('replyto')" id="replyto-link" class="iconlink add" tabindex="3"><roundcube:label name="addreplyto" /></a> + <a href="#followup-to" onclick="return UI.show_header_row('followupto')" id="followupto-link" class="iconlink add" tabindex="3"><roundcube:label name="addfollowupto" /></a> </td> </tr><tr> <td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td> - <td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="8" /></td> + <td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="1" /></td> </tr> </tbody> </table> <div id="composebuttons" class="formbuttons"> - <roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" content="[]" condition="!env:extwin" /> + <roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" label="openinextwin" condition="!env:extwin" /> </div> <!-- (collapsable) message options --> -<div id="composeoptions"> +<div id="composeoptions" role="region" aria-labelledby="aria-label-composeoptions"> + <h2 id="aria-label-composeoptions" class="voice"><roundcube:label name="arialabelcomposeoptions" /></h2> <roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" /> <span class="composeoption"> <label><roundcube:label name="editortype" /> - <roundcube:object name="editorSelector" editorid="composebody" tabindex="14" /></label> + <roundcube:object name="editorSelector" editorid="composebody" tabindex="4" /></label> </span> <roundcube:endif /> <span class="composeoption"> <label for="rcmcomposepriority"><roundcube:label name="priority" /> - <roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></label> + <roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" tabindex="4" /></label> </span> <span class="composeoption"> - <label><roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" /> <roundcube:label name="returnreceipt" /></label> + <label><roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" tabindex="4" /> <roundcube:label name="returnreceipt" /></label> </span> <roundcube:if condition="config:smtp_server != ''" /> <span class="composeoption"> - <label><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" /> <roundcube:label name="dsn" /></label> + <label><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" tabindex="4" /> <roundcube:label name="dsn" /></label> </span> <roundcube:endif /> <roundcube:if condition="!config:no_save_sent_messages" /> <span class="composeoption"> - <label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" /></label> + <label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="4" /></label> </span> <roundcube:endif /> <roundcube:container name="composeoptions" id="composeoptions" /> @@ -161,11 +167,13 @@ <!-- message compose body --> <div id="composeview-bottom"> <div id="composebodycontainer"> - <roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="9" /> + <label for="composebody" class="voice"><roundcube:label name="arialabelmessagebody" /></label> + <roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="1" /> </div> - <div id="compose-attachments" class="rightcol"> + <div id="compose-attachments" class="rightcol" role="region" aria-labelledby="aria-label-composeattachments"> + <h2 id="aria-label-composeattachments" class="voice"><roundcube:label name="attachments" /></h2> <div style="text-align:center; margin-bottom:20px"> - <roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform();return false" /> + <roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform(event);return false" tabindex="1" /> </div> <roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" /> <roundcube:object name="fileDropArea" id="compose-attachments" /> @@ -187,7 +195,8 @@ </div><!-- end mainscreen --> -<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='addattachment' />"> +<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='addattachment' />" aria-hidden="true"> + <h2 id="aria-label-uploaddialog" class="voice"><roundcube:label name="arialabelattachmentuploadform" /></h2> <roundcube:object name="composeAttachmentForm" id="uploadform" buttons="no" /> <div class="formbuttons"> <roundcube:button command="send-attachment" type="input" class="button mainaction" label="upload" /> @@ -195,15 +204,16 @@ </div> </div> -<div id="spellmenu" class="popupmenu"></div> +<div id="spellmenu" class="popupmenu" aria-hidden="true"></div> -<div id="responsesmenu" class="popupmenu"> - <ul class="toolbarmenu" id="textresponsesmenu"> - <li class="separator" id=""><label><roundcube:label name="insertresponse" /></label></li> +<div id="responsesmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-responsesmenu" class="voice"><roundcube:label name="arialabelresponsesmenu" /></h3> + <ul class="toolbarmenu" id="textresponsesmenu" role="menu" aria-labelledby="aria-label-responsesmenu"> + <li role="separator" class="separator" id=""><label><roundcube:label name="insertresponse" /></label></li> <roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" /> - <li class="separator"><label><roundcube:label name="manageresponses" /></label></li> - <li><roundcube:button command="save-response" type="link" label="savenewresponse" classAct="active" unselectable="on" /></li> - <li><roundcube:button command="responses" type="link" label="editresponses" classAct="active" /></li> + <li role="separator" class="separator"><label><roundcube:label name="manageresponses" /></label></li> + <li role="menuitem"><roundcube:button command="save-response" type="link" label="savenewresponse" classAct="active" unselectable="on" /></li> + <li role="menuitem"><roundcube:button command="responses" type="link" label="editresponses" classAct="active" /></li> </ul> </div> diff --git a/skins/larry/templates/contactedit.html b/skins/larry/templates/contactedit.html index 3467ebe8e..b7aafed31 100644 --- a/skins/larry/templates/contactedit.html +++ b/skins/larry/templates/contactedit.html @@ -16,7 +16,8 @@ <div id="sourcename"><roundcube:label name="addressbook" />: <roundcube:var name="env:sourcename" condition="env:action!='add'" /><roundcube:object name="sourceselector" id="sourceselect" condition="env:action=='add'" /></div> <roundcube:endif /> - <div id="contactphoto"> + <fieldset id="contactphoto"> + <legend class="voice"><roundcube:label name="contactphoto" /></legend> <roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" /> <roundcube:if condition="env:photocol" /> <roundcube:object name="fileDropArea" id="contactpic" /> @@ -25,7 +26,7 @@ <roundcube:button command="delete-photo" type="link" label="delete" class="iconlink delete disabled" classAct="iconlink delete active" condition="env:photocol" /> </div> <roundcube:endif /> - </div> + </fieldset> <roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" /> <br style="clear:both" /> diff --git a/skins/larry/templates/folders.html b/skins/larry/templates/folders.html index 56396bf1d..ffb0a7ee1 100644 --- a/skins/larry/templates/folders.html +++ b/skins/larry/templates/folders.html @@ -10,9 +10,11 @@ <div id="mainscreen" class="offset"> +<h1 class="voice"><roundcube:label name="settings" /> : <roundcube:label name="folders" /></h1> + <roundcube:include file="/includes/settingstabs.html" /> -<div id="settings-right"> +<div id="settings-right" role="main"> <div id="folderslist" class="uibox listbox"> <h2 id="folderslist-header" class="boxtitle"><span style="float:right"><roundcube:label name="subscribed" /></span><roundcube:label name="folders" /></h2> @@ -20,11 +22,22 @@ <roundcube:object name="foldersubscription" form="subscriptionform" id="subscription-table" class="listing" noheader="true" /> </div> <div id="folderslist-footer" class="boxfooter"> - <roundcube:button command="create-folder" type="link" title="createfolder" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="UI.show_popup('mailboxmenu');return false" innerClass="inner" content="⚙" /> + <roundcube:button command="create-folder" type="link" title="createfolder" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="createfolder" /><roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="return UI.toggle_popup('mailboxmenu',event)" innerClass="inner" content="⚙" aria-haspopup="true" aria-expanded="false" aria-owns="mailboxoptionsmenu" /> <roundcube:if condition="env:quota" /> + <span class="voice"><roundcube:label name="quota"></span> <roundcube:object name="quotaDisplay" id="quotadisplay" class="countdisplay" display="text" /> <roundcube:endif /> </div> + +<div id="mailboxmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-mailboxmenu" class="voice"><roundcube:label name="arialabelmailboxmenu" /></h3> + <ul class="toolbarmenu" id="mailboxoptionsmenu" role="menu" aria-labelledby="aria-label-mailboxmenu"> + <li role="menuitem"><roundcube:button command="delete-folder" label="delete" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li> + <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" /> + </ul> +</div> + </div> <div id="folder-details" class="uibox contentbox"> @@ -37,14 +50,6 @@ </div> -<div id="mailboxmenu" class="popupmenu"> - <ul class="toolbarmenu" id="mailboxoptionsmenu"> - <li><roundcube:button command="delete-folder" label="delete" classAct="active" /></li> - <li><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li> - <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" /> - </ul> -</div> - <roundcube:include file="/includes/footer.html" /> </body> diff --git a/skins/larry/templates/identities.html b/skins/larry/templates/identities.html index e3d2cc842..91f7f8f71 100644 --- a/skins/larry/templates/identities.html +++ b/skins/larry/templates/identities.html @@ -10,23 +10,25 @@ <div id="mainscreen" class="offset"> +<h1 class="voice"><roundcube:label name="settings" /> : <roundcube:label name="identities" /></h1> + <roundcube:include file="/includes/settingstabs.html" /> -<div id="settings-right"> +<div id="settings-right" role="main" aria-labelledby="aria-label-identitieslist"> <div id="identitieslist" class="uibox listbox"> -<h2 class="boxtitle"><roundcube:label name="identities" /></h2> +<h2 class="boxtitle" id="aria-label-identitieslist"><roundcube:label name="identities" /></h2> <div class="scroller withfooter"> -<roundcube:object name="identitiesList" id="identities-table" class="listing" cellspacing="0" summary="Identities list" noheader="true" editIcon="" /> +<roundcube:object name="identitiesList" id="identities-table" class="listing" noheader="true" editIcon="" role="listbox" /> </div> <div class="boxfooter"> -<roundcube:button command="add" type="link" title="newidentity" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" condition="config:identities_level:0<2" /><roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" content="-" condition="config:identities_level:0<2" /> +<roundcube:button command="add" type="link" title="newidentity" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="newidentity" condition="config:identities_level:0<2" /><roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" label="delete" condition="config:identities_level:0<2" /> </div> </div> <div id="identity-details" class="uibox contentbox"> <div class="iframebox"> - <roundcube:object name="identityframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" /> + <roundcube:object name="identityframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="arialabelidentityeditfrom" /> </div> </div> diff --git a/skins/larry/templates/importcontacts.html b/skins/larry/templates/importcontacts.html index a670d0354..2bc1d4a26 100644 --- a/skins/larry/templates/importcontacts.html +++ b/skins/larry/templates/importcontacts.html @@ -15,7 +15,7 @@ </div> <div id="pluginbody" class="offset uibox contentbox"> -<h2 class="boxtitle"><roundcube:label name="importcontacts" /></h2> +<h1 class="boxtitle"><roundcube:label name="importcontacts" /></h1> <div id="import-box" class="boxcontent"> <roundcube:object name="importstep" class="propform" /> diff --git a/skins/larry/templates/login.html b/skins/larry/templates/login.html index 64ff6be92..557b029ee 100644 --- a/skins/larry/templates/login.html +++ b/skins/larry/templates/login.html @@ -7,8 +7,10 @@ </head> <body> +<h1 class="voice"><roundcube:object name="productname" /> <roundcube:label name="login" /></h1> + <div id="login-form"> -<div class="box-inner"> +<div class="box-inner" role="main"> <roundcube:object name="logo" src="/images/roundcube_logo.png" id="logo" /> <roundcube:form name="form" method="post"> @@ -17,15 +19,15 @@ </div> -<div class="box-bottom"> +<div class="box-bottom" role="complementary"> <roundcube:object name="message" id="message" /> <noscript> <p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p> </noscript> </div> -<div id="bottomline"> - <roundcube:var name="config:product_name"> <roundcube:object name="version" condition="config:display_version" /> +<div id="bottomline" role="contentinfo"> + <roundcube:object name="productname" /> <roundcube:object name="version" condition="config:display_version" /> <roundcube:if condition="config:support_url" /> ● <a href="<roundcube:var name='config:support_url' />" target="_blank" class="support-link"><roundcube:label name="support" /></a> <roundcube:endif /> diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html index 1e4a3ce8c..1ca5fbba0 100644 --- a/skins/larry/templates/mail.html +++ b/skins/larry/templates/mail.html @@ -17,8 +17,11 @@ <div id="mainscreen"> +<h1 class="voice"><roundcube:label name="mail" /></h1> + <!-- toolbar --> -<div id="messagetoolbar" class="toolbar"> +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="messagetoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar"> <roundcube:button command="checkmail" type="link" class="button checkmail disabled" classAct="button checkmail" classSel="button checkmail pressed" label="refresh" title="checkmail" /> <roundcube:include file="/includes/mailtoolbar.html" /> </div> @@ -27,14 +30,35 @@ <!-- search filter --> <div id="searchfilter"> - <roundcube:object name="searchfilter" class="searchfilter decorated" /> + <label for="messagessearchfilter" class="voice"><roundcube:label name="arialabelmessagessearchfilter" /></label> + <roundcube:object name="searchfilter" class="searchfilter decorated" id="messagessearchfilter" aria-controls="messagelist" /> </div> <!-- search box --> -<div id="quicksearchbar" class="searchbox"> +<div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform"> +<h2 id="aria-label-searchform" class="voice"><roundcube:label name="arialabelmailsearchform" /></h2> +<label for="quicksearchbox" class="voice"><roundcube:label name="arialabelmailquicksearchbox" /></label> <roundcube:object name="searchform" id="quicksearchbox" /> -<roundcube:button name="searchmenulink" id="searchmenulink" class="iconbutton searchoptions" onclick="UI.show_popup('searchmenu');return false" title="searchmod" content=" " /> -<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " /> +<roundcube:button command="menu-open" prop="searchmenu" id="searchmenulink" class="iconbutton searchoptions" title="searchmod" label="options" aria-haspopup="true" aria-expanded="false" aria-owns="searchmenu-menu" /> +<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" /> + +<div id="searchmenu" class="popupmenu" data-editable="true"> + <h3 id="aria-label-searchmenu" class="voice"><roundcube:label name="searchmod" /></h3> + <ul class="toolbarmenu" id="searchmenu-menu" role="menu" aria-labelledby="aria-label-searchmenu"> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="subject" id="s_mod_subject" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="subject" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="from" id="s_mod_from" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="from" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="to" id="s_mod_to" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="to" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="cc" id="s_mod_cc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="cc" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="bcc" id="s_mod_bcc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="bcc" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="body" id="s_mod_body" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="body" /></span></label></li> + <li role="menuitem"><label><input type="checkbox" name="s_mods[]" value="text" id="s_mod_text" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="msgtext" /></span></label></li> + <li role="separator" class="separator"><label><roundcube:label name="searchscope" /></label></li> + <li role="menuitem"><label><input type="radio" name="s_scope" value="base" id="s_scope_base" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="currentfolder" /></span></label></li> + <li role="menuitem"><label><input type="radio" name="s_scope" value="sub" id="s_scope_sub" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="subfolders" /></span></label></li> + <li role="menuitem"><label><input type="radio" name="s_scope" value="all" id="s_scope_all" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="allfolders" /></span></label></li> + </ul> +</div> + </div> </div> @@ -43,13 +67,15 @@ <div id="mailview-left"> <!-- folders list --> -<div id="mailboxcontainer" class="uibox listbox"> +<div id="mailboxcontainer" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist"> +<h2 id="aria-label-folderlist" class="voice"><roundcube:label name="arialabelfolderlist" /></h2> <div id="folderlist-content" class="scroller withfooter"> <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> </div> <div id="folderlist-footer" class="boxfooter"> - <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="UI.show_popup('mailboxmenu');return false" innerClass="inner" content="⚙" /> + <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="UI.toggle_popup('mailboxmenu',event);return false" innerClass="inner" content="⚙" aria-haspopup="true" aria-expanded="false" aria-owns="mailboxoptionsmenu" /> <roundcube:if condition="env:quota" /> + <span class="voice"><roundcube:label name="quota"></span> <roundcube:object name="quotaDisplay" id="quotadisplay" class="countdisplay" display="text" /> <roundcube:endif /> </div> @@ -57,7 +83,7 @@ </div> -<div id="mailview-right"> +<div id="mailview-right" role="main"> <roundcube:if condition="config:preview_pane == true" /> <div id="mailview-top" class="uibox"> @@ -67,10 +93,20 @@ <!-- messagelist --> <div id="messagelistcontainer" class="boxlistcontent"> +<h2 id="aria-label-messagelist" class="voice"><roundcube:label name="arialabelmessagelist" /></h2> <roundcube:object name="messages" id="messagelist" class="records-table messagelist sortheader fixedheader" - optionsmenuIcon="true" /> + optionsmenuIcon="true" + aria-labelledby="aria-label-messagelist" /> +</div> + +<div class="voice" role="note"> +<h3><roundcube:label name="helplistnavigation" /></h3> +<pre> +<roundcube:label name="helplistkeyboardnavigation" /> +<roundcube:label name="helplistkeyboardnavmessages" /> +</pre> </div> <!-- list footer --> @@ -81,33 +117,34 @@ </div> <div id="listselectors"> - <a href="#select" id="listselectmenulink" class="menuselector" onclick="UI.show_popup('listselectmenu');return false"><span class="handle"><roundcube:label name="select" /></span></a> + <a href="#select" id="listselectmenulink" class="menuselector" onclick="UI.toggle_popup('listselectmenu', event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="listselectmenu-menu"><span class="handle"><roundcube:label name="select" /></span></a> <roundcube:if condition="env:threads" /> - <a href="#threads" id="threadselectmenulink" class="menuselector" onclick="UI.show_popup('threadselectmenu');return false"><span class="handle"><roundcube:label name="threads" /></span></a> + <a href="#threads" id="threadselectmenulink" class="menuselector" onclick="UI.toggle_popup('threadselectmenu', event);return false" aria-haspopup="true" aria-expanded="false" aria-owns="threadselectmenu-menu"><span class="handle"><roundcube:label name="threads" /></span></a> <roundcube:endif /> </div> <div id="countcontrols" class="pagenav dark"> - <roundcube:object name="messageCountDisplay" class="countdisplay" /> + <roundcube:object name="messageCountDisplay" class="countdisplay" aria-live="polite" aria-relevant="text" /> <span class="pagenavbuttons"> - <roundcube:button command="firstpage" type="link" class="button firstpage disabled" classAct="button firstpage" classSel="button firstpage pressed" innerClass="inner" title="firstpage" content="|&lt;" /> - <roundcube:button command="previouspage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previouspage" content="&lt;" /> - <roundcube:button command="nextpage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextpage" content="&gt;" /> - <roundcube:button command="lastpage" type="link" class="button lastpage disabled" classAct="button lastpage" classSel="button lastpage pressed" innerClass="inner" title="lastpage" content="&gt;|" /> + <roundcube:button command="firstpage" type="link" class="button firstpage disabled" classAct="button firstpage" classSel="button firstpage pressed" innerClass="inner" title="firstpage" label="first" /> + <roundcube:button command="previouspage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previouspage" label="previous" /> + <roundcube:button command="nextpage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextpage" label="next" /> + <roundcube:button command="lastpage" type="link" class="button lastpage disabled" classAct="button lastpage" classSel="button lastpage pressed" innerClass="inner" title="lastpage" label="last" /> </span> </div> <roundcube:container name="listcontrols" id="listcontrols" /> - <a href="#preview" id="mailpreviewtoggle" title="<roundcube:label name='previewpane' />"></a> + <a href="#preview" id="mailpreviewtoggle" class="iconbutton" title="<roundcube:label name='previewpane' />" role="button" tabindex="0"><roundcube:label name="previewpane" /></a> </div> </div><!-- end mailview-top --> <div id="mailview-bottom" class="uibox"> -<div id="mailpreviewframe" class="iframebox"> -<roundcube:object name="messagecontentframe" id="messagecontframe" style="width:100%; height:100%" frameborder="0" src="/watermark.html" /> +<div id="mailpreviewframe" class="iframebox" role="complementary" aria-labelledby="aria-label-mailpreviewframe"> +<h2 id="aria-label-mailpreviewframe" class="voice"><roundcube:label name="arialabelmailpreviewframe" /></h2> +<roundcube:object name="messagecontentframe" id="messagecontframe" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="arialabelmailpreviewframe" /> </div> </div><!-- end mailview-bottom --> @@ -118,59 +155,47 @@ </div><!-- end mainscreen --> -<div id="searchmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><label><input type="checkbox" name="s_mods[]" value="subject" id="s_mod_subject" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="subject" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="from" id="s_mod_from" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="from" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="to" id="s_mod_to" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="to" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="cc" id="s_mod_cc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="cc" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="bcc" id="s_mod_bcc" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="bcc" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="body" id="s_mod_body" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="body" /></span></label></li> - <li><label><input type="checkbox" name="s_mods[]" value="text" id="s_mod_text" onclick="UI.set_searchmod(this)" /> <span><roundcube:label name="msgtext" /></span></label></li> - <li class="separator"><label><roundcube:label name="searchscope" /></label></li> - <li><label><input type="radio" name="s_scope" value="base" id="s_scope_base" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="currentfolder" /></span></label></li> - <li><label><input type="radio" name="s_scope" value="sub" id="s_scope_sub" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="subfolders" /></span></label></li> - <li><label><input type="radio" name="s_scope" value="all" id="s_scope_all" onclick="UI.set_searchscope(this)" /> <span><roundcube:label name="allfolders" /></span></label></li> - </ul> -</div> - -<div id="dragmessagemenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li> - <li><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li> +<div id="dragmessagemenu" class="popupmenu" aria-hidden="true"> + <ul class="toolbarmenu" role="menu"> + <li role="menuitem"><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li> </ul> </div> -<div id="mailboxmenu" class="popupmenu"> - <ul class="toolbarmenu" id="mailboxoptionsmenu"> - <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li> - <li><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li> - <li><roundcube:button command="import-messages" name="messageimport" type="link" classAct="active" label="importmessages" onclick="if(rcmail.command_enabled('import-messages'))UI.show_uploadform();return false" /></li> - <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li> +<div id="mailboxmenu" class="popupmenu" aria-hidden="true"> + <h3 id="aria-label-mailboxmenu" class="voice"><roundcube:label name="arialabelmailboxmenu" /></h3> + <ul class="toolbarmenu" id="mailboxoptionsmenu" role="menu" aria-labelledby="aria-label-mailboxmenu"> + <li role="menuitem"><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li> + <li role="menuitem"><roundcube:button command="import-messages" name="messageimport" type="link" classAct="active" label="importmessages" onclick="if(rcmail.command_enabled('import-messages'))UI.show_uploadform();return false" /></li> + <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li> <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" /> </ul> </div> -<div id="listselectmenu" class="popupmenu dropdown"> - <ul class="toolbarmenu iconized"> - <li><roundcube:button command="select-all" type="link" label="all" class="icon" classAct="icon active" innerclass="icon mail" /></li> - <li><roundcube:button command="select-all" type="link" prop="page" label="currpage" class="icon" classAct="icon active" innerclass="icon list" /></li> - <li><roundcube:button command="select-all" type="link" prop="unread" label="unread" class="icon" classAct="icon active" innerclass="icon unread" /></li> - <li><roundcube:button command="select-all" type="link" prop="flagged" label="flagged" class="icon" classAct="icon active" innerclass="icon flagged" /></li> - <li><roundcube:button command="select-all" type="link" prop="invert" label="invert" class="icon" classAct="icon active" innerclass="icon invert" /></li> - <li><roundcube:button command="select-none" type="link" label="none" class="icon" classAct="icon active" innerclass="icon cross" /></li> +<div id="listselectmenu" class="popupmenu dropdown" aria-hidden="true"> + <h3 id="aria-label-listselectmenu" class="voice"><roundcube:label name="arialabellistselectmenu" /></h3> + <ul id="listselectmenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-listselectmenu"> + <li role="menuitem"><roundcube:button command="select-all" type="link" label="all" class="icon" classAct="icon active" innerclass="icon mail" /></li> + <li role="menuitem"><roundcube:button command="select-all" type="link" prop="page" label="currpage" class="icon" classAct="icon active" innerclass="icon list" /></li> + <li role="menuitem"><roundcube:button command="select-all" type="link" prop="unread" label="unread" class="icon" classAct="icon active" innerclass="icon unread" /></li> + <li role="menuitem"><roundcube:button command="select-all" type="link" prop="flagged" label="flagged" class="icon" classAct="icon active" innerclass="icon flagged" /></li> + <li role="menuitem"><roundcube:button command="select-all" type="link" prop="invert" label="invert" class="icon" classAct="icon active" innerclass="icon invert" /></li> + <li role="menuitem"><roundcube:button command="select-none" type="link" label="none" class="icon" classAct="icon active" innerclass="icon cross" /></li> </ul> </div> -<div id="threadselectmenu" class="popupmenu dropdown"> - <ul class="toolbarmenu"> - <li><roundcube:button command="expand-all" type="link" label="expand-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li> - <li><roundcube:button command="expand-unread" type="link" label="expand-unread" class="icon" classAct="icon active" innerclass="icon conversation" /></li> - <li><roundcube:button command="collapse-all" type="link" label="collapse-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li> +<div id="threadselectmenu" class="popupmenu dropdown" aria-hidden="true"> + <h3 id="aria-label-threadselectmenu" class="voice"><roundcube:label name="arialabelthreadselectmenu" /></h3> + <ul id="threadselectmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-threadselectmenu"> + <li role="menuitem"><roundcube:button command="expand-all" type="link" label="expand-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li> + <li role="menuitem"><roundcube:button command="expand-unread" type="link" label="expand-unread" class="icon" classAct="icon active" innerclass="icon conversation" /></li> + <li role="menuitem"><roundcube:button command="collapse-all" type="link" label="collapse-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li> </ul> </div> -<div id="listoptions" class="propform popupdialog"> +<div id="listoptions" class="propform popupdialog" role="dialog" aria-labelledby="aria-label-listoptions" aria-hidden="true"> +<h2 id="aria-label-listoptions" class="voice"><roundcube:label name="arialabelmessagelistoptions" /></h2> <roundcube:if condition="!in_array('list_cols', (array)config:dont_override)" /> <fieldset class="floating"> <legend><roundcube:label name="listcolumns" /></legend> @@ -219,11 +244,12 @@ <br style="clear:both" /> <div class="formbuttons"> <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" /> - <roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" /> + <roundcube:button command="menu-close" prop="messagelistmenu" id="listmenucancel" type="input" class="button" label="cancel" /> </div> </div> -<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='importmessages' />"> +<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='importmessages' />" aria-hidden="true"> + <h2 id="aria-label-uploaddialog" class="voice"><roundcube:label name="arialabelmailimportdialog" /></h2> <roundcube:object name="messageimportform" id="uploadform" buttons="no" /> <div class="formbuttons"> <roundcube:button command="import-messages" type="input" class="button mainaction" label="upload" /> diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html index a661f5720..30c3d3d3e 100644 --- a/skins/larry/templates/message.html +++ b/skins/larry/templates/message.html @@ -10,8 +10,11 @@ <div id="mainscreen"> +<h1 class="voice"><roundcube:object name="messageHeaders" valueOf="subject" /></h1> + <!-- toolbar --> -<div id="messagetoolbar" class="toolbar fullwidth"> +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar"> <roundcube:if condition="!env:extwin" /> <roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" /> <roundcube:endif /> @@ -25,7 +28,8 @@ <div id="mailview-left"> <!-- folders list --> -<div id="mailboxcontainer" class="uibox listbox"> +<div id="mailboxcontainer" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist"> +<h2 id="aria-label-folderlist" class="voice"><roundcube:label name="arialabelfolderlist" /></h2> <div class="scroller"> <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> </div> @@ -33,27 +37,22 @@ </div> -<div id="mailview-right" class="uibox"> +<div id="mailview-right" class="uibox" role="main"> <roundcube:else /> <roundcube:object name="mailboxlist" folder_filter="mail" type="js" /> -<div id="mailview-right" class="offset fullwidth uibox"> +<div id="mailview-right" class="offset fullwidth uibox" role="main"> <roundcube:endif /> <div id="messageheader"> <span class="moreheaderstoggle"></span> -<h2 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h2> -<div class="message-headers"> -<roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" max="20" /> -</div> -<roundcube:object name="messageFullHeaders" id="full-headers" /> - <!-- record navigation --> -<div id="countcontrols" class="pagenav"> +<div id="countcontrols" class="pagenav" role="navigation" aria-labelledby="aria-label-countcontrols"> + <h2 id="aria-label-countcontrols" class="voice"><roundcube:label name="arialabelmessagenav" /></h2> <roundcube:object name="messageCountDisplay" class="countdisplay" /> - <roundcube:button command="previousmessage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previousmessage" content="&lt;" /> - <roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" content="&gt;" /> + <roundcube:button command="previousmessage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previousmessage" label="previous" /> + <roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" label="next" /> </div> <roundcube:if condition="env:optional_format=='text'" /> @@ -70,15 +69,22 @@ </div> <roundcube:endif /> +<h2 class="subject"><span class="voice"><roundcube:label name="subject" />: </span><roundcube:object name="messageHeaders" valueOf="subject" /></h2> +<div class="message-headers"> +<roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" max="20" /> +</div> +<roundcube:object name="messageFullHeaders" id="full-headers" /> <div id="contactphoto"><roundcube:object name="contactphoto" /></div> </div> <div id="messagecontent"> -<div class="rightcol"> +<div class="rightcol" role="region" aria-labelledby="aria-label-messageattachments"> +<h2 id="aria-label-messageattachments" class="voice"><roundcube:label name="attachments" /></h2> <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" /> </div> -<div class="leftcol"> +<div class="leftcol" role="region" aria-labelledby="aria-label-messagebody"> +<h2 id="aria-label-messagebody" class="voice"><roundcube:label name="arialabelmessagebody" /></h2> <roundcube:object name="messageObjects" id="message-objects" /> <roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" /> </div> @@ -92,11 +98,11 @@ </div><!-- end mainscreen --> -<div id="attachmentmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li> - <li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li> - <roundcube:container name="attachmentmenu" id="attachmentmenu" /> +<div id="attachmentmenu" class="popupmenu" aria-hidden="true"> + <ul class="toolbarmenu" id="attachmentoptionsmenu" role="menu"> + <li role="menuitem"><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li> + <li role="menuitem"><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li> + <roundcube:container name="attachmentmenu" id="attachmentoptionsmenu" /> </ul> </div> diff --git a/skins/larry/templates/messageerror.html b/skins/larry/templates/messageerror.html index d509ce804..c5c95214c 100644 --- a/skins/larry/templates/messageerror.html +++ b/skins/larry/templates/messageerror.html @@ -16,11 +16,12 @@ <div id="mainscreen"> +<h1 class="voice"><roundcube:label name="messageopenerror" /></h1> + <!-- toolbar --> -<div id="messagetoolbar" class="fullwidth"> - <div id="mailtoolbar" class="toolbar"> - <roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" /> - </div> +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar"> + <roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" /> </div> <div id="mainscreencontent"> @@ -28,7 +29,8 @@ <div id="mailview-left"> <!-- folders list --> -<div id="mailboxcontainer" class="uibox listbox"> +<div id="mailboxcontainer" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist"> +<h2 id="aria-label-folderlist" class="voice"><roundcube:label name="arialabelfolderlist" /></h2> <div class="scroller"> <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> </div> @@ -36,7 +38,7 @@ </div> -<div id="mailview-right" class="offset uibox"> +<div id="mailview-right" class="uibox"> <div id="messagecontent" class="watermark"></div> diff --git a/skins/larry/templates/messagepart.html b/skins/larry/templates/messagepart.html index 3b878c9d7..edf275f6e 100644 --- a/skins/larry/templates/messagepart.html +++ b/skins/larry/templates/messagepart.html @@ -10,7 +10,10 @@ <div id="mainscreen"> -<div id="messagetoolbar" class="toolbar fullwidth"> +<h1 class="voice"><roundcube:label name="attachment" />: <roundcube:var name="env:filename" /></h1> + +<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> +<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar"> <roundcube:button command="download" type="link" class="button download disabled" classAct="button download" classSel="button download pressed" label="download" /> <roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" /> <roundcube:container name="toolbar" id="messagetoolbar" /> @@ -18,16 +21,17 @@ <div id="mainscreencontent"> -<div id="messagepartheader" class="uibox listbox"> - <h2 class="boxtitle"><roundcube:label name="properties" /></h2> +<div id="messagepartheader" class="uibox listbox" role="contentinfo" aria-labelledby="aria-label-contentinfo"> + <h2 class="boxtitle" id="aria-label-contentinfo"><roundcube:label name="properties" /></h2> <div class="scroller"> <roundcube:object name="messagePartControls" class="listing" /> </div> </div> -<div id="messagepartcontainer" class="uibox"> +<div id="messagepartcontainer" class="uibox" role="main" aria-labelledby="aria-label-messagepart"> + <h2 id="aria-label-messagepart" class="voice"><roundcube:label name="arialabelattachmentpreview" /></h2> <div class="iframebox"> - <roundcube:object name="messagePartFrame" id="messagepartframe" frameborder="0" /> + <roundcube:object name="messagePartFrame" id="messagepartframe" frameborder="0" title="arialabelattachmentpreview" /> </div> </div> diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html index 4a6d76ead..03fc91505 100644 --- a/skins/larry/templates/messagepreview.html +++ b/skins/larry/templates/messagepreview.html @@ -7,9 +7,33 @@ <body class="iframe fullheight"> <div id="messageheader" class="previewheader"> -<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3> -<a href="#details" id="previewheaderstoggle" class="moreheaderstoggle"><span class="iconlink" title="<roundcube:label name='togglemoreheaders' />"></span></a> +<!-- record navigation --> +<div id="countcontrols" role="toolbar" aria-labelledby="aria-label-messagetoolbar"> +<h2 id="aria-label-messagetoolbar" class="voice"><roundcube:label name="arialabelmessageactions" /></h2> +<roundcube:if condition="env:optional_format=='text'" /> + <span class="buttongroup"> + <roundcube:button command="change-format" prop="html" type="link" class="button first changeformat html selected" innerClass="icon" title="changeformathtml" content="HTML" /><roundcube:button command="change-format" prop="text" type="link" class="button last changeformat text" classSel="button changeformat text pressed" innerClass="icon" title="changeformattext" content="Text" /> + </span> + +<roundcube:elseif condition="env:optional_format=='html'" /> + <span class="buttongroup"> + <roundcube:button command="change-format" prop="html" type="link" class="button first changeformat html" classSel="button changeformat html pressed" innerClass="icon" title="changeformathtml" content="HTML" /><roundcube:button command="change-format" prop="text" type="link" class="button last changeformat text selected" innerClass="icon" title="changeformattext" content="Text" /> + </span> + +<roundcube:endif /> +<roundcube:if condition="env:mailbox != config:drafts_mbox"> + <roundcube:button command="reply" type="link" class="button reply" classSel="button reply pressed" innerClass="icon" title="replytomessage" label="replytomessage" /> + <roundcube:button command="reply-all" type="link" class="button replyall" classSel="button replyall pressed" innerClass="icon" title="replytoallmessage" label="replytoallmessage" /> + <roundcube:button command="forward" type="link" class="button forward" classSel="button forward pressed" innerClass="icon" title="forwardmessage" label="forwardmessage" /> + +<roundcube:endif /> + <roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" label="openinextwin" /> +</div> + +<h3 class="subject"><span class="voice"><roundcube:label name="subject" />: </span><roundcube:object name="messageHeaders" valueOf="subject" /></h3> + +<a href="#details" id="previewheaderstoggle" class="moreheaderstoggle" aria-expanded="false"><span class="iconlink" title="<roundcube:label name='togglemoreheaders' />"></span></a> <div id="contactphoto"><roundcube:object name="contactphoto" /></div> <table class="headers-table" id="preview-shortheaders"><tbody><tr> @@ -28,45 +52,25 @@ <roundcube:object name="messageFullHeaders" id="full-headers" /> -<!-- record navigation --> -<div id="countcontrols"> -<roundcube:if condition="env:optional_format=='text'" /> - <span class="buttongroup"> - <roundcube:button command="change-format" prop="html" type="link" class="button first changeformat html selected" innerClass="icon" title="changeformathtml" content="HTML" /><roundcube:button command="change-format" prop="text" type="link" class="button last changeformat text" classSel="button changeformat text pressed" innerClass="icon" title="changeformattext" content="Text" /> - </span> - -<roundcube:elseif condition="env:optional_format=='html'" /> - <span class="buttongroup"> - <roundcube:button command="change-format" prop="html" type="link" class="button first changeformat html" classSel="button changeformat html pressed" innerClass="icon" title="changeformathtml" content="HTML" /><roundcube:button command="change-format" prop="text" type="link" class="button last changeformat text selected" innerClass="icon" title="changeformattext" content="Text" /> - </span> - -<roundcube:endif /> -<roundcube:if condition="env:mailbox != config:drafts_mbox"> - <roundcube:button command="reply" type="link" class="button reply" classSel="button reply pressed" innerClass="icon" title="replytomessage" content="<-" /> - <roundcube:button command="reply-all" type="link" class="button replyall" classSel="button replyall pressed" innerClass="icon" title="replytoallmessage" content="<<-" /> - <roundcube:button command="forward" type="link" class="button forward" classSel="button forward pressed" innerClass="icon" title="forwardmessage" content="->" /> - -<roundcube:endif /> - <roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" content="[]" /> -</div> - </div> -<div id="messagepreview"> -<div class="rightcol"> +<div id="messagepreview" role="main"> +<div class="rightcol" role="region" aria-labelledby="aria-label-messageattachments"> +<h2 id="aria-label-messageattachments" class="voice"><roundcube:label name="attachments" /></h2> <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" /> </div> -<div class="leftcol"> +<div class="leftcol" role="region" aria-labelledby="aria-label-messagebody"> +<h2 id="aria-label-messagebody" class="voice"><roundcube:label name="arialabelmessagebody" /></h2> <roundcube:object name="messageObjects" id="message-objects" /> <roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" /> </div> </div> -<div id="attachmentmenu" class="popupmenu"> - <ul class="toolbarmenu"> - <li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li> - <li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li> - <roundcube:container name="attachmentmenu" id="attachmentmenu" /> +<div id="attachmentmenu" class="popupmenu" aria-hidden="true"> + <ul class="toolbarmenu" id="attachmentoptionsmenu" role="menu"> + <li role="menuitem"><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li> + <li role="menuitem"><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li> + <roundcube:container name="attachmentmenu" id="attachmentoptionsmenu" /> </ul> </div> diff --git a/skins/larry/templates/responses.html b/skins/larry/templates/responses.html index 8e6884539..503ed2177 100644 --- a/skins/larry/templates/responses.html +++ b/skins/larry/templates/responses.html @@ -10,23 +10,25 @@ <div id="mainscreen" class="offset"> +<h1 class="voice"><roundcube:label name="settings" /> : <roundcube:label name="identities" /></h1> + <roundcube:include file="/includes/settingstabs.html" /> -<div id="settings-right"> +<div id="settings-right" role="main" aria-labelledby="aria-label-responseslist"> <div id="identitieslist" class="uibox listbox"> -<h2 class="boxtitle"><roundcube:label name="responses" /></h2> +<h2 class="boxtitle" id="aria-label-responseslist"><roundcube:label name="responses" /></h2> <div class="scroller withfooter"> -<roundcube:object name="responsesList" id="identities-table" class="listing" cellspacing="0" summary="Responses list" noheader="true" /> +<roundcube:object name="responsesList" id="identities-table" class="listing" cellspacing="0" noheader="true" role="listbox" /> </div> <div class="boxfooter"> -<roundcube:button command="add" type="link" title="savenewresponse" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" content="-" /> +<roundcube:button command="add" type="link" title="savenewresponse" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="savenewresponse" /><roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" label="delete" /> </div> </div> <div id="identity-details" class="uibox contentbox"> <div class="iframebox"> - <roundcube:object name="responseframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" /> + <roundcube:object name="responseframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="arialabelresonseeditfrom" /> </div> </div> diff --git a/skins/larry/templates/settings.html b/skins/larry/templates/settings.html index 08df7686e..406b9c9b3 100644 --- a/skins/larry/templates/settings.html +++ b/skins/larry/templates/settings.html @@ -10,19 +10,22 @@ <div id="mainscreen" class="offset"> +<h1 class="voice"><roundcube:label name="settings" /> : <roundcube:label name="preferences" /></h1> + <roundcube:include file="/includes/settingstabs.html" /> <div id="settings-right"> -<div id="sectionslist" class="uibox listbox"> +<div id="sectionslist" class="uibox listbox" role="navigation" aria-labelledby="aria-label-pefsection"> +<h2 id="aria-label-pefsection" class="boxtitle"><roundcube:label name="section" /></h2> <div class="scroller"> - <roundcube:object name="sectionslist" id="sections-table" class="listing" /> + <roundcube:object name="sectionslist" id="sections-table" class="listing iconized" noheader="true" /> </div> </div> -<div id="preferences-box" class="uibox contentbox"> +<div id="preferences-box" class="uibox contentbox" role="main"> <div class="iframebox"> - <roundcube:object name="prefsframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" /> + <roundcube:object name="prefsframe" id="preferences-frame" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="arialabelpreferencesform" /> </div> </div> diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 391e7ab9d..47183f73b 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -20,13 +20,10 @@ function rcube_mail_ui() searchmenu: { editable:1, callback:searchmenu }, attachmentmenu: { }, listoptions: { editable:1 }, - dragmenu: { sticky:1 }, groupmenu: { above:1 }, mailboxmenu: { above:1 }, spellmenu: { callback: spellmenu }, - // toggle: #1486823, #1486930 - 'attachment-form': { editable:1, above:1, toggle:!bw.ie&&!bw.linux }, - 'upload-form': { editable:1, toggle:!bw.ie&&!bw.linux } + 'folder-selector': { iconized:1 } }; var me = this; @@ -40,6 +37,7 @@ function rcube_mail_ui() this.init_tabs = init_tabs; this.show_about = show_about; this.show_popup = show_popup; + this.toggle_popup = toggle_popup; this.add_popup = add_popup; this.set_searchmod = set_searchmod; this.set_searchscope = set_searchscope; @@ -138,8 +136,9 @@ function rcube_mail_ui() /*** mail task ***/ if (rcmail.env.task == 'mail') { - rcmail.addEventListener('menu-open', menu_open) - .addEventListener('menu-save', menu_save) + rcmail.addEventListener('menu-open', menu_toggle) + .addEventListener('menu-close', menu_toggle) + .addEventListener('menu-save', save_listoptions) .addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) }) .addEventListener('responseaftersearch', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) }); @@ -157,7 +156,14 @@ function rcube_mail_ui() // add menu link for each attachment $('#attachment-list > li').each(function() { - $(this).append($('<a class="drop"></a>').click(function() { attachmentmenu(this); })); + $(this).append($('<a class="drop" tabindex="0" aria-haspopup="true">Show options</a>') + .bind('click keypress', function(e) { + if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) { + attachmentmenu(this, e); + return false; + } + }) + ); }); if (get_pref('previewheaders') == '1') { @@ -184,11 +190,13 @@ function rcube_mail_ui() } } - $('#composeoptionstoggle').click(function(){ - $('#composeoptionstoggle').toggleClass('remove'); - $('#composeoptions').toggle(); + $('#composeoptionstoggle').click(function(e){ + var expanded = $('#composeoptions').toggle().is(':visible'); + $('#composeoptionstoggle').toggleClass('remove').attr('aria-expanded', expanded ? 'true' : 'false'); layout_composeview(); - save_pref('composeoptions', $('#composeoptions').is(':visible') ? '1' : '0'); + save_pref('composeoptions', expanded ? '1' : '0'); + if (!rcube_event.is_keyboard(e)) + this.blur(); return false; }).css('cursor', 'pointer'); @@ -210,7 +218,7 @@ function rcube_mail_ui() } else if (rcmail.env.action == 'list' || !rcmail.env.action) { var previewframe = $('#mailpreviewframe').is(':visible'); - $('#mailpreviewtoggle').addClass(previewframe ? 'enabled' : 'closed').click(function(e){ toggle_preview_pane(e); return false }); + $('#mailpreviewtoggle').addClass(previewframe ? 'enabled' : 'closed').attr('aria-expanded', previewframe ? 'true' : 'false').click(function(e){ toggle_preview_pane(e); return false }); $('#maillistmode').addClass(rcmail.env.threading ? '' : 'selected').click(function(e){ switch_view_mode('list'); return false }); $('#mailthreadmode').addClass(rcmail.env.threading ? 'selected' : '').click(function(e){ switch_view_mode('thread'); return false }); @@ -265,7 +273,9 @@ function rcube_mail_ui() orientation:'v', relative:true, start:266, min:180, size:12 }).init(); } else if (rcmail.env.action == 'edit-prefs') { - $('<a href="#toggle">▼</a>') + $('<a href="#toggle"></a>') + .text(env.toggleoptions) + .attr('title', env.toggleoptions) .addClass('advanced-toggle') .appendTo('#preferences-details fieldset.advanced legend'); @@ -333,6 +343,10 @@ function rcube_mail_ui() var val = $('option:selected', this).text(); $(this).next().children().text(val); }); + + select + .on('focus', function(e){ overlay.addClass('focus'); }) + .on('blur', function(e){ overlay.removeClass('focus'); }); }); // set min-width to show all toolbar buttons @@ -341,63 +355,12 @@ function rcube_mail_ui() screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').width() + $('#searchfilter').width() + 30); } - $(document.body) - .bind('mouseup', body_mouseup) - .bind('keyup', function(e){ - if (e.keyCode == 27) { - for (var id in popups) { - if (popups[id].is(':visible')) - show_popup(id, false); - } - } - }); - - $('iframe').load(function(e){ - // this = iframe - try { - var doc = this.contentDocument ? this.contentDocument : this.contentWindow ? this.contentWindow.document : null; - $(doc).mouseup(body_mouseup); - } - catch (e) { - // catch possible "Permission denied" error in IE - }; - }) - .contents().mouseup(body_mouseup); - // don't use $(window).resize() due to some unwanted side-effects window.onresize = resize; resize(); } /** - * Handler for mouse-up events on the document body. - * This will close all open popup menus - */ - function body_mouseup(e) - { - var config, obj, target = e.target; - - if (target.className == 'inner') - target = e.target.parentNode; - - for (var id in popups) { - obj = popups[id]; - config = popupconfig[id]; - if (obj.is(':visible') - && target.id != id+'link' - && target != obj.get(0) // check if scroll bar was clicked (#1489832) - && !config.toggle - && (!config.editable || !target_overlaps(target, obj.get(0))) - && (!config.sticky || !rcube_mouse_is_over(e, obj.get(0))) - && !$(target).is('.folder-selector-link') - ) { - var myid = id+''; - window.setTimeout(function() { show_popupmenu(myid, false); }, 10); - } - } - } - - /** * Update UI on window resize */ function resize(e) @@ -475,6 +438,8 @@ function rcube_mail_ui() minHeight: 90 }).show(); + me.messagedialog.closest('div[role=dialog]').attr('role', 'alertdialog'); + me.message_timer = window.setTimeout(dialog_close, p.timeout); } } @@ -489,7 +454,7 @@ function rcube_mail_ui() $('#message-objects div a').addClass('button'); if (!$('#attachment-list li').length) { - $('div.rightcol').hide(); + $('div.rightcol').hide().attr('aria-hidden', 'true'); $('div.leftcol').css('margin-right', '0'); } } @@ -586,75 +551,35 @@ function rcube_mail_ui() /** * Trigger for popup menus */ - function show_popup(popup, show, config) + function toggle_popup(popup, e, config) { // auto-register menu object if (config || !popupconfig[popup]) add_popup(popup, config); - var visible = show_popupmenu(popup, show), - config = popupconfig[popup]; - if (typeof config.callback == 'function') - config.callback(visible); + return rcmail.command('menu-open', popup, e.target, e); } /** - * Show/hide a specific popup menu + * (Deprecated) trigger for popup menus */ - function show_popupmenu(popup, show) + function show_popup(popup, show, config) { - var obj = popups[popup], - config = popupconfig[popup], - ref = $(config.link ? config.link : '#'+popup+'link'), - above = config.above; - - if (!obj) { - obj = popups[popup] = $('#'+popup); - obj.appendTo(document.body); // move them to top for proper absolute positioning - } - - if (!obj || !obj.length) - return false; - - if (typeof show == 'undefined') - show = obj.is(':visible') ? false : true; - else if (config.toggle && show && obj.is(':visible')) - show = false; - - if (show && ref.length) { - var parent = ref.parent(), - win = $(window), - pos; - - if (parent.hasClass('dropbutton')) - ref = parent; + // auto-register menu object + if (config || !popupconfig[popup]) + add_popup(popup, config); + config = popupconfig[popup] || {}; + var ref = $(config.link ? config.link : '#'+popup+'link'), pos = ref.offset(); - ref.offsetHeight = ref.outerHeight(); - if (!above && pos.top + ref.offsetHeight + obj.height() > win.height()) - above = true; - if (pos.left + obj.width() > win.width()) - pos.left = win.width() - obj.width() - 12; - - obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) }); - } - - obj[show?'show':'hide'](); - - return show; - } - - /** - * - */ - function target_overlaps(target, elem) - { - while (target.parentNode) { - if (target.parentNode == elem) - return true; - target = target.parentNode; - } - return false; + if (ref.has('.inner')) + ref = ref.children('.inner'); + + // fire command with simulated mouse click event + return rcmail.command('menu-open', + { menu:popup, show:show }, + ref.get(0), + $.Event('click', { target:ref.get(0), pageX:pos.left, pageY:pos.top, clientX:pos.left, clientY:pos.top })); } @@ -670,7 +595,7 @@ function rcube_mail_ui() topstyles, bottomstyles, uid; frame.toggle(); - button.removeClass().addClass(visible ? 'enabled' : 'closed'); + button.removeClass().toggleClass('enabled closed').attr('aria-expanded', visible ? 'true' : 'false'); if (visible) { $('#mailview-top').removeClass('fullheight').css({ bottom:'auto' }); @@ -720,9 +645,9 @@ function rcube_mail_ui() // add toggle button to full headers table if (full.is(':visible')) - button.attr('href', '#hide').removeClass('add').addClass('remove') + button.attr('href', '#hide').removeClass('add').addClass('remove').attr('aria-expanded', 'true'); else - button.attr('href', '#details').removeClass('remove').addClass('add') + button.attr('href', '#details').removeClass('remove').addClass('add').attr('aria-expanded', 'false'); save_pref('previewheaders', full.is(':visible') ? '1' : '0'); } @@ -734,25 +659,57 @@ function rcube_mail_ui() function switch_view_mode(mode, force) { if (force || !$('#mail'+mode+'mode').hasClass('disabled')) { - $('#maillistmode, #mailthreadmode').removeClass('selected'); - $('#mail'+mode+'mode').addClass('selected'); + $('#maillistmode, #mailthreadmode').removeClass('selected').attr('tabindex', '0').attr('aria-disabled', 'false'); + $('#mail'+mode+'mode').addClass('selected').attr('tabindex', '-1').attr('aria-disabled', 'true'); } } - /**** popup callbacks ****/ + /**** popup menu callbacks ****/ - function menu_open(p) + /** + * Handler for menu-open and menu-close events + */ + function menu_toggle(p) { - if (p && p.props && p.props.menu == 'attachmentmenu') - show_popupmenu('attachmentmenu'); - else - show_listoptions(); - } + if (p && p.name == 'messagelistmenu') { + show_listoptions(p); + } + else if (p) { + // adjust menu position according to config + var config = popupconfig[p.name] || {}, + ref = $(config.link || '#'+p.name+'link'), + visible = p.obj && p.obj.is(':visible'), + above = config.above; + + // fix position according to config + if (p.obj && visible && ref.length) { + var parent = ref.parent(), + win = $(window), pos; + + if (parent.hasClass('dropbutton')) + ref = parent; + + if (config.above || ref.hasClass('dropbutton')) { + pos = ref.offset(); + p.obj.css({ left:pos.left+'px', top:(pos.top + (config.above ? -p.obj.height() : ref.outerHeight()))+'px' }); + } + } - function menu_save(prop) - { - save_listoptions(); + // add the right classes + if (p.obj && config.iconized) { + p.obj.children('ul').addClass('iconized'); + } + + // apply some data-attributes from menu config + if (p.obj && config.editable) + p.obj.attr('data-editable', 'true'); + + // trigger callback function + if (typeof config.callback == 'function') { + config.callback(visible, p); + } + } } function searchmenu(show) @@ -789,7 +746,7 @@ function rcube_mail_ui() } } - function attachmentmenu(elem) + function attachmentmenu(elem, event) { var id = elem.parentNode.id.replace(/^attach/, ''); @@ -802,41 +759,44 @@ function rcube_mail_ui() }); popupconfig.attachmentmenu.link = elem; - rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}); + rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}, elem, event); } - function spellmenu(show) + function spellmenu(show, p) { - var link, li, + var k, link, li, lang = rcmail.spellcheck_lang(), - menu = popups.spellmenu, - ul = $('ul', menu); + ul = $('ul', p.obj); if (!ul.length) { - ul = $('<ul class="toolbarmenu selectable">'); - - for (i in rcmail.env.spell_langs) { - li = $('<li>'); - link = $('<a href="#"></a>').text(rcmail.env.spell_langs[i]) - .addClass('active').data('lang', i) - .click(function() { - rcmail.spellcheck_lang_set($(this).data('lang')); + ul = $('<ul class="toolbarmenu selectable" role="menu">'); + + for (k in rcmail.env.spell_langs) { + li = $('<li role="menuitem">'); + link = $('<a href="#'+k+'" tabindex="0"></a>').text(rcmail.env.spell_langs[k]) + .addClass('active').data('lang', k) + .bind('click keypress', function(e) { + if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) { + rcmail.spellcheck_lang_set($(this).data('lang')); + rcmail.hide_menu('spellmenu', e); + return false; + } }); link.appendTo(li); li.appendTo(ul); } - ul.appendTo(menu); + ul.appendTo(p.obj); } // select current language $('li', ul).each(function() { var el = $('a', this); if (el.data('lang') == lang) - el.addClass('selected'); + el.addClass('selected').attr('aria-selected', 'true'); else if (el.hasClass('selected')) - el.removeClass('selected'); + el.removeClass('selected').removeAttr('aria-selected'); }); } @@ -844,13 +804,13 @@ function rcube_mail_ui() /** * */ - function show_listoptions() + function show_listoptions(p) { var $dialog = $('#listoptions'); // close the dialog if ($dialog.is(':visible')) { - $dialog.dialog('close'); + $dialog.dialog('close', p.originalEvent); return; } @@ -869,8 +829,13 @@ function rcube_mail_ui() resizable: false, closeOnEscape: true, title: null, - close: function() { + open: function(e) { + setTimeout(function(){ $dialog.find('a, input:not(:disabled)').not('[aria-disabled=true]').first().focus(); }, 100); + }, + close: function(e) { $dialog.dialog('destroy').hide(); + if (e.originalEvent && rcube_event.is_keyboard(e.originalEvent)) + $('#listmenulink').focus(); }, minWidth: 500, width: $dialog.width()+25 @@ -881,10 +846,13 @@ function rcube_mail_ui() /** * */ - function save_listoptions() + function save_listoptions(p) { $('#listoptions').dialog('close'); + if (rcube_event.is_keyboard(p.originalEvent)) + $('#listmenulink').focus(); + var sort = $('input[name="sort_col"]:checked').val(), ord = $('input[name="sort_ord"]:checked').val(), cols = $('input[name="list_col[]"]:checked') @@ -982,7 +950,7 @@ function rcube_mail_ui() }); } - function show_uploadform() + function show_uploadform(e) { var $dialog = $('#upload-dialog'); @@ -1008,6 +976,10 @@ function rcube_mail_ui() resizable: false, closeOnEscape: true, title: $dialog.attr('title'), + open: function(e) { + if (!document.all) + $('input[type=file]', $dialog).first().click(); + }, close: function() { try { $('#upload-dialog form').get(0).reset(); } catch(e){ } // ignore errors @@ -1017,9 +989,6 @@ function rcube_mail_ui() }, width: 480 }).show(); - - if (!document.all) - $('input[type=file]', $dialog).first().click(); } function add_uploadfile(e) @@ -1086,47 +1055,35 @@ function rcube_mail_ui() content.attr('id', id); } - // first hide not selected tabs - current = current || 0; - fs.each(function(idx) { if (idx != current) $(this).hide(); }); - // create tabs container - var tabs = $('<div>').addClass('tabsbar').prependTo(content); + var tabs = $('<ul>').addClass('tabsbar').prependTo(content); // convert fildsets into tabs fs.each(function(idx) { - var tab, a, elm = $(this), legend = elm.children('legend'); + var tab, a, elm = $(this), + legend = elm.children('legend'), + tid = id + '-t' + idx; // create a tab - a = $('<a>').text(legend.text()).attr('href', '#'); - tab = $('<span>').attr({'id': 'tab'+idx, 'class': 'tablink'}) - .click(function() { show_tab(id, idx); return false }) + a = $('<a>').text(legend.text()).attr('href', '#' + tid); + tab = $('<li>').addClass('tablink'); // remove legend legend.remove(); - // style fieldset - elm.addClass('tab'); - // style selected tab - if (idx == current) - tab.addClass('selected'); + + // link fieldset with tab item + elm.attr('id', tid); // add the tab to container tab.append(a).appendTo(tabs); }); - } - - function show_tab(id, index) - { - var fs = $('#'+id).children('fieldset'); - fs.each(function(idx) { - // Show/hide fieldset (tab content) - $(this)[index==idx ? 'show' : 'hide'](); - // Select/unselect tab - $('#tab'+idx).toggleClass('selected', idx==index); + // use jquery UI tabs widget to do the interaction and styling + content.tabs({ + active: current || 0, + heightStyle: 'content', + activate: function(e, ui) {resize(); } }); - - resize(); } /** @@ -1237,6 +1194,7 @@ function rcube_splitter(p) this.handle = $('<div>') .attr('id', this.id) .attr('unselectable', 'on') + .attr('role', 'presentation') .addClass('splitter ' + (this.horizontal ? 'splitter-h' : 'splitter-v')) .appendTo(this.parent) .bind('mousedown', onDragStart); |