Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube
This commit is contained in:
commit
ace9517958
8 changed files with 184 additions and 34 deletions
|
@ -449,9 +449,9 @@ class calendar extends rcube_plugin
|
|||
$event['uid'] = $this->generate_uid();
|
||||
|
||||
// set current user as organizer
|
||||
if (!$event['attendees']) {
|
||||
if (FALSE && !$event['attendees']) {
|
||||
$identity = $this->rc->user->get_identity();
|
||||
$event['attendees'][] = array('role' => 'OWNER', 'name' => $identity['name'], 'email' => $identity['email']);
|
||||
$event['attendees'][] = array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email']);
|
||||
}
|
||||
|
||||
$this->prepare_event($event);
|
||||
|
@ -1163,6 +1163,9 @@ class calendar extends rcube_plugin
|
|||
}
|
||||
}
|
||||
|
||||
// let this information be cached for 15min
|
||||
send_future_expire_header(90);
|
||||
|
||||
echo $status;
|
||||
exit;
|
||||
}
|
||||
|
|
|
@ -440,7 +440,7 @@ function rcube_calendar_ui(settings)
|
|||
attendees_list = $('#edit-attendees-table > tbody').html('');
|
||||
if (calendar.attendees && event.attendees) {
|
||||
for (var j=0; j < event.attendees.length; j++)
|
||||
add_attendee(event.attendees[j]);
|
||||
add_attendee(event.attendees[j], true);
|
||||
}
|
||||
|
||||
// attachments
|
||||
|
@ -509,6 +509,12 @@ function rcube_calendar_ui(settings)
|
|||
if (i.match(/^rcmfile([0-9a-z]+)/))
|
||||
attachments.push(RegExp.$1);
|
||||
data.attachments = attachments;
|
||||
|
||||
// read attendee roles
|
||||
$('select.edit-attendee-role').each(function(i, elem){
|
||||
if (data.attendees[i])
|
||||
data.attendees[i].role = $(elem).val();
|
||||
});
|
||||
|
||||
// gather recurrence settings
|
||||
var freq;
|
||||
|
@ -597,6 +603,19 @@ function rcube_calendar_ui(settings)
|
|||
title.select();
|
||||
};
|
||||
|
||||
// update event properties and attendees availability if event times have changed
|
||||
var event_times_changed = function()
|
||||
{
|
||||
alert('event_times_changed')
|
||||
if (me.selected_event) {
|
||||
var allday = $('#edit-allday').get(0);
|
||||
me.selected_event.start = parse_datetime(allday.checked ? '00:00' : $('#edit-starttime').val(), $('#edit-startdate').val());
|
||||
me.selected_event.end = parse_datetime(allday.checked ? '23:59' : $('#edit-endtime').val(), $('#edit-enddate').val());
|
||||
if (me.selected_event.attendees)
|
||||
update_freebusy_status(me.selected_event);
|
||||
}
|
||||
};
|
||||
|
||||
// add the given list of participants
|
||||
var add_attendees = function(names)
|
||||
{
|
||||
|
@ -623,7 +642,7 @@ function rcube_calendar_ui(settings)
|
|||
}
|
||||
|
||||
if (email) {
|
||||
add_attendee({ email:email, name:name, role:'REQUIRED', status:'unknown' });
|
||||
add_attendee({ email:email, name:name, role:'REQ-PARTICIPANT', status:'NEEDS-ACTION' });
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
|
@ -635,33 +654,93 @@ function rcube_calendar_ui(settings)
|
|||
};
|
||||
|
||||
// add the given attendee to the list
|
||||
var add_attendee = function(data)
|
||||
var add_attendee = function(data, edit)
|
||||
{
|
||||
var dispname = (data.email && data.name) ? data.name + ' <' + data.email + '>' : (data.email || data.name);
|
||||
// check for dupes...
|
||||
var exists = false;
|
||||
$.each(event_attendees, function(i, v){ exists |= (v.email == data.email); });
|
||||
if (exists)
|
||||
return false;
|
||||
|
||||
var dispname = Q(data.name || data.email);
|
||||
if (data.email)
|
||||
dispname = '<span title="' + Q(data.email) + '">' + dispname + '</span>';
|
||||
|
||||
// role selection
|
||||
var opts = {
|
||||
'ORGANIZER': rcmail.gettext('calendar.roleorganizer'),
|
||||
'REQ-PARTICIPANT': rcmail.gettext('calendar.rolerequired'),
|
||||
'OPT-PARTICIPANT': rcmail.gettext('calendar.roleoptional'),
|
||||
'CHAIR': rcmail.gettext('calendar.roleresource')
|
||||
};
|
||||
var select = '<select class="edit-attendee-role">';
|
||||
for (var r in opts)
|
||||
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
|
||||
select += '</select>';
|
||||
|
||||
// availability
|
||||
var avail = data.email ? 'loading' : 'unknown';
|
||||
if (edit && data.role == 'ORGANIZER' && data.status == 'ACCEPTED')
|
||||
avail = 'free';
|
||||
|
||||
// delete icon
|
||||
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
|
||||
var dellink = '<a href="#delete" class="deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
|
||||
|
||||
var html = '<td class="role"></td>' +
|
||||
'<td class="name">' + Q(dispname) + '</td>' +
|
||||
'<td class="availability">' + '' + '</td>' +
|
||||
'<td class="confirmstate">' + Q(data.status) + '</td>' +
|
||||
'<td class="options">' + (data.role != 'OWNER' ? dellink : '') + '</td>';
|
||||
var html = '<td class="role">' + select + '</td>' +
|
||||
'<td class="name">' + dispname + '</td>' +
|
||||
'<td class="availability"><img src="./program/blank.gif" class="availabilityicon ' + avail + '" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '">' + Q(data.status) + '</span></td>' +
|
||||
'<td class="options">' + dellink + '</td>';
|
||||
|
||||
$('<tr>')
|
||||
var tr = $('<tr>')
|
||||
.addClass(String(data.role).toLowerCase())
|
||||
.html(html)
|
||||
.appendTo(attendees_list)
|
||||
.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||
.appendTo(attendees_list);
|
||||
|
||||
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||
|
||||
// check free-busy status
|
||||
if (avail == 'loading') {
|
||||
check_freebusy_status(tr.find('img.availabilityicon'), data.email, me.selected_event);
|
||||
}
|
||||
|
||||
event_attendees.push(data);
|
||||
};
|
||||
|
||||
// iterate over all attendees and update their free-busy status display
|
||||
var update_freebusy_status = function(event)
|
||||
{
|
||||
var icons = attendees_list.find('img.availabilityicon');
|
||||
for (var i=0; i < event.attendees.length; i++) {
|
||||
if (icons.get(i) && event.attendees[i].email && event.attendees[i].status != 'ACCEPTED')
|
||||
check_freebusy_status(icons.get(i), event.attendees[i].email, event);
|
||||
}
|
||||
};
|
||||
|
||||
// load free-busy status from server and update icon accordingly
|
||||
var check_freebusy_status = function(icon, email, event)
|
||||
{
|
||||
icon = $(icon).removeClass().addClass('availabilityicon loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
dataType: 'html',
|
||||
url: rcmail.url('freebusy-status'),
|
||||
data: { email:email, start:date2unixtime(event.start), end:date2unixtime(event.end), _remote: 1 },
|
||||
success: function(status){
|
||||
icon.removeClass('loading').addClass(String(status).toLowerCase());
|
||||
},
|
||||
error: function(){
|
||||
icon.removeClass('loading').addClass('unknown');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// remove an attendee from the list
|
||||
var remove_attendee = function(elem, id)
|
||||
{
|
||||
$(elem).closest('tr').hide();
|
||||
$(elem).closest('tr').remove();
|
||||
event_attendees = $.grep(event_attendees, function(data){ return (data.name != id && data.email != id) });
|
||||
};
|
||||
|
||||
|
@ -1234,7 +1313,8 @@ function rcube_calendar_ui(settings)
|
|||
}
|
||||
});
|
||||
$('#edit-enddate, input.edit-alarm-date').datepicker(datepicker_settings);
|
||||
$('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); });
|
||||
$('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); event_times_changed(); });
|
||||
$('#edit-enddate, #edit-starttime, #edit-endtime').change(function(){ event_times_changed(); });
|
||||
$('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); });
|
||||
|
||||
// configure drop-down menu on time input fields based on jquery UI autocomplete
|
||||
|
|
|
@ -282,6 +282,7 @@ abstract class calendar_driver
|
|||
*/
|
||||
public function get_freebusy_list($email, $start, $end)
|
||||
{
|
||||
sleep(2);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ class kolab_calendar
|
|||
private $search_fields = array('title', 'description', 'location');
|
||||
private $sensitivity_map = array('public', 'private', 'confidential');
|
||||
private $priority_map = array('low', 'normal', 'high');
|
||||
private $role_map = array('REQ-PARTICIPANT' => 'required', 'OPT-PARTICIPANT' => 'optional', 'CHAIR' => 'resource');
|
||||
private $status_map = array('NEEDS-ACTION' => 'none', 'TENTATIVE' => 'tentative', 'CONFIRMED' => 'accepted', 'DECLINED' => 'declined');
|
||||
private $month_map = array('', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december');
|
||||
private $weekday_map = array('MO'=>'monday', 'TU'=>'tuesday', 'WE'=>'wednesday', 'TH'=>'thursday', 'FR'=>'friday', 'SA'=>'saturday', 'SU'=>'sunday');
|
||||
|
||||
|
@ -430,6 +432,8 @@ class kolab_calendar
|
|||
|
||||
$sensitivity_map = array_flip($this->sensitivity_map);
|
||||
$priority_map = array_flip($this->priority_map);
|
||||
$status_map = array_flip($this->status_map);
|
||||
$role_map = array_flip($this->role_map);
|
||||
|
||||
if (!empty($rec['_attachments'])) {
|
||||
foreach ($rec['_attachments'] as $name => $attachment) {
|
||||
|
@ -444,22 +448,22 @@ class kolab_calendar
|
|||
|
||||
if ($rec['organizer']) {
|
||||
$attendees[] = array(
|
||||
'role' => 'OWNER',
|
||||
'role' => 'ORGANIZER',
|
||||
'name' => $rec['organizer']['display-name'],
|
||||
'email' => $rec['organizer']['smtp-address'],
|
||||
'status' => 'accepted',
|
||||
'status' => 'ACCEPTED',
|
||||
);
|
||||
}
|
||||
|
||||
foreach ((array)$rec['attendee'] as $attendee) {
|
||||
$attendees[] = array(
|
||||
'role' => strtoupper($attendee['role']),
|
||||
'role' => $role_map[$attendee['role']],
|
||||
'name' => $attendee['display-name'],
|
||||
'email' => $attendee['smtp-address'],
|
||||
'status' => $attendee['status'],
|
||||
'status' => $status_map[$attendee['status']],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return array(
|
||||
'id' => $rec['uid'],
|
||||
'uid' => $rec['uid'],
|
||||
|
@ -624,7 +628,7 @@ class kolab_calendar
|
|||
// process event attendees
|
||||
foreach ((array)$event['attendees'] as $attendee) {
|
||||
$role = $attendee['role'];
|
||||
if ($role == 'OWNER') {
|
||||
if ($role == 'ORGANIZER') {
|
||||
$object['organizer'] = array(
|
||||
'display-name' => $attendee['name'],
|
||||
'smtp-address' => $attendee['email'],
|
||||
|
@ -634,8 +638,8 @@ class kolab_calendar
|
|||
$object['attendee'][] = array(
|
||||
'display-name' => $attendee['name'],
|
||||
'smtp-address' => $attendee['email'],
|
||||
'status' => $attendee['status'],
|
||||
'role' => strtolower($role),
|
||||
'status' => $this->status_map[$attendee['status']],
|
||||
'role' => $this->role_map[$role],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1539,8 +1539,8 @@ function formatDates(date1, date2, format, options) {
|
|||
for (i2=i+1; i2<len; i2++) {
|
||||
if (format.charAt(i2) == ']') {
|
||||
var subformat = format.substring(i+1, i2);
|
||||
var subres = formatDate(otherDate, subformat, options);
|
||||
if (subres != formatDate(date, subformat, options)) {
|
||||
var subres = formatDate(date, subformat, options);
|
||||
if (subres != formatDate(otherDate, subformat, options)) {
|
||||
res += subres;
|
||||
}
|
||||
i = i2;
|
||||
|
@ -5334,6 +5334,8 @@ function ListEventRenderer() {
|
|||
segHash = opt('listTexts', 'thisMonth');
|
||||
} else if (md == 1) {
|
||||
segHash = opt('listTexts', 'nextMonth');
|
||||
} else if (md > 1) {
|
||||
segHash = opt('listTexts', 'future');
|
||||
}
|
||||
} else if (segmode == 'month') {
|
||||
segHash = formatDate(segDate, 'MMMM yyyy');
|
||||
|
@ -5447,7 +5449,7 @@ function ListEventRenderer() {
|
|||
if (event.start < seg.start) {
|
||||
datestr = opt('listTexts', 'until') + ' ' + formatDate(event.end, (event.allDay || event.end.getDate() != seg.start.getDate()) ? dateFormat : timeFormat);
|
||||
} else if (duration > DAY_MS) {
|
||||
datestr = formatDates(event.start, event.end, dateFormat + '[ - ' + dateFormat + ']');
|
||||
datestr = formatDates(event.start, event.end, dateFormat + '{ - ' + dateFormat + '}');
|
||||
} else if (seg.daydiff == 0) {
|
||||
datestr = opt('listTexts', 'today');
|
||||
} else if (seg.daydiff == 1) {
|
||||
|
@ -5458,7 +5460,7 @@ function ListEventRenderer() {
|
|||
datestr = formatDate(event.start, dateFormat);
|
||||
}
|
||||
} else if (segmode != 'day') {
|
||||
datestr = formatDates(event.start, event.end, dateFormat + (duration > DAY_MS ? '[ - ' + dateFormat + ']' : ''));
|
||||
datestr = formatDates(event.start, event.end, dateFormat + (duration > DAY_MS ? '{ - ' + dateFormat + '}' : ''));
|
||||
}
|
||||
|
||||
if (!datestr && event.allDay) {
|
||||
|
|
|
@ -90,8 +90,12 @@ $labels['alarmtitle'] = 'Upcoming events';
|
|||
$labels['attendee'] = 'Participant';
|
||||
$labels['role'] = 'Role';
|
||||
$labels['availability'] = 'Avail.';
|
||||
$labels['confirmstate'] = 'Confirmed';
|
||||
$labels['confirmstate'] = 'Status';
|
||||
$labels['addattendee'] = 'Add participant';
|
||||
$labels['roleorganizer'] = 'Organizer';
|
||||
$labels['rolerequired'] = 'Required';
|
||||
$labels['roleoptional'] = 'Optional';
|
||||
$labels['roleresource'] = 'Resource';
|
||||
|
||||
// event dialog tabs
|
||||
$labels['tabsummary'] = 'Summary';
|
||||
|
|
|
@ -106,7 +106,7 @@ pre {
|
|||
|
||||
#calendarslist li span {
|
||||
cursor: default;
|
||||
background: url(images/calendars.png) 0 -3px no-repeat;
|
||||
background: url('images/calendars.png') 0 -3px no-repeat;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
|
@ -480,11 +480,10 @@ td.topalign {
|
|||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
#edit-attendees-table tr.owner td {
|
||||
color: #999;
|
||||
#edit-attendees-table td.role {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.role,
|
||||
#edit-attendees-table td.availability {
|
||||
width: 4em;
|
||||
}
|
||||
|
@ -507,13 +506,70 @@ td.topalign {
|
|||
}
|
||||
|
||||
#edit-attendees-table thead td {
|
||||
background: url(images/listheader.gif) top left repeat-x #CCC;
|
||||
background: url('images/listheader.gif') top left repeat-x #CCC;
|
||||
}
|
||||
|
||||
#edit-attendees-form {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#edit-attendees-table select.edit-attendee-role {
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.loading {
|
||||
background: url('images/loading-small.gif') top left no-repeat;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.unknown {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.free {
|
||||
background: #0c0;
|
||||
}
|
||||
|
||||
#edit-attendees-table img.availabilityicon.busy {
|
||||
background: #c00;
|
||||
}
|
||||
|
||||
#edit-attendees-table tbody td.confirmstate {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-size: 75%;
|
||||
/* text-indent: -2000%; */
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span {
|
||||
display: block;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.needs-action {
|
||||
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.tentative {
|
||||
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.declined {
|
||||
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.accepted {
|
||||
|
||||
}
|
||||
|
||||
span.edit-alarm-set {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
BIN
plugins/calendar/skins/default/images/loading-small.gif
Normal file
BIN
plugins/calendar/skins/default/images/loading-small.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Loading…
Add table
Reference in a new issue