From a1d018875bc0065f372b6483ff3b1be13f4c787c Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 16 Jan 2019 13:40:11 +0100 Subject: [PATCH] Calendar agenda view and printing Note that agenda's smart sections feature is not implemented (yet) as it does not exist in fullCalendar (it was a Kolab's customization). --- plugins/calendar/calendar.php | 6 +- plugins/calendar/calendar_ui.js | 63 ++++----- plugins/calendar/print.js | 160 ++++++++++++---------- plugins/calendar/skins/larry/calendar.css | 60 +++++--- plugins/calendar/skins/larry/print.css | 116 ++++++---------- 5 files changed, 204 insertions(+), 201 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index d97d1b5d..c6e3eda0 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -1907,6 +1907,10 @@ class calendar extends rcube_plugin $event['end'] = $event['end']->add(new DateInterval('P1D')); } + if ($_GET['mode'] == 'print') { + $event['editable'] = false; + } + return array( '_id' => $event['calendar'] . ':' . $event['id'], // unique identifier for fullcalendar 'start' => $this->lib->adjust_timezone($event['start'], $event['allDay'])->format('c'), @@ -2424,7 +2428,7 @@ class calendar extends rcube_plugin if (!in_array($view, array('agendaWeek', 'agendaDay', 'month', 'list'))) $view = 'agendaDay'; - $this->rc->output->set_env('view',$view); + $this->rc->output->set_env('view', $view); if ($date = rcube_utils::get_input_value('date', rcube_utils::INPUT_GPC)) $this->rc->output->set_env('date', $date); diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index c54a6904..3868f994 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -73,12 +73,15 @@ function rcube_calendar_ui(settings) // global fullcalendar settings var fullcalendar_defaults = { + theme: false, aspectRatio: 1, timezone: false, // 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, + weekNumbers: settings.show_weekno > 0, + weekNumberTitle: rcmail.gettext('weekshort', 'calendar') + ' ', firstDay: settings.first_day, firstHour: settings.first_hour, slotDuration: {minutes: 60/settings.timeslots}, @@ -114,8 +117,6 @@ function rcube_calendar_ui(settings) axisFormat : settings.time_format, defaultView: rcmail.env.view || settings.default_view, allDayText: rcmail.gettext('all-day', 'calendar'), - weekNumbers: settings.show_weekno > 0, - weekNumberTitle: rcmail.gettext('weekshort', 'calendar') + ' ', buttonText: { today: settings['today'], day: rcmail.gettext('day', 'calendar'), @@ -127,12 +128,10 @@ function rcube_calendar_ui(settings) prev: 'left-single-arrow', next: 'right-single-arrow' }, - theme: false, nowIndicator: settings.time_indicator, eventLimitText: function(num) { return rcmail.gettext('andnmore', 'calendar').replace('$nr', num); }, - noEventsMessage: rcmail.gettext('calendar.searchnoresults'), /* listTexts: { until: rcmail.gettext('until', 'calendar'), @@ -154,8 +153,14 @@ function rcube_calendar_ui(settings) element.attr('title', prefix + event.title); } if (view.name != 'month') { - if (event.location) { - element.find('div.fc-title').after('
@ ' + Q(event.location) + '
'); + if (view.name == 'list') { + var loc = $('').attr('class', 'fc-event-location'); + if (event.location) + loc.text(event.location); + element.find('.fc-list-item-title').after(loc); + } + else if (event.location && view.name != 'month') { + element.find('div.fc-title').after('
').html('@ ' + Q(event.location)); } var time_element = element.find('div.fc-time'); if (event.sensitivity && event.sensitivity != 'public') @@ -2735,9 +2740,7 @@ function rcube_calendar_ui(settings) defaultView: 'agendaDay', header: { left: '', center: '', right: '' }, height: h - 50, - date: date.getDate(), - month: date.getMonth(), - year: date.getFullYear(), + defaultDate: date, eventSources: sources })); @@ -3427,36 +3430,27 @@ function rcube_calendar_ui(settings) this.reset_quicksearch = function() { $(rcmail.gui_objects.qsearchbox).val(''); - + if (this._search_message) rcmail.hide_message(this._search_message); - + if (this.search_request) { - // restore original event sources and view mode from fullcalendar - // fc.fullCalendar('option', 'listSections', settings.agenda_sections); - update_agenda_toolbar(); - $.each(fc.fullCalendar('getEventSources'), function() { this.url = this.url.replace(/&q=.+/, ''); me.calendars[this.id].url = this.url; }); - fc.fullCalendar('refetchEvents'); - this.search_request = this.search_query = null; + + fc.fullCalendar('refetchEvents'); } }; // callback if all sources have been fetched from server this.events_loaded = function() { - var addlinks, append = ''; - - // enhance list view when searching - if (this.search_request) { - if (!fc.fullCalendar('clientEvents').length) { - this._search_message = rcmail.display_message(rcmail.gettext('searchnoresults', 'calendar'), 'notice'); - } + if (this.search_request && !fc.fullCalendar('clientEvents').length) { + this._search_message = rcmail.display_message(rcmail.gettext('searchnoresults', 'calendar'), 'notice'); } if (this.fisheye_date) @@ -3482,7 +3476,6 @@ function rcube_calendar_ui(settings) this.selected_calendar = id; rcmail.update_state({source: id}); - rcmail.enable_command('addevent', this.calendars[id] && this.calendars[id].editable); }; @@ -3702,10 +3695,10 @@ function rcube_calendar_ui(settings) if (window.UI && UI.pretty_checkbox) { $(rcmail.gui_objects.calendarslist).find('input[type=checkbox]').each(function() { UI.pretty_checkbox($(this).addClass('flex-checkbox')); - }); - calendars_list.addEventListener('add-item', function(prop) { - UI.pretty_checkbox($(prop.li).find('input').addClass('flex-checkbox')); - }); + }); + calendars_list.addEventListener('add-item', function(prop) { + UI.pretty_checkbox($(prop.li).find('input').addClass('flex-checkbox')); + }); } // create list of event sources AKA calendars @@ -3745,16 +3738,14 @@ function rcube_calendar_ui(settings) center: 'title', left: 'agendaDay,agendaWeek,month,list' }, - date: viewdate.getDate(), - month: viewdate.getMonth(), - year: viewdate.getFullYear(), + defaultDate: viewdate, height: $('#calendar').height(), eventSources: event_sources, selectable: true, selectHelper: false, loading: function(isLoading) { me.is_loading = isLoading; - this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading); + this._rc_loading = rcmail.set_busy(isLoading, me.search_request ? 'searching' : 'loading', this._rc_loading); // trigger callback (using timeout, otherwise clientEvents is always empty) if (!isLoading) setTimeout(function() { me.events_loaded(); }, 20); @@ -3853,6 +3844,12 @@ function rcube_calendar_ui(settings) fc.fullCalendar('gotoDate', viewStart.add(settings.agenda_range, 'days')); }); } + }, + eventAfterAllRender: function(view) { + if (view.name == 'list') { + // Fix colspan of headers after we added Location column + fc.find('tr.fc-list-heading > td').attr('colspan', 4); + } } })); diff --git a/plugins/calendar/print.js b/plugins/calendar/print.js index bd156faf..d44bc0f0 100644 --- a/plugins/calendar/print.js +++ b/plugins/calendar/print.js @@ -37,12 +37,13 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { var rc_loading; var showdesc = true; + var desc_elements = {}; var settings = $.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings); // create list of event sources AKA calendars - var src, event_sources = []; + var id, src, event_sources = []; var add_url = (rcmail.env.search ? '&q='+escape(rcmail.env.search) : ''); - for (var id in rcmail.env.calendars) { + for (id in rcmail.env.calendars) { if (!rcmail.env.calendars[id].active) continue; @@ -54,6 +55,9 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { source.color = '#' + source.color.replace(/^#/, ''); + if (source.color == '#ffffff') + source.color = '#ccc'; + event_sources.push(source); } @@ -66,72 +70,89 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { header: { left: '', center: 'title', - right: 'agendaDay,agendaWeek,month,table' + right: 'agendaDay,agendaWeek,month,list' }, + theme: false, aspectRatio: 0.85, - ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone - date: viewdate.getDate(), - month: viewdate.getMonth(), - year: viewdate.getFullYear(), + selectable: false, + editable: false, + timezone: false, // 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, + weekNumbers: settings.show_weekno > 0, + weekNumberTitle: rcmail.gettext('weekshort', 'calendar') + ' ', + firstDay: settings.first_day, + firstHour: settings.first_hour, + slotDuration: {minutes: 60/settings.timeslots}, + businessHours: { + start: settings.work_start + ':00', + end: settings.work_end + ':00' + }, + views: { + list: { + titleFormat: settings.dates_long, + visibleRange: function(currentDate) { + return { + start: currentDate.clone(), + end: currentDate.clone().add(settings.agenda_range, 'days') + } + } + }, + month: { + columnFormat: 'ddd', // Mon + titleFormat: 'MMMM YYYY', + eventLimit: 6 + }, + week: { + columnFormat: 'ddd ' + settings.date_short, // Mon 9/7 + titleFormat: settings.dates_long + }, + day: { + columnFormat: 'dddd ' + settings.date_short, // Monday 9/7 + titleFormat: 'dddd ' + settings.date_long + } + }, + timeFormat: settings.time_format, + axisFormat : settings.time_format, + allDayText: rcmail.gettext('all-day', 'calendar'), + defaultDate: viewdate, defaultView: rcmail.env.view, 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 - list: settings['date_agenda'], - table: settings['date_agenda'] - }, - titleFormat: { - month: 'MMMM yyyy', - week: settings['dates_long'], - day: 'dddd ' + settings['date_long'], - list: settings['dates_long'], - table: settings['dates_long'] - }, - listSections: rcmail.env.listSections !== undefined ? rcmail.env.listSections : settings['agenda_sections'], - listRange: rcmail.env.listRange || settings['agenda_range'], - tableCols: ['handle', 'date', 'time', 'title', 'location'], - allDayText: rcmail.gettext('all-day', 'calendar'), buttonText: { + today: settings['today'], day: rcmail.gettext('day', 'calendar'), week: rcmail.gettext('week', 'calendar'), month: rcmail.gettext('month', 'calendar'), - table: rcmail.gettext('agenda', 'calendar') + list: 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') + buttonIcons: { + prev: 'left-single-arrow', + next: 'right-single-arrow' + }, + nowIndicator: settings.time_indicator, + eventLimitText: function(num) { + return rcmail.gettext('andnmore', 'calendar').replace('$nr', num); }, loading: function(isLoading) { rc_loading = rcmail.set_busy(isLoading, 'loading', rc_loading); }, // event rendering eventRender: function(event, element, view) { - if (view.name != 'month' && view.name != 'table') { - var cont = element.find('.fc-event-title'); + if (view.name == 'list') { + var loc = $('').attr('class', 'fc-event-location'); + if (event.location) + loc.text(event.location); + element.find('.fc-list-item-title').after(loc); + + // we can't add HTML elements after the curent element, + // so we store it for later. One description per event + if (event.description && showdesc && !desc_elements[event.uid]) + desc_elements[event.uid] = {element: element[0], description: event.description}; + } + else if (view.name != 'month') { + var cont = element.find('div.fc-title'); if (event.location) { cont.after('
@ ' + Q(event.location) + '
'); cont = cont.next(); @@ -140,31 +161,24 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { cont.after('
' + Q(event.description) + '
'); } } - if (view.name == 'table' && event.description && showdesc) { - var cols = element.children().css('border', 0).length; - element.after('' + Q(event.description) + ''); + }, + eventAfterAllRender: function(view) { + if (view.name == 'list') { + // Fix colspan of headers after we added Location column + fc.find('tr.fc-list-heading > td').attr('colspan', 4); + + $.each(desc_elements, function() { + $(this.element).after('' + Q(this.description) + ''); + }); } }, - viewDisplay: function(view) { - // remove hard-coded hight and make contents visible - window.setTimeout(function(){ - if (view.name == 'table') { - $('div.fc-list-content').css('overflow', 'visible').height('auto'); - } - else { - $('div.fc-agenda-divider') - .next().css('overflow', 'visible').height('auto') - .children('div').css('overflow', 'visible').height('auto'); - } - // adjust fixed height if vertical day slots - var h = $('table.fc-agenda-slots:visible').height() + $('table.fc-agenda-allday:visible').height() + 4; - if (h) $('table.fc-agenda-days td.fc-widget-content').children('div').height(h); - }, 20); + viewRender: function(view) { + desc_elements = {}; } }); // activate settings form - $('#propdescription').change(function(){ + $('#propdescription').change(function() { showdesc = this.checked; fc.fullCalendar('render'); }); @@ -173,7 +187,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { if (selector) { selector = $('#' + selector); - $('.fc-header-right > span').each(function() { + $('.fc-right > button').each(function() { var cl = 'btn btn-secondary', btn = $(this); if (btn.is('.fc-state-active')) { diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index fedaaeca..4df834ea 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -1581,12 +1581,9 @@ a.dropdown-link:after { bottom: -1px; } -#resource-freebusy-calendar .fc-content { - top: 0; -} - -#resource-freebusy-calendar .fc-content .fc-bg { - background: 0; +#resource-freebusy-calendar .fc-toolbar { + height: 0; + margin: 0; } #resource-freebusy-calendar .fc-event.status-busy, @@ -1634,20 +1631,22 @@ a.dropdown-link:after { /* fullcalendar style overrides */ +.calendarmain .fc-body, .fc-scroller { background: #fff; } -.calendarmain .fc-body { - background: #fff; -} - -.calendarmain.quickview-active .fc-body { +.calendarmain.quickview-active .fc-view .fc-scroller { background-image: url('images/focusview.png'); background-position: center; background-repeat: no-repeat; } +.fc-unthemed .fc-list-heading td { + background: rgba(0, 0, 0, .05); + padding: 8px; +} + #quickview-calendar { padding: 8px; overflow: hidden; @@ -1779,7 +1778,7 @@ a.dropdown-link:after { padding-top: 5px; } -#calendar .fc-toolbar.fc-header-toolbar { +.calendarmain #calendar .fc-toolbar.fc-header-toolbar { margin-bottom: 7px; margin-right: 250px; } @@ -1854,6 +1853,11 @@ a.dropdown-link:after { border-bottom: 0; } +.fc-title, +.fc-list-item-title { + font-weight: bold; +} + .fc-list-table tr.fc-invitation-tentative td, .fc-list-table tr.fc-invitation-declined td, .fc-list-table tr.fc-invitation-needs-action td { @@ -1877,10 +1881,6 @@ a.dropdown-link:after { box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6); } -.fc-title { - font-weight: bold; -} - .fc-needs-action, .fc-declined, .cal-event-status-cancelled { @@ -1934,13 +1934,13 @@ a.dropdown-link:after { background-color: rgba(198,198,198, 0.08); } -.calendarmain .fc-unthemed td.fc-today { +.calendarmain .fc-unthemed td.fc-day.fc-today { background-color: rgba(233,198,14, 0.12); } .fc-widget-header, .fc-widget-content { - border-color: #bbd3da !important; + border-color: #c3c3c3 !important; } .fc-widget-header .fc-agenda-divider-inner { @@ -1953,6 +1953,11 @@ a.dropdown-link:after { text-shadow: 0px 1px 1px #fff; } +.fc-popover .fc-header .fc-title, +.fc-list-heading .fc-widget-header { + color: #666; +} + .fc-view thead th.fc-widget-header { padding: 8px 0; color: #69939e; @@ -2000,7 +2005,7 @@ a.dropdown-link:after { } .fc-list-table tr.fc-list-item td { - border-color: #bbd3da; + border-color: #c3c3c3; padding: 6px 8px; white-space: nowrap; overflow: hidden; @@ -2037,6 +2042,10 @@ a.dropdown-link:after { width: 25%; } +.fc-event-dot { + vertical-align: middle; +} + .fc-list-empty { font-size: 150%; color: #999; @@ -2052,10 +2061,23 @@ a.dropdown-link:after { padding: 0 3px !important; } +.fc .fc-week-number { + text-align: center; +} + +.fc-month-view .fc-week-number { + width: 20px !important; + padding: 2px 0; +} + .fc-time-grid .fc-now-indicator { border: 1px solid #3ec400; } +.fc-list-empty { + display: none; +} + /* Settings section */ fieldset #calendarcategories div { diff --git a/plugins/calendar/skins/larry/print.css b/plugins/calendar/skins/larry/print.css index fc5de975..ec1766b2 100644 --- a/plugins/calendar/skins/larry/print.css +++ b/plugins/calendar/skins/larry/print.css @@ -79,6 +79,11 @@ body, td, th, div, p, h3, select, input, textarea { padding-bottom: 1em; } +#calendarlist li div a { + color: inherit; + text-decoration: none; +} + #calendarlist input, #calendarlist .handle { display: none; @@ -101,7 +106,7 @@ body, td, th, div, p, h3, select, input, textarea { @media print { .noprint, - .fc-header-right span { + .fc-right { display: none; } @@ -116,33 +121,19 @@ body, td, th, div, p, h3, select, input, textarea { overflow: visible; } -.fc-event-skin, -.fc-event-inner .fc-event-skin { +.fc-unthemed td.fc-day.fc-today { + background: transparent; +} + +a.fc-event { color: black; background-color: #fff !important; } -.fc-event-title { +.fc-title { font-weight: bold; } -.fc-event-hori .fc-event-title { - font-weight: normal; - white-space: nowrap; -} - -.fc-event-hori .fc-event-time { - white-space: nowrap; - font-weight: normal !important; - font-size: 10px; - padding-right: 0.6em; -} - -.fc-grid .fc-event-time { - font-weight: normal !important; - padding-right: 0.3em; -} - .fc-event-cateories { font-style: italic; } @@ -155,6 +146,13 @@ body, td, th, div, p, h3, select, input, textarea { height: 1.4em; } +.fc-axis, +.fc-week-number, +.fc-day-number, +.fc-view thead th.fc-widget-header { + color: #444; +} + .fc-widget-header, .fc-mon, .fc-tue, .fc-wed, .fc-thu, .fc-fri { background-color: #fff; @@ -164,66 +162,34 @@ body, td, th, div, p, h3, select, input, textarea { border-color: #ccc; } -.fc-icon-alarms, -.fc-icon-recurring { - display: inline-block; - width: 11px; - height: 11px; - background: url('images/eventicons.gif') 0 0 no-repeat; - margin-left: 3px; - line-height: 10px; +.fc-list-table tr.fc-list-item td, +.fc-list-view { + border: 0 !important; } -.fc-icon-alarms { - background-position: 0 -13px; +.fc-list-table tr:first-child td { + border-top-width: 1px; } -.fc-view-list, .fc-view-table { - border: 0; -} - -.fc-view-list div.fc-list-header, -.fc-view-table td.fc-list-header { - padding: 0.3em; - background: #fff; - font-weight: bold; - font-size: 1.2em; - color: #333; - border-color: #333; - border-style: solid; - border-width: 1px 0; - filter: none; -} - -.fc-list-section .fc-event { - cursor: auto; -} - -.fc-view-table tr.fc-event td, -.fc-view-table tr.fc-event td.fc-event-handle { - border-color: #999; - padding-top: 0.5em; - padding-bottom: 0.5em; -} - -.fc-view-table tr.fc-last td { - border: 0; -} - -.fc-view-table tr.fc-event .fc-event-description { - padding-left: 2em; - padding-top: 0em; -} - -.fc-event-vert .fc-event-description { +.fc-event-description { font-size: 90%; - font-style: italic; } -.fc-view-month .fc-event-hori .fc-event-inner { - background: #fff !important; -} - -.fc-view-table col.fc-event-location { +col.fc-event-location { width: 20%; } + +.fc-event-row-secondary td { + border: 0; + padding-top: 0 !important; +} + +.fc-scroller { + overflow: visible !important; + height: auto !important; +} +.fc-head .fc-row, +.fc-day-grid .fc-row { + margin-right: 0 !important; + border-right-width: 0 !important; +}