Accessibility enhancements for the calendar module (#3084)
This commit is contained in:
parent
6d9d854e65
commit
efecba6675
13 changed files with 315 additions and 152 deletions
|
@ -299,7 +299,7 @@ class calendar extends rcube_plugin
|
|||
$this->rc->output->set_env('calendar_driver', $this->rc->config->get('calendar_driver'), false);
|
||||
$this->rc->output->set_env('calendar_resources', (bool)$this->rc->config->get('calendar_resources_driver'));
|
||||
$this->rc->output->set_env('mscolors', jqueryui::get_color_values());
|
||||
$this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list')));
|
||||
$this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list', 'aria-label' => $this->gettext('roleorganizer'))));
|
||||
|
||||
$view = get_input_value('view', RCUBE_INPUT_GPC);
|
||||
if (in_array($view, array('agendaWeek', 'agendaDay', 'month', 'table')))
|
||||
|
|
|
@ -237,32 +237,33 @@ function rcube_calendar_ui(settings)
|
|||
if (edit) {
|
||||
rcmail.env.attachments[elem.id] = elem;
|
||||
// delete icon
|
||||
content = document.createElement('A');
|
||||
content.href = '#delete';
|
||||
content.title = rcmail.gettext('delete');
|
||||
content.className = 'delete';
|
||||
$(content).click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
|
||||
content = $('<a href="#delete" />')
|
||||
.attr('title', rcmail.gettext('delete'))
|
||||
.attr('aria-label', rcmail.gettext('delete') + ' ' + Q(elem.name))
|
||||
.addClass('delete')
|
||||
.click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
|
||||
|
||||
if (!rcmail.env.deleteicon)
|
||||
content.innerHTML = rcmail.gettext('delete');
|
||||
content.html(rcmail.gettext('delete'));
|
||||
else {
|
||||
img = document.createElement('IMG');
|
||||
img.src = rcmail.env.deleteicon;
|
||||
img.alt = rcmail.gettext('delete');
|
||||
content.appendChild(img);
|
||||
content.append(img);
|
||||
}
|
||||
|
||||
li.appendChild(content);
|
||||
content.appendTo(li);
|
||||
}
|
||||
|
||||
// name/link
|
||||
content = document.createElement('A');
|
||||
content.innerHTML = elem.name;
|
||||
content.className = 'file';
|
||||
content.href = '#load';
|
||||
$(content).click({event: event, att: elem}, function(e) {
|
||||
load_attachment(e.data.event, e.data.att); return false; });
|
||||
li.appendChild(content);
|
||||
content = $('<a href="#load" />')
|
||||
.html(Q(elem.name))
|
||||
.addClass('file')
|
||||
.click({event: event, att: elem}, function(e) {
|
||||
load_attachment(e.data.event, e.data.att);
|
||||
return false;
|
||||
})
|
||||
.appendTo(li);
|
||||
|
||||
ul.appendChild(li);
|
||||
}
|
||||
|
@ -283,7 +284,7 @@ function rcube_calendar_ui(settings)
|
|||
};
|
||||
|
||||
// event details dialog (show only)
|
||||
var event_show_dialog = function(event)
|
||||
var event_show_dialog = function(event, ev)
|
||||
{
|
||||
var $dialog = $("#eventshow").attr('class', 'uidialog');
|
||||
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
|
||||
|
@ -430,18 +431,28 @@ function rcube_calendar_ui(settings)
|
|||
modal: false,
|
||||
resizable: !bw.ie6,
|
||||
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
|
||||
title: Q(me.event_date_text(event)),
|
||||
title: me.event_date_text(event),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-button').first().focus();
|
||||
$dialog.attr('aria-hidden', 'false');
|
||||
setTimeout(function(){
|
||||
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
|
||||
}, 5);
|
||||
},
|
||||
close: function() {
|
||||
$dialog.dialog('destroy').hide();
|
||||
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
minWidth: 320,
|
||||
width: 420
|
||||
}).show();
|
||||
|
||||
// remember opener element (to be focused on close)
|
||||
$dialog.data('opener', ev && rcube_event.is_keyboard(ev) ? ev.target : null);
|
||||
|
||||
// set voice title on dialog widget
|
||||
$dialog.dialog('widget').removeAttr('aria-labelledby')
|
||||
.attr('aria-label', me.event_date_text(event, true) + ', ', event.title);
|
||||
|
||||
// set dialog size according to content
|
||||
me.dialog_resize($dialog.get(0), $dialog.height(), 420);
|
||||
/*
|
||||
|
@ -472,8 +483,11 @@ function rcube_calendar_ui(settings)
|
|||
// bring up the event dialog (jquery-ui popup)
|
||||
var event_edit_dialog = function(action, event)
|
||||
{
|
||||
// copy opener element from show dialog
|
||||
var op_elem = $("#eventshow:ui-dialog").data('opener');
|
||||
|
||||
// close show dialog first
|
||||
$("#eventshow:ui-dialog").dialog('close');
|
||||
$("#eventshow:ui-dialog").data('opener', null).dialog('close');
|
||||
|
||||
var $dialog = $('<div>');
|
||||
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' };
|
||||
|
@ -675,7 +689,7 @@ function rcube_calendar_ui(settings)
|
|||
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
|
||||
|
||||
// activate the first tab
|
||||
$('#eventtabs').tabs('select', 0);
|
||||
$('#eventtabs').tabs('option', 'active', 0);
|
||||
|
||||
// hack: set task to 'calendar' to make all dialog actions work correctly
|
||||
var comm_path_before = rcmail.env.comm_path;
|
||||
|
@ -690,15 +704,18 @@ function rcube_calendar_ui(settings)
|
|||
closeOnEscape: false,
|
||||
title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
|
||||
open: function() {
|
||||
editform.attr('aria-hidden', 'false');
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
editform.hide().appendTo(document.body);
|
||||
editform.hide().attr('aria-hidden', 'true').appendTo(document.body);
|
||||
$dialog.dialog("destroy").remove();
|
||||
rcmail.ksearch_blur();
|
||||
rcmail.ksearch_destroy();
|
||||
freebusy_data = {};
|
||||
rcmail.env.comm_path = comm_path_before; // restore comm_path
|
||||
if (op_elem)
|
||||
$(op_elem).focus();
|
||||
},
|
||||
buttons: buttons,
|
||||
minWidth: 500,
|
||||
|
@ -843,12 +860,13 @@ function rcube_calendar_ui(settings)
|
|||
closeOnEscape: (!bw.ie6 && !bw.ie7),
|
||||
title: rcmail.gettext('scheduletime', 'calendar'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().focus();
|
||||
$dialog.attr('aria-hidden', 'false').find('#shedule-find-next, #shedule-find-prev').not(':disabled').first().focus();
|
||||
},
|
||||
close: function() {
|
||||
if (bw.ie6)
|
||||
$("#edit-attendees-table").css('visibility','visible');
|
||||
$dialog.dialog("destroy").hide();
|
||||
$dialog.dialog("destroy").attr('aria-hidden', 'true').hide();
|
||||
// TODO: focus opener button
|
||||
},
|
||||
resizeStop: function() {
|
||||
render_freebusy_overlay();
|
||||
|
@ -1316,6 +1334,9 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
var now = new Date();
|
||||
$('#shedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime()));
|
||||
|
||||
// speak new selection
|
||||
rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice');
|
||||
}
|
||||
else {
|
||||
alert(rcmail.gettext('noslotfound','calendar'));
|
||||
|
@ -1407,7 +1428,7 @@ function rcube_calendar_ui(settings)
|
|||
if (organizer && !readonly)
|
||||
dispname = rcmail.env['identities-selector'];
|
||||
|
||||
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + '>';
|
||||
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','calendar') + '">';
|
||||
for (var r in opts)
|
||||
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
|
||||
select += '</select>';
|
||||
|
@ -1427,7 +1448,7 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
var html = '<td class="role">' + select + '</td>' +
|
||||
'<td class="name">' + dispname + '</td>' +
|
||||
'<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" /></td>' +
|
||||
'<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + Q(data.status || '') + '</span></td>' +
|
||||
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
|
||||
|
||||
|
@ -1482,10 +1503,11 @@ function rcube_calendar_ui(settings)
|
|||
url: rcmail.url('freebusy-status'),
|
||||
data: { email:email, start:date2servertime(clone_date(event.start, event.allDay?1:0)), end:date2servertime(clone_date(event.end, event.allDay?2:0)), _remote: 1 },
|
||||
success: function(status){
|
||||
icon.removeClass('loading').addClass(String(status).toLowerCase());
|
||||
var avail = String(status).toLowerCase();
|
||||
icon.removeClass('loading').addClass(avail).attr('alt', rcmail.gettext('avail' + avail, 'calendar'));
|
||||
},
|
||||
error: function(){
|
||||
icon.removeClass('loading').addClass('unknown');
|
||||
icon.removeClass('loading').addClass('unknown').attr('alt', rcmail.gettext('availunknown', 'calendar'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1522,11 +1544,14 @@ function rcube_calendar_ui(settings)
|
|||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
title: rcmail.gettext('findresources', 'calendar'),
|
||||
open: function() {
|
||||
$dialog.attr('aria-hidden', 'false');
|
||||
},
|
||||
close: function() {
|
||||
$dialog.dialog('destroy').hide();
|
||||
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
|
||||
},
|
||||
resize: function(e) {
|
||||
var container = $(rcmail.gui_objects.resourceinfocalendar)
|
||||
var container = $(rcmail.gui_objects.resourceinfocalendar);
|
||||
container.fullCalendar('option', 'height', container.height() + 4);
|
||||
},
|
||||
buttons: buttons,
|
||||
|
@ -1593,9 +1618,11 @@ function rcube_calendar_ui(settings)
|
|||
titleFormat: { day: 'dddd ' + settings['date_long'] },
|
||||
currentTimeIndicator: settings.time_indicator,
|
||||
eventRender: function(event, element, view) {
|
||||
var title = rcmail.get_label(event.status, 'calendar');
|
||||
element.addClass('status-' + event.status);
|
||||
element.find('.fc-event-head').hide();
|
||||
element.find('.fc-event-title').text(rcmail.get_label(event.status, 'calendar'));
|
||||
element.find('.fc-event-title').text(title);
|
||||
element.attr('aria-label', me.event_date_text(event, true) + ': ' + title);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1971,10 +1998,12 @@ function rcube_calendar_ui(settings)
|
|||
title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
|
||||
buttons: buttons,
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-button').first().focus();
|
||||
setTimeout(function(){
|
||||
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
|
||||
}, 5);
|
||||
},
|
||||
close: function(){
|
||||
$dialog.dialog("destroy").hide();
|
||||
$dialog.dialog("destroy").remove();
|
||||
if (!rcmail.busy)
|
||||
fc.fullCalendar('refetchEvents');
|
||||
}
|
||||
|
@ -2021,6 +2050,8 @@ function rcube_calendar_ui(settings)
|
|||
if (event.status) {
|
||||
element.addClass('cal-event-status-' + String(event.status).toLowerCase());
|
||||
}
|
||||
|
||||
element.attr('aria-label', event.title + ', ' + me.event_date_text(event, true));
|
||||
};
|
||||
|
||||
|
||||
|
@ -2099,8 +2130,8 @@ function rcube_calendar_ui(settings)
|
|||
allDayText: rcmail.gettext('all-day', 'calendar'),
|
||||
currentTimeIndicator: settings.time_indicator,
|
||||
eventRender: fc_event_render,
|
||||
eventClick: function(event) {
|
||||
event_show_dialog(event);
|
||||
eventClick: function(event, ev, view) {
|
||||
event_show_dialog(event, ev);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2743,6 +2774,7 @@ function rcube_calendar_ui(settings)
|
|||
id_prefix: 'rcmlical',
|
||||
selectable: true,
|
||||
save_state: true,
|
||||
keyboard: false,
|
||||
searchbox: '#calendarlistsearch',
|
||||
search_action: 'calendar/calendar',
|
||||
search_sources: [ 'folders', 'users' ],
|
||||
|
@ -2773,6 +2805,12 @@ function rcube_calendar_ui(settings)
|
|||
rcmail.http_post('calendar', { action:'subscribe', c:{ id:p.id, active:cal.active?1:0, permanent:cal.subscribed?1:0 } });
|
||||
}
|
||||
});
|
||||
calendars_list.addEventListener('search-complete', function(data) {
|
||||
if (data.length)
|
||||
rcmail.display_message(rcmail.gettext('nrcalendarsfound','calendar').replace('$nr', data.length), 'voice');
|
||||
else
|
||||
rcmail.display_message(rcmail.gettext('nocalendarsfound','calendar'), 'info');
|
||||
});
|
||||
|
||||
// init (delegate) event handler on calendar list checkboxes
|
||||
$(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e){
|
||||
|
@ -2922,9 +2960,9 @@ function rcube_calendar_ui(settings)
|
|||
day_clicked_ts = now;
|
||||
},
|
||||
// callback when a specific event is clicked
|
||||
eventClick: function(event) {
|
||||
eventClick: function(event, ev, view) {
|
||||
if (!event.temp)
|
||||
event_show_dialog(event);
|
||||
event_show_dialog(event, ev);
|
||||
},
|
||||
// callback when an event was dragged and finally dropped
|
||||
eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) {
|
||||
|
@ -3054,7 +3092,7 @@ function rcube_calendar_ui(settings)
|
|||
// scroll to current time
|
||||
var $this = $(this);
|
||||
var widget = $this.autocomplete('widget');
|
||||
var menu = $this.data('autocomplete').menu;
|
||||
var menu = $this.data('ui-autocomplete').menu;
|
||||
var amregex = /^(.+)(a[.m]*)/i;
|
||||
var pmregex = /^(.+)(a[.m]*)/i;
|
||||
var val = $(this).val().replace(amregex, '0:$1').replace(pmregex, '1:$1');
|
||||
|
@ -3107,9 +3145,9 @@ function rcube_calendar_ui(settings)
|
|||
return [ true, (active ? 'ui-datepicker-activerange ui-datepicker-active-' + view.name : ''), ''];
|
||||
}
|
||||
})) // set event handler for clicks on calendar week cell of the datepicker widget
|
||||
.click(function(e) {
|
||||
.on('click', 'td.ui-datepicker-week-col', function(e) {
|
||||
var cell = $(e.target);
|
||||
if (e.target.tagName == 'TD' && cell.hasClass('ui-datepicker-week-col')) {
|
||||
if (e.target.tagName == 'TD' && cell.hasClass('')) {
|
||||
var base_date = minical.datepicker('getDate');
|
||||
if (minical.data('month'))
|
||||
base_date.setMonth(minical.data('month')-1);
|
||||
|
@ -3126,7 +3164,9 @@ function rcube_calendar_ui(settings)
|
|||
fc.fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
|
||||
minical.datepicker('setDate', date);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
minical.find('.ui-datepicker-inline').attr('aria-labelledby', 'aria-label-minical');
|
||||
|
||||
if (rcmail.env.date) {
|
||||
var viewdate = new Date();
|
||||
|
@ -3136,10 +3176,11 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
// init event dialog
|
||||
$('#eventtabs').tabs({
|
||||
show: function(event, ui) {
|
||||
if (ui.panel.id == 'event-panel-attendees' || ui.panel.id == 'event-panel-resources') {
|
||||
var tab = ui.panel.id == 'event-panel-resources' ? 'resource' : 'attendee';
|
||||
$('#edit-'+tab+'-name').select();
|
||||
activate: function(event, ui) {
|
||||
if (ui.newPanel.selector == '#event-panel-attendees' || ui.newPanel.selector == '#event-panel-resources') {
|
||||
var tab = ui.newPanel.selector == '#event-panel-resources' ? 'resource' : 'attendee';
|
||||
if (!rcube_event.is_keyboard(event))
|
||||
$('#edit-'+tab+'-name').select();
|
||||
// update free-busy status if needed
|
||||
if (freebusy_ui.needsupdate && me.selected_event)
|
||||
update_freebusy_status(me.selected_event);
|
||||
|
@ -3162,6 +3203,7 @@ function rcube_calendar_ui(settings)
|
|||
.autocomplete({
|
||||
delay: 100,
|
||||
minLength: 1,
|
||||
appendTo: '#eventedit',
|
||||
source: autocomplete_times,
|
||||
open: autocomplete_open,
|
||||
change: event_times_changed,
|
||||
|
@ -3173,9 +3215,9 @@ function rcube_calendar_ui(settings)
|
|||
.click(function() { // show drop-down upon clicks
|
||||
$(this).autocomplete('search', $(this).val() ? $(this).val().replace(/\D.*/, "") : " ");
|
||||
}).each(function(){
|
||||
$(this).data('autocomplete')._renderItem = function(ul, item) {
|
||||
$(this).data('ui-autocomplete')._renderItem = function(ul, item) {
|
||||
return $('<li>')
|
||||
.data('item.autocomplete', item)
|
||||
.data('ui-autocomplete-item', item)
|
||||
.append('<a>' + item[0] + item[1] + '</a>')
|
||||
.appendTo(ul);
|
||||
};
|
||||
|
|
|
@ -303,10 +303,11 @@ class calendar_ui
|
|||
|
||||
$content = '';
|
||||
if (!$activeonly || $prop['active']) {
|
||||
$label_id = 'cl:' . $id;
|
||||
$content = html::div(join(' ', $classes),
|
||||
html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname']) .
|
||||
html::span(array('class' => 'calname', 'id' => $label_id, 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname']) .
|
||||
($prop['virtual'] ? '' :
|
||||
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id), '') .
|
||||
(isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe')), ' ') : '') .
|
||||
html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' ')
|
||||
)
|
||||
|
@ -326,7 +327,7 @@ class calendar_ui
|
|||
|
||||
$select_range = new html_select(array('name' => 'listrange', 'id' => 'agenda-listrange'));
|
||||
$select_range->add(1 . ' ' . preg_replace('/\(.+\)/', '', $this->cal->lib->gettext('days')), $days);
|
||||
foreach (array(2,5,7,14,30,60,90) as $days)
|
||||
foreach (array(2,5,7,14,30,60,90,180,365) as $days)
|
||||
$select_range->add($days . ' ' . preg_replace('/\(|\)/', '', $this->cal->lib->gettext('days')), $days);
|
||||
|
||||
$html .= html::label('agenda-listrange', $this->cal->gettext('listrange'));
|
||||
|
@ -334,8 +335,8 @@ class calendar_ui
|
|||
|
||||
$select_sections = new html_select(array('name' => 'listsections', 'id' => 'agenda-listsections'));
|
||||
$select_sections->add('---', '');
|
||||
foreach (array('day' => 'days', 'week' => 'weeks', 'month' => 'months', 'smart' => 'smartsections') as $val => $label)
|
||||
$select_sections->add(preg_replace('/\(|\)/', '', ucfirst($this->cal->gettext($label))), $val);
|
||||
foreach (array('day' => 'libcalendaring.days', 'week' => 'libcalendaring.weeks', 'month' => 'libcalendaring.months', 'smart' => 'calendar.smartsections') as $val => $label)
|
||||
$select_sections->add(preg_replace('/\(|\)/', '', ucfirst($this->rc->gettext($label))), $val);
|
||||
|
||||
$html .= html::span('spacer', ' ');
|
||||
$html .= html::label('agenda-listsections', $this->cal->gettext('listsections'));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* FullCalendar v1.6.4-rcube-1.0
|
||||
* FullCalendar v1.6.4-rcube-1.1
|
||||
* Docs & License: http://arshaw.com/fullcalendar/
|
||||
* (c) 2013 Adam Shaw, 2014 Kolab Systems AG
|
||||
*/
|
||||
|
@ -813,7 +813,7 @@ function Header(calendar, options) {
|
|||
var prevButton;
|
||||
$.each(this.split(','), function(j, buttonName) {
|
||||
if (buttonName == 'title') {
|
||||
e.append("<span class='fc-header-title'><h2> </h2></span>");
|
||||
e.append("<span class='fc-header-title'><h2 aria-live='polite' aria-relevant='text' aria-atomic='true'> </h2></span>");
|
||||
if (prevButton) {
|
||||
prevButton.addClass(tm + '-corner-right');
|
||||
}
|
||||
|
@ -833,7 +833,7 @@ function Header(calendar, options) {
|
|||
var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
|
||||
var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
|
||||
var button = $(
|
||||
"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
|
||||
"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default' role='button' tabindex='0'>" +
|
||||
(icon ?
|
||||
"<span class='fc-icon-wrap'>" +
|
||||
"<span class='ui-icon ui-icon-" + icon + "'/>" +
|
||||
|
@ -869,6 +869,10 @@ function Header(calendar, options) {
|
|||
.removeClass(tm + '-state-down');
|
||||
}
|
||||
)
|
||||
.keypress(function(ev) {
|
||||
if (ev.keyCode == 13)
|
||||
$(ev.target).trigger('click');
|
||||
})
|
||||
.appendTo(e);
|
||||
disableTextSelection(button);
|
||||
if (!prevButton) {
|
||||
|
@ -895,25 +899,25 @@ function Header(calendar, options) {
|
|||
|
||||
function activateButton(buttonName) {
|
||||
element.find('span.fc-button-' + buttonName)
|
||||
.addClass(tm + '-state-active');
|
||||
.addClass(tm + '-state-active').attr('tabindex', '-1');
|
||||
}
|
||||
|
||||
|
||||
function deactivateButton(buttonName) {
|
||||
element.find('span.fc-button-' + buttonName)
|
||||
.removeClass(tm + '-state-active');
|
||||
.removeClass(tm + '-state-active').attr('tabindex', '0');
|
||||
}
|
||||
|
||||
|
||||
function disableButton(buttonName) {
|
||||
element.find('span.fc-button-' + buttonName)
|
||||
.addClass(tm + '-state-disabled');
|
||||
.addClass(tm + '-state-disabled').attr('tabindex', '-1');
|
||||
}
|
||||
|
||||
|
||||
function enableButton(buttonName) {
|
||||
element.find('span.fc-button-' + buttonName)
|
||||
.removeClass(tm + '-state-disabled');
|
||||
.removeClass(tm + '-state-disabled').attr('tabindex', '0');
|
||||
}
|
||||
|
||||
|
||||
|
@ -1760,7 +1764,7 @@ function _exclEndDay(end, allDay) {
|
|||
|
||||
|
||||
function lazySegBind(container, segs, bindHandlers) {
|
||||
container.unbind('mouseover').mouseover(function(ev) {
|
||||
container.unbind('mouseover focusin').bind('mouseover focusin', function(ev) {
|
||||
var parent=ev.target, e,
|
||||
i, seg;
|
||||
while (parent != this) {
|
||||
|
@ -4051,7 +4055,7 @@ function AgendaEventRenderer() {
|
|||
"left:" + seg.left + "px;" +
|
||||
skinCss +
|
||||
"'" +
|
||||
">" +
|
||||
" tabindex='0'>" +
|
||||
"<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
|
||||
"<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
|
||||
"<div class='fc-event-time'>" +
|
||||
|
@ -4067,7 +4071,7 @@ function AgendaEventRenderer() {
|
|||
"</div>"; // close inner
|
||||
if (seg.isEnd && isEventResizable(event)) {
|
||||
html +=
|
||||
"<div class='ui-resizable-handle ui-resizable-s'>=</div>";
|
||||
"<div class='ui-resizable-handle ui-resizable-s' role='presentation'>=</div>";
|
||||
}
|
||||
html +=
|
||||
"</" + (url ? "a" : "div") + ">";
|
||||
|
@ -4941,7 +4945,7 @@ function ListEventRenderer() {
|
|||
}
|
||||
|
||||
function lazySegBind(container, seg, bindHandlers) {
|
||||
container.unbind('mouseover').mouseover(function(ev) {
|
||||
container.unbind('mouseover focusin').bind('mouseover focusin', function(ev) {
|
||||
var parent = ev.target, e = parent, i, event;
|
||||
while (parent != this) {
|
||||
e = parent;
|
||||
|
@ -5132,7 +5136,7 @@ function TableEventRenderer() {
|
|||
rowClasses.push('fc-today');
|
||||
}
|
||||
|
||||
s += "<tr class='" + rowClasses.join(' ') + "'>";
|
||||
s += "<tr class='" + rowClasses.join(' ') + "' tabindex='0'>";
|
||||
for (var col, c=0; c < tableCols.length; c++) {
|
||||
col = tableCols[c];
|
||||
if (col == 'handle') {
|
||||
|
@ -5358,7 +5362,11 @@ function View(element, calendar, viewName) {
|
|||
function(ev) {
|
||||
trigger('eventMouseout', this, event, ev);
|
||||
}
|
||||
);
|
||||
)
|
||||
.keypress(function(ev) {
|
||||
if (ev.keyCode == 13)
|
||||
$(this).trigger('click', { pointerType:'keyboard' });
|
||||
});
|
||||
// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
|
||||
// TODO: same for resizing
|
||||
}
|
||||
|
@ -6008,7 +6016,7 @@ function DayEventRenderer() {
|
|||
"left:" + segment.left + "px;" +
|
||||
skinCss +
|
||||
"'" +
|
||||
">" +
|
||||
" tabindex='0'>" +
|
||||
"<div class='fc-event-inner'>";
|
||||
if (!event.allDay && segment.isStart) {
|
||||
html +=
|
||||
|
|
|
@ -53,7 +53,9 @@ $labels['location'] = 'Location';
|
|||
$labels['url'] = 'URL';
|
||||
$labels['date'] = 'Date';
|
||||
$labels['start'] = 'Start';
|
||||
$labels['starttime'] = 'Start time';
|
||||
$labels['end'] = 'End';
|
||||
$labels['endtime'] = 'End time';
|
||||
$labels['repeat'] = 'Repeat';
|
||||
$labels['selectdate'] = 'Choose date';
|
||||
$labels['freebusy'] = 'Show me as';
|
||||
|
@ -87,8 +89,11 @@ $labels['showurl'] = 'Show calendar URL';
|
|||
$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
|
||||
$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
|
||||
$labels['findcalendars'] = 'Find calendars...';
|
||||
$labels['searchterms'] = 'Search terms';
|
||||
$labels['calsearchresults'] = 'Available Calendars';
|
||||
$labels['calendarsubscribe'] = 'List permanently';
|
||||
$labels['nocalendarsfound'] = 'No calendars found';
|
||||
$labels['nrcalendarsfound'] = '$nr calendars found';
|
||||
|
||||
// agenda view
|
||||
$labels['listrange'] = 'Range to display:';
|
||||
|
@ -99,6 +104,7 @@ $labels['today'] = 'Today';
|
|||
$labels['tomorrow'] = 'Tomorrow';
|
||||
$labels['thisweek'] = 'This week';
|
||||
$labels['nextweek'] = 'Next week';
|
||||
$labels['prevweek'] = 'Previous week';
|
||||
$labels['thismonth'] = 'This month';
|
||||
$labels['nextmonth'] = 'Next month';
|
||||
$labels['weekofyear'] = 'Week';
|
||||
|
@ -140,6 +146,7 @@ $labels['onlyworkinghours'] = 'Find availability within my working hours';
|
|||
$labels['reqallattendees'] = 'Required/all participants';
|
||||
$labels['prevslot'] = 'Previous Slot';
|
||||
$labels['nextslot'] = 'Next Slot';
|
||||
$labels['suggestedslot'] = 'Suggested Slot';
|
||||
$labels['noslotfound'] = 'Unable to find a free time slot';
|
||||
$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
|
||||
$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
|
||||
|
@ -232,4 +239,16 @@ $labels['birthdayscalendarsources'] = 'From these address books';
|
|||
$labels['birthdayeventtitle'] = '$name\'s Birthday';
|
||||
$labels['birthdayage'] = 'Age $age';
|
||||
|
||||
// (hidden) titles and labels for accessibility annotations
|
||||
$labels['arialabelminical'] = 'Calendar date selection';
|
||||
$labels['arialabelcalendarview'] = 'Calendar view';
|
||||
$labels['arialabelsearchform'] = 'Event search form';
|
||||
$labels['arialabelquicksearchbox'] = 'Event search input';
|
||||
$labels['arialabelcalsearchform'] = 'Calendars search form';
|
||||
$labels['calendaractions'] = 'Calendar actions';
|
||||
$labels['arialabeleventattendees'] = 'Event participants list';
|
||||
$labels['arialabeleventresources'] = 'Event resources list';
|
||||
$labels['arialabelresourcesearchform'] = 'Resources search form';
|
||||
$labels['arialabelresourceselection'] = 'Available resources';
|
||||
|
||||
?>
|
||||
|
|
|
@ -85,6 +85,14 @@ body.attachmentwin #topnav .topright {
|
|||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d9f1fb', endColorstr='#c5e3ee', GradientType=0);
|
||||
}
|
||||
|
||||
#datepicker .ui-datepicker-days-cell-over a.ui-state-default {
|
||||
color: #fff;
|
||||
border-color: #2fa0c0;
|
||||
background: rgba(73,180,210,0.6);
|
||||
text-shadow: 0px 1px 1px #666;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
#datepicker .ui-datepicker-activerange a.ui-state-active {
|
||||
color: #fff;
|
||||
background: #00acd4;
|
||||
|
@ -265,12 +273,19 @@ pre {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#calendars .treelist div:hover > a.subscribed {
|
||||
background-position: 1px -110px;
|
||||
#calendars .treelist div > a.subscribed:focus {
|
||||
border-radius: 3px;
|
||||
outline: 2px solid rgba(30,150,192, 0.5);
|
||||
}
|
||||
|
||||
#calendars .treelist div.subscribed a.subscribed {
|
||||
background-position: -15px -110px;
|
||||
#calendars .treelist div:hover > a.subscribed,
|
||||
#calendars .treelist div > a.subscribed:focus {
|
||||
background-position: 0 -110px;
|
||||
}
|
||||
|
||||
#calendars .treelist div.subscribed a.subscribed,
|
||||
#calendars .treelist div.subscribed a.subscribed:focus {
|
||||
background-position: -16px -110px;
|
||||
}
|
||||
|
||||
#calendars .treelist li input {
|
||||
|
@ -420,7 +435,7 @@ pre {
|
|||
}
|
||||
|
||||
body.calendarmain #quicksearchbar {
|
||||
z-index: 200;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
body.calendarmain #searchmenulink {
|
||||
|
@ -600,7 +615,7 @@ a.miniColors-trigger {
|
|||
.calendarmain .fc-view-table td.fc-list-header,
|
||||
#attendees-freebusy-table h3.boxtitle,
|
||||
#schedule-freebusy-times thead th,
|
||||
.edit-attendees-table thead td
|
||||
.edit-attendees-table thead th
|
||||
{
|
||||
color: #69939e;
|
||||
font-size: 11px;
|
||||
|
@ -614,6 +629,7 @@ a.miniColors-trigger {
|
|||
border: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
padding: 8px 7px 3px 7px;
|
||||
}
|
||||
|
||||
|
@ -775,21 +791,26 @@ td.topalign {
|
|||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.edit-attendees-table th.role,
|
||||
.edit-attendees-table td.role {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.edit-attendees-table th.availability,
|
||||
.edit-attendees-table td.availability,
|
||||
.edit-attendees-table th.confirmstate,
|
||||
.edit-attendees-table td.confirmstate {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.edit-attendees-table th.options,
|
||||
.edit-attendees-table td.options {
|
||||
width: 3em;
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.edit-attendees-table th.name,
|
||||
.edit-attendees-table td.name {
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
|
@ -1120,7 +1141,7 @@ a.dropdown-link:after {
|
|||
left: 0;
|
||||
right: 0;
|
||||
height: auto;
|
||||
z-index: 200;
|
||||
z-index: 10;
|
||||
padding: 4px 5px;
|
||||
border: 1px solid #c3c3c3;
|
||||
border-top-color: #ddd;
|
||||
|
@ -1376,11 +1397,21 @@ a.dropdown-link:after {
|
|||
-o-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.calendarmain .fc-header-left .fc-button:focus {
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 1px #666;
|
||||
background-color: rgba(30,150,192, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.calendarmain .fc-header-left .fc-button.fc-state-active {
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
text-shadow: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.calendarmain .fc-header-left .fc-button-agendaDay {
|
||||
|
@ -1428,6 +1459,13 @@ a.dropdown-link:after {
|
|||
font-size: 1em !important;
|
||||
}
|
||||
|
||||
.calendarmain .fc-event:focus {
|
||||
outline: 1px solid rgba(71,135,177, 0.4);
|
||||
-webkit-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
|
||||
-moz-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
|
||||
-o-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
|
||||
box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
|
||||
}
|
||||
.fc-event-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -269,6 +269,10 @@ html .fc,
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
.fc-event:focus {
|
||||
outline: 2px solid ActiveBorder;
|
||||
}
|
||||
|
||||
a.fc-event {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
|
||||
<roundcube:include file="/includes/header.html" />
|
||||
|
||||
<h1 class="voice"><roundcube:label name="calendar.calendar" /></h1>
|
||||
|
||||
<div id="mainscreen">
|
||||
<div id="calendarsidebar">
|
||||
<div id="calendartoolbar" class="toolbar">
|
||||
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
|
||||
<div id="calendartoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
|
||||
<roundcube:button command="addevent" type="link" class="button addevent disabled" classAct="button addevent" classSel="button addevent pressed" label="calendar.new_event" title="calendar.new_event" />
|
||||
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="calendar.print" title="calendar.printtitle" />
|
||||
<roundcube:button command="events-import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="calendar.importevents" />
|
||||
|
@ -19,35 +22,41 @@
|
|||
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||
</div>
|
||||
|
||||
<div id="datepicker" class="uibox"></div>
|
||||
<h2 id="aria-label-minical" class="voice"><roundcube:label name="calendar.arialabelminical" /></h2>
|
||||
<div id="datepicker" class="uibox" role="presentation"></div>
|
||||
|
||||
<div id="calendars" class="uibox listbox" style="visibility:hidden">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.calendars" />
|
||||
<a class="iconbutton search" title="<roundcube:label name='calendar.findcalendars' />"></a>
|
||||
<div id="calendars" class="uibox listbox" style="visibility:hidden" role="navigation" aria-labelledby="aria-label-calendarlist">
|
||||
<h2 class="boxtitle" id="aria-label-calendarlist"><roundcube:label name="calendar.calendars" />
|
||||
<a href="#calendars" class="iconbutton search" title="<roundcube:label name='calendar.findcalendars' />" tabindex="0"><roundcube:label name='calendar.findcalendars' /></a>
|
||||
</h2>
|
||||
<div class="listsearchbox">
|
||||
<div class="searchbox">
|
||||
<div class="searchbox" role="search" aria-labelledby="aria-label-calsearchform" aria-controls="calendarslist">
|
||||
<h3 id="aria-label-calsearchform" class="voice"><roundcube:label name="calendar.arialabelcalsearchform" /></h3>
|
||||
<label for="calendarlistsearch" class="voice"><roundcube:label name="calendar.searchterms" /></label>
|
||||
<input type="text" name="q" id="calendarlistsearch" placeholder="<roundcube:label name='calendar.findcalendars' />" />
|
||||
<a class="iconbutton searchicon"></a>
|
||||
<roundcube:button command="reset-listsearch" id="calendarlistsearch-reset" class="iconbutton reset" title="resetsearch" content="x" />
|
||||
<roundcube:button command="reset-listsearch" id="calendarlistsearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroller withfooter">
|
||||
<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
|
||||
<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
|
||||
</div>
|
||||
<div class="boxfooter">
|
||||
<roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('calendaroptionsmenu', undefined, { above:true });return false" innerClass="inner" content="⚙" />
|
||||
<roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('calendaroptionsmenu', event, { above:true })" innerClass="inner" label="calendar.calendaractions" aria-haspopup="true" aria-expanded="false" aria-owns="calendaroptionsmenu-menu" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="quicksearchbar">
|
||||
<div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform">
|
||||
<h2 id="aria-label-searchform" class="voice"><roundcube:label name="calendar.arialabelsearchform" /></h2>
|
||||
<label for="quicksearchbox" class="voice"><roundcube:label name="calendar.arialabelquicksearchbox" /></label>
|
||||
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
|
||||
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
|
||||
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
|
||||
<a id="searchmenulink" class="iconbutton searchoptions" tabindex="-1"> </a>
|
||||
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
|
||||
</div>
|
||||
|
||||
<div id="calendar">
|
||||
<h2 id="aria-label-calendarview" class="voice"><roundcube:label name="calendar.arialabelcalendarview" /></h2>
|
||||
<div id="calendar" role="main" aria-labelledby="aria-label-calendarview">
|
||||
<roundcube:object name="plugin.angenda_options" class="boxfooter" id="agendaoptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,18 +65,19 @@
|
|||
|
||||
<roundcube:object name="message" id="messagestack" />
|
||||
|
||||
<div id="calendaroptionsmenu" class="popupmenu">
|
||||
<ul class="toolbarmenu">
|
||||
<li><roundcube:button command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-remove" label="calendar.remove" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
|
||||
<div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true">
|
||||
<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
|
||||
<ul id="calendaroptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-calendaroptions">
|
||||
<li role="menuitem"><roundcube:button command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li role="menuitem"><roundcube:button command="calendar-remove" label="calendar.remove" classAct="active" /></li>
|
||||
<li role="menuitem"><roundcube:button command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
|
||||
<roundcube:if condition="env:calendar_driver == 'kolab'" />
|
||||
<li class="separator_above"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
|
||||
<li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
|
||||
<roundcube:endif />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="eventshow" class="uidialog">
|
||||
<div id="eventshow" class="uidialog" aria-hidden="true">
|
||||
<h1 id="event-title">Event Title</h1>
|
||||
<div class="event-section" id="event-location">Location</div>
|
||||
<div class="event-section" id="event-date">From-To</div>
|
||||
|
@ -125,14 +135,17 @@
|
|||
|
||||
<roundcube:include file="/templates/eventedit.html" />
|
||||
|
||||
<div id="eventresourcesdialog" class="uidialog">
|
||||
<div id="eventresourcesdialog" class="uidialog" aria-hidden="true">
|
||||
<div id="resource-dialog-left">
|
||||
<div id="resource-selection" class="uibox listbox">
|
||||
<div id="resource-selection" class="uibox listbox" role="navigation" aria-labelledby="aria-label-resourceselection">
|
||||
<h2 class="voice" id="aria-label-resourceselection"><roundcube:label name="calendar.arialabelresourceselection" /></h2>
|
||||
<div id="resourcequicksearch">
|
||||
<div class="searchbox">
|
||||
<div class="searchbox" role="search" aria-labelledby="aria-label-resourcesearchform" aria-controls="resources-list">
|
||||
<h3 id="aria-label-resourcesearchform" class="voice"><roundcube:label name="calendar.arialabelresourcesearchform" /></h3>
|
||||
<label for="resourcesearchbox" class="voice"><roundcube:label name="calendar.searchterms" /></label>
|
||||
<roundcube:object name="plugin.resources_searchform" id="resourcesearchbox" />
|
||||
<a id="resourcesearchmenulink" class="iconbutton searchoptions"> </a>
|
||||
<roundcube:button command="reset-resource-search" id="resourcesearchreset" class="iconbutton reset" title="resetsearch" content=" " />
|
||||
<roundcube:button command="reset-resource-search" id="resourcesearchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroller">
|
||||
|
@ -142,25 +155,25 @@
|
|||
</div>
|
||||
|
||||
<div id="resource-dialog-right">
|
||||
<div id="resource-info" class="uibox contentbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourcedetails" /></h2>
|
||||
<div id="resource-info" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourcedetails">
|
||||
<h2 class="boxtitle" id="aria-label-resourcedetails"><roundcube:label name="calendar.resourcedetails" /></h2>
|
||||
<div class="scroller">
|
||||
<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" />
|
||||
<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" aria-live="polite" aria-relevant="text" aria-atomic="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resource-availability" class="uibox contentbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
|
||||
<div id="resource-availability" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourceavailability">
|
||||
<h2 class="boxtitle" id="aria-label-resourceavailability"><roundcube:label name="calendar.resourceavailability" /></h2>
|
||||
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
|
||||
<div class="boxpagenav">
|
||||
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" content="&lt;" />
|
||||
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" content="&gt;" />
|
||||
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" label="calendar.prevweek" />
|
||||
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" label="calendar.nextweek" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="eventfreebusy" class="uidialog">
|
||||
<div id="eventfreebusy" class="uidialog" aria-hidden="true">
|
||||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellpadding="0" />
|
||||
|
||||
<div class="schedule-options">
|
||||
|
@ -203,7 +216,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendarform" class="uidialog">
|
||||
<div id="calendarform" class="uidialog" aria-hidden="true">
|
||||
<roundcube:label name="loading" />
|
||||
</div>
|
||||
|
||||
|
@ -262,6 +275,8 @@ $(document).ready(function(e){
|
|||
// TODO: save state in localStorage
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -275,8 +290,8 @@ function calendarview_splitter(p)
|
|||
this.collapsed = false;
|
||||
this.dragging = false;
|
||||
this.threshold = 80;
|
||||
this.lastpos = 0;
|
||||
this._lastpos = 0;
|
||||
this.lastpos = -1;
|
||||
this._lastpos = -1;
|
||||
this._min = p.min;
|
||||
|
||||
var me = this;
|
||||
|
@ -316,6 +331,9 @@ function calendarview_splitter(p)
|
|||
this.p1.resize();
|
||||
this.lastpos = this.pos;
|
||||
|
||||
if (this._lastpos == -1)
|
||||
this._lastpos = this.pos;
|
||||
|
||||
// also resize iframe covers
|
||||
if (this.drag_active) {
|
||||
$('iframe').each(function(i, elem) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div id="eventedit" class="uidialog uidialog-tabbed">
|
||||
<div id="eventedit" class="uidialog uidialog-tabbed" aria-hidden="true">
|
||||
<form id="eventtabs" action="#" method="post" enctype="multipart/form-data">
|
||||
<ul>
|
||||
<li><a href="#event-panel-summary"><roundcube:label name="calendar.tabsummary" /></a></li><li id="edit-tab-recurrence"><a href="#event-panel-recurrence"><roundcube:label name="calendar.tabrecurrence" /></a></li><li id="edit-tab-attendees"><a href="#event-panel-attendees"><roundcube:label name="calendar.tabattendees" /></a></li><li id="edit-tab-resources"><a href="#event-panel-resources"><roundcube:label name="calendar.tabresources" /></a></li><li id="edit-tab-attachments"><a href="#event-panel-attachments"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||
|
@ -8,7 +8,7 @@
|
|||
<div class="event-section">
|
||||
<label for="edit-title"><roundcube:label name="calendar.title" /></label>
|
||||
<br />
|
||||
<input type="text" class="text" name="title" id="edit-title" size="40" />
|
||||
<input type="text" class="text" name="title" id="edit-title" size="40" required="true" />
|
||||
</div>
|
||||
<div class="event-section">
|
||||
<label for="edit-location"><roundcube:label name="calendar.location" /></label>
|
||||
|
@ -28,21 +28,21 @@
|
|||
<div class="event-section">
|
||||
<label style="float:right;padding-right:0.5em"><input type="checkbox" name="allday" id="edit-allday" value="1" /><roundcube:label name="calendar.all-day" /></label>
|
||||
<label for="edit-startdate"><roundcube:label name="calendar.start" /></label>
|
||||
<input type="text" name="startdate" size="11" id="edit-startdate" />
|
||||
<input type="text" name="starttime" size="6" id="edit-starttime" />
|
||||
<input type="text" name="startdate" size="11" id="edit-startdate" required="true" />
|
||||
<input type="text" name="starttime" size="6" id="edit-starttime" aria-label="<roundcube:label name='calendar.starttime' />" />
|
||||
</div>
|
||||
<div class="event-section">
|
||||
<label for="edit-enddate"><roundcube:label name="calendar.end" /></label>
|
||||
<input type="text" name="enddate" size="11" id="edit-enddate" />
|
||||
<input type="text" name="endtime" size="6" id="edit-endtime" />
|
||||
<input type="text" name="enddate" size="11" id="edit-enddate" required="true" />
|
||||
<input type="text" name="endtime" size="6" id="edit-endtime" aria-label="<roundcube:label name='calendar.endtime' />" />
|
||||
</div>
|
||||
<div class="event-section" id="edit-alarms">
|
||||
<div class="edit-alarm-item first">
|
||||
<label><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<label for="edit-alarm-item"><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" id="edit-alarm-item" />
|
||||
<span class="edit-alarm-buttons">
|
||||
<a href="#add" class="iconbutton add add-alarm">+</a>
|
||||
<a href="#delete" class="iconbutton remove delete-alarm">-</a>
|
||||
<a href="#add" class="iconbutton add add-alarm"><roundcube:label name="libcalendaring.addalarm" /></a>
|
||||
<a href="#delete" class="iconbutton remove delete-alarm"><roundcube:label name="libcalendaring.removealarm" /></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -97,13 +97,15 @@
|
|||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-panel-attendees">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" />
|
||||
<h3 id="aria-label-attendeestable" class="voice"><roundcube:label name="calendar.arialabeleventattendees" /></h3>
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" aria-labelledby="aria-label-attendeestable" />
|
||||
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- resources list -->
|
||||
<div id="event-panel-resources">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="records-table edit-attendees-table" coltitle="resource" />
|
||||
<h3 id="aria-label-resourcestable" class="voice"><roundcube:label name="calendar.arialabeleventresources" /></h3>
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="records-table edit-attendees-table" coltitle="resource" aria-labelledby="aria-label-resourcestable" />
|
||||
<roundcube:object name="plugin.resources_form" id="edit-resources-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
|
@ -112,7 +114,8 @@
|
|||
<div id="edit-attachments">
|
||||
<roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" />
|
||||
</div>
|
||||
<div id="edit-attachments-form">
|
||||
<div id="edit-attachments-form" role="region" aria-labelledby="aria-label-attachmentuploadform">
|
||||
<h3 id="aria-label-attachmentuploadform" class="voice"><roundcube:label name="arialabelattachmentuploadform" /></h2>
|
||||
<roundcube:object name="plugin.attachments_form" id="calendar-attachment-form" attachmentFieldSize="30" />
|
||||
</div>
|
||||
<roundcube:object name="plugin.filedroparea" id="event-panel-attachments" />
|
||||
|
|
|
@ -68,25 +68,26 @@ function rcube_libcalendaring(settings)
|
|||
/**
|
||||
* Create a nice human-readable string for the date/time range
|
||||
*/
|
||||
this.event_date_text = function(event)
|
||||
this.event_date_text = function(event, voice)
|
||||
{
|
||||
if (!event.start)
|
||||
return '';
|
||||
if (!event.end)
|
||||
event.end = event.start;
|
||||
|
||||
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
|
||||
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000,
|
||||
until = voice ? ' ' + rcmail.gettext('until','libcalendaring') + ' ' : ' — ';
|
||||
if (event.allDay) {
|
||||
fromto = this.format_datetime(event.start, 1)
|
||||
+ (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + this.format_datetime(event.end, 1) : '');
|
||||
fromto = this.format_datetime(event.start, 1, voice)
|
||||
+ (duration > 86400 || event.start.getDay() != event.end.getDay() ? until + this.format_datetime(event.end, 1, voice) : '');
|
||||
}
|
||||
else if (duration < 86400 && event.start.getDay() == event.end.getDay()) {
|
||||
fromto = this.format_datetime(event.start, 0)
|
||||
+ (duration > 0 ? ' — ' + this.format_datetime(event.end, 2) : '');
|
||||
fromto = this.format_datetime(event.start, 0, voice)
|
||||
+ (duration > 0 ? until + this.format_datetime(event.end, 2, voice) : '');
|
||||
}
|
||||
else {
|
||||
fromto = this.format_datetime(event.start, 0)
|
||||
+ (duration > 0 ? ' — ' + this.format_datetime(event.end, 0) : '');
|
||||
fromto = this.format_datetime(event.start, 0, voice)
|
||||
+ (duration > 0 ? until + this.format_datetime(event.end, 0, voice) : '');
|
||||
}
|
||||
|
||||
return fromto;
|
||||
|
@ -178,15 +179,18 @@ function rcube_libcalendaring(settings)
|
|||
/**
|
||||
* Format the given date object according to user's prefs
|
||||
*/
|
||||
this.format_datetime = function(date, mode)
|
||||
this.format_datetime = function(date, mode, voice)
|
||||
{
|
||||
var res = '';
|
||||
if (!mode || mode == 1)
|
||||
res += $.datepicker.formatDate(datepicker_settings.dateFormat, date, datepicker_settings);
|
||||
if (!mode)
|
||||
res += ' ';
|
||||
if (!mode || mode == 2)
|
||||
res += this.format_time(date);
|
||||
if (!mode || mode == 1) {
|
||||
res += $.datepicker.formatDate(voice ? 'MM d yy' : datepicker_settings.dateFormat, date, datepicker_settings);
|
||||
}
|
||||
if (!mode) {
|
||||
res += voice ? ' ' + rcmail.gettext('at','libcalendaring') + ' ' : ' ';
|
||||
}
|
||||
if (!mode || mode == 2) {
|
||||
res += this.format_time(date, voice);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -194,7 +198,7 @@ function rcube_libcalendaring(settings)
|
|||
/**
|
||||
* Clone from fullcalendar.js
|
||||
*/
|
||||
this.format_time = function(date)
|
||||
this.format_time = function(date, voice)
|
||||
{
|
||||
var zeroPad = function(n) { return (n < 10 ? '0' : '') + n; }
|
||||
var formatters = {
|
||||
|
@ -212,7 +216,8 @@ function rcube_libcalendaring(settings)
|
|||
TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }
|
||||
};
|
||||
|
||||
var i, i2, c, formatter, res = '', format = settings['time_format'];
|
||||
var i, i2, c, formatter, res = '',
|
||||
format = voice ? settings['time_format'].replace(':',' ').replace('HH','H').replace('hh','h').replace('mm','m').replace('ss','s') : settings['time_format'];
|
||||
for (i=0; i < format.length; i++) {
|
||||
c = format.charAt(i);
|
||||
for (i2=Math.min(i+2, format.length); i2 > i; i2--) {
|
||||
|
@ -316,10 +321,13 @@ function rcube_libcalendaring(settings)
|
|||
.replace(/\n/g, "<br/>");
|
||||
};
|
||||
|
||||
this.init_alarms_edit = function(prefix)
|
||||
this.init_alarms_edit = function(prefix, index)
|
||||
{
|
||||
var edit_type = $(prefix+' select.edit-alarm-type'),
|
||||
dom_id = edit_type.attr('id');
|
||||
|
||||
// register events on alarm fields
|
||||
$(prefix+' select.edit-alarm-type').change(function(){
|
||||
edit_type.change(function(){
|
||||
$(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
|
||||
});
|
||||
$(prefix+' select.edit-alarm-offset').change(function(){
|
||||
|
@ -337,13 +345,20 @@ function rcube_libcalendaring(settings)
|
|||
return false;
|
||||
});
|
||||
|
||||
// set a unique id attribute and set label reference accordingly
|
||||
if ((index || 0) > 0 && dom_id) {
|
||||
dom_id += ':' + (new Date().getTime());
|
||||
edit_type.attr('id', dom_id);
|
||||
$(prefix+' label:first').attr('for', dom_id);
|
||||
}
|
||||
|
||||
$(prefix).on('click', 'a.add-alarm', function(e){
|
||||
var i = $(this).closest('.edit-alarm-item').siblings().length + 1;
|
||||
var item = $(this).closest('.edit-alarm-item').clone(false)
|
||||
.removeClass('first')
|
||||
.appendTo(prefix);
|
||||
|
||||
me.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')');
|
||||
me.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')', i);
|
||||
$('select.edit-alarm-type, select.edit-alarm-offset', item).change();
|
||||
return false;
|
||||
});
|
||||
|
@ -364,7 +379,7 @@ function rcube_libcalendaring(settings)
|
|||
}
|
||||
else {
|
||||
domnode = $(prefix + ' .edit-alarm-item').eq(0).clone(false).removeClass('first').appendTo(prefix);
|
||||
this.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')');
|
||||
this.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')', i);
|
||||
}
|
||||
|
||||
$('select.edit-alarm-type', domnode).val(alarm.action);
|
||||
|
|
|
@ -288,7 +288,7 @@ class libcalendaring extends rcube_plugin
|
|||
public function alarm_select($attrib, $alarm_types, $absolute_time = true)
|
||||
{
|
||||
unset($attrib['name']);
|
||||
$select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type'));
|
||||
$select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type', 'id' => $attrib['id']));
|
||||
$select_type->add($this->gettext('none'), '');
|
||||
foreach ($alarm_types as $type)
|
||||
$select_type->add($this->gettext(strtolower("alarm{$type}option")), $type);
|
||||
|
@ -991,6 +991,7 @@ class libcalendaring extends rcube_plugin
|
|||
'class' => 'delete',
|
||||
'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
|
||||
'title' => $this->rc->gettext('delete'),
|
||||
'aria-label' => $this->rc->gettext('delete') . ' ' . $attachment['name'],
|
||||
), $button);
|
||||
|
||||
$content .= Q($attachment['name']);
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
$labels = array();
|
||||
|
||||
// words for spoken dates
|
||||
$labels['until'] = 'until';
|
||||
$labels['at'] = 'at';
|
||||
|
||||
// alarms related labels
|
||||
$labels['alarmemail'] = 'Send Email';
|
||||
$labels['alarmdisplay'] = 'Show message';
|
||||
|
@ -18,7 +22,8 @@ $labels['trigger+M'] = 'minutes after';
|
|||
$labels['trigger+H'] = 'hours after';
|
||||
$labels['trigger+D'] = 'days after';
|
||||
$labels['triggerattime'] = 'at time';
|
||||
$labels['addalarm'] = 'add alarm';
|
||||
$labels['addalarm'] = 'Add alarm';
|
||||
$labels['removealarm'] = 'Remove alarm';
|
||||
|
||||
$labels['alarmtitle'] = 'Upcoming events';
|
||||
$labels['dismissall'] = 'Dismiss all';
|
||||
|
|
|
@ -46,8 +46,9 @@ function kolab_folderlist(node, p)
|
|||
if (results.length) {
|
||||
// create treelist widget to present the search results
|
||||
if (!search_results_widget) {
|
||||
var list_id = (me.container.attr('id') || p.id_prefix || '0')
|
||||
search_results_container = $('<div class="searchresults"></div>')
|
||||
.html(p.search_title ? '<h2 class="boxtitle">' + p.search_title + '</h2>' : '')
|
||||
.html(p.search_title ? '<h2 class="boxtitle" id="st:' + list_id + '">' + p.search_title + '</h2>' : '')
|
||||
.insertAfter(me.container);
|
||||
|
||||
search_results_widget = new rcube_treelist_widget('<ul>', {
|
||||
|
@ -55,7 +56,7 @@ function kolab_folderlist(node, p)
|
|||
selectable: false
|
||||
});
|
||||
// copy classes from main list
|
||||
search_results_widget.container.addClass(me.container.attr('class'));
|
||||
search_results_widget.container.addClass(me.container.attr('class')).attr('aria-labelledby', 'st:' + list_id);
|
||||
|
||||
// register click handler on search result's checkboxes to select the given item for listing
|
||||
search_results_widget.container
|
||||
|
@ -87,6 +88,11 @@ function kolab_folderlist(node, p)
|
|||
else {
|
||||
li.remove();
|
||||
}
|
||||
|
||||
// set focus to cloned checkbox
|
||||
if (rcube_event.is_keyboard(e)) {
|
||||
$(me.get_item(id, true)).find('input[type=checkbox]').first().focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -178,7 +184,7 @@ function kolab_folderlist(node, p)
|
|||
}
|
||||
|
||||
if (listsearch_request) {
|
||||
// ignore, let the currently runnung sequest finish
|
||||
// ignore, let the currently running request finish
|
||||
if (listsearch_request.query == search.query) {
|
||||
return;
|
||||
}
|
||||
|
@ -196,7 +202,10 @@ function kolab_folderlist(node, p)
|
|||
postdata: { action:'search', q:search.query, source:'%s' },
|
||||
lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'),
|
||||
onresponse: render_search_results,
|
||||
whendone: function(e){ listsearch_request = null; }
|
||||
whendone: function(data){
|
||||
listsearch_request = null;
|
||||
me.triggerEvent('search-complete', data);
|
||||
}
|
||||
});
|
||||
|
||||
listsearch_request = { id:reqid, query:search.query };
|
||||
|
|
Loading…
Add table
Reference in a new issue