diff --git a/plugins/calendar/calendar.js b/plugins/calendar/calendar.js index 103e06c9..d0ea38a0 100644 --- a/plugins/calendar/calendar.js +++ b/plugins/calendar/calendar.js @@ -39,10 +39,15 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // Roundcube calendar client class function rcube_calendar(settings) { + // member vars this.settings = settings; - var me = this; + this.alarm_ids = []; + this.alarm_dialog = null; + this.snooze_popup = null; + this.dismiss_link = null // private vars + var me = this; var day_clicked = day_clicked_ts = 0; var ignore_click = false; @@ -116,8 +121,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { resizable: true, title: null, close: function() { - $dialog.dialog('destroy'); - $dialog.hide(); + $dialog.dialog('destroy').hide(); }, buttons: buttons, minWidth: 320, @@ -178,15 +182,16 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { for (var alarm, i=0; i < event.alarms.length; i++) { alarm = String(event.alarms[i]).split(':'); - $('select.edit-alarm-type').val(alarm[0]); + if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY'; + $('select.edit-alarm-type').val(alarm[1]); - if (alarm[1].match(/@(\d+)/)) { + if (alarm[0].match(/@(\d+)/)) { var ondate = new Date(parseInt(RegExp.$1) * 1000); $('select.edit-alarm-offset').val('@'); $('input.edit-alarm-date').val($.fullCalendar.formatDate(ondate, settings['date_format'])); $('input.edit-alarm-time').val($.fullCalendar.formatDate(ondate, settings['time_format'])); } - else if (alarm[1].match(/([-+])(\d+)([MHD])/)) { + else if (alarm[0].match(/([-+])(\d+)([MHD])/)) { $('input.edit-alarm-value').val(RegExp.$2); $('select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3); } @@ -252,7 +257,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // post data to server var data = { - action: action, start: start.getTime()/1000, end: end.getTime()/1000, allday: allday.checked?1:0, @@ -272,9 +276,9 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { if (alarm) { var val, offset = $('select.edit-alarm-offset').val(); if (offset == '@') - data.alarms = alarm + ':@' + (me.parse_datetime($('input.edit-alarm-time').val(), $('input.edit-alarm-date').val()).getTime()/1000); + data.alarms = '@' + (me.parse_datetime($('input.edit-alarm-time').val(), $('input.edit-alarm-date').val()).getTime()/1000) + ':' + alarm; else if ((val = parseInt($('input.edit-alarm-value').val())) && !isNaN(val) && val >= 0) - data.alarms = alarm + ':' + offset[0] + val + offset[1]; + data.alarms = offset[0] + val + offset[1] + ':' + alarm; } // gather recurrence settings @@ -319,7 +323,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { else data.calendar = calendars.val(); - rcmail.http_post('plugin.event', { e:data }); + rcmail.http_post('plugin.event', { action:action, e:data }); $dialog.dialog("close"); }; @@ -347,8 +351,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { resizable: true, title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'), close: function() { - $dialog.dialog("destroy"); - $dialog.hide(); + $dialog.dialog("destroy").hide(); }, buttons: buttons, minWidth: 440, @@ -416,7 +419,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { this.delete_event = function(event) { // send remove request to plugin if (confirm(rcmail.gettext('deleteventconfirm', 'calendar'))) { - rcmail.http_post('plugin.event', { e:{ action:'remove', id:event.id } }); + rcmail.http_post('plugin.event', { action:'remove', e:{ id:event.id } }); return true; } @@ -426,42 +429,103 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // display a notification for the given pending alarms this.display_alarms = function(alarms) { // clear old alert first - $('#alarm-display').dialog('destroy'); + if (this.alarm_dialog) + this.alarm_dialog.dialog('destroy'); - var event_ids = []; - var display = $('
').attr('id', 'alarm-display'); - for (var html, alarm, i=0; i < alarms.length; i++) { + this.alarm_dialog = $('
').attr('id', 'alarm-display'); + + var actions, adismiss, asnooze, alarm, html, event_ids = []; + for (var actions, html, alarm, i=0; i < alarms.length; i++) { alarm = alarms[i]; - alarm.start = new Date(alarm.start); - alarm.end = new Date(alarm.end); + alarm.start = new Date(alarm.start * 1000); + alarm.end = new Date(alarm.end * 1000); event_ids.push(alarm.id); html = '

' + Q(alarm.title) + '

'; html += '
' + Q(alarm.location) + '
'; html += '
' + Q(event_date_text(alarm)) + '
'; - $('
').addClass('alarm-item').html(html).appendTo(display); + + adismiss = $('').html(rcmail.gettext('dismiss','calendar')).click(function(){ + me.dismiss_link = $(this); + me.dismiss_alarm(me.dismiss_link.data('id'), 0); + }); + asnooze = $('').html(rcmail.gettext('snooze','calendar')).click(function(){ + me.snooze_dropdown($(this)); + }); + actions = $('
').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id)); + + $('
').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog); } - display.appendTo(document.body).dialog({ - modal: true, + var buttons = {}; + buttons[rcmail.gettext('dismissall','calendar')] = function() { + // submit dismissed event_ids to server + me.dismiss_alarm(me.alarm_ids.join(','), 0); + $(this).dialog('close'); + }; + + this.alarm_dialog.appendTo(document.body).dialog({ + modal: false, resizable: true, closeOnEscape: false, - dialogClass: 'alert', + dialogClass: 'alarm', title: rcmail.gettext('alarmtitle', 'calendar'), - buttons: { - "Snooze": function() { - $(this).dialog('close'); - }, - "Dismiss": function() { - $(this).dialog('close'); - } - }, + buttons: buttons, close: function() { - $(this).dialog('destroy'); - // TODO: submit dismissed event_ids to server - $(this).remove(); + $('#alarm-snooze-dropdown').hide(); + $(this).dialog('destroy').remove(); + me.alarm_dialog = null; + me.alarm_ids = null; + }, + drag: function(event, ui) { + $('#alarm-snooze-dropdown').hide(); } - }).data('event_ids', event_ids.join(',')); + }); + this.alarm_ids = event_ids; + }; + + // show a drop-down menu with a selection of snooze times + this.snooze_dropdown = function(link) + { + if (!this.snooze_popup) { + this.snooze_popup = $('#alarm-snooze-dropdown'); + $('#alarm-snooze-dropdown a').click(function(e){ + var time = String(this.href).replace(/.+#/, ''); + me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time); + return false; + }); + } + + // hide visible popup + if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) { + this.snooze_popup.hide(); + this.dismiss_link = null; + } + else { // open popup below the clicked link + var pos = link.offset(); + pos.top += link.height() + 2; + this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show(); + this.dismiss_link = link; + } + }; + + // dismiss or snooze alarms for the given event + this.dismiss_alarm = function(id, snooze) + { + $('#alarm-snooze-dropdown').hide(); + rcmail.http_post('plugin.event', { action:'dismiss', e:{ id:id, snooze:snooze } }); + + // remove dismissed alarm from list + if (this.dismiss_link) { + this.dismiss_link.closest('div.alarm-item').hide(); + var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; }); + if (new_ids.length) + this.alarm_ids = new_ids; + else + this.alarm_dialog.dialog('close'); + } + + this.dismiss_link = null; }; @@ -588,24 +652,22 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { } // send move request to server var data = { - action: 'move', id: event.id, start: event.start.getTime()/1000, end: event.end.getTime()/1000, allday: allDay?1:0 }; - rcmail.http_post('plugin.event', { e:data }); + rcmail.http_post('plugin.event', { action:'move', e:data }); }, // callback for event resizing eventResize : function(event, delta) { // send resize request to server var data = { - action: 'resize', id: event.id, start: event.start.getTime()/1000, end: event.end.getTime()/1000, }; - rcmail.http_post('plugin.event', { e:data }); + rcmail.http_post('plugin.event', { action:'resize', e:data }); } }); diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 07d25cac..884fbb60 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -111,6 +111,9 @@ class calendar extends rcube_plugin } } + /** + * Render the main calendar view from skin template + */ function calendar_view() { $this->rc->output->set_pagetitle($this->gettext('calendar')); @@ -128,6 +131,7 @@ class calendar extends rcube_plugin $this->register_handler('plugin.freebusy_select', array($this->ui, 'freebusy_select')); $this->register_handler('plugin.priority_select', array($this->ui, 'priority_select')); $this->register_handler('plugin.alarm_select', array($this->ui, 'alarm_select')); + $this->register_handler('plugin.snooze_select', array($this->ui, 'snooze_select')); $this->register_handler('plugin.recurrence_form', array($this->ui, 'recurrence_form')); $this->rc->output->set_env('calendar_settings', $this->load_settings()); @@ -321,39 +325,56 @@ class calendar extends rcube_plugin return $p; } + /** + * Dispatcher for event actions initiated by the client + */ function event() { + $action = get_input_value('action', RCUBE_INPUT_POST); $event = get_input_value('e', RCUBE_INPUT_POST); - $success = false; + $success = $reload = false; - switch ($event['action']) { + switch ($action) { case "new": - // create UID for new event - $events['uid'] = strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16)); - $success = $this->driver->new_event($event); - break; + // create UID for new event + $events['uid'] = strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16)); + $success = $this->driver->new_event($event); + $reload = true; + break; case "edit": - $success = $this->driver->edit_event($event); - break; + $success = $this->driver->edit_event($event); + $reload = true; + break; case "resize": - $success = $this->driver->resize_event($event); - break; + $success = $this->driver->resize_event($event); + $reload = true; + break; case "move": - $success = $this->driver->move_event($event); - break; + $success = $this->driver->move_event($event); + $reload = true; + break; case "remove": - $success = $this->driver->remove_event($event); - break; + $success = $this->driver->remove_event($event); + $reload = true; + break; + case "dismiss": + foreach (explode(',', $event['id']) as $id) + $success |= $this->driver->dismiss_alarm($id, $event['snooze']); + break; } - if ($success) { - $this->rc->output->command('plugin.reload_calendar', array()); - } - else { + if (!$success) { $this->rc->output->show_message('calendar.errorsaving', 'error'); } + else if ($reload) { + $this->rc->output->command('plugin.reload_calendar', array()); + } } + /** + * Handler for load-requests from fullcalendar + * This will return pure JSON formatted output + */ function load_events() { $events = $this->driver->load_events(get_input_value('start', RCUBE_INPUT_GET), get_input_value('end', RCUBE_INPUT_GET), get_input_value('source', RCUBE_INPUT_GET)); @@ -368,8 +389,8 @@ class calendar extends rcube_plugin function keep_alive($attr) { $alarms = $this->driver->pending_alarms(time()); - #if ($alarms) - # $this->rc->output->command('plugin.display_alarms', $this->_alarms_output($alarms)); + if ($alarms) + $this->rc->output->command('plugin.display_alarms', $this->_alarms_output($alarms)); } /** @@ -523,7 +544,7 @@ class calendar extends rcube_plugin */ private function _alarms_text($alarm) { - list($action, $trigger) = explode(':', $alarm); + list($trigger, $action) = explode(':', $alarm); $text = ''; switch ($action) { diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index db0592e1..40f235ba 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -28,6 +28,7 @@ abstract class calendar_driver public $alarms = false; public $attendees = false; public $attachments = false; + public $alarm_types = array('DISPLAY'); /** * Get a list of available calendars from this source @@ -150,7 +151,7 @@ abstract class calendar_driver * @param string Event identifier * @param integer Suspend the alarm for this number of seconds */ - abstract function confirm_alarm($event_id, $snooze = 0); + abstract function dismiss_alarm($event_id, $snooze = 0); /** * Save an attachment related to the given event diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 08d83e4f..2a7d309f 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -29,6 +29,7 @@ class database_driver extends calendar_driver public $alarms = true; public $attendees = true; public $attachments = true; + public $alarm_types = array('DISPLAY','EMAIL'); private $rc; private $cal; @@ -229,7 +230,7 @@ class database_driver extends calendar_driver // compute absolute time to notify the user if ($event['alarms']) { - list($action, $trigger) = explode(':', $event['alarms']); + list($trigger, $action) = explode(':', $event['alarms']); $notify = calendar::parse_alaram_value($trigger); if (!empty($notify[1])){ // offset $mult = 1; @@ -249,7 +250,8 @@ class database_driver extends calendar_driver $notify_at = $notify[0]; } - $event['notifyat'] = date('Y-m-d H:i:s', $notify_at); + if ($notify_at > time()) + $event['notifyat'] = date('Y-m-d H:i:s', $notify_at); } else $event['notifyat'] = null; @@ -466,17 +468,12 @@ class database_driver extends calendar_driver /** * Feedback after showing/sending an alarm notification * - * @see Driver:confirm_alarm() + * @see Driver:dismiss_alarm() */ - public function confirm_alarm($event_id, $snooze = 0) + public function dismiss_alarm($event_id, $snooze = 0) { - // set new notifyat time - if ($snooze > 0) { - $event = $this->get_event($event_id); - $notify_at = date('Y-m-d H:i:s', strtotime($event['notifyat']) + $snooze); - } - else // unset notifyat value - $notify_at = null; + // set new notifyat time or unset if not snoozed + $notify_at = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null; $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " @@ -487,6 +484,7 @@ class database_driver extends calendar_driver $notify_at, $event_id ); + return $this->rc->db->affected_rows($query); } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 1a23da31..a4205bdc 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -236,9 +236,9 @@ class kolab_driver extends calendar_driver /** * Feedback after showing/sending an alarm notification * - * @see Driver:confirm_alarm() + * @see Driver:dismiss_alarm() */ - public function confirm_alarm($event_id, $snooze = 0) + public function dismiss_alarm($event_id, $snooze = 0) { // TBD. return false; diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index afb622a2..4dbe335a 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -209,9 +209,9 @@ class calendar_ui { unset($attrib['name']); $select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type')); - $select_type->add( - array($this->calendar->gettext('none'), $this->calendar->gettext('alarmdisplayoption'), $this->calendar->gettext('alarmemailoption')), - array('','DISPLAY','EMAIL')); + $select_type->add($this->calendar->gettext('none'), ''); + foreach ($this->calendar->driver->alarm_types as $type) + $select_type->add($this->calendar->gettext(strtolower("alarm{$type}option")), $type); $input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3)); $input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10)); @@ -241,6 +241,29 @@ class calendar_ui return $html; } + function snooze_select($attrib = array()) + { + $steps = array( + 5 => 'repeatinmin', + 10 => 'repeatinmin', + 15 => 'repeatinmin', + 20 => 'repeatinmin', + 30 => 'repeatinmin', + 60 => 'repeatinhr', + 120 => 'repeatinhrs', + 1440 => 'repeattomorrow', + 10080 => 'repeatinweek', + ); + + $items = array(); + foreach ($steps as $n => $label) { + $items[] = html::tag('li', null, html::a(array('href' => "#" . ($n * 60), 'class' => 'active'), + $this->calendar->gettext(array('name' => $label, 'vars' => array('min' => $n % 60, 'hrs' => intval($n / 60)))))); + } + + return html::tag('ul', $attrib, join("\n", $items)); + } + /** * Generate the form for recurrence settings */ diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 7cb773e1..bf274acb 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -65,6 +65,14 @@ $labels['trigger+D'] = 'days after'; $labels['addalarm'] = 'add alarm'; $labels['defaultalarmtype'] = 'Default reminder setting'; $labels['defaultalarmoffset'] = 'Default reminder time'; +$labels['dismissall'] = 'Dismiss all'; +$labels['dismiss'] = 'Dismiss'; +$labels['snooze'] = 'Snooze'; +$labels['repeatinmin'] = 'Repeat in $min minutes'; +$labels['repeatinhr'] = 'Repeat in 1 hour'; +$labels['repeatinhrs'] = 'Repeat in $hrs hours'; +$labels['repeattomorrow'] = 'Repeat tomorrow'; +$labels['repeatinweek'] = 'Repeat in a week'; $labels['alarmtitle'] = 'Upcoming events'; // event dialog tabs diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 281b90c1..68f5b542 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -355,6 +355,26 @@ a.dropdown-link:after { margin-bottom: 0.3em; } +.alarm-item .alarm-actions { + margin-top: 0.4em; +} + +.alarm-item div.alarm-actions a { + color: #CC0000; + margin-right: 0.8em; + text-decoration: none; +} + +a.alarm-action-snooze:after { + content: ' ▼'; + font-size: 10px; + color: #666; +} + +#alarm-snooze-dropdown { + z-index: 5000; +} + .ui-dialog-buttonset a.dropdown-link { margin-right: 1em; } diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html index 1b0a4e0f..7302caaf 100644 --- a/plugins/calendar/skins/default/templates/calendar.html +++ b/plugins/calendar/skins/default/templates/calendar.html @@ -153,6 +153,9 @@
+
+ +