Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube

This commit is contained in:
Aleksander Machniak 2011-06-08 11:34:05 +02:00
commit 7de02c94d1
16 changed files with 543 additions and 62 deletions

View file

@ -15,7 +15,7 @@
- List (Agenda) view
- Individual days selection
+ Show list of calendars in a (hideable) drawer
- View: 3.1: Folder list
+ View: 3.1: Folder list
- View: 3.2: Add / Remove / Rename / Share Folders
+ View: 3.6: Combined calendar view (Turn calendars on/off)
+ View: 3.7: Small month overview calendar
@ -30,8 +30,8 @@
+ Support for multiple calendars (replace categories)
- Remember last visited view
- Allow user to create/edit/delete calendars
- Colors for calendars should be user-configurable
+ Allow user to create/edit/delete calendars
+ Colors for calendars should be user-configurable
- ICS parser/generator ((http://code.google.com/p/qcal/)
- Importing ICS files
- Create/manage invdividual views

View file

@ -30,11 +30,13 @@ function rcube_calendar(settings)
this.alarm_ids = [];
this.alarm_dialog = null;
this.snooze_popup = null;
this.dismiss_link = null
this.dismiss_link = null;
this.eventcount = [];
/*** private vars ***/
var me = this;
var fcselector = '#calendar';
var day_clicked = day_clicked_ts = 0;
var ignore_click = false;
@ -157,14 +159,15 @@ function rcube_calendar(settings)
minWidth: 320,
width: 420
}).show();
/*
// add link for "more options" drop-down
$('<a>')
.attr('href', '#')
.html('More Options')
.addClass('dropdown-link')
.click(function(){ return false; })
.insertBefore($dialog.parent().find('.ui-dialog-buttonset').children().first());
*/
};
// bring up the event dialog (jquery-ui popup)
@ -297,6 +300,7 @@ function rcube_calendar(settings)
// post data to server
var data = {
calendar: event.calendar,
start: start.getTime()/1000,
end: end.getTime()/1000,
allday: allday.checked?1:0,
@ -308,7 +312,7 @@ function rcube_calendar(settings)
priority: priority.val(),
sensitivity: sensitivity.val(),
recurrence: '',
alarms:'',
alarms: ''
};
// serialize alarm settings
@ -459,7 +463,7 @@ function rcube_calendar(settings)
],
close: function(){
$dialog.dialog("destroy").hide();
$('#calendar').fullCalendar('refetchEvents');
$(fcselector).fullCalendar('refetchEvents');
}
}).show();
@ -473,7 +477,7 @@ function rcube_calendar(settings)
this.add_event = function() {
if (this.selected_calendar) {
var now = new Date();
var date = $('#calendar').fullCalendar('getDate') || now;
var date = $(fcselector).fullCalendar('getDate') || now;
date.setHours(now.getHours()+1);
date.setMinutes(0);
var end = new Date(date.getTime());
@ -599,10 +603,88 @@ function rcube_calendar(settings)
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)
{
// close show dialog first
var $dialog = $("#calendarform").dialog('close');
if (!calendar)
calendar = { name:'', color:'cc0000' };
// reset form first
$('#calendarform > form').get(0).reset();
var name = $('#calendar-name').val(calendar.name);
var color = $('#calendar-color').val(calendar.color).miniColors('value', calendar.color);
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save', 'calendar')] = function() {
// TODO: do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidcalendarproperties', 'calendar'));
name.select();
return;
}
// post data to server
var data = {
name: name.val(),
color: color.val().replace(/^#/, '')
};
if (calendar.id)
data.id = calendar.id;
rcmail.http_post('plugin.calendar', { action:(calendar.id ? 'edit' : 'new'), c:data });
$dialog.dialog("close");
};
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
title: rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'),
close: function() {
$dialog.dialog("destroy").hide();
},
buttons: buttons,
minWidth: 400,
width: 420
}).show();
name.select();
};
this.calendar_remove = function(calendar)
{
if (confirm(rcmail.gettext('deletecalendarconfirm', 'calendar'))) {
rcmail.http_post('plugin.calendar', { action:'remove', c:{ id:calendar.id } });
return true;
}
return false;
};
this.calendar_destroy_source = function(id)
{
if (this.calendars[id]) {
$(fcselector).fullCalendar('removeEventSource', this.calendars[id]);
$(rcmail.get_folder_li(id, 'rcmlical')).remove();
$('#edit-calendar option[value="'+id+'"]').remove();
delete this.calendars[id];
}
};
/*** startup code ***/
// create list of event sources AKA calendars
this.calendars = {};
var li, cal, event_sources = [];
var li, cal, active, event_sources = [];
for (var id in rcmail.env.calendars) {
cal = rcmail.env.calendars[id];
this.calendars[id] = $.extend({
@ -611,6 +693,8 @@ function rcube_calendar(settings)
className: 'fc-event-cal-'+id,
id: id
}, cal);
if ((active = ($.inArray(String(id), settings.hidden_calendars) < 0)))
event_sources.push(this.calendars[id]);
// init event handler on calendar list checkbox
@ -618,13 +702,24 @@ function rcube_calendar(settings)
$('#'+li.id+' input').click(function(e){
var id = $(this).data('id');
if (me.calendars[id]) { // add or remove event source on click
var action = this.checked ? 'addEventSource' : 'removeEventSource';
$('#calendar').fullCalendar(action, me.calendars[id]);
var action;
if (this.checked) {
action = 'addEventSource';
settings.hidden_calendars = $.map(settings.hidden_calendars, function(v){ return v == id ? null : v; });
}
}).data('id', id);
else {
action = 'removeEventSource';
settings.hidden_calendars.push(id);
}
$(fcselector).fullCalendar(action, me.calendars[id]);
rcmail.save_pref({ name:'hidden_calendars', value:settings.hidden_calendars.join(',') });
}
}).data('id', id).get(0).checked = active;
$(li).click(function(e){
var id = $(this).data('id');
rcmail.select_folder(id, me.selected_calendar, 'rcmlical');
rcmail.enable_command('plugin.calendar-edit','plugin.calendar-remove', true);
me.selected_calendar = id;
}).data('id', id);
}
@ -636,14 +731,15 @@ function rcube_calendar(settings)
}
// initalize the fullCalendar plugin
$('#calendar').fullCalendar({
$(fcselector).fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'agendaDay,agendaWeek,month'
},
aspectRatio: 1,
height: $(window).height() - 95,
ignoreTimezone: false, // will translate event dates to the client's timezone
height: $(window).height() - 96,
eventSources: event_sources,
monthNames : settings['months'],
monthNamesShort : settings['months_short'],
@ -679,7 +775,23 @@ function rcube_calendar(settings)
},
// event rendering
eventRender: function(event, element, view) {
if(view.name != "month") {
element.attr('title', event.title);
if (view.name == 'month') {
/* attempt to limit the number of events displayed
(could also be used to init fish-eye-view)
var max = 4; // to be derrived from window size
var sday = event.start.getMonth()*12 + event.start.getDate();
var eday = event.end.getMonth()*12 + event.end.getDate();
if (!me.eventcount[sday]) me.eventcount[sday] = 1;
else me.eventcount[sday]++;
if (!me.eventcount[eday]) me.eventcount[eday] = 1;
else if (eday != sday) me.eventcount[eday]++;
if (me.eventcount[sday] > max || me.eventcount[eday] > max)
return false;
*/
}
else {
if (event.location) {
element.find('div.fc-event-title').after('<div class="fc-event-location">@&nbsp;' + Q(event.location) + '</div>');
}
@ -724,6 +836,7 @@ function rcube_calendar(settings)
// send move request to server
var data = {
id: event.id,
calendar: event.calendar,
start: event.start.getTime()/1000,
end: event.end.getTime()/1000,
allday: allDay?1:0
@ -738,13 +851,21 @@ function rcube_calendar(settings)
// send resize request to server
var data = {
id: event.id,
calendar: event.calendar,
start: event.start.getTime()/1000,
end: event.end.getTime()/1000,
end: event.end.getTime()/1000
};
if (event.recurrence)
recurring_edit_confirm(data, 'resize');
else
rcmail.http_post('plugin.event', { action:'resize', e:data });
},
viewDisplay: function(view) {
me.eventcount = [];
window.setTimeout(function(){ $('div.fc-content').css('overflow', view.name == 'month' ? 'auto' : 'hidden') }, 10);
},
windowResize: function(view) {
me.eventcount = [];
}
});
@ -760,7 +881,7 @@ function rcube_calendar(settings)
var diff = (kw - base_kw) * 7 * 86400000;
// select monday of the chosen calendar week
var date = new Date(base_date.getTime() - day_off * 86400000 + diff);
$('#calendar').fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
$(fcselector).fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
$("#datepicker").datepicker('setDate', date);
window.setTimeout(init_week_events, 10);
}).css('cursor', 'pointer');
@ -775,7 +896,7 @@ function rcube_calendar(settings)
onSelect: function(dateText, inst) {
ignore_click = true;
var d = $("#datepicker").datepicker('getDate'); //parse_datetime('0:0', dateText);
$('#calendar').fullCalendar('gotoDate', d).fullCalendar('select', d, d, true);
$(fcselector).fullCalendar('gotoDate', d).fullCalendar('select', d, d, true);
window.setTimeout(init_week_events, 10);
},
onChangeMonthYear: function(year, month, inst) {
@ -784,14 +905,14 @@ function rcube_calendar(settings)
d.setYear(year);
d.setMonth(month - 1);
$("#datepicker").data('year', year).data('month', month);
//$('#calendar').fullCalendar('gotoDate', d).fullCalendar('setDate', d);
//$(fcselector).fullCalendar('gotoDate', d).fullCalendar('setDate', d);
},
}));
window.setTimeout(init_week_events, 10);
// react on fullcalendar buttons
var fullcalendar_update = function() {
var d = $('#calendar').fullCalendar('getDate');
var d = $(fcselector).fullCalendar('getDate');
$("#datepicker").datepicker('setDate', d);
window.setTimeout(init_week_events, 10);
};
@ -889,6 +1010,8 @@ function rcube_calendar(settings)
});
$('#edit-recurrence-enddate').datepicker(datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) });
$('#calendar-color').miniColors();
// hide event dialog when clicking somewhere into document
$(document).bind('mousedown', dialog_check);
@ -901,21 +1024,26 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// configure toobar buttons
rcmail.register_command('plugin.addevent', function(){ cal.add_event(); }, true);
// configure list operations
rcmail.register_command('plugin.calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
rcmail.register_command('plugin.calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('plugin.calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);
// export events
rcmail.register_command('plugin.export', function(){ rcmail.goto_url('plugin.export_events', { source:cal.selected_calendar }); }, true);
rcmail.enable_command('plugin.export', true);
// register callback commands
rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
rcmail.addEventListener('plugin.reload_calendar', function(){ $('#calendar').fullCalendar('refetchEvents'); });
rcmail.addEventListener('plugin.reload_calendar', function(p){ $('#calendar').fullCalendar('refetchEvents', cal.calendars[p.source]); });
rcmail.addEventListener('plugin.calendar_destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
// let's go
var cal = new rcube_calendar(rcmail.env.calendar_settings);
cal.init_ui();
$(window).resize(function() {
$('#calendar').fullCalendar('option', 'height', $(window).height() - 95);
$('#calendar').fullCalendar('option', 'height', $(window).height() - 96);
}).resize();
// show toolbar

View file

@ -50,7 +50,7 @@ class calendar extends rcube_plugin
}
// load localizations
$this->add_texts('localization/', true);
$this->add_texts('localization/', !$this->rc->action || $this->rc->task != 'calendar');
// load Calendar user interface which includes jquery-ui
$this->require_plugin('jqueryui');
@ -71,10 +71,11 @@ class calendar extends rcube_plugin
// register calendar actions
$this->register_action('index', array($this, 'calendar_view'));
$this->register_action('plugin.calendar', array($this, 'calendar_view'));
$this->register_action('plugin.event', array($this, 'event_action'));
$this->register_action('plugin.calendar', array($this, 'calendar_action'));
$this->register_action('plugin.load_events', array($this, 'load_events'));
$this->register_action('plugin.event', array($this, 'event'));
$this->register_action('plugin.export_events', array($this, 'export_events'));
$this->register_action('plugin.randomdata', array($this, 'generate_randomdata'));
$this->add_hook('keep_alive', array($this, 'keep_alive'));
// set user's timezone
@ -180,7 +181,7 @@ class calendar extends rcube_plugin
'title' => html::label($field_id, Q($this->gettext('default_view'))),
'content' => $select->show($this->rc->config->get('calendar_default_view', "agendaWeek")),
);
/*
$field_id = 'rcmfd_time_format';
$choices = array('HH:mm', 'H:mm', 'h:mmt');
$select = new html_select(array('name' => '_time_format', 'id' => $field_id));
@ -189,7 +190,7 @@ class calendar extends rcube_plugin
'title' => html::label($field_id, Q($this->gettext('time_format'))),
'content' => $select->show($this->rc->config->get('calendar_time_format', "HH:mm")),
);
*/
$field_id = 'rcmfd_timeslot';
$choices = array('1', '2', '3', '4', '6');
$select = new html_select(array('name' => '_timeslots', 'id' => $field_id));
@ -216,8 +217,8 @@ class calendar extends rcube_plugin
$field_id = 'rcmfd_alarm';
$select_type = new html_select(array('name' => '_alarm_type', 'id' => $field_id));
$select_type->add($this->gettext('none'), '');
$select_type->add($this->gettext('alarmdisplayoption'), 'DISPLAY');
$select_type->add($this->gettext('alarmemailoption'), 'EMAIL');
foreach ($this->driver->alarm_types as $type)
$select_type->add($this->gettext(strtolower("alarm{$type}option")), $type);
$input_value = new html_inputfield(array('name' => '_alarm_value', 'id' => $field_id . 'value', 'size' => 3));
$select_offset = new html_select(array('name' => '_alarm_offset', 'id' => $field_id . 'offset'));
@ -246,7 +247,7 @@ class calendar extends rcube_plugin
$field_class = 'rcmfd_category_' . str_replace(' ', '_', $name);
$category_remove = new html_inputfield(array('type' => 'button', 'value' => 'X', 'class' => 'button', 'onclick' => '$(this).parent().remove()', 'title' => $this->gettext('remove_category')));
$category_name = new html_inputfield(array('name' => "_categories[$key]", 'class' => $field_class, 'size' => 30));
$category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => $field_class, 'size' => 6));
$category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => "$field_class colors", 'size' => 6));
$categories_list .= html::div(null, $category_name->show($name) . '&nbsp;' . $category_color->show($color) . '&nbsp;' . $category_remove->show());
}
@ -265,11 +266,17 @@ class calendar extends rcube_plugin
var name = $("#rcmfd_new_category").val();
if (name.length) {
var input = $("<input>").attr("type", "text").attr("name", "_categories[]").attr("size", 30).val(name);
var color = $("<input>").attr("type", "text").attr("name", "_colors[]").attr("size", 6).val("000000");
var color = $("<input>").attr("type", "text").attr("name", "_colors[]").attr("size", 6).addClass("colors").val("000000");
var button = $("<input>").attr("type", "button").attr("value", "X").addClass("button").click(function(){ $(this).parent().remove() });
$("<div>").append(input).append("&nbsp;").append(color).append("&nbsp;").append(button).appendTo("#calendarcategories");
color.miniColors();
}
}');
// include color picker
$this->include_script('lib/js/jquery.miniColors.min.js');
$this->include_stylesheet('skins/' .$this->rc->config->get('skin') . '/jquery.miniColors.css');
$this->rc->output->add_script('$("input.colors").miniColors()', 'docready');
}
}
@ -333,10 +340,43 @@ class calendar extends rcube_plugin
return $p;
}
/**
* Dispatcher for calendar actions initiated by the client
*/
function calendar_action()
{
$action = get_input_value('action', RCUBE_INPUT_POST);
$cal = get_input_value('c', RCUBE_INPUT_POST);
$success = $reload = false;
switch ($action) {
case "new":
$success = $this->driver->create_calendar($cal);
$reload = true;
break;
case "edit":
$success = $this->driver->edit_calendar($cal);
$reload = true;
break;
case "remove":
if ($success = $this->driver->remove_calendar($cal))
$this->rc->output->command('plugin.calendar_destroy_source', array('id' => $cal['id']));
break;
}
if ($success)
$this->rc->output->show_message('successfullysaved', 'confirmation');
else
$this->rc->output->show_message('calendar.errorsaving', 'error');
if ($success && $reload)
$this->rc->output->redirect('');
}
/**
* Dispatcher for event actions initiated by the client
*/
function event()
function event_action()
{
$action = get_input_value('action', RCUBE_INPUT_POST);
$event = get_input_value('e', RCUBE_INPUT_POST);
@ -377,7 +417,7 @@ class calendar extends rcube_plugin
$this->rc->output->show_message('calendar.errorsaving', 'error');
if ($success && $reload)
$this->rc->output->command('plugin.reload_calendar', array());
$this->rc->output->command('plugin.reload_calendar', array('source' => $event['calendar']));
}
/**
@ -473,6 +513,9 @@ class calendar extends rcube_plugin
);
$settings['today'] = rcube_label('today');
// user prefs
$settings['hidden_calendars'] = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
return $settings;
}
@ -500,7 +543,8 @@ class calendar extends rcube_plugin
function fromGMT($datetime, $user_tz = true)
{
$tz = $user_tz ? $this->gmt_offset : date('Z');
return strtotime($datetime) + $tz;
$ts = is_numeric($datetime) ? $datetime : strtotime($datetime);
return $ts + $tz;
}
/**
@ -695,4 +739,53 @@ class calendar extends rcube_plugin
));
}
/**
* TEMPORARY: generate random event data for testing
* Create events by opening http://<roundcubeurl>/?_task=calendar&_action=plugin.randomdata&_num=500
*/
public function generate_randomdata()
{
$cats = array_keys($this->driver->list_categories());
$cals = $this->driver->list_calendars();
$num = $_REQUEST['_num'] ? intval($_REQUEST['_num']) : 100;
while ($count++ < $num) {
$start = round((time() + rand(-2600, 2600) * 1000) / 300) * 300;
$duration = round(rand(30, 360) / 30) * 30 * 60;
$allday = rand(0,20) > 18;
$alarm = rand(-30,12) * 5;
$fb = rand(0,2);
if (date('G', $start) > 23)
$start -= 3600;
if ($allday) {
$start = strtotime(date('Y-m-d 00:00:00', $start));
$duration = 86399;
}
$title = '';
$len = rand(2, 12);
$words = explode(" ", "The Hough transform is named after Paul Hough who patented the method in 1962. It is a technique which can be used to isolate features of a particular shape within an image. Because it requires that the desired features be specified in some parametric form, the classical Hough transform is most commonly used for the de- tection of regular curves such as lines, circles, ellipses, etc. A generalized Hough transform can be employed in applications where a simple analytic description of a feature(s) is not possible. Due to the computational complexity of the generalized Hough algorithm, we restrict the main focus of this discussion to the classical Hough transform. Despite its domain restrictions, the classical Hough transform (hereafter referred to without the classical prefix ) retains many applications, as most manufac- tured parts (and many anatomical parts investigated in medical imagery) contain feature boundaries which can be described by regular curves. The main advantage of the Hough transform technique is that it is tolerant of gaps in feature boundary descriptions and is relatively unaffected by image noise.");
$chars = "!# abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890";
for ($i = 0; $i < $len; $i++)
$title .= $words[rand(0,count($words)-1)] . " ";
$this->driver->new_event(array(
'uid' => $this->generate_uid(),
'start' => $start,
'end' => $start + $duration,
'allday' => $allday,
'title' => rtrim($title),
'free_busy' => $fb == 2 ? 'outofoffice' : ($fb ? 'busy' : 'free'),
'categories' => $cats[array_rand($cats)],
'calendar' => array_rand($cals),
'alarms' => $alarm > 0 ? "-{$alarm}M:DISPLAY" : '',
'priority' => 1,
));
}
$this->rc->output->redirect('');
}
}

