From ab43057b1fa5104da744fc2deb6264d0524d2965 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 26 Jun 2014 17:42:32 +0200 Subject: [PATCH] Implement quickview for calendars, showing free-busy data for other user's calendars (#3043) --- plugins/calendar/calendar.php | 7 +- plugins/calendar/calendar_ui.js | 304 +++++++++++------- .../drivers/kolab/kolab_user_calendar.php | 9 +- plugins/calendar/lib/calendar_ui.php | 1 + plugins/calendar/localization/en_US.inc | 1 + plugins/calendar/skins/larry/calendar.css | 124 +++++-- .../calendar/skins/larry/images/calendars.png | Bin 3337 -> 2698 bytes 7 files changed, 289 insertions(+), 157 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index f8635809..0313bb17 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -3,12 +3,11 @@ /** * Calendar plugin for Roundcube webmail * - * @version @package_version@ * @author Lazlo Westerhof * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2012, Kolab Systems AG + * Copyright (C) 2014, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -1397,7 +1396,9 @@ class calendar extends rcube_plugin 'title' => strval($event['title']), 'description' => strval($event['description']), 'location' => strval($event['location']), - 'className' => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower(join('-', (array)$event['categories'])), true), + 'className' => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . + 'fc-event-cat-' . asciiwords(strtolower(join('-', (array)$event['categories'])), true) . + rtrim(' ' . $event['className']), 'allDay' => ($event['allday'] == 1), ) + $event; } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 3558d915..985a3b61 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -8,7 +8,7 @@ * JavaScript code in this file. * * Copyright (C) 2010, Lazlo Westerhof - * Copyright (C) 2012, Kolab Systems AG + * Copyright (C) 2014, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -83,6 +83,78 @@ function rcube_calendar_ui(settings) selectOtherMonths: true }; + // global fullcalendar settings + var fullcalendar_defaults = { + aspectRatio: 1, + ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone + monthNames : settings.months, + monthNamesShort : settings.months_short, + dayNames : settings.days, + dayNamesShort : settings.days_short, + firstDay : settings.first_day, + firstHour : settings.first_hour, + slotMinutes : 60/settings.timeslots, + timeFormat: { + '': settings.time_format, + agenda: settings.time_format + '{ - ' + settings.time_format + '}', + list: settings.time_format + '{ - ' + settings.time_format + '}', + table: settings.time_format + '{ - ' + settings.time_format + '}' + }, + axisFormat : settings.time_format, + columnFormat: { + month: 'ddd', // Mon + week: 'ddd ' + settings.date_short, // Mon 9/7 + day: 'dddd ' + settings.date_short, // Monday 9/7 + table: settings.date_agenda + }, + titleFormat: { + month: 'MMMM yyyy', + week: settings.dates_long, + day: 'dddd ' + settings['date_long'], + table: settings.dates_long + }, + listPage: 1, // advance one day in agenda view + listRange: settings.agenda_range, + listSections: settings.agenda_sections, + tableCols: ['handle', 'date', 'time', 'title', 'location'], + defaultView: rcmail.env.view || settings.default_view, + allDayText: rcmail.gettext('all-day', 'calendar'), + buttonText: { + prev: ' ◄ ', + next: ' ► ', + today: settings['today'], + day: rcmail.gettext('day', 'calendar'), + week: rcmail.gettext('week', 'calendar'), + month: rcmail.gettext('month', 'calendar'), + table: rcmail.gettext('agenda', 'calendar') + }, + listTexts: { + until: rcmail.gettext('until', 'calendar'), + past: rcmail.gettext('pastevents', 'calendar'), + today: rcmail.gettext('today', 'calendar'), + tomorrow: rcmail.gettext('tomorrow', 'calendar'), + thisWeek: rcmail.gettext('thisweek', 'calendar'), + nextWeek: rcmail.gettext('nextweek', 'calendar'), + thisMonth: rcmail.gettext('thismonth', 'calendar'), + nextMonth: rcmail.gettext('nextmonth', 'calendar'), + future: rcmail.gettext('futureevents', 'calendar'), + week: rcmail.gettext('weekofyear', 'calendar') + }, + currentTimeIndicator: settings.time_indicator, + // event rendering + eventRender: fc_event_render, + // render element indicating more (invisible) events + overflowRender: function(data, element) { + element.html(rcmail.gettext('andnmore', 'calendar').replace('$nr', data.count)) + .click(function(e){ me.fisheye_view(data.date); }); + }, + // callback when a specific event is clicked + eventClick: function(event, ev, view) { + if (!event.temp && String(event.className).indexOf('fc-type-freebusy') < 0) + event_show_dialog(event, ev); + } + }; + /*** imports ***/ var Q = this.quote_html; var text2html = this.text2html; @@ -1598,25 +1670,13 @@ function rcube_calendar_ui(settings) // initialize resource calendar display var resource_cal = $(rcmail.gui_objects.resourceinfocalendar); - resource_cal.fullCalendar({ + resource_cal.fullCalendar($.extend({}, fullcalendar_defaults, { 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) { var title = rcmail.get_label(event.status, 'calendar'); element.addClass('status-' + event.status); @@ -1624,7 +1684,7 @@ function rcube_calendar_ui(settings) element.find('.fc-event-title').text(title); element.attr('aria-label', me.event_date_text(event, true) + ': ' + title); } - }); + })); $('#resource-calendar-prev').click(function(){ resource_cal.fullCalendar('prev'); @@ -2107,37 +2167,69 @@ function rcube_calendar_ui(settings) me.fisheye_date = null; } }) - .fullCalendar({ + .fullCalendar($.extend({}, fullcalendar_defaults, { header: { left: '', center: '', right: '' }, height: h - 50, - defaultView: 'agendaDay', date: date.getDate(), month: date.getMonth(), year: date.getFullYear(), - ignoreTimezone: true, /* will treat the given date strings as in local (browser's) timezone */ - eventSources: sources, - monthNames : settings['months'], - monthNamesShort : settings['months_short'], - dayNames : settings['days'], - dayNamesShort : settings['days_short'], - firstDay : settings['first_day'], - firstHour : settings['first_hour'], - slotMinutes : 60/settings['timeslots'], - timeFormat: { '': settings['time_format'] }, - axisFormat : settings['time_format'], - columnFormat: { day: 'dddd ' + settings['date_short'] }, - titleFormat: { day: 'dddd ' + settings['date_long'] }, - allDayText: rcmail.gettext('all-day', 'calendar'), - currentTimeIndicator: settings.time_indicator, - eventRender: fc_event_render, - eventClick: function(event, ev, view) { - event_show_dialog(event, ev); - } - }); + eventSources: sources + })); this.fisheye_date = date; }; + // opens the given calendar in a popup dialog + this.quickview = function(id) + { + $('#quickview-calendar:ui-dialog').dialog('close'); + + var dialog, src, cal = this.calendars[id], date = fc.fullCalendar('getDate'), + h = $(window).height() - 50, + me = this; + + // clone and modify calendar properties + src = $.extend({}, cal); + src.editable = false; + src.url += '&_quickview=1'; + + dialog = $('
') + .attr('id', 'quickview-calendar') + .dialog({ + modal: true, + width: Math.min(1000, $(window).width() - 100), + height: h, + title: cal.name.replace('»', 'ยป').replace(' ', ' '), + open: function() { + setTimeout(function() { dialog.find('.fc-button-next').first().focus(); }, 10); + }, + close: function() { + dialog.dialog('destroy').fullCalendar('destroy').remove(); + me.quickview_active = null; + }, + resize: function(e) { + // adjust height when dialog resizes + dialog.fullCalendar('option', 'height', dialog.height() + 8); + } + }) + .fullCalendar($.extend({}, fullcalendar_defaults, { + header: { + left: 'agendaDay,agendaWeek,month,table', + center: 'title', + right: 'prev,next today' + }, + height: h - 50, + defaultView: fc.fullCalendar('getView').name || fullcalendar_defaults.defaultView, + date: date.getDate(), + month: date.getMonth(), + year: date.getFullYear(), + slotMinutes: 60, + eventSources: [ src ] + })); + + me.quickview_active = id; + }; + //public method to show the print dialog. this.print_calendars = function(view) { @@ -2464,6 +2556,22 @@ function rcube_calendar_ui(settings) { var source = me.calendars[p.source]; + // helper function to update the given fullcalendar view + function update_view(view, event, source) { + var existing = view.fullCalendar('clientEvents', event._id); + if (existing.length) { + $.extend(existing[0], event); + view.fullCalendar('updateEvent', existing[0]); + // remove old recurrence instances + if (event.recurrence && !event.recurrence_id) + view.fullCalendar('removeEvents', function(e){ return e._id.indexOf(event._id+'-') == 0; }); + } + else { + event.source = source; // link with source + view.fullCalendar('renderEvent', event); + } + } + if (source && (p.refetch || (p.update && !source.active))) { // activate event source if new event was added to an invisible calendar if (!source.active) { @@ -2473,31 +2581,31 @@ function rcube_calendar_ui(settings) } else fc.fullCalendar('refetchEvents', source); + + if (this.quickview_active) + $('#quickview-calendar').fullCalendar('refetchEvents'); } // add/update single event object else if (source && p.update) { var event = p.update; event.temp = false; + + // update quickview + if (this.quickview_active) + update_view($('#quickview-calendar'), event, source); + // update fish-eye view + if (this.fisheye_date) + update_view($('#fish-eye-view'), event, source); + + // update main view event.editable = source.editable; - var existing = fc.fullCalendar('clientEvents', event._id); - if (existing.length) { - $.extend(existing[0], event); - fc.fullCalendar('updateEvent', existing[0]); - // remove old recurrence instances - if (event.recurrence && !event.recurrence_id) - fc.fullCalendar('removeEvents', function(e){ return e._id.indexOf(event._id+'-') == 0; }); - } - else { - event.source = source; // link with source - fc.fullCalendar('renderEvent', event); - } - // refresh fish-eye view - if (me.fisheye_date) - me.fisheye_view(me.fisheye_date); + update_view(fc, event, source); } // refetch all calendars else if (p.refetch) { fc.fullCalendar('refetchEvents'); + if (this.quickview_active) + $('#quickview-calendar').fullCalendar('refetchEvents'); } // remove temp events @@ -2811,6 +2919,18 @@ function rcube_calendar_ui(settings) else rcmail.display_message(rcmail.gettext('nocalendarsfound','calendar'), 'info'); }); + calendars_list.addEventListener('click-item', function(event) { + // handle clicks on quickview icon: temprarily add this source and open in quickview + if ($(event.target).hasClass('quickview') && event.data) { + if (!me.calendars[event.data.id]) { + event.data.readonly = true; + event.data.active = false; + event.data.subscribed = false; + add_calendar_source(event.data); + } + me.quickview(event.data.id); + } + }); // init (delegate) event handler on calendar list checkboxes $(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e){ @@ -2839,6 +2959,14 @@ function rcube_calendar_ui(settings) calendars_list.select(this.value); return rcube_event.cancel(e); } + }) + // init (delegate) event handler on quickview links + .on('click', 'a.quickview', function(e) { + var id = $(this).closest('li').attr('id').replace(/^rcmlical/, ''); + if (me.calendars[id]) + me.quickview(id); + e.stopPropagation(); + return false; }); // register dbl-click handler to open calendar edit dialog @@ -2859,75 +2987,19 @@ function rcube_calendar_ui(settings) viewdate.setTime(fromunixtime(rcmail.env.date)); // initalize the fullCalendar plugin - var fc = $('#calendar').fullCalendar({ + var fc = $('#calendar').fullCalendar($.extend({}, fullcalendar_defaults, { header: { right: 'prev,next today', center: 'title', left: 'agendaDay,agendaWeek,month,table' }, - aspectRatio: 1, date: viewdate.getDate(), month: viewdate.getMonth(), year: viewdate.getFullYear(), - ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone height: $('#calendar').height(), eventSources: event_sources, - monthNames : settings['months'], - monthNamesShort : settings['months_short'], - dayNames : settings['days'], - dayNamesShort : settings['days_short'], - firstDay : settings['first_day'], - firstHour : settings['first_hour'], - slotMinutes : 60/settings['timeslots'], - timeFormat: { - '': settings['time_format'], - agenda: settings['time_format'] + '{ - ' + settings['time_format'] + '}', - list: settings['time_format'] + '{ - ' + settings['time_format'] + '}', - table: settings['time_format'] + '{ - ' + settings['time_format'] + '}' - }, - axisFormat : settings['time_format'], - columnFormat: { - month: 'ddd', // Mon - week: 'ddd ' + settings['date_short'], // Mon 9/7 - day: 'dddd ' + settings['date_short'], // Monday 9/7 - table: settings['date_agenda'] - }, - titleFormat: { - month: 'MMMM yyyy', - week: settings['dates_long'], - day: 'dddd ' + settings['date_long'], - table: settings['dates_long'] - }, - listPage: 1, // advance one day in agenda view - listRange: settings['agenda_range'], - listSections: settings['agenda_sections'], - tableCols: ['handle', 'date', 'time', 'title', 'location'], - defaultView: rcmail.env.view || settings['default_view'], - allDayText: rcmail.gettext('all-day', 'calendar'), - buttonText: { - prev: (bw.ie6 ? ' << ' : ' ◄ '), - next: (bw.ie6 ? ' >> ' : ' ► '), - today: settings['today'], - day: rcmail.gettext('day', 'calendar'), - week: rcmail.gettext('week', 'calendar'), - month: rcmail.gettext('month', 'calendar'), - table: rcmail.gettext('agenda', 'calendar') - }, - listTexts: { - until: rcmail.gettext('until', 'calendar'), - past: rcmail.gettext('pastevents', 'calendar'), - today: rcmail.gettext('today', 'calendar'), - tomorrow: rcmail.gettext('tomorrow', 'calendar'), - thisWeek: rcmail.gettext('thisweek', 'calendar'), - nextWeek: rcmail.gettext('nextweek', 'calendar'), - thisMonth: rcmail.gettext('thismonth', 'calendar'), - nextMonth: rcmail.gettext('nextmonth', 'calendar'), - future: rcmail.gettext('futureevents', 'calendar'), - week: rcmail.gettext('weekofyear', 'calendar') - }, selectable: true, selectHelper: false, - currentTimeIndicator: settings.time_indicator, loading: function(isLoading) { me.is_loading = isLoading; this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading); @@ -2935,13 +3007,6 @@ function rcube_calendar_ui(settings) if (!isLoading) me.events_loaded($(this).fullCalendar('clientEvents').length); }, - // event rendering - eventRender: fc_event_render, - // render element indicating more (invisible) events - overflowRender: function(data, element) { - element.html(rcmail.gettext('andnmore', 'calendar').replace('$nr', data.count)) - .click(function(e){ me.fisheye_view(data.date); }); - }, // callback for date range selection select: function(start, end, allDay, e, view) { var range_select = (!allDay || start.getDate() != end.getDate()) @@ -2966,11 +3031,6 @@ function rcube_calendar_ui(settings) day_clicked = date.getTime(); day_clicked_ts = now; }, - // callback when a specific event is clicked - eventClick: function(event, ev, view) { - if (!event.temp) - event_show_dialog(event, ev); - }, // callback when an event was dragged and finally dropped eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) { if (event.end == null || event.end.getTime() < event.start.getTime()) { @@ -3040,7 +3100,7 @@ function rcube_calendar_ui(settings) if (fc && view.name == 'month') fc.fullCalendar('option', 'maxHeight', Math.floor((view.element.parent().height()-18) / 6) - 35); } - }); + })); // format time string var formattime = function(hour, minutes, start) { diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php index c52bfd72..bcfc7721 100644 --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -210,8 +210,10 @@ class kolab_user_calendar extends kolab_calendar } } - // get events from the user's free/busy feed - $this->fetch_freebusy($limit_changed); + // get events from the user's free/busy feed (for quickview only) + if (!empty($_REQUEST['_quickview']) && empty($search)) { + $this->fetch_freebusy($limit_changed); + } $events = array(); foreach ($this->events as $id => $event) { @@ -281,7 +283,7 @@ class kolab_user_calendar extends kolab_calendar // console('_fetch_freebusy', kolab_storage::get_freebusy_url($this->userdata['mail']), $fbdata); - // parse free-busy information using Horde classes + // parse free-busy information $count = 0; if ($fbdata) { $ical = $this->cal->get_ical(); @@ -304,6 +306,7 @@ class kolab_user_calendar extends kolab_calendar 'start' => $from, 'end' => $to, 'free_busy' => $statusmap[$type] ?: 'busy', + 'className' => 'fc-type-freebusy', 'organizer' => array( 'email' => $this->userdata['mail'], 'name' => $this->userdata['displayname'], diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 5e1bd445..4fef6919 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -308,6 +308,7 @@ class calendar_ui html::span(array('class' => 'calname', 'id' => $label_id, 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname']) . ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id), '') . + html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->cal->gettext('quickview'), 'role' => 'checkbox', 'aria-checked' => 'false'), '') . (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '') . html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' ') ) diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 80d36cb1..87a3e652 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -94,6 +94,7 @@ $labels['calsearchresults'] = 'Available Calendars'; $labels['calendarsubscribe'] = 'List permanently'; $labels['nocalendarsfound'] = 'No calendars found'; $labels['nrcalendarsfound'] = '$nr calendars found'; +$labels['quickview'] = 'View only this calendar'; // agenda view $labels['listrange'] = 'Range to display:'; diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index d78d9ecd..de6aab53 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -219,11 +219,11 @@ pre { #calendars .treelist li span.calname { display: block; - padding: 0px 30px 2px 2px; + padding: 0px 18px 2px 2px; position: absolute; top: 7px; left: 38px; - right: 40px; + right: 60px; cursor: default; background: url(images/calendars.png) right 20px no-repeat; overflow: hidden; @@ -240,7 +240,7 @@ pre { #calendars .treelist.flat li span.calname { left: 24px; - right: 22px; + right: 42px; } #calendars .treelist li span.handle { @@ -288,6 +288,29 @@ pre { background-position: -16px -110px; } +#calendars .treelist li a.quickview { + display: inline-block; + position: absolute; + top: 6px; + right: 42px; + width: 16px; + height: 16px; + padding: 0; + background: url(images/calendars.png) -100px 0 no-repeat; + overflow: hidden; + text-indent: -5000px; + cursor: pointer; +} + +#calendars .treelist div > a.quickview:focus, +#calendars .treelist li div:hover > a.quickview { + background-position: 0 -128px; +} + +#calendars .treelist li div.focusview > a.quickview { + background-position: -18px -128px; +} + #calendars .treelist li input { position: absolute; top: 5px; @@ -1316,9 +1339,26 @@ a.dropdown-link:after { bottom: 2px; } +#quickview-calendar { + padding: 8px; + overflow: hidden; +} + .calendarmain .fc-button, .calendarmain .fc-button.fc-state-default, .calendarmain .fc-button.fc-state-hover { + background-color: #f5f5f5; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-position: 0 0; +} + +.calendarmain #calendar .fc-button, +.calendarmain #calendar .fc-button.fc-state-default, +.calendarmain #calendar .fc-button.fc-state-hover { margin: 0 0 0 0; height: 20px; line-height: 20px; @@ -1338,22 +1378,15 @@ a.dropdown-link:after { text-decoration: none; } -.calendarmain .fc-button.fc-state-disabled { +.calendarmain #calendar .fc-button.fc-state-disabled { color: #999; background: #d8d8d8; } -.calendarmain .fc-button.fc-state-down { - margin: 0; - background: #bababa; - background: -moz-linear-gradient(top, #bababa 0%, #d8d8d8 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bababa), color-stop(100%,#d8d8d8)); - background: -o-linear-gradient(top, #bababa 0%, #d8d8d8 100%); - background: -ms-linear-gradient(top, #bababa 0%, #d8d8d8 100%); - background: linear-gradient(top, #bababa 0%, #d8d8d8 100%); -} - -.calendarmain .fc-button.fc-state-active { +.calendarmain .fc-button.fc-state-active, +.calendarmain .fc-button.fc-state-down, +.calendarmain #calendar .fc-button.fc-state-active, +.calendarmain #calendar .fc-button.fc-state-down { color: #333; background: #bababa; background: -moz-linear-gradient(top, #bababa 0%, #d8d8d8 100%); @@ -1363,12 +1396,12 @@ a.dropdown-link:after { background: linear-gradient(top, #bababa 0%, #d8d8d8 100%); } -.calendarmain .fc-header .fc-button { +.calendarmain #calendar .fc-header .fc-button { margin-left: -1px; margin-right: 0; } -.calendarmain .fc-header-left .fc-button { +.calendarmain #calendar .fc-header-left .fc-button { display: inline-block; margin: 0; text-align: center; @@ -1393,58 +1426,58 @@ a.dropdown-link:after { outline: none; } -.calendarmain .fc-header-left .fc-button:focus { +.calendarmain #calendar .fc-header-left .fc-button:focus { color: #fff; text-shadow: 0px 1px 1px #666; background-color: rgba(30,150,192, 0.5); border-radius: 3px; } -.calendarmain .fc-header-left .fc-button.fc-state-active { +.calendarmain #calendar .fc-header-left .fc-button.fc-state-active { font-weight: bold; color: #222; text-shadow: none; background-color: transparent; } -.calendarmain .fc-header-left .fc-button-agendaDay { +.calendarmain #calendar .fc-header-left .fc-button-agendaDay { background-position: center -120px; } -.calendarmain .fc-header-left .fc-button-agendaDay.fc-state-active { +.calendarmain #calendar .fc-header-left .fc-button-agendaDay.fc-state-active { background-position: center -160px; } -.calendarmain .fc-header-left .fc-button-agendaWeek { +.calendarmain #calendar .fc-header-left .fc-button-agendaWeek { background-position: center -200px; } -.calendarmain .fc-header-left .fc-button-agendaWeek.fc-state-active { +.calendarmain #calendar .fc-header-left .fc-button-agendaWeek.fc-state-active { background-position: center -240px; } -.calendarmain .fc-header-left .fc-button-month { +.calendarmain #calendar .fc-header-left .fc-button-month { background-position: center -280px; } -.calendarmain .fc-header-left .fc-button-month.fc-state-active { +.calendarmain #calendar .fc-header-left .fc-button-month.fc-state-active { background-position: center -320px; } -.calendarmain .fc-header-left .fc-button-table { +.calendarmain #calendar .fc-header-left .fc-button-table { background-position: center -360px; } -.calendarmain .fc-header-left .fc-button-table.fc-state-active { +.calendarmain #calendar .fc-header-left .fc-button-table.fc-state-active { background-position: center -400px; } -.calendarmain .fc-header-right { +.calendarmain #calendar .fc-header-right { padding-right: 252px; padding-top: 4px; } -.fc-header-title { +.calendarmain #calendar .fc-header-title { padding-top: 5px; } @@ -1452,6 +1485,39 @@ a.dropdown-link:after { font-size: 1em !important; } +.fc-type-freebusy { + opacity: 0.55; + color: #fff !important; + + background: rgba(80,80,80,0.85) !important; + background: -moz-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.9) 100%) !important; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(80,80,80,0.85)), color-stop(100%,rgba(48,48,48,0.9))) !important; + background: -webkit-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important; + background: -o-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important; + background: -ms-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important; + background: linear-gradient(to bottom, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important; + + -moz-box-shadow: inset 0px 1px 0 0px #888; + -webkit-box-shadow: inset 0px 1px 0 0px #888; + -o-box-shadow: inset 0px 1px 0 0px #888; + box-shadow: inset 0px 1px 0 0px #888; + + border-color: #444 !important; + cursor: default !important; +} + +.fc-type-freebusy .fc-event-skin, +.fc-type-freebusy .fc-event-inner { + background-color: transparent !important; + border-color: #444 !important; + color: #fff !important; + text-shadow: 0 1px 1px #000; +} + +.fc-type-freebusy .fc-event-title { + display: none; +} + .calendarmain .fc-event:focus { outline: 1px solid rgba(71,135,177, 0.4); -webkit-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6); diff --git a/plugins/calendar/skins/larry/images/calendars.png b/plugins/calendar/skins/larry/images/calendars.png index 117d3295fb298413bfb848e27802ccf2c49578ba..bf84f3ac59c7d86c2453e888d915f783eb1537ec 100644 GIT binary patch literal 2698 zcmV;53U&2~P)fs!OIZzor%>{#cdQ)cYg*8y8{eXYay9abNkj*Hc^_E?fPY*vKPaOBO+S3>2>|S| z@9&3@bAEEBw^ac6CS)L9W&y&%S>zlae;b&dha`$3k;Q`_-~??Bz`+9Xh#dm?w!2(B zZ5?=gTW+cXMdg)9_EOvrEeaqYHi1n>AF3!p9+G(E6cV9324yLcUs5*0_3?>C0Tigu z*z+>7ZZGUKgPR`6Z+yL0%a; zR#b%|iz-o6i5dwD%8_4U1|sdGAL(ya0A3>L?>X^lNTjSlv3T8+0wmQ>o&$-RcHKF- zPTEO7(%-BA${Sm@leJHRk;;FYlXlXN^fxPj=PzFT?)i(C=*7$Ro%AF9%}vENV&C*0 zzLD9o8MCO)Lz@xN)p=+$BDy*cZAL^_=b_Dr=;}PQ7?C-29$JjZ96ApzMr011hZZBE zqw|=B5t*X%n3WNkqVt%Q5t*X%n3WNkqVq6}h_PQHk=cm-1FQdsmBIFUasGJFKSW1l z4)_KJ*n9X`S0n1bKJ@4I{;McMtwEf`Oyn97L&%KlZWm7*s{rtik?lIu(c^%Z8+ChX z8p$iqAPS`*ryv23&^?O=xA*k5Il>p~ws5y&$HUVr)O#?H09AW8Ix0)YAcU4x%udxe zu`XQu4=QMEMI3n+`bsK8E)g+~7=&YCQ=2Xm`#C{&76afV6n~;>Zri7woA(%*oJN8Z z#mEa2TxXHgPCGZ}!B^mOsVsVrhlRunH-R|ZStvy=qG)kr*Tp|g+fDx&%SiF+4Gg}_V~h5*V%*42wk05wXZMGL|AHyOJ9~xt-7_4!t62dY7DXct zehnk6$xR~Dy;BT-cL!-_=Z@-w+F{>rvFb0ZZ$`Y_5)^!*3`sEu)CaXCqqoY_kF4{> zbF2JW@aW!5SIX@rKMszqaVjUXyW}mZej)$t84C> z3V_0%rFX~ozW;2?K!~CQFd+e@Qpqv}KpA$n-rs8)^7HdqSryfQH!$FzC;^Q|!+V#} zKEvK^(k9zr!LC$fL`~TpP$$E4-7&C4vKe72+A!60UmuM1|TXq4WW5ufVXns zrsY9-a~nK*^a#l2v|500QgkHktF*E8ZQ-jtg2|>c_+(}VO1|y|QEAN^o>+A(+c6Juxs~bRwM{aOIu^NU($H}O>3E1`O!I4bG$_J4? z_3P?;U6(Eo;w>5YqyY6EuB-%QeKXY7*0SEG`t1zQ0sY^vfvsh!B^Aq<7eGo~3rs$E z1oeGaA+%5h{G5~PuLeB;5)|nJ+v$K|@RH>5Km!gyrRB5Z=BLSUaS55DjTfZZNG4WAuyW3Lr>i*eu!7cMCK`}>vc?d`@u{$!91a!8QQ&Q9gy$B&ic2W zd@<~D_bD?DzPYuvHN`M$ym=(`^z?8I0SU0DPoFvyP)6C<@sr^06w6H{o#v*VfYh2M zm|I+$h$$?u!(tbB<4m}U~!`1k<#Jd!G@9JO(YgSE;p~3 ze`QBUhcmwZhsnuF1v$s}kmD8ufFog%E!>n`h_9>%td0zkN3stN9HJ=J(KAn!0zT6C zY(m3&t_{soiJ5@KjQPJDanT{i3off@M_b?OX6U^?cXQ2;5WsvdG()7hKso1y^s*d#cXlI6Mq zr#F7({F34`20*4jUnjZbe0X?RNit#pFgi7TOH^D9SkMB8Ac1avv+B-*Sfzo8vRdHc z>$k@5WZQfDvyTX+vm88CmFGKuB&PVEty5D|#sQcvgCc(3e ze_#gzVZ7vUA;#h~A8TuCOEX*}nIU!ZmaUe$j6p;dSAjpS2X8#@lKhgP^s2gkpSa|E z?$QMC!E>K}x)FYuUl>~L2X>Gpcm4Wx<;of$1xf(oK0-sCybZ*o8LhUe<01s*6oG%L z07C7Na=%#nAM32I%4Y>3lmGw#07*qoM6N<$ Eg7?S{7XSbN literal 3337 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