From bb72711315856a13b08b45adfe0e927502eb9542 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 12 Jul 2011 08:30:13 +0200 Subject: [PATCH 1/3] Fix usage of formatDates(); revert supposed fix in fullcalendar --- plugins/calendar/lib/js/fullcalendar.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/calendar/lib/js/fullcalendar.js b/plugins/calendar/lib/js/fullcalendar.js index 36813fa3..e55c47f1 100644 --- a/plugins/calendar/lib/js/fullcalendar.js +++ b/plugins/calendar/lib/js/fullcalendar.js @@ -1539,8 +1539,8 @@ function formatDates(date1, date2, format, options) { for (i2=i+1; i2 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) { From 8827ea8341dad350559e6f11606b78cd45970808 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 13 Jul 2011 13:42:11 +0200 Subject: [PATCH 2/3] Going on with ressurce planning/free-busy stuff --- plugins/calendar/calendar.php | 7 +- plugins/calendar/calendar_ui.js | 108 +++++++++++++++--- plugins/calendar/drivers/calendar_driver.php | 1 + .../calendar/drivers/kolab/kolab_calendar.php | 20 ++-- plugins/calendar/localization/en_US.inc | 6 +- plugins/calendar/skins/default/calendar.css | 66 ++++++++++- 6 files changed, 178 insertions(+), 30 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 91975a3d..b1975ac2 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -448,9 +448,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); @@ -1162,6 +1162,9 @@ class calendar extends rcube_plugin } } + // let this information be cached for 15min + send_future_expire_header(90); + echo $status; exit; } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index fe0925ca..0096f084 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -419,7 +419,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 @@ -488,6 +488,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; @@ -576,6 +582,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) { @@ -602,7 +621,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 { @@ -614,33 +633,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 = '' + dispname + ''; + + // 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 = ''; + + // availability + var avail = data.email ? 'loading' : 'unknown'; + if (edit && data.role == 'ORGANIZER' && data.status == 'ACCEPTED') + avail = 'free'; + // delete icon var icon = rcmail.env.deleteicon ? '' : rcmail.gettext('delete'); var dellink = '' + icon + ''; - var html = '' + - '' + Q(dispname) + '' + - '' + '' + '' + - '' + Q(data.status) + '' + - '' + (data.role != 'OWNER' ? dellink : '') + ''; + var html = '' + select + '' + + '' + dispname + '' + + '' + + '' + Q(data.status) + '' + + '' + dellink + ''; - $('') + var 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) }); }; @@ -1206,7 +1285,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 diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index 01c68d52..be9bde5e 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -282,6 +282,7 @@ abstract class calendar_driver */ public function get_freebusy_list($email, $start, $end) { + sleep(2); return false; } diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index d7ebd10a..edc12b12 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -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], ); } } diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 43183ee3..a74d76db 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -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'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index d9aebb71..b155b790 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -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; } From 489e9a127f22e5828863e39da3bcd2aec307a7c3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 13 Jul 2011 13:43:07 +0200 Subject: [PATCH 3/3] Add small loading throbber --- .../skins/default/images/loading-small.gif | Bin 0 -> 1849 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 plugins/calendar/skins/default/images/loading-small.gif diff --git a/plugins/calendar/skins/default/images/loading-small.gif b/plugins/calendar/skins/default/images/loading-small.gif new file mode 100644 index 0000000000000000000000000000000000000000..d42f72c723644bbf8cf8d6e1b7ff0bea7ddd305a GIT binary patch literal 1849 zcma*odr%wI9tZGc_iT2vk7P+x3@LU(2yGIQCcHvgYTblq0THkTesI znR7n#{hi;OjP2?A&1ME-pkE;9;lqaz1T8KudOV)_`T6wp^p=(uf2Fv%SSFK=kB@u3 zUZGGpJUk2l(CKv5)z$vpzrP~?FHUK&nD<(COPZ{Et0m?db93z;^X^U7=d1QWkq-bw z_z#PGNam*Pcq+w^mln54i-h<~s=yrqB!o6eBrwd4{B{&hHR9I{{bQLC?)mG!al!d3 z<_YADRELlGj+H!fNocrRe7`?$gk5?^y3^@6pC85jwesu>G6*{Wrto)mL5D?z)j-L++&!SNH!MDuhyZ1*)jGIdEgHH>qdP6}DTp>kJ!bZd!oke!Q0J5vRHHSl&=?=ft+HzcfBB?ZKg#rgtngf(C zDL)0I;%QR6Z_49x&crpS#vVCUpEoP)gxevZsOEytw5Vq|*bf39%i#K5VVMGzL^=13 zR-}G|UaU(m68*HWc{(R=BrQG$CK$N`HcDv@S=IAWm(o~Np>ZF8X|_V%3_!1*u`vm}?t+-#K2Qs= zlXouK&f(YJG|lD7kLqye?NcYji#s*HOSm|xQ~{R)Ao3VZxwQ7l4$VQO;kkhWM?7Tq zV7I5-(5BO!(yinIf+@AjEfNKCk>b~u*83@)>qu(a3nA*y_1a7za7?y8mYaN9Z=br4iilUPLeBE8>z7SjUY!R@qdmA$~nkLiZ z9qy{OsKf4~y1^q+D*!YY&=vr^tMUUJQrx*Do>dYDlPO!l`DoZR5vApf95=i4Lz;yXc(}m~6Zh#oS@fHF;-MHT+t;8_YoNLuk zSUE;16g_GThjJ{n2e=qnXWb70jIOhk#;lMy!K4=hr0tBKfB(rz4e z+1U)aJZHl#TYU{%(($JI!F!<+%Jp+Jdk!#Y(|CzO!nka;h@9x_wBI_{i{tgbHY(SI zVOYV2N)E%tOc-CGkW(0fy>Or+s~>c2t0?1P8+jRZNqDzxRf7dRy(%%W_#a<6{e3caEW8DdD|KsdZBw z(K6nRmw&K{D1uvtp&1a;%;nPiAvOlI0*Zq9*kdsS7JEd^O*1FLwTe{>9&A}oMlrlF z`pQPbaQ2zQZ_j_#qk8qy|9IetJFQG!>l{9_AvsafGtVRnQr)xR?bImN4qo-#?gP`}S)0UYaWY9t`6HQwY4B_($TWNu`l?!*nIBy_|7=pQc9cnFU zs%%oO^oje|8ddh7^1-E9_|T~KBxydL{KcW06CqFQ?Ym3~cb^|wPx?lUyCBD|_nRZU zsA}@ctKd0_^(q#G?{G;+$w=8-md|N>W6e5@3AT19SLRCCSyG z=oV%uMus5!Ry9PhrZMnpmb0lXJuACSOl2|krLXzwsrVsNe(8N);u`z?E#W-RKNi9E z;z;dGgCSKfqEhya^?xWt|DBmvWexQ%SfTtu(C0Mdv9@(g3WttPh3GsE1Niv~Ituk; zXltbvX7Jn^Z4>QMtd0~JELrW+CaT^$N~khm@~tceYLLGjk=3uK@XnOQ1!btG^Pus9 zYDnDM@%;wTR-(h+ec@l>g6c`Cs7=j$FkqTfajDLuzmAAh_qHK0!$~G+eL?P46^V2( gTVr?dnQ{(H{Ie1a9NS3OKPMyDfF?Qc5iPGj047fkWdHyG literal 0 HcmV?d00001