From e8bcf08c72a18b3bf396e6448d6658227ecb46f2 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 30 Apr 2014 16:21:29 +0200 Subject: 1. Prepare core and Larry skin for improved accessibility 2. Implement full keyboard navigation in main mail view --- program/lib/Roundcube/html.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'program/lib/Roundcube/html.php') diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index f47ef299a..5e07a7806 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'); @@ -286,7 +286,8 @@ class html // ignore not allowed attributes if (!empty($allowed)) { $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0; - if (!isset($allowed_f[$key]) && (!$is_data_attr || !isset($allowed_f['data-*']))) { + $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; } } -- cgit v1.2.3 From 72afe3153cfaf0f8aaa0a4db115fea62959ff6d1 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 4 Jun 2014 15:29:37 +0200 Subject: Use tags for table headers as suggested by the WCAG 2.0 Guidelines --- program/js/app.js | 2 +- program/js/list.js | 6 +- program/lib/Roundcube/html.php | 15 +++- program/steps/mail/func.inc | 4 +- skins/classic/common.css | 2 + skins/classic/mail.css | 152 +++++++++++++++++++++-------------------- skins/larry/mail.css | 137 +++++++++++++++++++------------------ skins/larry/styles.css | 22 +++--- 8 files changed, 179 insertions(+), 161 deletions(-) (limited to 'program/lib/Roundcube/html.php') diff --git a/program/js/app.js b/program/js/app.js index 1f6582bf6..9d08a3f75 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6712,7 +6712,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; diff --git a/program/js/list.js b/program/js/list.js index 837a8459d..cf6c3f011 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -199,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', '').find('a').attr('tabindex', '-1'); // 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(); @@ -855,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; iheader)) { $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); @@ -890,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['*']; } /** @@ -899,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/steps/mail/func.inc b/program/steps/mail/func.inc index cbfb1200b..436475f64 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -509,8 +509,8 @@ 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', null, event)"; - $inner = 'v'; + $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'))); } diff --git a/skins/classic/common.css b/skins/classic/common.css index 126408444..c0d0a2610 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -607,6 +607,7 @@ img.uploading /***** common table settings ******/ +table.records-table thead tr th, table.records-table thead tr td { height: 20px; @@ -617,6 +618,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 diff --git a/skins/classic/mail.css b/skins/classic/mail.css index 3310ac54f..45661f7f8 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; @@ -804,10 +807,10 @@ 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; @@ -815,29 +818,29 @@ table.messagelist.fixedcopy .messagelist tr td div.collapsed, .messagelist tr td div.expanded, -.messagelist tr td.threads .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; @@ -846,99 +849,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; } @@ -955,7 +958,7 @@ table.messagelist.fixedcopy cursor: pointer; } -.messagelist tr td.threads .listmenu +.messagelist tr > .threads .listmenu { background-position: 0 -238px; cursor: pointer; @@ -980,45 +983,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; } @@ -1041,6 +1044,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; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index c5919ffe3..a0b66d9be 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -477,66 +477,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; } @@ -553,13 +553,14 @@ 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-image: url(images/listicons.png); @@ -567,7 +568,7 @@ table.messagelist.fixedcopy { background-position: right -912px; } -.messagelist thead tr td.sortedASC a { +.messagelist thead tr th.sortedASC a { background-position: right -944px; } @@ -589,39 +590,39 @@ 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; } .messagelist tr td div.collapsed, .messagelist tr td div.expanded, -.messagelist tr td.threads .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; @@ -634,7 +635,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; } @@ -642,7 +643,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; } @@ -666,15 +667,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; } @@ -717,10 +718,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; } @@ -734,10 +735,10 @@ table.messagelist.fixedcopy { cursor: pointer; } -.messagelist tr td.threads .listmenu { - background-position: 6px -972px; +.messagelist tr th.threads .listmenu { + background-position: 4px -972px; cursor: pointer; - width: 26px; + width: 24px; height: 21px; overflow: hidden; text-indent: -5000px; @@ -745,12 +746,12 @@ table.messagelist.fixedcopy { padding: 3px 5px 2px 6px; } -.messagelist tr td.threads .listmenu:focus { +.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; diff --git a/skins/larry/styles.css b/skins/larry/styles.css index 27a0cb99c..a2cad34f1 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -1131,6 +1131,7 @@ a.iconlink.upload { } .boxtitle, +.uibox .listing thead th, .uibox .listing thead td { font-size: 12px; font-weight: bold; @@ -1142,12 +1143,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; @@ -1212,11 +1215,7 @@ a.iconlink.upload { /* 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 #b0ccd7; -} - -.listing.focus tbody tr.selected.focused > td:first-child { - border-left-color: #9ec2d0; + border-left: 2px solid #739da8; } .listbox .listitem.selected, @@ -1511,6 +1510,7 @@ table.records-table { border: 0; } +.records-table thead th, .records-table thead td { color: #69939e; font-size: 11px; @@ -1525,13 +1525,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; @@ -1541,6 +1545,7 @@ 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); @@ -1561,6 +1566,7 @@ table.records-table { } /* 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: 2px solid transparent; @@ -1569,11 +1575,7 @@ table.records-table { /* 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 #b0ccd7; -} - -.records-table.focus tbody tr.selected.focused > td:first-child { - border-left-color: #49b3d2; + border-left: 2px solid #49b3d2; } .records-table tr.selected td { -- cgit v1.2.3 From ebfdc0925f7169aac45c2437d7a1196687a200c8 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 5 Jun 2014 12:03:46 +0200 Subject: Fix check for data-* attributes after merge with dev-accessibility --- program/lib/Roundcube/html.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'program/lib/Roundcube/html.php') diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 0209d1bf2..3aae7cfe7 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -283,11 +283,11 @@ class html continue; } - // ignore not allowed attributes, except data-* + // ignore not allowed attributes, except aria-* and data-* if (!empty($allowed)) { $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-*']))) { + if (!$is_aria_attr && !$is_data_attr && !isset($allowed_f[$key])) { continue; } } -- cgit v1.2.3 From 0ee2db7a68b011af85c6b3ba924ff459e438a16a Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 24 Jul 2014 09:02:09 +0200 Subject: Support allowfullscreen attribute on iframe elements --- program/lib/Roundcube/html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib/Roundcube/html.php') diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 3aae7cfe7..bcf89d7df 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -218,7 +218,7 @@ class html $attr = array('src' => $attr); } return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib, - array('src','name','width','height','border','frameborder','onload'))); + array('src','name','width','height','border','frameborder','onload','allowfullscreen'))); } /** -- cgit v1.2.3 From fcb7d4fc034335d960917abd37254bd3997cf2f3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 1 Aug 2014 12:49:37 +0200 Subject: Fix various iCloud vCard issues, added fallback for external photos (#1489993) --- CHANGELOG | 1 + program/lib/Roundcube/html.php | 2 +- program/lib/Roundcube/rcube_vcard.php | 10 +++++----- program/steps/addressbook/func.inc | 10 +++++++--- program/steps/addressbook/photo.inc | 8 ++++++-- program/steps/mail/show.inc | 4 +++- tests/Framework/VCard.php | 19 +++++++++++++++++++ 7 files changed, 42 insertions(+), 12 deletions(-) (limited to 'program/lib/Roundcube/html.php') diff --git a/CHANGELOG b/CHANGELOG index a6ade5bfc..a2de91fa6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ CHANGELOG Roundcube Webmail - Fix some mime-type to extension mapping checks in Installer (#1489983) - Fix errors when using localStorage in Safari's private browsing mode (#1489996) - Fix bug where $Forwarded flag was being set even if server didn't support it (#1490000) +- Fix various iCloud vCard issues, added fallback for external photos (#1489993) RELEASE 1.0.2 ------------- diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index bcf89d7df..f18cad0bf 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -153,7 +153,7 @@ class html $attr = array('src' => $attr); } return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib, - array('src','alt','width','height','border','usemap','onclick'))); + array('src','alt','width','height','border','usemap','onclick','onerror'))); } /** diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 4a2684f10..96add110f 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -110,7 +110,7 @@ class rcube_vcard public function load($vcard, $charset = RCUBE_CHARSET, $detect = false) { self::$values_decoded = false; - $this->raw = self::vcard_decode($vcard); + $this->raw = self::vcard_decode(self::cleanup($vcard)); // resolve charset parameters if ($charset == null) { @@ -496,7 +496,7 @@ class rcube_vcard if (preg_match('/^END:VCARD$/i', $line)) { // parse vcard - $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); + $obj = new rcube_vcard($vcard_block, $charset, true, self::$fieldmap); // FN and N is required by vCard format (RFC 2426) // on import we can be less restrictive, let's addressbook decide if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !empty($obj->email)) { @@ -532,9 +532,9 @@ class rcube_vcard // Cleanup $vcard = preg_replace(array( // convert special types (like Skype) to normal type='skype' classes with this simple regex ;) - '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', - '/^item\d*\.X-AB.*$/m', // remove cruft like item1.X-AB* - '/^item\d*\./m', // remove item1.ADR instead of ADR + '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./si', + '/^item\d*\.X-AB.*$/mi', // remove cruft like item1.X-AB* + '/^item\d*\./mi', // remove item1.ADR instead of ADR '/\n+/', // remove empty lines '/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some ), diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index d4c57cc9d..38de93d05 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -816,11 +816,15 @@ function rcmail_contact_photo($attrib) } $photo_img = $RCMAIL->url($url); } - else + else { $ff_value = '-del-'; // will disable delete-photo action + } - $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => $RCMAIL->gettext('contactphoto'))); - $content = html::div($attrib, $img); + $content = html::div($attrib, html::img(array( + 'src' => $photo_img, + 'alt' => $RCMAIL->gettext('contactphoto'), + 'onerror' => 'this.src = rcmail.env.photo_placeholder', + ))); if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) { $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']); diff --git a/program/steps/addressbook/photo.inc b/program/steps/addressbook/photo.inc index 482185735..30d09ffcc 100644 --- a/program/steps/addressbook/photo.inc +++ b/program/steps/addressbook/photo.inc @@ -72,8 +72,12 @@ $plugin = $RCMAIL->plugins->exec_hook('contact_photo', if ($plugin['url']) { $RCMAIL->output->redirect($plugin['url']); } -else { - $data = $plugin['data']; + +$data = $plugin['data']; + +// detect if photo data is an URL +if (strlen($data) < 1024 && filter_var($data, FILTER_VALIDATE_URL)) { + $RCMAIL->output->redirect($data); } // deliver alt image diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 4b2d78d31..d4121fdd8 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -349,8 +349,10 @@ function rcmail_message_contactphoto($attrib) '_task' => 'addressbook', '_action' => 'photo', '_email' => $MESSAGE->sender['mailto'], - '_alt' => $placeholder + '_alt' => $placeholder, )); + + $attrib['onerror'] = "this.src = '" . ($placeholder ? $placeholder : 'program/resources/blank.gif') . "'"; } else { $photo_img = $placeholder ? $placeholder : 'program/resources/blank.gif'; diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php index 0a34fc51f..c23dba844 100644 --- a/tests/Framework/VCard.php +++ b/tests/Framework/VCard.php @@ -79,6 +79,25 @@ class Framework_VCard extends PHPUnit_Framework_TestCase $this->assertEquals("http://domain.tld", $vcard['website:other'][0], "Decode dummy backslash character"); } + /** + * Some Apple vCard quirks (#1489993) + */ + function test_parse_six() + { + $vcard = new rcube_vcard("BEGIN:VCARD\n" + . "VERSION:3.0\n" + . "N:;;;;\n" + . "FN:Apple Computer AG\n" + . "ITEM1.ADR;type=WORK;type=pref:;;Birgistrasse 4a;Wallisellen-Zürich;;8304;Switzerland\n" + . "PHOTO;ENCODING=B:aHR0cDovL3Rlc3QuY29t\n" + . "END:VCARD" + ); + + $result = $vcard->get_assoc(); + + $this->assertCount(1, $result['address:work'], "ITEM1.-prefixed entry"); + } + function test_import() { $input = file_get_contents($this->_srcpath('apple.vcf')); -- cgit v1.2.3