View file

@ -81,6 +81,24 @@ abstract class calendar_driver
*/
abstract function create_calendar($prop);
/**
* Update properties of an existing calendar
*
* @param array Hash array with calendar properties
* id: Calendar Identifier
* name: Calendar name
* color: The color of the calendar
* @return boolean True on success, Fales on failure
*/
abstract function edit_calendar($prop);
/**
* Delete the given calendar with all its contents
*
* @return boolean True on success, Fales on failure
*/
abstract function remove_calendar($prop);
/**
* Add a single event to the database
*

View file

@ -75,7 +75,7 @@ class database_driver extends calendar_driver
if (!empty($this->rc->user->ID)) {
$calendar_ids = array();
$result = $this->rc->db->query(
"SELECT * FROM " . $this->db_calendars . "
"SELECT *, calendar_id AS id FROM " . $this->db_calendars . "
WHERE user_id=?",
$this->rc->user->ID
);
@ -121,11 +121,60 @@ class database_driver extends calendar_driver
);
if ($result)
return $this->rc->db->insert_id($this->$sequence_calendars);
return $this->rc->db->insert_id($this->sequence_calendars);
return false;
}
/**
* Update properties of an existing calendar
*
* @see calendar_driver::edit_calendar()
*/
public function edit_calendar($prop)
{
$query = $this->rc->db->query(
"UPDATE " . $this->db_calendars . "
SET name=?, color=?
WHERE calendar_id=?
AND user_id=?",
$prop['name'],
$prop['color'],
$prop['id'],
$this->rc->user->ID
);
return $this->rc->db->affected_rows($query);
}
/**
* Delete the given calendar with all its contents
*
* @see calendar_driver::remove_calendar()
*/
public function remove_calendar($prop)
{
if (!$this->calendars[$prop['id']])
return false;
// delete all events of this calendar
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_events . "
WHERE calendar_id=?",
$prop['id']
);
// TODO: also delete linked attachments
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_calendars . "
WHERE calendar_id=?",
$prop['id']
);
return $this->rc->db->affected_rows($query);
}
/**
* Add a single event to the database
*
@ -317,7 +366,7 @@ class database_driver extends calendar_driver
$notify_at = $notify[0];
}
if ($notify_at > time())
if ($event['start'] > time())
return date('Y-m-d H:i:s', $notify_at);
}
@ -508,7 +557,7 @@ class database_driver extends calendar_driver
$success = $this->rc->db->affected_rows($query);
if ($success && $update_master)
$this->_update_event($master, true);
console($savemode, $master['id'], $success);
return $success;
}
@ -699,8 +748,15 @@ console($savemode, $master['id'], $success);
*/
public function remove_category($name)
{
// TBD. alter events accordingly
return false;
$query = $this->rc->db->query(
"UPDATE " . $this->db_events . "
SET categories=''
WHERE categories=?
AND calendar_id IN (" . $this->calendar_ids . ")",
$name
);
return $this->rc->db->affected_rows($query);
}
/**
@ -708,8 +764,16 @@ console($savemode, $master['id'], $success);
*/
public function replace_category($oldname, $name, $color)
{
// TBD. alter events accordingly
return false;
$query = $this->rc->db->query(
"UPDATE " . $this->db_events . "
SET categories=?
WHERE categories=?
AND calendar_id IN (" . $this->calendar_ids . ")",
$name,
$oldname
);
return $this->rc->db->affected_rows($query);
}
}

