Make basic calendar functions available in every Roundcube step. Currently used to display alarm notifications
This commit is contained in:
parent
0a3738a0f9
commit
a8579090e7
5 changed files with 238 additions and 141 deletions
|
@ -54,17 +54,23 @@ class calendar extends rcube_plugin
|
|||
$this->load_config();
|
||||
|
||||
// load localizations
|
||||
$this->add_texts('localization/', !$this->rc->action || $this->rc->task != 'calendar');
|
||||
|
||||
// load Calendar user interface which includes jquery-ui
|
||||
$this->require_plugin('jqueryui');
|
||||
$this->add_texts('localization/', $this->rc->task == 'calendar' && !$this->rc->action);
|
||||
|
||||
require($this->home . '/lib/calendar_ui.php');
|
||||
$this->ui = new calendar_ui($this);
|
||||
$this->ui->init();
|
||||
|
||||
$skin = $this->rc->config->get('skin');
|
||||
$this->include_stylesheet('skins/' . $skin . '/calendar.css');
|
||||
// load Calendar user interface which includes jquery-ui
|
||||
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
|
||||
$this->require_plugin('jqueryui');
|
||||
|
||||
$this->ui->init();
|
||||
|
||||
// settings are required in every GUI step
|
||||
$this->rc->output->set_env('calendar_settings', $this->load_settings());
|
||||
|
||||
$skin = $this->rc->config->get('skin');
|
||||
$this->include_stylesheet('skins/' . $skin . '/calendar.css');
|
||||
}
|
||||
|
||||
if ($this->rc->task == 'calendar') {
|
||||
$this->load_driver();
|
||||
|
@ -81,15 +87,6 @@ class calendar extends rcube_plugin
|
|||
$this->register_action('search_events', array($this, 'search_events'));
|
||||
$this->register_action('export_events', array($this, 'export_events'));
|
||||
$this->register_action('randomdata', array($this, 'generate_randomdata'));
|
||||
$this->add_hook('keep_alive', array($this, 'keep_alive'));
|
||||
|
||||
// set user's timezone
|
||||
if ($this->rc->config->get('timezone') === 'auto')
|
||||
$this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z');
|
||||
else
|
||||
$this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active')));
|
||||
|
||||
$this->gmt_offset = $this->timezone * 3600;
|
||||
}
|
||||
else if ($this->rc->task == 'settings') {
|
||||
$this->load_driver();
|
||||
|
@ -99,10 +96,27 @@ class calendar extends rcube_plugin
|
|||
$this->add_hook('preferences_list', array($this, 'preferences_list'));
|
||||
$this->add_hook('preferences_save', array($this, 'preferences_save'));
|
||||
}
|
||||
|
||||
// add hook to display alarms
|
||||
$this->add_hook('keep_alive', array($this, 'keep_alive'));
|
||||
|
||||
// set user's timezone
|
||||
if ($this->rc->config->get('timezone') === 'auto')
|
||||
$this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z');
|
||||
else
|
||||
$this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active')));
|
||||
|
||||
$this->gmt_offset = $this->timezone * 3600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load the backend driver according to local config
|
||||
*/
|
||||
private function load_driver()
|
||||
{
|
||||
if (is_object($this->driver))
|
||||
return;
|
||||
|
||||
$driver_name = $this->rc->config->get('calendar_driver', 'database');
|
||||
$driver_class = $driver_name . '_driver';
|
||||
|
||||
|
@ -480,9 +494,16 @@ class calendar extends rcube_plugin
|
|||
*/
|
||||
function keep_alive($attr)
|
||||
{
|
||||
$this->load_driver();
|
||||
$alarms = $this->driver->pending_alarms(time());
|
||||
if ($alarms)
|
||||
if ($alarms) {
|
||||
// make sure texts and env vars are available on client
|
||||
if ($this->rc->task != 'calendar') {
|
||||
$this->add_texts('localization/', true);
|
||||
$this->rc->output->set_env('snooze_select', $this->ui->snooze_select());
|
||||
}
|
||||
$this->rc->output->command('plugin.display_alarms', $this->_alarms_output($alarms));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
176
plugins/calendar/calendar_base.js
Normal file
176
plugins/calendar/calendar_base.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
+-------------------------------------------------------------------------+
|
||||
| Base Javascript class for the Calendar Plugin |
|
||||
| Version 0.3 beta |
|
||||
| |
|
||||
| This program is free software; you can redistribute it and/or modify |
|
||||
| it under the terms of the GNU General Public License version 2 |
|
||||
| as published by the Free Software Foundation. |
|
||||
| |
|
||||
| This program is distributed in the hope that it will be useful, |
|
||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
| GNU General Public License for more details. |
|
||||
| |
|
||||
| You should have received a copy of the GNU General Public License along |
|
||||
| with this program; if not, write to the Free Software Foundation, Inc., |
|
||||
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
||||
| |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Lazlo Westerhof <hello@lazlo.me> |
|
||||
| Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// Basic setup for Roundcube calendar client class
|
||||
function rcube_calendar(settings)
|
||||
{
|
||||
// member vars
|
||||
this.settings = settings;
|
||||
this.alarm_ids = [];
|
||||
this.alarm_dialog = null;
|
||||
this.snooze_popup = null;
|
||||
this.dismiss_link = null;
|
||||
|
||||
// private vars
|
||||
var me = this;
|
||||
|
||||
// quote html entities
|
||||
var Q = this.quote_html = function(str)
|
||||
{
|
||||
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
// create a nice human-readable string for the date/time range
|
||||
this.event_date_text = function(event)
|
||||
{
|
||||
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
|
||||
if (event.allDay)
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']) : '');
|
||||
else if (duration < 86400 && event.start.getDay() == event.end.getDay())
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' — '
|
||||
+ $.fullCalendar.formatDate(event.end, settings['time_format']);
|
||||
else
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format']) + ' — '
|
||||
+ $.fullCalendar.formatDate(event.end, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.end, settings['time_format']);
|
||||
|
||||
return fromto;
|
||||
};
|
||||
|
||||
// display a notification for the given pending alarms
|
||||
this.display_alarms = function(alarms) {
|
||||
// clear old alert first
|
||||
if (this.alarm_dialog)
|
||||
this.alarm_dialog.dialog('destroy');
|
||||
|
||||
this.alarm_dialog = $('<div>').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 = $.fullCalendar.parseISO8601(alarm.start, true);
|
||||
alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
|
||||
event_ids.push(alarm.id);
|
||||
|
||||
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
|
||||
html += '<div class="event-section">' + Q(alarm.location) + '</div>';
|
||||
html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
|
||||
|
||||
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
|
||||
me.dismiss_link = $(this);
|
||||
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
|
||||
});
|
||||
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(){
|
||||
me.snooze_dropdown($(this));
|
||||
});
|
||||
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
|
||||
|
||||
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
|
||||
}
|
||||
|
||||
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: 'alarm',
|
||||
title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
|
||||
buttons: buttons,
|
||||
close: function() {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
$(this).dialog('destroy').remove();
|
||||
me.alarm_dialog = null;
|
||||
me.alarm_ids = null;
|
||||
},
|
||||
drag: function(event, ui) {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
}
|
||||
});
|
||||
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');
|
||||
// create popup if not found
|
||||
if (!this.snooze_popup.length) {
|
||||
this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
|
||||
this.snooze_popup.html(rcmail.env.snooze_select)
|
||||
}
|
||||
$('#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('calendar/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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/* calendar plugin initialization (for non-calendar tasks) */
|
||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||
if (rcmail.task != 'calendar') {
|
||||
var cal = new rcube_calendar(rcmail.env.calendar_settings);
|
||||
rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
|
||||
}
|
||||
});
|
||||
|
|
@ -22,15 +22,13 @@
|
|||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
// Roundcube calendar client class
|
||||
function rcube_calendar(settings)
|
||||
// Roundcube calendar UI client class
|
||||
function rcube_calendar_ui(settings)
|
||||
{
|
||||
// extend base class
|
||||
rcube_calendar.call(this, settings);
|
||||
|
||||
/*** member vars ***/
|
||||
this.settings = settings;
|
||||
this.alarm_ids = [];
|
||||
this.alarm_dialog = null;
|
||||
this.snooze_popup = null;
|
||||
this.dismiss_link = null;
|
||||
this.selected_event = null;
|
||||
this.selected_calendar = null;
|
||||
this.search_request = null;
|
||||
|
@ -59,12 +57,9 @@ function rcube_calendar(settings)
|
|||
|
||||
|
||||
/*** private methods ***/
|
||||
|
||||
// quote html entities
|
||||
var Q = function(str)
|
||||
{
|
||||
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
var Q = this.quote_html;
|
||||
|
||||
// php equivalent
|
||||
var nl2br = function(str)
|
||||
{
|
||||
|
@ -72,7 +67,8 @@ function rcube_calendar(settings)
|
|||
};
|
||||
|
||||
// from time and date strings to a real date object
|
||||
var parse_datetime = function(time, date) {
|
||||
var parse_datetime = function(time, date)
|
||||
{
|
||||
// we use the utility function from datepicker to parse dates
|
||||
var date = $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings);
|
||||
var time_arr = time.split(/[:.]/);
|
||||
|
@ -82,11 +78,13 @@ function rcube_calendar(settings)
|
|||
};
|
||||
|
||||
// convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
|
||||
var date2unixtime = function(date) {
|
||||
var date2unixtime = function(date)
|
||||
{
|
||||
return date.getTime()/1000 + gmt_offset * 3600;
|
||||
};
|
||||
|
||||
var fromunixtime = function(ts) {
|
||||
var fromunixtime = function(ts)
|
||||
{
|
||||
ts -= gmt_offset * 3600;
|
||||
return new Date(ts * 1000);
|
||||
}
|
||||
|
@ -122,7 +120,7 @@ function rcube_calendar(settings)
|
|||
$('#event-description').show().children('.event-text').html(nl2br(Q(event.description))); // TODO: format HTML with clickable links and stuff
|
||||
|
||||
// render from-to in a nice human-readable way
|
||||
$('#event-date').html(Q(event_date_text(event))).show();
|
||||
$('#event-date').html(Q(me.event_date_text(event))).show();
|
||||
|
||||
if (event.recurrence && event.recurrence_text)
|
||||
$('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
|
||||
|
@ -516,108 +514,6 @@ function rcube_calendar(settings)
|
|||
return false;
|
||||
};
|
||||
|
||||
// display a notification for the given pending alarms
|
||||
this.display_alarms = function(alarms) {
|
||||
// clear old alert first
|
||||
if (this.alarm_dialog)
|
||||
this.alarm_dialog.dialog('destroy');
|
||||
|
||||
this.alarm_dialog = $('<div>').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 = $.fullCalendar.parseISO8601(alarm.start, true);
|
||||
alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
|
||||
event_ids.push(alarm.id);
|
||||
|
||||
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
|
||||
html += '<div class="event-section">' + Q(alarm.location) + '</div>';
|
||||
html += '<div class="event-section">' + Q(event_date_text(alarm)) + '</div>';
|
||||
|
||||
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
|
||||
me.dismiss_link = $(this);
|
||||
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
|
||||
});
|
||||
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(){
|
||||
me.snooze_dropdown($(this));
|
||||
});
|
||||
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
|
||||
|
||||
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
|
||||
}
|
||||
|
||||
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: 'alarm',
|
||||
title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
|
||||
buttons: buttons,
|
||||
close: function() {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
$(this).dialog('destroy').remove();
|
||||
me.alarm_dialog = null;
|
||||
me.alarm_ids = null;
|
||||
},
|
||||
drag: function(event, ui) {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
}
|
||||
});
|
||||
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('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;
|
||||
};
|
||||
|
||||
// opens a jquery UI dialog with event properties (or empty for creating a new calendar)
|
||||
this.calendar_edit_dialog = function(calendar)
|
||||
{
|
||||
|
@ -1167,7 +1063,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
|
||||
|
||||
// let's go
|
||||
var cal = new rcube_calendar(rcmail.env.calendar_settings);
|
||||
var cal = new rcube_calendar_ui(rcmail.env.calendar_settings);
|
||||
|
||||
$(window).resize(function() {
|
||||
$('#calendar').fullCalendar('option', 'height', $('#main').height());
|
|
@ -331,8 +331,9 @@ class database_driver extends calendar_driver
|
|||
$event['recurrence'] = rtrim($rrule, ';');
|
||||
$event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
|
||||
|
||||
if (isset($event['allday']))
|
||||
if (isset($event['allday'])) {
|
||||
$event['all_day'] = $event['allday'] ? 1 : 0;
|
||||
}
|
||||
|
||||
// compute absolute time to notify the user
|
||||
$event['notifyat'] = $this->_get_notification($event);
|
||||
|
|
|
@ -45,6 +45,10 @@ class calendar_ui
|
|||
'label' => 'calendar.calendar',
|
||||
'href' => './?_task=calendar',
|
||||
), 'taskbar');
|
||||
|
||||
// load basic client script (which - unfortunately - requires fullcalendar)
|
||||
$this->calendar->include_script('lib/js/fullcalendar.js');
|
||||
$this->calendar->include_script('calendar_base.js');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,9 +66,8 @@ class calendar_ui
|
|||
*/
|
||||
public function addJS()
|
||||
{
|
||||
$this->calendar->include_script('lib/js/fullcalendar.js');
|
||||
$this->calendar->include_script('lib/js/jquery.miniColors.min.js');
|
||||
$this->calendar->include_script('calendar.js');
|
||||
$this->calendar->include_script('calendar_ui.js');
|
||||
$this->calendar->include_script('lib/js/jquery.miniColors.min.js');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue