Merge branch 'dev/calendar-resources'
Add resource display and booking features to master
This commit is contained in:
commit
54cc4aac74
21 changed files with 1427 additions and 164 deletions
|
@ -38,6 +38,7 @@ class calendar extends rcube_plugin
|
|||
public $rc;
|
||||
public $lib;
|
||||
public $driver;
|
||||
public $resources_dir;
|
||||
public $home; // declare public to be used in other classes
|
||||
public $urlbase;
|
||||
public $timezone;
|
||||
|
@ -143,6 +144,10 @@ class calendar extends rcube_plugin
|
|||
$this->register_action('check-recent', array($this, 'check_recent'));
|
||||
$this->register_action('itip-status', array($this, 'event_itip_status'));
|
||||
$this->register_action('itip-remove', array($this, 'event_itip_remove'));
|
||||
$this->register_action('resources-list', array($this, 'resources_list'));
|
||||
$this->register_action('resources-owner', array($this, 'resources_owner'));
|
||||
$this->register_action('resources-calendar', array($this, 'resources_calendar'));
|
||||
$this->register_action('resources-autocomplete', array($this, 'resources_autocomplete'));
|
||||
$this->add_hook('refresh', array($this, 'refresh'));
|
||||
|
||||
// remove undo information...
|
||||
|
@ -210,16 +215,10 @@ class calendar extends rcube_plugin
|
|||
require_once($this->home . '/drivers/calendar_driver.php');
|
||||
require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
|
||||
|
||||
switch ($driver_name) {
|
||||
case "kolab":
|
||||
$this->require_plugin('libkolab');
|
||||
default:
|
||||
$this->driver = new $driver_class($this);
|
||||
break;
|
||||
}
|
||||
$this->driver = new $driver_class($this);
|
||||
|
||||
if ($this->driver->undelete)
|
||||
$this->driver->undelete = $this->rc->config->get('undo_timeout', 0) > 0;
|
||||
if ($this->driver->undelete)
|
||||
$this->driver->undelete = $this->rc->config->get('undo_timeout', 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -285,13 +284,14 @@ class calendar extends rcube_plugin
|
|||
$this->ui->addJS();
|
||||
|
||||
$this->ui->init_templates();
|
||||
$this->rc->output->add_label('lowest','low','normal','high','highest','delete','cancel','uploading','noemailwarning');
|
||||
$this->rc->output->add_label('lowest','low','normal','high','highest','delete','cancel','uploading','noemailwarning','close');
|
||||
|
||||
// initialize attendees autocompletion
|
||||
rcube_autocomplete_init();
|
||||
|
||||
$this->rc->output->set_env('timezone', $this->timezone->getName());
|
||||
$this->rc->output->set_env('calendar_driver', $this->rc->config->get('calendar_driver'), false);
|
||||
$this->rc->output->set_env('calendar_resources', (bool)$this->rc->config->get('calendar_resources_driver'));
|
||||
$this->rc->output->set_env('mscolors', $this->driver->get_color_values());
|
||||
$this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list')));
|
||||
|
||||
|
@ -1908,6 +1908,103 @@ class calendar extends rcube_plugin
|
|||
}
|
||||
|
||||
|
||||
/**** Resource management functions ****/
|
||||
|
||||
/**
|
||||
* Getter for the configured implementation of the resource directory interface
|
||||
*/
|
||||
private function resources_directory()
|
||||
{
|
||||
if (is_object($this->resources_dir)) {
|
||||
return $this->resources_dir;
|
||||
}
|
||||
|
||||
if ($driver_name = $this->rc->config->get('calendar_resources_driver')) {
|
||||
$driver_class = 'resources_driver_' . $driver_name;
|
||||
|
||||
require_once($this->home . '/drivers/resources_driver.php');
|
||||
require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
|
||||
|
||||
$this->resources_dir = new $driver_class($this);
|
||||
}
|
||||
|
||||
return $this->resources_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for resoruce autocompletion requests
|
||||
*/
|
||||
public function resources_autocomplete()
|
||||
{
|
||||
$search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true);
|
||||
$sid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
|
||||
$maxnum = (int)$this->rc->config->get('autocomplete_max', 15);
|
||||
$results = array();
|
||||
|
||||
if ($directory = $this->resources_directory()) {
|
||||
foreach ($directory->load_resources($search, $maxnum) as $rec) {
|
||||
$results[] = array(
|
||||
'name' => $rec['name'],
|
||||
'email' => $rec['email'],
|
||||
'type' => $rec['_type'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->rc->output->command('ksearch_query_results', $results, $search, $sid);
|
||||
$this->rc->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for load-requests for resource data
|
||||
*/
|
||||
function resources_list()
|
||||
{
|
||||
$data = array();
|
||||
|
||||
if ($directory = $this->resources_directory()) {
|
||||
foreach ($directory->load_resources() as $rec) {
|
||||
$data[] = $rec;
|
||||
}
|
||||
}
|
||||
|
||||
$this->rc->output->command('plugin.resource_data', $data);
|
||||
$this->rc->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for requests loading resource owner information
|
||||
*/
|
||||
function resources_owner()
|
||||
{
|
||||
if ($directory = $this->resources_directory()) {
|
||||
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
|
||||
$data = $directory->get_resource_owner($id);
|
||||
}
|
||||
|
||||
$this->rc->output->command('plugin.resource_owner', $data);
|
||||
$this->rc->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver event data for a resource's calendar
|
||||
*/
|
||||
function resources_calendar()
|
||||
{
|
||||
$events = array();
|
||||
|
||||
if ($directory = $this->resources_directory()) {
|
||||
$events = $directory->get_resource_calendar(
|
||||
rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC),
|
||||
rcube_utils::get_input_value('start', RCUBE_INPUT_GET),
|
||||
rcube_utils::get_input_value('end', RCUBE_INPUT_GET));
|
||||
}
|
||||
|
||||
echo $this->encode($events);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/**** Event invitation plugin hooks ****/
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,7 @@ function rcube_calendar(settings)
|
|||
).then(function() {
|
||||
// disable attendees feature (autocompletion and stuff is not initialized)
|
||||
for (var c in rcmail.env.calendars)
|
||||
rcmail.env.calendars[c].attendees = false;
|
||||
rcmail.env.calendars[c].attendees = rcmail.env.calendars[c].resources = false;
|
||||
|
||||
me.ui_loaded = true;
|
||||
me.ui = new rcube_calendar_ui(me.settings);
|
||||
|
|
|
@ -47,6 +47,12 @@ function rcube_calendar_ui(settings)
|
|||
var event_defaults = { free_busy:'busy', alarms:'' };
|
||||
var event_attendees = [];
|
||||
var attendees_list;
|
||||
var resources_list;
|
||||
var resources_treelist;
|
||||
var resources_data = {};
|
||||
var resources_index = [];
|
||||
var resource_owners = {};
|
||||
var resources_events_source = { url:null, editable:false };
|
||||
var freebusy_ui = { workinhoursonly:false, needsupdate:false };
|
||||
var freebusy_data = {};
|
||||
var current_view = null;
|
||||
|
@ -103,6 +109,11 @@ function rcube_calendar_ui(settings)
|
|||
return result;
|
||||
};
|
||||
|
||||
// Change the first charcter to uppercase
|
||||
var ucfirst = function(str)
|
||||
{
|
||||
return str.charAt(0).toUpperCase() + str.substr(1);
|
||||
};
|
||||
|
||||
// clone the given date object and optionally adjust time
|
||||
var clone_date = function(date, adjust)
|
||||
|
@ -276,7 +287,7 @@ function rcube_calendar_ui(settings)
|
|||
// event details dialog (show only)
|
||||
var event_show_dialog = function(event)
|
||||
{
|
||||
var $dialog = $("#eventshow").removeClass().addClass('uidialog');
|
||||
var $dialog = $("#eventshow").attr('class', 'uidialog');
|
||||
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
|
||||
me.selected_event = event;
|
||||
|
||||
|
@ -304,9 +315,9 @@ function rcube_calendar_ui(settings)
|
|||
$('#event-alarm').show().children('.event-text').html(Q(event.alarms_text));
|
||||
|
||||
if (calendar.name)
|
||||
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).removeClass().addClass('event-text').addClass('cal-'+calendar.id);
|
||||
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).attr('class', 'event-text').addClass('cal-'+calendar.id);
|
||||
if (event.categories)
|
||||
$('#event-category').show().children('.event-text').html(Q(event.categories)).removeClass().addClass('event-text cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, ''));
|
||||
$('#event-category').show().children('.event-text').html(Q(event.categories)).attr('class', 'event-text cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, ''));
|
||||
if (event.free_busy)
|
||||
$('#event-free-busy').show().children('.event-text').html(Q(rcmail.gettext(event.free_busy, 'calendar')));
|
||||
if (event.priority > 0) {
|
||||
|
@ -332,19 +343,33 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
// list event attendees
|
||||
if (calendar.attendees && event.attendees) {
|
||||
var data, dispname, organizer = false, rsvp = false, line, morelink, html = '',overflow = '';
|
||||
// sort resources to the end
|
||||
event.attendees.sort(function(a,b) {
|
||||
var j = a.cutype == 'RESOURCE' ? 1 : 0,
|
||||
k = b.cutype == 'RESOURCE' ? 1 : 0;
|
||||
return (j - k);
|
||||
});
|
||||
|
||||
var data, dispname, tooltip, organizer = false, rsvp = false, line, morelink, html = '',overflow = '';
|
||||
for (var j=0; j < event.attendees.length; j++) {
|
||||
data = event.attendees[j];
|
||||
dispname = Q(data.name || data.email);
|
||||
tooltip = '';
|
||||
if (data.email) {
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink">' + dispname + '</a>';
|
||||
tooltip = data.email;
|
||||
dispname = '<a href="mailto:' + data.email + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||
if (data.role == 'ORGANIZER')
|
||||
organizer = true;
|
||||
else if ((data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE') && settings.identity.emails.indexOf(';'+data.email) >= 0)
|
||||
else if ((data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp) && settings.identity.emails.indexOf(';'+data.email) >= 0)
|
||||
rsvp = data.status.toLowerCase();
|
||||
}
|
||||
|
||||
line = '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '">' + dispname + '</span> ';
|
||||
if (data['delegated-to'])
|
||||
tooltip = rcmail.gettext('delegatedto', 'calendar') + data['delegated-to'];
|
||||
else if (data['delegated-from'])
|
||||
tooltip = rcmail.gettext('delegatedfrom', 'calendar') + data['delegated-from'];
|
||||
|
||||
line = '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + dispname + '</span> ';
|
||||
if (morelink)
|
||||
overflow += line;
|
||||
else
|
||||
|
@ -360,7 +385,7 @@ function rcube_calendar_ui(settings)
|
|||
$('#event-attendees').show()
|
||||
.children('.event-text')
|
||||
.html(html)
|
||||
.find('a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
|
||||
.find('a.mailtolink').click(event_attendee_click);
|
||||
|
||||
// display all attendees in a popup when clicking the "more" link
|
||||
if (morelink) {
|
||||
|
@ -371,7 +396,7 @@ function rcube_calendar_ui(settings)
|
|||
rcmail.gettext('tabattendees','calendar'),
|
||||
null,
|
||||
{ width:450, modal:false });
|
||||
$('#all-event-attendees a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
|
||||
$('#all-event-attendees a.mailtolink').click(event_attendee_click);
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
@ -427,6 +452,20 @@ function rcube_calendar_ui(settings)
|
|||
*/
|
||||
};
|
||||
|
||||
// event handler for clicks on an attendee link
|
||||
var event_attendee_click = function(e)
|
||||
{
|
||||
var cutype = $(this).attr('data-cutype'),
|
||||
mailto = this.href.substr(7);
|
||||
if (rcmail.env.calendar_resources && cutype == 'RESOURCE') {
|
||||
event_resources_dialog(mailto);
|
||||
}
|
||||
else {
|
||||
rcmail.redirect(rcmail.url('mail/compose', { _to:mailto }));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// bring up the event dialog (jquery-ui popup)
|
||||
var event_edit_dialog = function(action, event)
|
||||
{
|
||||
|
@ -573,6 +612,7 @@ function rcube_calendar_ui(settings)
|
|||
allow_invitations = organizer || (calendar.owner && calendar.owner == 'anonymous') || settings.invite_shared;
|
||||
event_attendees = [];
|
||||
attendees_list = $('#edit-attendees-table > tbody').html('');
|
||||
resources_list = $('#edit-resources-table > tbody').html('');
|
||||
$('#edit-attendees-notify')[(notify.checked && allow_invitations ? 'show' : 'hide')]();
|
||||
$('#edit-localchanges-warning')[(has_attendees(event) && !(allow_invitations || (calendar.owner && is_organizer(event, calendar.owner))) ? 'show' : 'hide')]();
|
||||
|
||||
|
@ -760,6 +800,7 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
// show/hide tabs according to calendar's feature support
|
||||
$('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
|
||||
$('#edit-tab-resources')[(rcmail.env.calendar_resources?'show':'hide')]();
|
||||
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
|
||||
|
||||
// activate the first tab
|
||||
|
@ -777,6 +818,9 @@ function rcube_calendar_ui(settings)
|
|||
resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
editform.hide().appendTo(document.body);
|
||||
$dialog.dialog("destroy").remove();
|
||||
|
@ -879,7 +923,7 @@ function rcube_calendar_ui(settings)
|
|||
var j = $.inArray(attendee.role, roles);
|
||||
j = (j+1) % roles.length;
|
||||
attendee.role = roles[j];
|
||||
$(e.target).parent().removeClass().addClass('attendee '+String(attendee.role).toLowerCase());
|
||||
$(e.target).parent().attr('class', 'attendee '+String(attendee.role).toLowerCase());
|
||||
|
||||
// update total display if required-status changed
|
||||
if (req != (roles[j] != 'OPT-PARTICIPANT' && roles[j] != 'NON-PARTICIPANT')) {
|
||||
|
@ -1312,7 +1356,7 @@ function rcube_calendar_ui(settings)
|
|||
var event = me.selected_event,
|
||||
eventstart = clone_date(event.start, event.allDay ? 1 : 0).getTime(), // calculate with integers
|
||||
eventend = clone_date(event.end, event.allDay ? 2 : 0).getTime(),
|
||||
duration = eventend - eventstart - (event.allDay ? HOUR_MS : 0), // make sure we don't cross day borders on DST change
|
||||
duration = eventend - eventstart - (event.allDay ? HOUR_MS : 0), /* make sure we don't cross day borders on DST change */
|
||||
sinterval = freebusy_data.interval * 60000,
|
||||
intvlslots = 1,
|
||||
numslots = Math.ceil(duration / sinterval),
|
||||
|
@ -1421,9 +1465,10 @@ function rcube_calendar_ui(settings)
|
|||
$('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// add the given list of participants
|
||||
var add_attendees = function(names)
|
||||
var add_attendees = function(names, params)
|
||||
{
|
||||
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
|
||||
|
||||
|
@ -1446,9 +1491,8 @@ function rcube_calendar_ui(settings)
|
|||
email = RegExp.$1;
|
||||
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
|
||||
}
|
||||
|
||||
if (email) {
|
||||
add_attendee({ email:email, name:name, role:'REQ-PARTICIPANT', status:'NEEDS-ACTION' });
|
||||
add_attendee($.extend({ email:email, name:name }, params));
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
|
@ -1458,19 +1502,24 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
return success;
|
||||
};
|
||||
|
||||
|
||||
// add the given attendee to the list
|
||||
var add_attendee = function(data, readonly)
|
||||
{
|
||||
if (!me.selected_event)
|
||||
return false;
|
||||
|
||||
// check for dupes...
|
||||
var exists = false;
|
||||
$.each(event_attendees, function(i, v){ exists |= (v.email == data.email); });
|
||||
if (exists)
|
||||
return false;
|
||||
|
||||
var calendar = me.selected_event && me.calendars[me.selected_event.calendar] ? me.calendars[me.selected_event.calendar] : me.calendars[me.selected_calendar];
|
||||
|
||||
var dispname = Q(data.name || data.email);
|
||||
if (data.email)
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink">' + dispname + '</a>';
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||
|
||||
// role selection
|
||||
var organizer = data.role == 'ORGANIZER';
|
||||
|
@ -1480,8 +1529,10 @@ function rcube_calendar_ui(settings)
|
|||
opts['REQ-PARTICIPANT'] = rcmail.gettext('calendar.rolerequired');
|
||||
opts['OPT-PARTICIPANT'] = rcmail.gettext('calendar.roleoptional');
|
||||
opts['NON-PARTICIPANT'] = rcmail.gettext('calendar.rolenonparticipant');
|
||||
opts['CHAIR'] = rcmail.gettext('calendar.rolechair');
|
||||
|
||||
|
||||
if (data.cutype != 'RESOURCE')
|
||||
opts['CHAIR'] = rcmail.gettext('calendar.rolechair');
|
||||
|
||||
if (organizer && !readonly)
|
||||
dispname = rcmail.env['identities-selector'];
|
||||
|
||||
|
@ -1496,20 +1547,27 @@ function rcube_calendar_ui(settings)
|
|||
// delete icon
|
||||
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
|
||||
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
|
||||
|
||||
var tooltip = data.status || '';
|
||||
|
||||
if (data['delegated-to'])
|
||||
tooltip = rcmail.gettext('delegatedto', 'calendar') + data['delegated-to'];
|
||||
else if (data['delegated-from'])
|
||||
tooltip = rcmail.gettext('delegatedfrom', 'calendar') + data['delegated-from'];
|
||||
|
||||
var html = '<td class="role">' + select + '</td>' +
|
||||
'<td class="name">' + dispname + '</td>' +
|
||||
'<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(data.status || '') + '">' + Q(data.status || '') + '</span></td>' +
|
||||
'<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + Q(data.status || '') + '</span></td>' +
|
||||
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
|
||||
|
||||
|
||||
var table = rcmail.env.calendar_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
|
||||
var tr = $('<tr>')
|
||||
.addClass(String(data.role).toLowerCase())
|
||||
.html(html)
|
||||
.appendTo(attendees_list);
|
||||
|
||||
.appendTo(table);
|
||||
|
||||
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||
tr.find('a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
|
||||
tr.find('a.mailtolink').click(event_attendee_click);
|
||||
|
||||
// select organizer identity
|
||||
if (data.identity_id)
|
||||
|
@ -1521,16 +1579,17 @@ function rcube_calendar_ui(settings)
|
|||
}
|
||||
|
||||
event_attendees.push(data);
|
||||
return true;
|
||||
};
|
||||
|
||||
// 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)
|
||||
check_freebusy_status(icons.get(i), event_attendees[i].email, event);
|
||||
}
|
||||
attendees_list.find('img.availabilityicon').each(function(i,v) {
|
||||
var email, icon = $(this);
|
||||
if (email = icon.attr('data-email'))
|
||||
check_freebusy_status(icon, email, event);
|
||||
});
|
||||
|
||||
freebusy_ui.needsupdate = false;
|
||||
};
|
||||
|
@ -1540,11 +1599,11 @@ function rcube_calendar_ui(settings)
|
|||
{
|
||||
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { freebusy:false };
|
||||
if (!calendar.freebusy) {
|
||||
$(icon).removeClass().addClass('availabilityicon unknown');
|
||||
$(icon).attr('class', 'availabilityicon unknown');
|
||||
return;
|
||||
}
|
||||
|
||||
icon = $(icon).removeClass().addClass('availabilityicon loading');
|
||||
icon = $(icon).attr('class', 'availabilityicon loading');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
|
@ -1566,7 +1625,313 @@ function rcube_calendar_ui(settings)
|
|||
$(elem).closest('tr').remove();
|
||||
event_attendees = $.grep(event_attendees, function(data){ return (data.name != id && data.email != id) });
|
||||
};
|
||||
|
||||
|
||||
// open a dialog to display detailed free-busy information and to find free slots
|
||||
var event_resources_dialog = function(search)
|
||||
{
|
||||
var $dialog = $('#eventresourcesdialog');
|
||||
|
||||
if ($dialog.is(':ui-dialog'))
|
||||
$dialog.dialog('close');
|
||||
|
||||
// dialog buttons
|
||||
var buttons = {};
|
||||
|
||||
buttons[rcmail.gettext('addresource', 'calendar')] = function() {
|
||||
rcmail.command('add-resource');
|
||||
};
|
||||
|
||||
buttons[rcmail.gettext('close')] = function() {
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
// open jquery UI dialog
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
title: rcmail.gettext('findresources', 'calendar'),
|
||||
close: function() {
|
||||
$dialog.dialog('destroy').hide();
|
||||
},
|
||||
resize: function(e) {
|
||||
var container = $(rcmail.gui_objects.resourceinfocalendar)
|
||||
container.fullCalendar('option', 'height', container.height() + 4);
|
||||
},
|
||||
buttons: buttons,
|
||||
width: 900,
|
||||
height: 500
|
||||
}).show();
|
||||
|
||||
// define add-button as main action
|
||||
$('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().addClass('mainaction').attr('id', 'rcmbtncalresadd');
|
||||
|
||||
me.dialog_resize($dialog.get(0), 540, Math.min(1000, $(window).width() - 50));
|
||||
|
||||
// set search query
|
||||
$('#resourcesearchbox').val(search || '');
|
||||
|
||||
// initialize the treelist widget
|
||||
if (!resources_treelist) {
|
||||
resources_treelist = new rcube_treelist_widget(rcmail.gui_objects.resourceslist, {
|
||||
id_prefix: 'rcres',
|
||||
id_encode: rcmail.html_identifier_encode,
|
||||
id_decode: rcmail.html_identifier_decode,
|
||||
selectable: true
|
||||
});
|
||||
resources_treelist.addEventListener('select', function(node) {
|
||||
if (resources_data[node.id]) {
|
||||
resource_showinfo(resources_data[node.id]);
|
||||
rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible') ? true : false);
|
||||
}
|
||||
else {
|
||||
rcmail.enable_command('add-resource', false);
|
||||
$(rcmail.gui_objects.resourceinfo).hide();
|
||||
$(rcmail.gui_objects.resourceownerinfo).hide();
|
||||
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
|
||||
}
|
||||
});
|
||||
|
||||
// fetch (all) resource data from server
|
||||
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
|
||||
rcmail.http_request('resources-list', {}, me.loading_lock);
|
||||
|
||||
// register button
|
||||
rcmail.register_button('add-resource', 'rcmbtncalresadd', 'uibutton');
|
||||
|
||||
// initialize resource calendar display
|
||||
var resource_cal = $(rcmail.gui_objects.resourceinfocalendar);
|
||||
resource_cal.fullCalendar({
|
||||
header: { left: '', center: '', right: '' },
|
||||
height: resource_cal.height() + 4,
|
||||
defaultView: 'agendaWeek',
|
||||
ignoreTimezone: true,
|
||||
eventSources: [],
|
||||
monthNames: settings['months'],
|
||||
monthNamesShort: settings['months_short'],
|
||||
dayNames: settings['days'],
|
||||
dayNamesShort : settings['days_short'],
|
||||
firstDay: settings['first_day'],
|
||||
firstHour: settings['first_hour'],
|
||||
slotMinutes: 60,
|
||||
allDaySlot: false,
|
||||
timeFormat: { '': settings['time_format'] },
|
||||
axisFormat: settings['time_format'],
|
||||
columnFormat: { day: 'dddd ' + settings['date_short'] },
|
||||
titleFormat: { day: 'dddd ' + settings['date_long'] },
|
||||
currentTimeIndicator: settings.time_indicator,
|
||||
eventRender: function(event, element, view) {
|
||||
element.addClass('status-' + event.status);
|
||||
element.find('.fc-event-head').hide();
|
||||
element.find('.fc-event-title').text(rcmail.get_label(event.status, 'calendar'));
|
||||
}
|
||||
});
|
||||
|
||||
$('#resource-calendar-prev').click(function(){
|
||||
resource_cal.fullCalendar('prev');
|
||||
return false;
|
||||
});
|
||||
$('#resource-calendar-next').click(function(){
|
||||
resource_cal.fullCalendar('next');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else if (search) {
|
||||
resource_search();
|
||||
}
|
||||
else {
|
||||
resource_render_list(resources_index);
|
||||
}
|
||||
|
||||
if (me.selected_event && me.selected_event.start) {
|
||||
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('gotoDate', me.selected_event.start);
|
||||
}
|
||||
};
|
||||
|
||||
// render the resource details UI box
|
||||
var resource_showinfo = function(resource)
|
||||
{
|
||||
// inline function to render a resource attribute
|
||||
function render_attrib(value) {
|
||||
if (typeof value == 'boolean') {
|
||||
return value ? rcmail.get_label('yes') : rcmail.get_label('no');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (rcmail.gui_objects.resourceinfo) {
|
||||
var tr, table = $(rcmail.gui_objects.resourceinfo).show().find('tbody').html(''),
|
||||
attribs = $.extend({ name:resource.name }, resource.attributes||{})
|
||||
attribs.description = resource.description;
|
||||
|
||||
for (var k in attribs) {
|
||||
if (typeof attribs[k] == 'undefined')
|
||||
continue;
|
||||
table.append($('<tr>').addClass(k)
|
||||
.append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
|
||||
.append('<td class="value">' + text2html(render_attrib(attribs[k])) + '</td>')
|
||||
);
|
||||
}
|
||||
|
||||
$(rcmail.gui_objects.resourceownerinfo).hide();
|
||||
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
|
||||
|
||||
if (resource.owner) {
|
||||
// display cached data
|
||||
if (resource_owners[resource.owner]) {
|
||||
resource_owner_load(resource_owners[resource.owner]);
|
||||
}
|
||||
else {
|
||||
// fetch owner data from server
|
||||
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
|
||||
rcmail.http_request('resources-owner', { _id: resource.owner }, me.loading_lock);
|
||||
}
|
||||
}
|
||||
|
||||
// load resource calendar
|
||||
resources_events_source.url = "./?_task=calendar&_action=resources-calendar&_id="+escape(resource.ID);
|
||||
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('addEventSource', resources_events_source);
|
||||
}
|
||||
};
|
||||
|
||||
// callback from server for resource listing
|
||||
var resource_data_load = function(data)
|
||||
{
|
||||
var resources_tree = {};
|
||||
|
||||
// store data by ID
|
||||
$.each(data, function(i, rec) {
|
||||
resources_data[rec.ID] = rec;
|
||||
|
||||
// assign parent-relations
|
||||
if (rec.members) {
|
||||
$.each(rec.members, function(j, m){
|
||||
resources_tree[m] = rec.ID;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// walk the parent-child tree to determine the depth of each node
|
||||
$.each(data, function(i, rec) {
|
||||
rec._depth = 0;
|
||||
if (resources_tree[rec.ID])
|
||||
rec.parent_id = resources_tree[rec.ID];
|
||||
|
||||
var parent_id = resources_tree[rec.ID];
|
||||
while (parent_id) {
|
||||
rec._depth++;
|
||||
parent_id = resources_tree[parent_id];
|
||||
}
|
||||
});
|
||||
|
||||
// sort by depth, collection and name
|
||||
data.sort(function(a,b) {
|
||||
var j = a._type == 'collection' ? 1 : 0,
|
||||
k = b._type == 'collection' ? 1 : 0,
|
||||
d = a._depth - b._depth;
|
||||
if (!d) d = (k - j);
|
||||
if (!d) d = b.name < a.name ? 1 : -1;
|
||||
return d;
|
||||
});
|
||||
|
||||
$.each(data, function(i, rec) {
|
||||
resources_index.push(rec.ID);
|
||||
});
|
||||
|
||||
// apply search filter...
|
||||
if ($('#resourcesearchbox').val() != '')
|
||||
resource_search();
|
||||
else // ...or render full list
|
||||
resource_render_list(resources_index);
|
||||
|
||||
rcmail.set_busy(false, null, me.loading_lock);
|
||||
};
|
||||
|
||||
// renders the given list of resource records into the treelist
|
||||
var resource_render_list = function(index) {
|
||||
var rec, link;
|
||||
|
||||
resources_treelist.reset();
|
||||
|
||||
$.each(index, function(i, dn) {
|
||||
if (rec = resources_data[dn]) {
|
||||
link = $('<a>').attr('href', '#')
|
||||
.attr('rel', rec.ID)
|
||||
.html(Q(rec.name));
|
||||
|
||||
resources_treelist.insert({ id:rec.ID, html:link, classes:[rec._type], collapsed:true }, rec.parent_id, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// callback from server for owner information display
|
||||
var resource_owner_load = function(data)
|
||||
{
|
||||
if (data) {
|
||||
// cache this!
|
||||
resource_owners[data.ID] = data;
|
||||
|
||||
var table = $(rcmail.gui_objects.resourceownerinfo).find('tbody').html('');
|
||||
|
||||
for (var k in data) {
|
||||
if (k == 'event' || k == 'ID')
|
||||
continue;
|
||||
|
||||
table.append($('<tr>').addClass(k)
|
||||
.append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
|
||||
.append('<td class="value">' + text2html(data[k]) + '</td>')
|
||||
);
|
||||
}
|
||||
|
||||
table.parent().show();
|
||||
}
|
||||
}
|
||||
|
||||
// quick-filter the loaded resource data
|
||||
var resource_search = function()
|
||||
{
|
||||
var dataset, rec, q = $('#resourcesearchbox').val().toLowerCase();
|
||||
if (q.length && resources_data) {
|
||||
dataset = [];
|
||||
|
||||
// search by iterating over all resource records
|
||||
for (var dn in resources_data) {
|
||||
rec = resources_data[dn];
|
||||
if (String(rec.name).toLowerCase().indexOf(q) >= 0 || String(rec.email).toLowerCase() == q) {
|
||||
dataset.push(rec.ID);
|
||||
}
|
||||
}
|
||||
|
||||
resource_render_list(dataset);
|
||||
|
||||
// select single match
|
||||
if (dataset.length == 1) {
|
||||
resources_treelist.select(dataset[0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$('#resourcesearchbox').val('');
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
var reset_resource_search = function()
|
||||
{
|
||||
$('#resourcesearchbox').val('').focus();
|
||||
resource_render_list(resources_index);
|
||||
};
|
||||
|
||||
//
|
||||
var add_resource2event = function()
|
||||
{
|
||||
var resource = resources_data[resources_treelist.get_selection()];
|
||||
if (resource) {
|
||||
if (add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource)))
|
||||
rcmail.display_message(rcmail.get_label('resourceadded', 'calendar'), 'confirmation');
|
||||
}
|
||||
}
|
||||
|
||||
// when the user accepts or declines an event invitation
|
||||
var event_rsvp = function(response)
|
||||
{
|
||||
|
@ -1706,13 +2071,8 @@ function rcube_calendar_ui(settings)
|
|||
return false;
|
||||
});
|
||||
|
||||
var buttons = [{
|
||||
text: rcmail.gettext('cancel', 'calendar'),
|
||||
click: function() {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}];
|
||||
|
||||
var buttons = [];
|
||||
|
||||
if (!event.recurrence) {
|
||||
buttons.push({
|
||||
text: rcmail.gettext((action == 'remove' ? 'remove' : 'save'), 'calendar'),
|
||||
|
@ -1724,13 +2084,23 @@ function rcube_calendar_ui(settings)
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
buttons.push({
|
||||
text: rcmail.gettext('cancel', 'calendar'),
|
||||
click: function() {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
});
|
||||
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
width: 460,
|
||||
dialogClass: 'warning',
|
||||
title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
|
||||
buttons: buttons,
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-button').first().focus();
|
||||
},
|
||||
close: function(){
|
||||
$dialog.dialog("destroy").hide();
|
||||
if (!rcmail.busy)
|
||||
|
@ -1838,7 +2208,7 @@ function rcube_calendar_ui(settings)
|
|||
date: date.getDate(),
|
||||
month: date.getMonth(),
|
||||
year: date.getFullYear(),
|
||||
ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone
|
||||
ignoreTimezone: true, /* will treat the given date strings as in local (browser's) timezone */
|
||||
eventSources: sources,
|
||||
monthNames : settings['months'],
|
||||
monthNamesShort : settings['months_short'],
|
||||
|
@ -1963,6 +2333,9 @@ function rcube_calendar_ui(settings)
|
|||
resizable: true,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
$dialog.html('').dialog("destroy").hide();
|
||||
},
|
||||
|
@ -2056,6 +2429,9 @@ function rcube_calendar_ui(settings)
|
|||
resizable: false,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext('importevents', 'calendar'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
$dialog.dialog("destroy").hide();
|
||||
|
@ -2117,7 +2493,7 @@ function rcube_calendar_ui(settings)
|
|||
if (range == 'custom')
|
||||
start = date2unixtime(parse_datetime('00:00', $('#event-export-startdate').val()));
|
||||
else if (range > 0)
|
||||
start = 'today -' + range + '^months';
|
||||
start = 'today -' + range + ' months';
|
||||
|
||||
rcmail.goto_url('export_events', { source:source, start:start, attachments:attachmt?1:0 });
|
||||
}
|
||||
|
@ -2133,6 +2509,9 @@ function rcube_calendar_ui(settings)
|
|||
resizable: false,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext('exporttitle', 'calendar'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
$dialog.dialog("destroy").hide();
|
||||
|
@ -2255,6 +2634,12 @@ function rcube_calendar_ui(settings)
|
|||
window.history.replaceState({}, document.title, rcmail.url('', query).replace('&_action=', ''));
|
||||
};
|
||||
|
||||
this.resource_search = resource_search;
|
||||
this.reset_resource_search = reset_resource_search;
|
||||
this.add_resource2event = add_resource2event;
|
||||
this.resource_data_load = resource_data_load;
|
||||
this.resource_owner_load = resource_owner_load;
|
||||
|
||||
|
||||
/*** event searching ***/
|
||||
|
||||
|
@ -2404,6 +2789,11 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
/*** startup code ***/
|
||||
|
||||
// destroy wrongly configured treelist widget for the calendars list
|
||||
if (rcmail.gui_objects.folderlist && rcmail.treelist) {
|
||||
rcmail.treelist = null;
|
||||
}
|
||||
|
||||
// create list of event sources AKA calendars
|
||||
this.calendars = {};
|
||||
var id, li, cal, active, color, brightness, event_sources = [];
|
||||
|
@ -2475,6 +2865,9 @@ function rcube_calendar_ui(settings)
|
|||
if (settings.default_calendar && this.calendars[settings.default_calendar] && !this.calendars[settings.default_calendar].readonly)
|
||||
this.selected_calendar = settings.default_calendar;
|
||||
|
||||
if (this.selected_calendar)
|
||||
rcmail.select_folder(this.selected_calendar, 'rcmlical');
|
||||
|
||||
var viewdate = new Date();
|
||||
if (rcmail.env.date)
|
||||
viewdate.setTime(fromunixtime(rcmail.env.date));
|
||||
|
@ -2803,8 +3196,9 @@ function rcube_calendar_ui(settings)
|
|||
// init event dialog
|
||||
$('#eventtabs').tabs({
|
||||
show: function(event, ui) {
|
||||
if (ui.panel.id == 'event-tab-3') {
|
||||
$('#edit-attendee-name').select();
|
||||
if (ui.panel.id == 'event-panel-attendees' || ui.panel.id == 'event-panel-resources') {
|
||||
var tab = ui.panel.id == 'event-panel-resources' ? 'resource' : 'attendee';
|
||||
$('#edit-'+tab+'-name').select();
|
||||
// update free-busy status if needed
|
||||
if (freebusy_ui.needsupdate && me.selected_event)
|
||||
update_freebusy_status(me.selected_event);
|
||||
|
@ -2889,16 +3283,39 @@ function rcube_calendar_ui(settings)
|
|||
};
|
||||
}
|
||||
rcmail.init_address_input_events($('#edit-attendee-name'), ac_props);
|
||||
rcmail.addEventListener('autocomplete_insert', function(e){ $('#edit-attendee-add').click(); });
|
||||
rcmail.addEventListener('autocomplete_insert', function(e){
|
||||
if (e.field.name == 'participant') {
|
||||
$('#edit-attendee-add').click();
|
||||
}
|
||||
else if (e.field.name == 'resource' && e.data && e.data.email) {
|
||||
add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }));
|
||||
e.field.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-attendee-add').click(function(){
|
||||
var input = $('#edit-attendee-name');
|
||||
rcmail.ksearch_blur();
|
||||
if (add_attendees(input.val())) {
|
||||
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) {
|
||||
input.val('');
|
||||
}
|
||||
});
|
||||
|
||||
rcmail.init_address_input_events($('#edit-resource-name'), { action:'calendar/resources-autocomplete' });
|
||||
|
||||
$('#edit-resource-add').click(function(){
|
||||
var input = $('#edit-resource-name');
|
||||
rcmail.ksearch_blur();
|
||||
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })) {
|
||||
input.val('');
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-resource-find').click(function(){
|
||||
event_resources_dialog();
|
||||
return false;
|
||||
});
|
||||
|
||||
// keep these two checkboxes in sync
|
||||
$('#edit-attendees-donotify, #edit-attendees-invite').click(function(){
|
||||
$('#edit-attendees-donotify, #edit-attendees-invite').prop('checked', this.checked);
|
||||
|
@ -2981,6 +3398,11 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.register_command('search', function(){ cal.quicksearch(); }, true);
|
||||
rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
|
||||
|
||||
// resource invitation dialog
|
||||
rcmail.register_command('search-resource', function(){ cal.resource_search(); }, true);
|
||||
rcmail.register_command('reset-resource-search', function(){ cal.reset_resource_search(); }, true);
|
||||
rcmail.register_command('add-resource', function(){ cal.add_resource2event(); }, false);
|
||||
|
||||
// register callback commands
|
||||
rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
|
||||
rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); });
|
||||
|
@ -2988,6 +3410,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); });
|
||||
rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); });
|
||||
rcmail.addEventListener('plugin.reload_view', function(p){ cal.reload_view(p); });
|
||||
rcmail.addEventListener('plugin.resource_data', function(p){ cal.resource_data_load(p); });
|
||||
rcmail.addEventListener('plugin.resource_owner', function(p){ cal.resource_owner_load(p); });
|
||||
rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); });
|
||||
|
||||
// let's go
|
||||
|
|
|
@ -135,4 +135,11 @@ $rcmail_config['calendar_itip_smtp_pass'] = '123456';
|
|||
// %i - Calendar UUID
|
||||
// $rcmail_config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
|
||||
|
||||
// Driver to provide a resource directory ('ldap' is the only implementation yet).
|
||||
// Leave empty or commented to disable resources support.
|
||||
// $rcmail_config['calendar_resources_driver'] = 'ldap';
|
||||
|
||||
// LDAP directory configuration to find avilable resources for events
|
||||
// $rcmail_config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */)
|
||||
|
||||
?>
|
||||
|
|
|
@ -529,23 +529,4 @@ abstract class calendar_driver
|
|||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store alarm dismissal for birtual birthay events
|
||||
*
|
||||
* @param string Event identifier
|
||||
* @param integer Suspend the alarm for this number of seconds
|
||||
*/
|
||||
public function dismiss_birthday_alarm($event_id, $snooze = 0)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$cache = $rcmail->get_cache('calendar.birthdayalarms', 'db', 86400 * 30);
|
||||
$cache->remove($event_id);
|
||||
|
||||
// compute new notification time or disable if not snoozed
|
||||
$notifyat = $snooze > 0 ? time() + $snooze : null;
|
||||
$cache->set($event_id, array('snooze' => $snooze, 'notifyat' => $notifyat));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -218,8 +218,18 @@ class kolab_calendar
|
|||
public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
|
||||
{
|
||||
// convert to DateTime for comparisons
|
||||
$start = new DateTime('@'.$start);
|
||||
$end = new DateTime('@'.$end);
|
||||
try {
|
||||
$start = new DateTime('@'.$start);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$start = new DateTime('@0');
|
||||
}
|
||||
try {
|
||||
$end = new DateTime('@'.$end);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$end = new DateTime('today +10 years');
|
||||
}
|
||||
|
||||
// query Kolab storage
|
||||
$query[] = array('dtstart', '<=', $end);
|
||||
|
|
|
@ -47,6 +47,8 @@ class kolab_driver extends calendar_driver
|
|||
*/
|
||||
public function __construct($cal)
|
||||
{
|
||||
$cal->require_plugin('libkolab');
|
||||
|
||||
$this->cal = $cal;
|
||||
$this->rc = $cal->rc;
|
||||
$this->_read_calendars();
|
||||
|
|
150
plugins/calendar/drivers/ldap/resources_driver_ldap.php
Normal file
150
plugins/calendar/drivers/ldap/resources_driver_ldap.php
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* LDAP-based resource directory class using rcube_ldap functionality
|
||||
*
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* LDAP-based resource directory implementation
|
||||
*/
|
||||
class resources_driver_ldap extends resources_driver
|
||||
{
|
||||
private $rc;
|
||||
private $ldap;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
function __construct($cal)
|
||||
{
|
||||
$this->cal = $cal;
|
||||
$this->rc = $cal->rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch resource objects to be displayed for booking
|
||||
*
|
||||
* @param string Search query (optional)
|
||||
* @return array List of resource records available for booking
|
||||
*/
|
||||
public function load_resources($query = null, $num = 5000)
|
||||
{
|
||||
if (!($ldap = $this->connect())) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// TODO: apply paging
|
||||
$ldap->set_pagesize($num);
|
||||
|
||||
if (isset($query)) {
|
||||
$results = $ldap->search('*', $query, 0, true, true);
|
||||
}
|
||||
else {
|
||||
$results = $ldap->list_records();
|
||||
}
|
||||
|
||||
if ($results instanceof ArrayAccess) {
|
||||
foreach ($results as $i => $rec) {
|
||||
$results[$i] = $this->decode_resource($rec);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return properties of a single resource
|
||||
*
|
||||
* @param string Unique resource identifier
|
||||
* @return array Resource object as hash array
|
||||
*/
|
||||
public function get_resource($dn)
|
||||
{
|
||||
$rec = null;
|
||||
|
||||
if ($ldap = $this->connect()) {
|
||||
$rec = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
|
||||
|
||||
if (!empty($rec)) {
|
||||
$rec = $this->decode_resource($rec);
|
||||
}
|
||||
}
|
||||
|
||||
return $rec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return properties of a resource owner
|
||||
*
|
||||
* @param string Owner identifier
|
||||
* @return array Resource object as hash array
|
||||
*/
|
||||
public function get_resource_owner($dn)
|
||||
{
|
||||
$owner = null;
|
||||
|
||||
if ($ldap = $this->connect()) {
|
||||
$owner = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
|
||||
$owner['ID'] = rcube_ldap::dn_decode($owner['ID']);
|
||||
unset($owner['_raw_attrib'], $owner['_type']);
|
||||
}
|
||||
|
||||
return $owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract JSON-serialized attributes
|
||||
*/
|
||||
private function decode_resource($rec)
|
||||
{
|
||||
$rec['ID'] = rcube_ldap::dn_decode($rec['ID']);
|
||||
|
||||
if (is_array($rec['attributes']) && $rec['attributes'][0]) {
|
||||
$attributes = array();
|
||||
|
||||
foreach ($rec['attributes'] as $sattr) {
|
||||
$attr = @json_decode($sattr, true);
|
||||
$attributes += $attr;
|
||||
}
|
||||
|
||||
$rec['attributes'] = $attributes;
|
||||
}
|
||||
|
||||
// force $rec['members'] to be an array
|
||||
if (!empty($rec['members']) && !is_array($rec['members'])) {
|
||||
$rec['members'] = array($rec['members']);
|
||||
}
|
||||
|
||||
// remove unused cruft
|
||||
unset($rec['_raw_attrib']);
|
||||
|
||||
return $rec;
|
||||
}
|
||||
|
||||
private function connect()
|
||||
{
|
||||
if (!isset($this->ldap)) {
|
||||
$this->ldap = new rcube_ldap($this->rc->config->get('calendar_resources_directory'), true);
|
||||
}
|
||||
|
||||
return $this->ldap->ready ? $this->ldap : null;
|
||||
}
|
||||
|
||||
}
|
114
plugins/calendar/drivers/resources_driver.php
Normal file
114
plugins/calendar/drivers/resources_driver.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Resources directory interface definition
|
||||
*
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Interface definition for a resources directory driver classe
|
||||
*/
|
||||
abstract class resources_driver
|
||||
{
|
||||
protected$cal;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
function __construct($cal)
|
||||
{
|
||||
$this->cal = $cal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch resource objects to be displayed for booking
|
||||
*
|
||||
* @param string Search query (optional)
|
||||
* @return array List of resource records available for booking
|
||||
*/
|
||||
abstract public function load_resources($query = null);
|
||||
|
||||
/**
|
||||
* Return properties of a single resource
|
||||
*
|
||||
* @param string Unique resource identifier
|
||||
* @return array Resource object as hash array
|
||||
*/
|
||||
abstract public function get_resource($id);
|
||||
|
||||
/**
|
||||
* Return properties of a resource owner
|
||||
*
|
||||
* @param string Owner identifier
|
||||
* @return array Resource object as hash array
|
||||
*/
|
||||
public function get_resource_owner($id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event data to display a resource's calendar
|
||||
*
|
||||
* The default implementation extracts the resource's email address
|
||||
* and fetches free-busy data using the calendar backend driver.
|
||||
*
|
||||
* @param integer Event's new start (unix timestamp)
|
||||
* @param integer Event's new end (unix timestamp)
|
||||
* @return array A list of event objects (see calendar_driver specification)
|
||||
*/
|
||||
public function get_resource_calendar($id, $start, $end)
|
||||
{
|
||||
$events = array();
|
||||
$rec = $this->get_resource($id);
|
||||
if ($rec && !empty($rec['email']) && $this->cal->driver) {
|
||||
$fbtypemap = array(
|
||||
calendar::FREEBUSY_BUSY => 'busy',
|
||||
calendar::FREEBUSY_TENTATIVE => 'tentative',
|
||||
calendar::FREEBUSY_OOF => 'outofoffice',
|
||||
);
|
||||
|
||||
// if the backend has free-busy information
|
||||
$fblist = $this->cal->driver->get_freebusy_list($rec['email'], $start, $end);
|
||||
if (is_array($fblist)) {
|
||||
foreach ($fblist as $slot) {
|
||||
list($from, $to, $type) = $slot;
|
||||
if ($type == calendar::FREEBUSY_FREE || $type == calendar::FREEBUSY_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
if ($from < $end && $to > $start) {
|
||||
$event = array(
|
||||
'id' => sha1($id . $from . $to),
|
||||
'title' => $rec['name'],
|
||||
'start' => new DateTime('@' . $from),
|
||||
'end' => new DateTime('@' . $to),
|
||||
'status' => $fbtypemap[$type],
|
||||
'calendar' => '_resource',
|
||||
);
|
||||
$events[] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -84,6 +84,11 @@ class calendar_ui
|
|||
$this->cal->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
|
||||
$this->cal->register_handler('plugin.attendees_list', array($this, 'attendees_list'));
|
||||
$this->cal->register_handler('plugin.attendees_form', array($this, 'attendees_form'));
|
||||
$this->cal->register_handler('plugin.resources_form', array($this, 'resources_form'));
|
||||
$this->cal->register_handler('plugin.resources_list', array($this, 'resources_list'));
|
||||
$this->cal->register_handler('plugin.resources_searchform', array($this, 'resources_search_form'));
|
||||
$this->cal->register_handler('plugin.resource_info', array($this, 'resource_info'));
|
||||
$this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar'));
|
||||
$this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table'));
|
||||
$this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
|
||||
$this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning'));
|
||||
|
@ -112,6 +117,7 @@ class calendar_ui
|
|||
$this->cal->include_script('calendar_ui.js');
|
||||
$this->cal->include_script('lib/js/fullcalendar.js');
|
||||
$this->cal->include_script('lib/js/jquery.miniColors.min.js');
|
||||
$this->rc->output->include_script('treelist.js');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -740,7 +746,7 @@ class calendar_ui
|
|||
{
|
||||
$table = new html_table(array('cols' => 5, 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable'));
|
||||
$table->add_header('role', $this->cal->gettext('role'));
|
||||
$table->add_header('name', $this->cal->gettext('attendee'));
|
||||
$table->add_header('name', $this->cal->gettext($attrib['coltitle'] ?: 'attendee'));
|
||||
$table->add_header('availability', $this->cal->gettext('availability'));
|
||||
$table->add_header('confirmstate', $this->cal->gettext('confirmstate'));
|
||||
$table->add_header('options', '');
|
||||
|
@ -763,7 +769,97 @@ class calendar_ui
|
|||
html::p('attendees-invitebox', html::label(null, $checkbox->show(1) . $this->cal->gettext('sendinvitations')))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function resources_form($attrib = array())
|
||||
{
|
||||
$input = new html_inputfield(array('name' => 'resource', 'id' => 'edit-resource-name', 'size' => 30));
|
||||
|
||||
return html::div($attrib,
|
||||
html::div(null, $input->show() . " " .
|
||||
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-add', 'value' => $this->cal->gettext('addresource'))) . " " .
|
||||
html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-find', 'value' => $this->cal->gettext('findresources').'...')))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function resources_list($attrib = array())
|
||||
{
|
||||
$attrib += array('id' => 'calendar-resources-list');
|
||||
|
||||
$this->rc->output->add_gui_object('resourceslist', $attrib['id']);
|
||||
|
||||
return html::tag('ul', $attrib, '', html::$common_attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function resource_info($attrib = array())
|
||||
{
|
||||
$attrib += array('id' => 'calendar-resources-info');
|
||||
|
||||
$this->rc->output->add_gui_object('resourceinfo', $attrib['id']);
|
||||
$this->rc->output->add_gui_object('resourceownerinfo', $attrib['id'] . '-owner');
|
||||
|
||||
// copy address book labels for owner details to client
|
||||
$this->rc->output->add_label('name','firstname','surname','department','jobtitle','email','phone','address');
|
||||
|
||||
$table_attrib = array('id','class','style','width','summary','cellpadding','cellspacing','border');
|
||||
|
||||
return html::tag('table', $attrib,
|
||||
html::tag('tbody', null, ''), $table_attrib) .
|
||||
|
||||
html::tag('table', array('id' => $attrib['id'] . '-owner', 'style' => 'display:none') + $attrib,
|
||||
html::tag('thead', null,
|
||||
html::tag('tr', null,
|
||||
html::tag('td', array('colspan' => 2), Q($this->cal->gettext('resourceowner')))
|
||||
)
|
||||
) .
|
||||
html::tag('tbody', null, ''),
|
||||
$table_attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function resource_calendar($attrib = array())
|
||||
{
|
||||
$attrib += array('id' => 'calendar-resources-calendar');
|
||||
|
||||
$this->rc->output->add_gui_object('resourceinfocalendar', $attrib['id']);
|
||||
|
||||
return html::div($attrib, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* GUI object 'searchform' for the resource finder dialog
|
||||
*
|
||||
* @param array Named parameters
|
||||
* @return string HTML code for the gui object
|
||||
*/
|
||||
function resources_search_form($attrib)
|
||||
{
|
||||
$attrib += array('command' => 'search-resource', 'id' => 'rcmcalresqsearchbox', 'autocomplete' => 'off');
|
||||
$attrib['name'] = '_q';
|
||||
|
||||
$input_q = new html_inputfield($attrib);
|
||||
$out = $input_q->show();
|
||||
|
||||
// add form tag around text field
|
||||
$out = $this->rc->output->form_tag(array(
|
||||
'name' => "rcmcalresoursqsearchform",
|
||||
'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('" . $attrib['command'] . "'); return false",
|
||||
'style' => "display:inline"),
|
||||
$out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -123,6 +123,8 @@ $labels['availbusy'] = 'Busy';
|
|||
$labels['availunknown'] = 'Unknown';
|
||||
$labels['availtentative'] = 'Tentative';
|
||||
$labels['availoutofoffice'] = 'Out of Office';
|
||||
$labels['delegatedto'] = 'Delegated to: ';
|
||||
$labels['delegatedfrom'] = 'Delegated from: ';
|
||||
$labels['scheduletime'] = 'Find availability';
|
||||
$labels['sendinvitations'] = 'Send invitations';
|
||||
$labels['sendnotifications'] = 'Notify participants about modifications';
|
||||
|
@ -155,10 +157,20 @@ $labels['eventcancelled'] = 'The event has been cancelled';
|
|||
$labels['saveincalendar'] = 'save in';
|
||||
$labels['updatemycopy'] = 'Update in my calendar';
|
||||
|
||||
// resources
|
||||
$labels['resource'] = 'Resource';
|
||||
$labels['addresource'] = 'Book resource';
|
||||
$labels['findresources'] = 'Find resources';
|
||||
$labels['resourcedetails'] = 'Details';
|
||||
$labels['resourceavailability'] = 'Availability';
|
||||
$labels['resourceowner'] = 'Owner';
|
||||
$labels['resourceadded'] = 'The resource was added to your event';
|
||||
|
||||
// event dialog tabs
|
||||
$labels['tabsummary'] = 'Summary';
|
||||
$labels['tabrecurrence'] = 'Recurrence';
|
||||
$labels['tabattendees'] = 'Participants';
|
||||
$labels['tabresources'] = 'Resources';
|
||||
$labels['tabattachments'] = 'Attachments';
|
||||
$labels['tabsharing'] = 'Sharing';
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ body.calendarmain {
|
|||
#main {
|
||||
position: absolute;
|
||||
clear: both;
|
||||
top: 90px;
|
||||
top: 72px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 10px;
|
||||
|
@ -24,13 +24,15 @@ body.calendarmain {
|
|||
|
||||
#calendarsidebar {
|
||||
position: absolute;
|
||||
top: 37px;
|
||||
top: 0px;
|
||||
left: 10px;
|
||||
bottom: 0;
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
#datepicker {
|
||||
position: relative;
|
||||
top: 42px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -62,7 +64,7 @@ body.calendarmain {
|
|||
position: absolute;
|
||||
left: 244px;
|
||||
width: 8px;
|
||||
top: 37px;
|
||||
top: 4px;
|
||||
bottom: 0;
|
||||
background: url(images/toggle.gif) 0 48% no-repeat transparent;
|
||||
cursor: pointer;
|
||||
|
@ -78,7 +80,7 @@ div.sidebarclosed {
|
|||
|
||||
#calendar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 4px;
|
||||
left: 256px;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
|
@ -94,7 +96,7 @@ pre {
|
|||
|
||||
#calendars {
|
||||
position: absolute;
|
||||
top: 220px;
|
||||
top: 228px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
@ -207,8 +209,8 @@ pre {
|
|||
|
||||
#calendartoolbar {
|
||||
position: absolute;
|
||||
top: 45px;
|
||||
left: 256px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
|
@ -269,7 +271,8 @@ pre {
|
|||
background-position: -128px -32px;
|
||||
}
|
||||
|
||||
#quicksearchbar {
|
||||
.calendarmain #quicksearchbar {
|
||||
top: 82px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
|
@ -410,7 +413,7 @@ a.miniColors-trigger {
|
|||
}
|
||||
|
||||
.event-attendees span.delegated {
|
||||
background-position: right -160px;
|
||||
background-position: right -180px;
|
||||
}
|
||||
|
||||
.event-attendees span.organizer {
|
||||
|
@ -621,7 +624,7 @@ td.topalign {
|
|||
border: 1px solid #C2D071;
|
||||
}
|
||||
|
||||
#edit-attendees-table {
|
||||
.edit-attendees-table {
|
||||
width: 100%;
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
|
@ -629,49 +632,51 @@ td.topalign {
|
|||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#edit-attendees-table td {
|
||||
.edit-attendees-table td {
|
||||
padding: 3px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.role {
|
||||
.edit-attendees-table td.role {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.availability,
|
||||
#edit-attendees-table td.confirmstate {
|
||||
.edit-attendees-table td.availability,
|
||||
.edit-attendees-table td.confirmstate {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.options {
|
||||
.edit-attendees-table td.options {
|
||||
width: 3em;
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.name {
|
||||
.edit-attendees-table td.name {
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#edit-attendees-table thead td {
|
||||
.edit-attendees-table thead td {
|
||||
background: url(images/listheader.gif) top left repeat-x #CCC;
|
||||
}
|
||||
|
||||
#edit-attendees-form {
|
||||
#edit-attendees-form,
|
||||
#edit-resources-form {
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#edit-attendees-form #edit-attendee-schedule {
|
||||
#edit-attendees-form #edit-attendee-schedule,
|
||||
#edit-resources-form #edit-resource-find {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#edit-attendees-table select.edit-attendee-role {
|
||||
.edit-attendees-table select.edit-attendee-role {
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
background: white;
|
||||
|
@ -744,35 +749,35 @@ td.topalign {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#edit-attendees-table tbody td.confirmstate {
|
||||
.edit-attendees-table tbody td.confirmstate {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-indent: -2000%;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span {
|
||||
.edit-attendees-table td.confirmstate span {
|
||||
display: block;
|
||||
width: 20px;
|
||||
background: url(images/attendee-status.gif) 5px 0 no-repeat;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.needs-action {
|
||||
.edit-attendees-table td.confirmstate span.needs-action {
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.accepted {
|
||||
.edit-attendees-table td.confirmstate span.accepted {
|
||||
background-position: 5px -20px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.declined {
|
||||
.edit-attendees-table td.confirmstate span.declined {
|
||||
background-position: 5px -40px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.tentative {
|
||||
.edit-attendees-table td.confirmstate span.tentative {
|
||||
background-position: 5px -60px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.delegated {
|
||||
background-position: 5px -160px;
|
||||
.edit-attendees-table td.confirmstate span.delegated {
|
||||
background-position: 5px -180px;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table {
|
||||
|
@ -836,10 +841,14 @@ td.topalign {
|
|||
background-position: 2px -117px;
|
||||
}
|
||||
|
||||
.attendees-list .chair {
|
||||
.attendees-list .non-participant {
|
||||
background-position: 2px -137px;
|
||||
}
|
||||
|
||||
.attendees-list .chair {
|
||||
background-position: 2px -157px;
|
||||
}
|
||||
|
||||
.attendees-list .loading {
|
||||
background: url(images/loading_blue.gif) 1px 50% no-repeat;
|
||||
}
|
||||
|
@ -1085,12 +1094,159 @@ span.spacer {
|
|||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#resource-dialog-right {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 300px;
|
||||
right: 8px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#resource-info,
|
||||
#resource-availability {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 48%;
|
||||
border: 1px solid #999;
|
||||
background-color: #F9F9F9;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#resource-availability {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
height: 49%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#resource-info .boxtitle,
|
||||
#resource-availability .boxtitle {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-content {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-content .fc-event-bg {
|
||||
background: 0;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-busy,
|
||||
#resource-freebusy-calendar .status-busy .fc-event-skin {
|
||||
border-color: #e26569;
|
||||
background-color: #e26569;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-tentative,
|
||||
#resource-freebusy-calendar .status-tentative .fc-event-skin {
|
||||
border-color: #8383fc;
|
||||
background: #8383fc;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-outofoffice,
|
||||
#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
|
||||
border-color: #fbaa68;
|
||||
background: #fbaa68;
|
||||
}
|
||||
|
||||
#resources-list div.treetoggle {
|
||||
left: 3px !important;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
#resources-list li ul div.treetoggle {
|
||||
left: 23px !important;
|
||||
}
|
||||
|
||||
#resource-selection {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 8px;
|
||||
width: 280px;
|
||||
border: 1px solid #999999;
|
||||
background-color: #F9F9F9;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#resource-selection .boxlistcontent {
|
||||
top: 25px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#resourcequicksearch {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 7px;
|
||||
right: 4px;
|
||||
height: 17px;
|
||||
background: #fff;
|
||||
border: 1px solid #888;
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
#resourcesearchbox {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 24px;
|
||||
width: 140px;
|
||||
height: 15px;
|
||||
font-size: 11px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#resourcesearchreset {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#resource-details,
|
||||
#resource-details-owner {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
#resource-details td.title,
|
||||
#resource-details-owner td.title {
|
||||
color: #666;
|
||||
padding-right: 10px;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
#resource-details-owner thead td {
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* fullcalendar style overrides */
|
||||
|
||||
#calendar .fc-header-right {
|
||||
padding-right: 200px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.rcube-fc-content {
|
||||
position: absolute !important;
|
||||
top: 37px;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
@ -1169,7 +1325,7 @@ div.fc-event-location {
|
|||
|
||||
.fc-view-list div.fc-list-header,
|
||||
.fc-view-table td.fc-list-header,
|
||||
#edit-attendees-table thead td {
|
||||
.edit-attendees-table thead td {
|
||||
padding: 3px;
|
||||
background: #dddddd;
|
||||
background-image: -moz-linear-gradient(center top, #f4f4f4, #d2d2d2);
|
||||
|
@ -1272,6 +1428,7 @@ div.calendar-invitebox .rsvp-status.loading {
|
|||
|
||||
div.calendar-invitebox .rsvp-status.declined,
|
||||
div.calendar-invitebox .rsvp-status.tentative,
|
||||
div.calendar-invitebox .rsvp-status.delegated,
|
||||
div.calendar-invitebox .rsvp-status.accepted {
|
||||
padding: 0 0 1px 22px;
|
||||
background: url(images/attendee-status.gif) 2px -20px no-repeat;
|
||||
|
@ -1285,6 +1442,10 @@ div.calendar-invitebox .rsvp-status.tentative {
|
|||
background-position: 2px -60px;
|
||||
}
|
||||
|
||||
div.calendar-invitebox .rsvp-status.delegated {
|
||||
background-position: 2px -180px;
|
||||
}
|
||||
|
||||
/* iTIP attend reply page */
|
||||
|
||||
.calendaritipattend .centerbox {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.4 KiB |
|
@ -13,6 +13,14 @@
|
|||
|
||||
<div id="main">
|
||||
<div id="calendarsidebar">
|
||||
<div id="calendartoolbar">
|
||||
<roundcube:button command="addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
|
||||
<roundcube:button command="print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
|
||||
<roundcube:button command="events-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="calendar.importevents" content=" " />
|
||||
<roundcube:button command="export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="calendar.export" content=" " />
|
||||
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||
</div>
|
||||
|
||||
<div id="datepicker"></div>
|
||||
<div id="calendars" style="visibility:hidden">
|
||||
<div class="boxtitle"><roundcube:label name="calendar.calendars" /></div>
|
||||
|
@ -96,6 +104,32 @@
|
|||
|
||||
<roundcube:include file="/templates/eventedit.html" />
|
||||
|
||||
<div id="eventresourcesdialog" class="uidialog">
|
||||
<div id="resource-dialog-left">
|
||||
<div id="resource-selection" class="">
|
||||
<div id="resourcequicksearch">
|
||||
<roundcube:object name="plugin.resources_searchform" id="resourcesearchbox" />
|
||||
<roundcube:button command="reset-resource-search" id="resourcesearchreset" image="/images/icons/reset.gif" title="resetsearch" width="13" height="13" />
|
||||
</div>
|
||||
<div class="boxlistcontent">
|
||||
<roundcube:object name="plugin.resources_list" id="resources-list" class="treelist" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resource-dialog-right">
|
||||
<div id="resource-info">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourcedetails" /></h2>
|
||||
<roundcube:object name="plugin.resource_info" id="resource-details" />
|
||||
</div>
|
||||
|
||||
<div id="resource-availability">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
|
||||
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="eventfreebusy" class="uidialog">
|
||||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
|
||||
|
@ -134,6 +168,7 @@
|
|||
<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
|
||||
<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
|
||||
<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
|
||||
<span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
|
||||
<span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159,14 +194,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendartoolbar">
|
||||
<roundcube:button command="addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
|
||||
<roundcube:button command="print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
|
||||
<roundcube:button command="events-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="calendar.importevents" content=" " />
|
||||
<roundcube:button command="export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="calendar.export" content=" " />
|
||||
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||
</div>
|
||||
|
||||
<div id="quicksearchbar">
|
||||
<roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
|
||||
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<div id="eventedit" class="uidialog">
|
||||
<form id="eventtabs" action="#" method="post" enctype="multipart/form-data">
|
||||
<ul>
|
||||
<li><a href="#event-tab-1"><roundcube:label name="calendar.tabsummary" /></a></li>
|
||||
<li id="edit-tab-recurrence"><a href="#event-tab-2"><roundcube:label name="calendar.tabrecurrence" /></a></li>
|
||||
<li id="edit-tab-attendees"><a href="#event-tab-3"><roundcube:label name="calendar.tabattendees" /></a></li>
|
||||
<li id="edit-tab-attachments"><a href="#event-tab-4"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||
<li><a href="#event-panel-1"><roundcube:label name="calendar.tabsummary" /></a></li>
|
||||
<li id="edit-tab-recurrence"><a href="#event-panel-recurrence"><roundcube:label name="calendar.tabrecurrence" /></a></li>
|
||||
<li id="edit-tab-attendees"><a href="#event-panel-attendees"><roundcube:label name="calendar.tabattendees" /></a></li>
|
||||
<li id="edit-tab-resources"><a href="#event-panel-resources"><roundcube:label name="calendar.tabresources" /></a></li>
|
||||
<li id="edit-tab-attachments"><a href="#event-panel-attachments"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||
</ul>
|
||||
<!-- basic info -->
|
||||
<div id="event-tab-1">
|
||||
<div id="event-panel-1">
|
||||
<div class="event-section">
|
||||
<label for="edit-title"><roundcube:label name="calendar.title" /></label>
|
||||
<br />
|
||||
|
@ -65,7 +66,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- recurrence settings -->
|
||||
<div id="event-tab-2">
|
||||
<div id="event-panel-recurrence">
|
||||
<div class="event-section border-after">
|
||||
<roundcube:object name="plugin.recurrence_form" part="frequency" />
|
||||
</div>
|
||||
|
@ -89,13 +90,19 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
<div id="event-panel-attendees">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- resources list -->
|
||||
<div id="event-panel-resources">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" coltitle="resource" />
|
||||
<roundcube:object name="plugin.resources_form" id="edit-resources-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- attachments list (with upload form) -->
|
||||
<div id="event-tab-4">
|
||||
<div id="event-panel-attachments">
|
||||
<div id="edit-attachments" class="attachments-list">
|
||||
<roundcube:object name="plugin.attachments_list" id="attachmentlist" deleteIcon="/images/icons/delete.png" cancelIcon="/images/icons/delete.png" loadingIcon="/images/display/loading_blue.gif" />
|
||||
</div>
|
||||
|
|
|
@ -441,7 +441,7 @@ a.miniColors-trigger {
|
|||
.event-attendees span.attendee {
|
||||
padding-right: 18px;
|
||||
margin-right: 0.5em;
|
||||
background: url(images/attendee-status.gif) right 0 no-repeat;
|
||||
background: url(images/attendee-status.png) right 0 no-repeat;
|
||||
}
|
||||
|
||||
.event-attendees span.attendee a.mailtolink {
|
||||
|
@ -467,7 +467,7 @@ a.miniColors-trigger {
|
|||
}
|
||||
|
||||
.event-attendees span.delegated {
|
||||
background-position: right -160px;
|
||||
background-position: right -180px;
|
||||
}
|
||||
|
||||
.event-attendees span.organizer {
|
||||
|
@ -484,7 +484,7 @@ a.miniColors-trigger {
|
|||
.calendarmain .fc-view-table td.fc-list-header,
|
||||
#attendees-freebusy-table h3.boxtitle,
|
||||
#schedule-freebusy-times thead th,
|
||||
#edit-attendees-table thead td
|
||||
.edit-attendees-table thead td
|
||||
{
|
||||
color: #69939e;
|
||||
font-size: 11px;
|
||||
|
@ -683,34 +683,34 @@ td.topalign {
|
|||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#edit-attendees-table {
|
||||
.edit-attendees-table {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.role {
|
||||
.edit-attendees-table td.role {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.availability,
|
||||
#edit-attendees-table td.confirmstate {
|
||||
.edit-attendees-table td.availability,
|
||||
.edit-attendees-table td.confirmstate {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.options {
|
||||
.edit-attendees-table td.options {
|
||||
width: 3em;
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.name {
|
||||
.edit-attendees-table td.name {
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#edit-attendees-table a.deletelink {
|
||||
.edit-attendees-table a.deletelink {
|
||||
display: block;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
|
@ -719,18 +719,20 @@ td.topalign {
|
|||
text-indent: 1000px;
|
||||
}
|
||||
|
||||
#edit-attendees-form {
|
||||
#edit-attendees-form,
|
||||
#edit-resources-form {
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#edit-attendees-form #edit-attendee-schedule {
|
||||
#edit-attendees-form #edit-attendee-schedule,
|
||||
#edit-resources-form #edit-resource-find {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#edit-attendees-table select.edit-attendee-role {
|
||||
.edit-attendees-table select.edit-attendee-role {
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
background: white;
|
||||
|
@ -803,35 +805,35 @@ td.topalign {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#edit-attendees-table tbody td.confirmstate {
|
||||
.edit-attendees-table tbody td.confirmstate {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-indent: -2000%;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span {
|
||||
.edit-attendees-table td.confirmstate span {
|
||||
display: block;
|
||||
width: 20px;
|
||||
background: url(images/attendee-status.gif) 5px 0 no-repeat;
|
||||
background: url(images/attendee-status.png) 5px 0 no-repeat;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.needs-action {
|
||||
.edit-attendees-table td.confirmstate span.needs-action {
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.accepted {
|
||||
.edit-attendees-table td.confirmstate span.accepted {
|
||||
background-position: 5px -20px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.declined {
|
||||
.edit-attendees-table td.confirmstate span.declined {
|
||||
background-position: 5px -40px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.tentative {
|
||||
.edit-attendees-table td.confirmstate span.tentative {
|
||||
background-position: 5px -60px;
|
||||
}
|
||||
|
||||
#edit-attendees-table td.confirmstate span.delegated {
|
||||
background-position: 5px -160px;
|
||||
.edit-attendees-table td.confirmstate span.delegated {
|
||||
background-position: 5px -180px;
|
||||
}
|
||||
|
||||
#attendees-freebusy-table {
|
||||
|
@ -863,7 +865,7 @@ td.topalign {
|
|||
|
||||
.attendees-list .attendee {
|
||||
padding: 4px 4px 4px 1px;
|
||||
background: url(images/attendee-status.gif) 2px -97px no-repeat;
|
||||
background: url(images/attendee-status.png) 2px -97px no-repeat;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
@ -891,10 +893,14 @@ td.topalign {
|
|||
background-position: 2px -117px;
|
||||
}
|
||||
|
||||
.attendees-list .chair {
|
||||
.attendees-list .non-participant {
|
||||
background-position: 2px -137px;
|
||||
}
|
||||
|
||||
.attendees-list .chair {
|
||||
background-position: 2px -157px;
|
||||
}
|
||||
|
||||
.attendees-list .loading {
|
||||
background: url(images/loading_blue.gif) 1px 50% no-repeat;
|
||||
}
|
||||
|
@ -1067,6 +1073,122 @@ a.dropdown-link:after {
|
|||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
#resource-selection {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 8px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#resource-selection .scroller {
|
||||
top: 34px;
|
||||
}
|
||||
|
||||
#resource-dialog-left {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 380px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#resource-dialog-right {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 392px;
|
||||
right: 8px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#resource-info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 48%;
|
||||
}
|
||||
|
||||
#resource-info table {
|
||||
margin: 8px;
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
#resource-info thead td {
|
||||
background: none;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#resource-availability {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 49%;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-content {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-content .fc-event-bg {
|
||||
background: 0;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-busy,
|
||||
#resource-freebusy-calendar .status-busy .fc-event-skin {
|
||||
border-color: #e26569;
|
||||
background-color: #e26569;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-tentative,
|
||||
#resource-freebusy-calendar .status-tentative .fc-event-skin {
|
||||
border-color: #8383fc;
|
||||
background: #8383fc;
|
||||
}
|
||||
|
||||
#resource-freebusy-calendar .fc-event.status-outofoffice,
|
||||
#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
|
||||
border-color: #fbaa68;
|
||||
background: #fbaa68;
|
||||
}
|
||||
|
||||
#resourcequicksearch {
|
||||
padding: 4px;
|
||||
background: #c7e3ef;
|
||||
}
|
||||
|
||||
#resourcesearchbox {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#resourcequicksearch .iconbutton.searchoptions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 6px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.searchbox .iconbutton.reset {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* fullcalendar style overrides */
|
||||
|
||||
.rcube-fc-content {
|
||||
|
@ -1472,7 +1594,7 @@ div.calendar-invitebox .rsvp-status.tentative,
|
|||
div.calendar-invitebox .rsvp-status.accepted,
|
||||
div.calendar-invitebox .rsvp-status.delegated {
|
||||
padding: 0 0 1px 22px;
|
||||
background: url(images/attendee-status.gif) 2px -20px no-repeat;
|
||||
background: url(images/attendee-status.png) 2px -20px no-repeat;
|
||||
}
|
||||
|
||||
div.calendar-invitebox .rsvp-status.declined {
|
||||
|
@ -1484,7 +1606,7 @@ div.calendar-invitebox .rsvp-status.tentative {
|
|||
}
|
||||
|
||||
div.calendar-invitebox .rsvp-status.delegated {
|
||||
background-position: 2px -160px;
|
||||
background-position: 2px -180px;
|
||||
}
|
||||
|
||||
/* iTIP attend reply page */
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
BIN
plugins/calendar/skins/larry/images/attendee-status.png
Normal file
BIN
plugins/calendar/skins/larry/images/attendee-status.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -113,6 +113,41 @@
|
|||
|
||||
<roundcube:include file="/templates/eventedit.html" />
|
||||
|
||||
<div id="eventresourcesdialog" class="uidialog">
|
||||
<div id="resource-dialog-left">
|
||||
<div id="resource-selection" class="uibox listbox">
|
||||
<div id="resourcequicksearch">
|
||||
<div class="searchbox">
|
||||
<roundcube:object name="plugin.resources_searchform" id="resourcesearchbox" />
|
||||
<a id="resourcesearchmenulink" class="iconbutton searchoptions"> </a>
|
||||
<roundcube:button command="reset-resource-search" id="resourcesearchreset" class="iconbutton reset" title="resetsearch" content=" " />
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroller">
|
||||
<roundcube:object name="plugin.resources_list" id="resources-list" class="listing treelist" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resource-dialog-right">
|
||||
<div id="resource-info" class="uibox contentbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourcedetails" /></h2>
|
||||
<div class="scroller">
|
||||
<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resource-availability" class="uibox contentbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
|
||||
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
|
||||
<div class="boxpagenav">
|
||||
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" content="&lt;" />
|
||||
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" content="&gt;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="eventfreebusy" class="uidialog">
|
||||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellpadding="0" />
|
||||
|
||||
|
@ -151,6 +186,7 @@
|
|||
<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
|
||||
<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
|
||||
<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
|
||||
<span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
|
||||
<span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -205,6 +241,9 @@ $(document).ready(function(e){
|
|||
})
|
||||
.data('offset', $('#calendarsidebartoggle').position().left)
|
||||
.data('sidebarwidth', $('#calendarsidebar').width() + $('#calendarsidebar').position().left);
|
||||
|
||||
new rcube_splitter({ id:'calresourceviewsplitter', p1:'#resource-dialog-left', p2:'#resource-dialog-right',
|
||||
orientation:'v', relative:true, start:380, min:220, size:10, offset:-3 }).init();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<div id="eventedit" class="uidialog uidialog-tabbed">
|
||||
<form id="eventtabs" action="#" method="post" enctype="multipart/form-data">
|
||||
<ul>
|
||||
<li><a href="#event-tab-1"><roundcube:label name="calendar.tabsummary" /></a></li><li id="edit-tab-recurrence"><a href="#event-tab-2"><roundcube:label name="calendar.tabrecurrence" /></a></li><li id="edit-tab-attendees"><a href="#event-tab-3"><roundcube:label name="calendar.tabattendees" /></a></li><li id="edit-tab-attachments"><a href="#event-tab-4"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||
<li><a href="#event-panel-summary"><roundcube:label name="calendar.tabsummary" /></a></li><li id="edit-tab-recurrence"><a href="#event-panel-recurrence"><roundcube:label name="calendar.tabrecurrence" /></a></li><li id="edit-tab-attendees"><a href="#event-panel-attendees"><roundcube:label name="calendar.tabattendees" /></a></li><li id="edit-tab-resources"><a href="#event-panel-resources"><roundcube:label name="calendar.tabresources" /></a></li><li id="edit-tab-attachments"><a href="#event-panel-attachments"><roundcube:label name="calendar.tabattachments" /></a></li>
|
||||
</ul>
|
||||
<!-- basic info -->
|
||||
<div id="event-tab-1">
|
||||
<div id="event-panel-summary">
|
||||
<div class="event-section">
|
||||
<label for="edit-title"><roundcube:label name="calendar.title" /></label>
|
||||
<br />
|
||||
|
@ -62,7 +62,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- recurrence settings -->
|
||||
<div id="event-tab-2">
|
||||
<div id="event-panel-recurrence">
|
||||
<div class="event-section border-after">
|
||||
<roundcube:object name="plugin.recurrence_form" part="frequency" />
|
||||
</div>
|
||||
|
@ -86,20 +86,26 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table" />
|
||||
<div id="event-panel-attendees">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" />
|
||||
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- resources list -->
|
||||
<div id="event-panel-resources">
|
||||
<roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="records-table edit-attendees-table" coltitle="resource" />
|
||||
<roundcube:object name="plugin.resources_form" id="edit-resources-form" />
|
||||
<roundcube:include file="/templates/freebusylegend.html" />
|
||||
</div>
|
||||
<!-- attachments list (with upload form) -->
|
||||
<div id="event-tab-4">
|
||||
<div id="event-panel-attachments">
|
||||
<div id="edit-attachments">
|
||||
<roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" />
|
||||
</div>
|
||||
<div id="edit-attachments-form">
|
||||
<roundcube:object name="plugin.attachments_form" id="calendar-attachment-form" attachmentFieldSize="30" />
|
||||
</div>
|
||||
<roundcube:object name="plugin.filedroparea" id="event-tab-4" />
|
||||
<roundcube:object name="plugin.filedroparea" id="event-panel-attachments" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -1341,6 +1341,9 @@ function rcube_tasklist_ui(settings)
|
|||
resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext((action == 'edit' ? 'edittask' : 'newtask'), 'tasklist'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
editform.hide().appendTo(document.body);
|
||||
$dialog.dialog('destroy').remove();
|
||||
|
@ -1664,7 +1667,12 @@ function rcube_tasklist_ui(settings)
|
|||
resizable: true,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'tasklist'),
|
||||
close: function() { $dialog.dialog('destroy').hide(); },
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
$dialog.dialog('destroy').hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
minWidth: 400,
|
||||
width: 420
|
||||
|
|
Loading…
Add table
Reference in a new issue