View file

@ -54,6 +54,7 @@ class calendar_ui
{
$skin = $this->rc->config->get('skin');
$this->calendar->include_stylesheet('skins/' . $skin . '/fullcalendar.css');
$this->calendar->include_stylesheet('skins/' . $skin . '/jquery.miniColors.css');
}
/**
@ -62,6 +63,7 @@ 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');
}
@ -126,6 +128,7 @@ class calendar_ui
function calendar_list($attrib = array())
{
$calendars = $this->calendar->driver->list_calendars();
$hidden = explode(',', $this->rc->config->get('hidden_calendars', ''));
$li = '';
foreach ((array)$calendars as $id => $prop) {
@ -137,7 +140,7 @@ class calendar_ui
$html_id = html_identifier($id);
$li .= html::tag('li', array('id' => 'rcmlical' . $html_id, 'class' =>'cal-' . asciiwords($id, true)),
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => true), '') . html::span(null, Q($prop['name'])));
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => !in_array($id, $hidden)), '') . html::span(null, Q($prop['name'])));
}
$this->rc->output->set_env('calendars', $jsenv);

View file

@ -0,0 +1,33 @@
--- js/fullcalendar.js.orig 2011-06-04 13:45:44.000000000 -0600
+++ js/fullcalendar.js 2011-06-05 18:58:59.000000000 -0600
@@ -500,8 +500,8 @@
}
- function refetchEvents() {
- fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
+ function refetchEvents(source) {
+ fetchEvents(currentView.visStart, currentView.visEnd, source); // will call reportEvents
}
@@ -897,15 +897,16 @@
}
- function fetchEvents(start, end) {
+ function fetchEvents(start, end, src) {
rangeStart = start;
rangeEnd = end;
cache = [];
var fetchID = ++currentFetchID;
var len = sources.length;
- pendingSourceCnt = len;
+ pendingSourceCnt = typeof src == 'undefined' ? len : 1;
for (var i=0; i<len; i++) {
- fetchEventSource(sources[i], fetchID);
+ if (typeof src == 'undefined' || src == sources[i])
+ fetchEventSource(sources[i], fetchID);
}
}

View file

@ -500,8 +500,8 @@ function Calendar(element, options, eventSources) {
}
function refetchEvents() {
fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
function refetchEvents(source) {
fetchEvents(currentView.visStart, currentView.visEnd, source); // will call reportEvents
}
@ -897,14 +897,15 @@ function EventManager(options, _sources) {
}
function fetchEvents(start, end) {
function fetchEvents(start, end, src) {
rangeStart = start;
rangeEnd = end;
cache = [];
var fetchID = ++currentFetchID;
var len = sources.length;
pendingSourceCnt = len;
pendingSourceCnt = typeof src == 'undefined' ? len : 1;
for (var i=0; i<len; i++) {
if (typeof src == 'undefined' || src == sources[i])
fetchEventSource(sources[i], fetchID);
}
}

View file

@ -0,0 +1,17 @@
// http://plugins.jquery.com/project/jQueryMiniColors
if(jQuery)(function($){$.extend($.fn,{miniColors:function(o,data){var create=function(input,o,data){var color=cleanHex(input.val());if(!color)color='FFFFFF';var hsb=hex2hsb(color);var trigger=$('<a class="miniColors-trigger" style="background-color: #'+color+'" href="#"></a>');trigger.insertAfter(input);input.addClass('miniColors').attr('maxlength',7).attr('autocomplete','off');input.data('trigger',trigger);input.data('hsb',hsb);if(o.change)input.data('change',o.change);if(o.readonly)input.attr('readonly',true);if(o.disabled)disable(input);trigger.bind('click.miniColors',function(event){event.preventDefault();input.trigger('focus');});input.bind('focus.miniColors',function(event){show(input);});input.bind('blur.miniColors',function(event){var hex=cleanHex(input.val());input.val(hex?'#'+hex:'');});input.bind('keydown.miniColors',function(event){if(event.keyCode===9)hide(input);});input.bind('keyup.miniColors',function(event){var filteredHex=input.val().replace(/[^A-F0-9#]/ig,'');input.val(filteredHex);if(!setColorFromInput(input)){input.data('trigger').css('backgroundColor','#FFF');}});input.bind('paste.miniColors',function(event){setTimeout(function(){input.trigger('keyup');},5);});};var destroy=function(input){hide();input=$(input);input.data('trigger').remove();input.removeAttr('autocomplete');input.removeData('trigger');input.removeData('selector');input.removeData('hsb');input.removeData('huePicker');input.removeData('colorPicker');input.removeData('mousebutton');input.removeData('moving');input.unbind('click.miniColors');input.unbind('focus.miniColors');input.unbind('blur.miniColors');input.unbind('keyup.miniColors');input.unbind('keydown.miniColors');input.unbind('paste.miniColors');$(document).unbind('mousedown.miniColors');$(document).unbind('mousemove.miniColors');};var enable=function(input){input.attr('disabled',false);input.data('trigger').css('opacity',1);};var disable=function(input){hide(input);input.attr('disabled',true);input.data('trigger').css('opacity',.5);};var show=function(input){if(input.attr('disabled'))return false;hide();var selector=$('<div class="miniColors-selector"></div>');selector.append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"></div></div>');selector.append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>');selector.css({top:input.is(':visible')?input.offset().top+input.outerHeight():input.data('trigger').offset().top+input.data('trigger').outerHeight(),left:input.is(':visible')?input.offset().left:input.data('trigger').offset().left,display:'none'}).addClass(input.attr('class'));var hsb=input.data('hsb');selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));var colorPosition=input.data('colorPosition');if(!colorPosition)colorPosition=getColorPositionFromHSB(hsb);selector.find('.miniColors-colorPicker').css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=input.data('huePosition');if(!huePosition)huePosition=getHuePositionFromHSB(hsb);selector.find('.miniColors-huePicker').css('top',huePosition.y+'px');input.data('selector',selector);input.data('huePicker',selector.find('.miniColors-huePicker'));input.data('colorPicker',selector.find('.miniColors-colorPicker'));input.data('mousebutton',0);$('BODY').append(selector);selector.fadeIn(100);selector.bind('selectstart',function(){return false;});$(document).bind('mousedown.miniColors',function(event){input.data('mousebutton',1);if($(event.target).parents().andSelf().hasClass('miniColors-colors')){event.preventDefault();input.data('moving','colors');moveColor(input,event);}
if($(event.target).parents().andSelf().hasClass('miniColors-hues')){event.preventDefault();input.data('moving','hues');moveHue(input,event);}
if($(event.target).parents().andSelf().hasClass('miniColors-selector')){event.preventDefault();return;}
if($(event.target).parents().andSelf().hasClass('miniColors'))return;hide(input);});$(document).bind('mouseup.miniColors',function(event){input.data('mousebutton',0);input.removeData('moving');});$(document).bind('mousemove.miniColors',function(event){if(input.data('mousebutton')===1){if(input.data('moving')==='colors')moveColor(input,event);if(input.data('moving')==='hues')moveHue(input,event);}});};var hide=function(input){if(!input)input='.miniColors';$(input).each(function(){var selector=$(this).data('selector');$(this).removeData('selector');$(selector).fadeOut(100,function(){$(this).remove();});});$(document).unbind('mousedown.miniColors');$(document).unbind('mousemove.miniColors');};var moveColor=function(input,event){var colorPicker=input.data('colorPicker');colorPicker.hide();var position={x:event.clientX-input.data('selector').find('.miniColors-colors').offset().left+$(document).scrollLeft()-5,y:event.clientY-input.data('selector').find('.miniColors-colors').offset().top+$(document).scrollTop()-5};if(position.x<=-5)position.x=-5;if(position.x>=144)position.x=144;if(position.y<=-5)position.y=-5;if(position.y>=144)position.y=144;input.data('colorPosition',position);colorPicker.css('left',position.x).css('top',position.y).show();var s=Math.round((position.x+5)*.67);if(s<0)s=0;if(s>100)s=100;var b=100-Math.round((position.y+5)*.67);if(b<0)b=0;if(b>100)b=100;var hsb=input.data('hsb');hsb.s=s;hsb.b=b;setColor(input,hsb,true);};var moveHue=function(input,event){var huePicker=input.data('huePicker');huePicker.hide();var position={y:event.clientY-input.data('selector').find('.miniColors-colors').offset().top+$(document).scrollTop()-1};if(position.y<=-1)position.y=-1;if(position.y>=149)position.y=149;input.data('huePosition',position);huePicker.css('top',position.y).show();var h=Math.round((150-position.y-1)*2.4);if(h<0)h=0;if(h>360)h=360;var hsb=input.data('hsb');hsb.h=h;setColor(input,hsb,true);};var setColor=function(input,hsb,updateInputValue){input.data('hsb',hsb);var hex=hsb2hex(hsb);if(updateInputValue)input.val('#'+hex);input.data('trigger').css('backgroundColor','#'+hex);if(input.data('selector'))input.data('selector').find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));if(input.data('change')){input.data('change').call(input,'#'+hex,hsb2rgb(hsb));}};var setColorFromInput=function(input){var hex=cleanHex(input.val());if(!hex)return false;var hsb=hex2hsb(hex);var currentHSB=input.data('hsb');if(hsb.h===currentHSB.h&&hsb.s===currentHSB.s&&hsb.b===currentHSB.b)return true;var colorPosition=getColorPositionFromHSB(hsb);var colorPicker=$(input.data('colorPicker'));colorPicker.css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=getHuePositionFromHSB(hsb);var huePicker=$(input.data('huePicker'));huePicker.css('top',huePosition.y+'px');setColor(input,hsb,false);return true;};var getColorPositionFromHSB=function(hsb){var x=Math.ceil(hsb.s/.67);if(x<0)x=0;if(x>150)x=150;var y=150-Math.ceil(hsb.b/.67);if(y<0)y=0;if(y>150)y=150;return{x:x-5,y:y-5};}
var getHuePositionFromHSB=function(hsb){var y=150-(hsb.h/2.4);if(y<0)h=0;if(y>150)h=150;return{y:y-1};}
var cleanHex=function(hex){hex=hex.replace(/[^A-Fa-f0-9]/,'');if(hex.length==3){hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];}
return hex.length===6?hex:null;};var hsb2rgb=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s==0){rgb.r=rgb.g=rgb.b=v;}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h==360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3;}
else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3;}
else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3;}
else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3;}
else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3;}
else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3;}
else{rgb.r=0;rgb.g=0;rgb.b=0;}}
return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)};};var rgb2hex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length==1)hex[nr]='0'+val;});return hex.join('');};var hex2rgb=function(hex){var hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)};};var rgb2hsb=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!=0?255*delta/max:0;if(hsb.s!=0){if(rgb.r==max){hsb.h=(rgb.g-rgb.b)/delta;}else if(rgb.g==max){hsb.h=2+(rgb.b-rgb.r)/delta;}else{hsb.h=4+(rgb.r-rgb.g)/delta;}}else{hsb.h=-1;}
hsb.h*=60;if(hsb.h<0){hsb.h+=360;}
hsb.s*=100/255;hsb.b*=100/255;return hsb;};var hex2hsb=function(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb;};var hsb2hex=function(hsb){return rgb2hex(hsb2rgb(hsb));};switch(o){case'readonly':$(this).each(function(){$(this).attr('readonly',data);});return $(this);break;case'disabled':$(this).each(function(){if(data){disable($(this));}else{enable($(this));}});return $(this);case'value':$(this).each(function(){$(this).val(data).trigger('keyup');});return $(this);break;case'destroy':$(this).each(function(){destroy($(this));});return $(this);default:if(!o)o={};$(this).each(function(){if($(this)[0].tagName.toLowerCase()!=='input')return;if($(this).data('trigger'))return;create($(this),o,data);});return $(this);}}});})(jQuery);

View file

@ -17,7 +17,7 @@ $labels = array();
// config
$labels['default_view'] = 'Ansicht';
$labels['time_format'] = 'Zeitformatierung';
$labels['timeslots'] = 'Zeitfenster pro Stunde';
$labels['timeslots'] = 'Zeitraster pro Stunde';
$labels['first_day'] = 'Erster Wochentag';
// calendar

View file

@ -17,6 +17,9 @@ $labels['calendars'] = 'Calendars';
$labels['category'] = 'Category';
$labels['categories'] = 'Categories';
$labels['createcalendar'] = 'Create new calendar';
$labels['editcalendar'] = 'Edit calendar properties';
$labels['name'] = 'Name';
$labels['color'] = 'Color';
$labels['day'] = 'Day';
$labels['week'] = 'Week';
$labels['month'] = 'Month';
@ -87,9 +90,11 @@ $labels['tabattachments'] = 'Attachments';
// messages
$labels['deleteventconfirm'] = "Do you really want to delete this event?";
$labels['deletecalendarconfirm'] = "Do you really want to delete this calendar with all its events?";
$labels['errorsaving'] = "Failed to save changes";
$labels['operationfailed'] = "The requested operation failed";
$labels['invalideventdates'] = "Invalid dates entered! Please check your input.";
$labels['invalidcalendarproperties'] = "Invalid calendar properties! Please set a valid name.";
// recurrence form
$labels['repeat'] = 'Repeat';

View file

@ -112,7 +112,7 @@ pre {
#calendarslist li.selected {
background-color: #ccc;
border-bottom: 1px solid #999;
border-bottom: 1px solid #bbb;
}
#calendarslist li.selected span {
@ -207,7 +207,8 @@ pre {
}
#eventshow,
#eventedit {
#eventedit,
#calendarform {
display: none;
}
@ -219,6 +220,10 @@ pre {
text-align: center;
}
a.miniColors-trigger {
margin-top: -3px;
}
/* jQuery UI overrides */
#eventshow h1 {
@ -256,6 +261,7 @@ pre {
border-radius: 0;
}
div.form-section,
#eventshow div.event-section,
#eventtabs div.event-section {
margin-top: 0.2em;
@ -287,7 +293,8 @@ pre {
}
#eventshow label,
#eventedit label {
#eventedit label,
.form-section label {
display: inline-block;
min-width: 7em;
padding-right: 0.5em;
@ -434,10 +441,31 @@ a.alarm-action-snooze:after {
/* fullcalendar style overrides */
.fc-content {
position: absolute !important;
top: 37px;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.fc-event-title {
font-weight: bold;
}
.fc-event-hori .fc-event-title {
font-weight: normal;
white-space: nowrap;
}
.fc-event-hori .fc-event-time {
white-space: nowrap;
font-weight: normal;
font-size: 10px;
padding-right: 0.6em;
}
.fc-event-cateories {
font-style:italic;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

View file

@ -0,0 +1,65 @@
.miniColors-trigger {
height: 22px;
width: 22px;
background: url('images/minicolors-all.png') -170px 0 no-repeat;
vertical-align: middle;
margin: 0 .25em;
display: inline-block;
outline: none;
}
.miniColors-selector {
position: absolute;
width: 175px;
height: 150px;
background: #FFF;
border: solid 1px #BBB;
-moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
-webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
box-shadow: 0 0 6px rgba(0, 0, 0, .25);
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 5px;
z-index: 999999;
}
.miniColors-selector.black {
background: #000;
border-color: #000;
}
.miniColors-colors {
position: absolute;
top: 5px;
left: 5px;
width: 150px;
height: 150px;
background: url('images/minicolors-all.png') top left no-repeat;
cursor: crosshair;
}
.miniColors-hues {
position: absolute;
top: 5px;
left: 160px;
width: 20px;
height: 150px;
background: url('images/minicolors-all.png') -150px 0 no-repeat;
cursor: crosshair;
}
.miniColors-colorPicker {
position: absolute;
width: 11px;
height: 11px;
background: url('images/minicolors-all.png') -170px -28px no-repeat;
}
.miniColors-huePicker {
position: absolute;
left: -3px;
width: 26px;
height: 3px;
background: url('images/minicolors-all.png') -170px -24px no-repeat;
}

View file

@ -18,8 +18,8 @@
<roundcube:object name="plugin.calendar_list" id="calendarslist" />
</div>
<div class="boxfooter">
<roundcube:button command="plugin.createcalendar" type="link" title="calendar.createcalendar" class="buttonPas addgroup" classAct="button addgroup" content=" " />
<roundcube:button name="calendarmenulink" id="calendarmenulink" type="link" title="calendaractions" class="button groupactions" onclick="return false" content=" " />
<roundcube:button command="plugin.calendar-create" type="link" title="calendar.createcalendar" class="buttonPas addgroup" classAct="button addgroup" content=" " />
<roundcube:button name="calendaroptionslink" id="calendaroptionslink" type="link" title="calendaractions" class="button groupactions" onclick="rcmail_ui.show_popup('calendaroptions');return false" content=" " />
</div>
</div>
</div>
@ -27,6 +27,13 @@
<div id="calendar"></div>
</div>
<div id="calendaroptionsmenu" class="popupmenu">
<ul>
<li><roundcube:button command="plugin.calendar-edit" label="calendar.edit" classAct="active" /></li>
<li><roundcube:button command="plugin.calendar-remove" label="calendar.remove" classAct="active" /></li>
</ul>
</div>
<div id="eventshow">
<h1 id="event-title">Event Title</h1>
<div class="event-section" id="event-location">Location</div>
@ -163,9 +170,24 @@
<roundcube:object name="plugin.edit_recurring_warning" class="edit-recurring-warning" style="display:none" />
</div>
<div id="calendarform">
<form action="#">
<div class="form-section">
<label for="calendar-name"><roundcube:label name="calendar.name" /></label>
<input type="text" name="name" size="20" id="calendar-name" />
</div>
<div class="form-section">
<label for="calendar-color"><roundcube:label name="calendar.color" /></label>
<input type="text" name="color" size="6" id="calendar-color" />
</div>
</form>
</div>
<div id="alarm-snooze-dropdown" class="popupmenu">
<roundcube:object name="plugin.snooze_select" type="ul" />
</div>
<div id="calendartoolbar">
<roundcube:button command="plugin.addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
<roundcube:button command="plugin.print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
@ -183,6 +205,10 @@
<script type="text/javascript">
// use skin functions to handle popup-menus
rcube_init_mail_ui();
rcmail_ui.popups.calendaroptions = { id:'calendaroptionsmenu', above:1, obj:$('#calendaroptionsmenu') };
$(document).ready(function(e){
// initialize sidebar toggle
$('#sidebartoggle').click(function() {