Skip to content

Instantly share code, notes, and snippets.

@georgehrke
Created August 16, 2016 12:28
Show Gist options
  • Save georgehrke/182bb08b75afc4d5a729e9d552a19316 to your computer and use it in GitHub Desktop.
Save georgehrke/182bb08b75afc4d5a729e9d552a19316 to your computer and use it in GitHub Desktop.
From 7b0532958a8d862cc5139a11bba58001f2c5945a Mon Sep 17 00:00:00 2001
From: Georg Ehrke <developer@georgehrke.com>
Date: Sat, 13 Feb 2016 00:07:30 +0100
Subject: [PATCH] add repeating events to editor
---
css/eventdialog.css | 43 +-
js/.jshintrc | 1 +
js/app/controllers/calcontroller.js | 33 +-
.../controllers/eventspopovereditorcontroller.js | 2 +
.../controllers/eventssidebareditorcontroller.js | 295 +++++++++++---
js/app/filters/daysfilter.js | 30 ++
js/app/filters/hoursfilter.js | 30 ++
js/app/filters/minutesfilter.js | 30 ++
js/app/filters/monthsfilter.js | 30 ++
js/app/filters/periodsfilter.js | 30 ++
js/app/filters/secondsfilter.js | 30 ++
js/app/filters/timesfilter.js | 30 ++
js/app/filters/weeksfilter.js | 30 ++
js/app/filters/yearsfilter.js | 30 ++
js/app/service/localizationservice.js | 61 +++
js/app/service/objectConverter.js | 38 +-
js/public/app.js | 451 ++++++++++++++++-----
templates/part.eventsrepeat.php | 134 +++++-
templates/part.eventssidebareditor.php | 4 +
19 files changed, 1110 insertions(+), 222 deletions(-)
create mode 100644 js/app/filters/daysfilter.js
create mode 100644 js/app/filters/hoursfilter.js
create mode 100644 js/app/filters/minutesfilter.js
create mode 100644 js/app/filters/monthsfilter.js
create mode 100644 js/app/filters/periodsfilter.js
create mode 100644 js/app/filters/secondsfilter.js
create mode 100644 js/app/filters/timesfilter.js
create mode 100644 js/app/filters/weeksfilter.js
create mode 100644 js/app/filters/yearsfilter.js
create mode 100644 js/app/service/localizationservice.js
diff --git a/css/eventdialog.css b/css/eventdialog.css
index b935c03..f4d3988 100644
--- a/css/eventdialog.css
+++ b/css/eventdialog.css
@@ -178,7 +178,7 @@ button.delete:hover, button.delete:focus {
}
.advanced .pull-half {
- width: 48%;
+ width: 48% !important;
}
@@ -228,3 +228,44 @@ button.delete:hover, button.delete:focus {
.dropdown-menu li a:hover {
background: #eee;
}
+
+.advanced--fieldset table {
+ width: 100%;
+}
+
+.advanced--fieldset table td {
+ text-align: center;
+}
+
+.advanced--fieldset table td.four-rows {
+ width: 25%;
+}
+
+.advanced--fieldset table td.seven-rows {
+ width: 14%;
+}
+
+.advanced--fieldset table label {
+ display: block;
+ width: 100%;
+ background-color: rgba(240,240,240,.9);
+ padding: 0.5em 0 0.5em 0;
+}
+
+.repeat_checkboxtable_checkbox:checked + .repeat_checkboxtable_label {
+ background-color: rgba(190,190,190,.9);
+}
+
+.advanced--fieldset table input {
+ display: none;
+}
+
+.advanced--fieldset input[type="number"] {
+ width: 3em;
+}
+
+.advanced--fieldset label.inline-label {
+ padding: 7px 6px 5px 0;
+ display: block;
+}
+
diff --git a/js/.jshintrc b/js/.jshintrc
index dd19c68..3a98f4d 100644
--- a/js/.jshintrc
+++ b/js/.jshintrc
@@ -34,6 +34,7 @@
"inject": true,
"module": true,
"t": true,
+ "n": true,
"it": true,
"exports": true,
"escapeHTML": true,
diff --git a/js/app/controllers/calcontroller.js b/js/app/controllers/calcontroller.js
index e04e479..3a8faf2 100644
--- a/js/app/controllers/calcontroller.js
+++ b/js/app/controllers/calcontroller.js
@@ -26,8 +26,8 @@
* Description: The fullcalendar controller.
*/
-app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'uiCalendarConfig', '$uibModal',
- function ($scope, $rootScope, $window, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, uiCalendarConfig, $uibModal) {
+app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'uiCalendarConfig', '$uibModal', 'LocalizationService',
+ function ($scope, $rootScope, $window, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, uiCalendarConfig, $uibModal, LocalizationService) {
'use strict';
is.loading = true;
@@ -334,39 +334,20 @@ app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarSer
/**
* Calendar UI Configuration.
*/
- var i;
-
- var monthNames = [];
- var monthNamesShort = [];
- for (i = 0; i < 12; i++) {
- monthNames.push(moment.localeData().months(moment([0, i]), ''));
- monthNamesShort.push(moment.localeData().monthsShort(moment([0, i]), ''));
- }
-
- var dayNames = [];
- var dayNamesShort = [];
- var momentWeekHelper = moment().startOf('week');
- momentWeekHelper.subtract(momentWeekHelper.format('d'));
- for (i = 0; i < 7; i++) {
- dayNames.push(moment.localeData().weekdays(momentWeekHelper));
- dayNamesShort.push(moment.localeData().weekdaysShort(momentWeekHelper));
- momentWeekHelper.add(1, 'days');
- }
-
$scope.uiConfig = {
calendar: {
height: w.height() - angular.element('#header').height(),
editable: true,
selectable: true,
lang: moment.locale(),
- monthNames: monthNames,
- monthNamesShort: monthNamesShort,
- dayNames: dayNames,
- dayNamesShort: dayNamesShort,
+ monthNames: LocalizationService.monthNames(),
+ monthNamesShort: LocalizationService.shortMonthNames(),
+ dayNames: LocalizationService.weekdayNames(),
+ dayNamesShort: LocalizationService.shortWeekdayNames(),
timezone: $scope.defaulttimezone,
defaultView: angular.element('#fullcalendar').attr('data-defaultView'),
header: false,
- firstDay: moment().startOf('week').format('d'),
+ firstDay: LocalizationService.firstDayOfWeek(),
select: $scope.newEvent,
eventLimit: true,
eventClick: function(fcEvent, jsEvent, view) {
diff --git a/js/app/controllers/eventspopovereditorcontroller.js b/js/app/controllers/eventspopovereditorcontroller.js
index cccda92..d8a7512 100644
--- a/js/app/controllers/eventspopovereditorcontroller.js
+++ b/js/app/controllers/eventspopovereditorcontroller.js
@@ -42,6 +42,8 @@ app.controller('EventsPopoverEditorController', ['$scope', 'TimezoneService', 'e
$scope.showTimezone = true;
}
+ console.log($scope.properties);
+
$scope.close = function(action) {
$scope.properties.dtstart.value = moment(angular.element('#from').datepicker('getDate'));
$scope.properties.dtend.value = moment(angular.element('#to').datepicker('getDate'));
diff --git a/js/app/controllers/eventssidebareditorcontroller.js b/js/app/controllers/eventssidebareditorcontroller.js
index 09ab5a2..b5a1ba9 100644
--- a/js/app/controllers/eventssidebareditorcontroller.js
+++ b/js/app/controllers/eventssidebareditorcontroller.js
@@ -26,8 +26,8 @@
* Description: Takes care of anything inside the Events Modal.
*/
-app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'AutoCompletionService', 'eventEditorHelper', '$window', '$uibModalInstance', 'vevent', 'recurrenceId', 'isNew', 'properties', 'emailAddress',
- function($scope, TimezoneService, AutoCompletionService, eventEditorHelper, $window, $uibModalInstance, vevent, recurrenceId, isNew, properties, emailAddress) {
+app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'AutoCompletionService', 'eventEditorHelper', '$window', '$uibModalInstance', 'LocalizationService', 'vevent', 'recurrenceId', 'isNew', 'properties', 'emailAddress',
+ function($scope, TimezoneService, AutoCompletionService, eventEditorHelper, $window, $uibModalInstance, LocalizationService, vevent, recurrenceId, isNew, properties, emailAddress) {
'use strict';
$scope.properties = properties;
@@ -38,6 +38,9 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.selected = 1;
$scope.timezones = [];
$scope.emailAddress = emailAddress;
+ $scope.rruleUnsupported = false;
+
+ console.log($scope.properties);
$scope.previousDtStartDate = null;
$scope.previousDtStartHour = null;
@@ -241,13 +244,106 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.previousDtStartMinute = angular.element('#advanced_fromtime').timepicker('getMinute');
$scope.tabopener(1);
+
+ if ($scope.properties.rrule.freq !== 'NONE') {
+ var partIds;
+ if (typeof $scope.properties.rrule.parameters !== 'undefined') {
+ partIds = Object.getOwnPropertyNames($scope.properties.rrule.parameters);
+ } else {
+ partIds = [];
+ }
+
+ var unsupportedFREQs = ['SECONDLY', 'MINUTELY', 'HOURLY'];
+ if (unsupportedFREQs.indexOf($scope.properties.rrule.freq) !== -1) {
+ $scope.rruleUnsupported = true;
+ }
+
+ var unsupportedParts = ['BYSECOND', 'BYMINUTE', 'BYHOUR', 'BYYEARDAY', 'BYWEEKNO'];
+ angular.forEach(unsupportedParts, function(unsupportedPart) {
+ if (partIds.indexOf(unsupportedPart) !== -1) {
+ $scope.rruleUnsupported = true;
+ }
+ });
+
+ if ($scope.rruleUnsupported) {
+ return;
+ }
+
+ if (typeof $scope.properties.rrule.interval === 'number') {
+ $scope.repeat.interval = $scope.properties.rrule.interval;
+ } else {
+ $scope.repeat.interval = 1;
+ }
+
+ $scope.repeat.freq = $scope.properties.rrule.freq;
+ if (partIds.length === 0 && $scope.repeat.interval === 1) {
+ $scope.repeat.simple = $scope.properties.rrule.freq;
+ } else {
+ $scope.repeat.simple = 'CUSTOM';
+
+ $scope._parseCustomRepeatRule(partIds);
+ }
+ }
});
- $scope.tabs = [{
- title: t('Calendar', 'Attendees'), value: 1
- }, {
- title: t('Calendar', 'Reminders'), value: 2
- }];
+ $scope._parseCustomRepeatRule = function(partIds) {
+ if ($scope.repeat.freq === 'DAILY' && partIds.length > 0) {
+ $scope.rruleUnsupported = true;
+ } else if ($scope.repeat.freq === 'WEEKLY') {
+ if (partIds.length === 0) {
+ $scope.repeat.weekly.weekdays = {
+ SU: true,
+ MO: true,
+ TU: true,
+ WE: true,
+ TH: true,
+ FR: true,
+ SA: true
+ };
+ } else if(partIds.length === 1 && partIds.indexOf('BYDAY') !== -1 || partIds.length === 2 && partIds.indexOf('BYDAY') !== -1 && partIds.indexOf('WKST') !== -1) {
+ angular.forEach($scope.properties.rrule.parameters.BYDAY, function (day) {
+ $scope.repeat.weekly.weekdays[day] = true;
+ });
+ } else {
+ $scope.rruleUnsupported = true;
+ }
+ } else if ($scope.repeat.freq === 'MONTHLY') {
+ if (partIds.indexOf('BYMONTH') !== -1) {
+ $scope.rruleUnsupported = true;
+ return;
+ }
+ if (partIds.indexOf('BYDAY') !== -1 && partIds.indexOf('BYMONTHDAY') !== -1) {
+ $scope.rruleUnsupported = true;
+ return;
+ }
+ if (partIds.indexOf('BYDAY') === -1 && partIds.indexOf('BYSETPOS') !== -1) {
+ $scope.rruleUnsupported = true;
+ return;
+ }
+
+ console.log(partIds);
+
+ } else if ($scope.repeat.freq === 'YEARLY') {
+ if (partIds.indexOf('BYMONTHDAY') !== -1) {
+ $scope.rruleUnsupported = true;
+ return;
+ }
+
+ if (partIds.indexOf('BYMONTH')) {
+ angular.forEach($scope.properties.rrule.parameters.BYMONTH, function (month) {
+ $scope.repeat.yearly.months[month] = true;
+ });
+
+ }
+
+ }
+ };
+
+ $scope.tabs = [
+ {title: t('Calendar', 'Attendees'), value: 1},
+ {title: t('Calendar', 'Reminders'), value: 2},
+ {title: t('Calendar', 'Repeating'), value: 3}
+ ];
$scope.tabopener = function (val) {
$scope.selected = val;
@@ -274,74 +370,147 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.properties.location.value = item.label;
};
- $scope.repeater = [
- { val: 'doesnotrepeat' , displayname: t('Calendar', 'Does not repeat')},
- { val: 'daily' , displayname: t('Calendar', 'Daily')},
- { val: 'weekly' , displayname: t('Calendar', 'Weekly')},
- { val: 'weekday' , displayname: t('Calendar', 'Every Weekday')},
- { val: 'biweekly' , displayname: t('Calendar', 'Bi-weekly')},
- { val: 'monthly' , displayname: t('Calendar', 'Monthly')},
- { val: 'yearly' , displayname: t('Calendar', 'Yearly')},
+ $scope.repeat = {
+ simple: 'NONE',
+ interval: 1,
+ freq: 'DAILY',
+ end: 'NEVER',
+ count: 1,
+ weekly: {
+ weekdays: {}
+ },
+ monthly: {
+ monthdays: {},
+ radio: 'on-days',
+ onthe_ordinal: 1,
+ onthe_day: 'DAY'
+ },
+ yearly: {
+ months: {},
+ onthe_ordinal: 1,
+ onthe_day: 'DAY'
+ },
+ //We will not support all crazy RRULEs in the first version
+ //when we can't deal with the RRULE, we'll show a warning in the editor
+ //instead of breaking the RRULE
+ unsupported: false
+ };
+ $scope.repeat_options_simple = [
+ {val: 'NONE', displayname: t('Calendar', 'None')},
+ {val: 'DAILY', displayname: t('Calendar', 'Every day')},
+ {val: 'WEEKLY', displayname: t('Calendar', 'Every week')},
+ {val: 'MONTHLY', displayname: t('Calendar', 'Every month')},
+ {val: 'YEARLY', displayname: t('Calendar', 'Every year')},
+ {val: 'CUSTOM', displayname: t('calendar', 'Custom')}
];
- $scope.repeatmodel = $scope.repeater[0].val;
- $scope.ender = [
- { val: 'never', displayname: t('Calendar','never')},
- { val: 'count', displayname: t('Calendar','by occurances')},
- { val: 'date', displayname: t('Calendar','by date')},
+ // This is not localized on purpose, because we'll pipe it thru a filter
+ $scope.repeat_options = [
+ {val: 'DAILY', displayname: 'days'},
+ {val: 'WEEKLY', displayname: 'weeks'},
+ {val: 'MONTHLY', displayname: 'months'},
+ {val: 'YEARLY', displayname: 'years'}
];
- $scope.monthdays = [
- { val: 'monthday', displayname: t('Calendar','by monthday')},
- { val: 'weekday', displayname: t('Calendar','by weekday')}
+ $scope.selected_repeat_end = 'NEVER';
+ $scope.repeat_end = [
+ {val: 'NEVER', displayname: t('Calendar', 'never')},
+ {val: 'COUNT', displayname: t('Calendar', 'after')},
+ {val: 'UNTIL', displayname: t('Calendar', 'on date')}
];
- $scope.monthdaymodel = $scope.monthdays[0].val;
- $scope.years = [
- { val: 'bydate', displayname: t('Calendar','by events date')},
- { val: 'byyearday', displayname: t('Calendar','by yearday(s)')},
- { val: 'byweekno', displayname: t('Calendar','by week no(s)')},
- { val: 'bydaymonth', displayname: t('Calendar','by day and month')}
- ];
+ $scope.repeat_weekdays = [];
+ $scope.repeat_short_weekdays = [];
+ var weekdayNames = LocalizationService.weekdayNames();
+ var shortWeekdayNames = LocalizationService.shortWeekdayNames();
+ var firstDayOfWeek = LocalizationService.firstDayOfWeek();
+ var icalDays = ['SU', 'MO', 'TU', 'WE' , 'TH', 'FR', 'SA'];
+ for (var i=0; i < 7; i++) {
+ $scope.repeat_weekdays.push({
+ val: icalDays[(i + firstDayOfWeek) % 7],
+ displayname: weekdayNames[(i + firstDayOfWeek) % 7]
+ });
+ $scope.repeat_short_weekdays.push({
+ val: icalDays[(i + firstDayOfWeek) % 7],
+ displayname: shortWeekdayNames[(i + firstDayOfWeek) % 7]
+ });
+ }
+
+ $scope.repeat_weekdays.push({
+ val: 'DAY',
+ displayname: t('Calendar', 'day')
+ });
+ $scope.repeat_weekdays.push({
+ val: 'DAY_OF_WEEK',
+ displayname: t('Calendar', 'day of week')
+ });
+ $scope.repeat_weekdays.push({
+ val: 'DAY_OF_WEEKEND',
+ displayname: t('Calendar', 'day of weekend')
+ });
- $scope.weeks = [
- { val: 'mon', displayname: t('Calendar','Monday')},
- { val: 'tue', displayname: t('Calendar','Tuesday')},
- { val: 'wed', displayname: t('Calendar','Wednesday')},
- { val: 'thu', displayname: t('Calendar','Thursday')},
- { val: 'fri', displayname: t('Calendar','Friday')},
- { val: 'sat', displayname: t('Calendar','Saturday')},
- { val: 'sun', displayname: t('Calendar','Sunday')}
+ $scope.repeat_ordinal = [
+ {val: 0, displayname: t('calendar', 'each')},
+ {val: 1, displayname: t('calendar', 'first')},
+ {val: 2, displayname: t('calendar', 'second')},
+ {val: 3, displayname: t('calendar', 'third')},
+ {val: 4, displayname: t('calendar', 'fourth')},
+ {val: 5, displayname: t('calendar', 'fifth')},
+ {val: -1, displayname: t('calendar', 'last')}
];
- $scope.changerepeater = function (repeat) {
- if (repeat.val === 'monthly') {
- $scope.monthday = false;
- $scope.yearly = true;
- $scope.weekly = true;
- } else if (repeat.val === 'yearly') {
- $scope.yearly = false;
- $scope.monthday = true;
- $scope.weekly = true;
- } else if (repeat.val === 'weekly') {
- $scope.weekly = false;
- $scope.monthday = true;
- $scope.yearly = true;
- } else {
- $scope.weekly = true;
- $scope.monthday = true;
- $scope.yearly = true;
+ $scope.repeat_month_groups = [];
+ var shortMonthNames = LocalizationService.shortMonthNames();
+ for (var j=0; j < 12; j++) {
+ var group = Math.floor(j/6);
+ var index = j % 6;
+
+ $scope.repeat_month_groups[group] = $scope.repeat_month_groups[group] || [];
+ $scope.repeat_month_groups[group][index] = {
+ val: j + 1,
+ displayname: shortMonthNames[j]
+ };
+ }
+
+ $scope.repeat_keep_one_checkbox_active = function(group, key) {
+ var isTrue = false;
+ angular.forEach(group, function(value) {
+ if (value) {
+ isTrue = true;
+ }
+ });
+
+ if (!isTrue) {
+ group[key] = true;
}
};
- // First Day Dropdown
- $scope.recurrenceSelect = [
- { val: t('calendar', 'Daily'), id: '0' },
- { val: t('calendar', 'Weekly'), id: '1' },
- { val: t('calendar', 'Monthly'), id: '2' },
- { val: t('calendar', 'Yearly'), id: '3' },
- { val: t('calendar', 'Other'), id: '4' }
- ];
+ $scope.repeat_did_prefill_already = {
+ DAILY: false,
+ WEEKLY: false,
+ MONTHLY: false,
+ YEARLY: false
+ };
+ $scope.repeat_change_freq = function(freq) {
+ if ($scope.repeat_did_prefill_already[freq]) {
+ //Don't prefill more than once
+ return;
+ }
+
+ $scope.repeat_did_prefill_already[freq] = true;
+ var date = angular.element('#advanced_from').datepicker('getDate');
+ if (freq === 'WEEKLY') {
+ var days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
+ $scope.repeat.weekly.weekdays[days[date.getDay()]] = true;
+ } else if (freq === 'MONTHLY') {
+ $scope.repeat.monthly.monthdays[date.getDate()] = true;
+ } else if (freq === 'YEARLY') {
+ $scope.repeat.yearly.months[date.getMonth() + 1] = true;
+ }
+ };
+
+
+
$scope.cutstats = [
{ displayname: t('Calendar', 'Individual'), val : 'INDIVIDUAL' },
diff --git a/js/app/filters/daysfilter.js b/js/app/filters/daysfilter.js
new file mode 100644
index 0000000..7e85666
--- /dev/null
+++ b/js/app/filters/daysfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('daysFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'day', 'days', count);
+ };
+});
diff --git a/js/app/filters/hoursfilter.js b/js/app/filters/hoursfilter.js
new file mode 100644
index 0000000..5a3bcb8
--- /dev/null
+++ b/js/app/filters/hoursfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('hoursFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'hour', 'hours', count);
+ };
+});
diff --git a/js/app/filters/minutesfilter.js b/js/app/filters/minutesfilter.js
new file mode 100644
index 0000000..6354c3c
--- /dev/null
+++ b/js/app/filters/minutesfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('minutesFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'minute', 'minutes', count);
+ };
+});
diff --git a/js/app/filters/monthsfilter.js b/js/app/filters/monthsfilter.js
new file mode 100644
index 0000000..b6b8b61
--- /dev/null
+++ b/js/app/filters/monthsfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('monthsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'month', 'months', count);
+ };
+});
diff --git a/js/app/filters/periodsfilter.js b/js/app/filters/periodsfilter.js
new file mode 100644
index 0000000..51ce351
--- /dev/null
+++ b/js/app/filters/periodsfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('periodsFilter', ['$filter', function($filter) {
+ 'use strict';
+
+ return function(label, count) {
+ return $filter(label + 'Filter')(count);
+ };
+}]);
diff --git a/js/app/filters/secondsfilter.js b/js/app/filters/secondsfilter.js
new file mode 100644
index 0000000..c9b94b4
--- /dev/null
+++ b/js/app/filters/secondsfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('secondsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'second', 'seconds', count);
+ };
+});
diff --git a/js/app/filters/timesfilter.js b/js/app/filters/timesfilter.js
new file mode 100644
index 0000000..f05047a
--- /dev/null
+++ b/js/app/filters/timesfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('timesFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'time', 'times', count);
+ };
+});
diff --git a/js/app/filters/weeksfilter.js b/js/app/filters/weeksfilter.js
new file mode 100644
index 0000000..88742d9
--- /dev/null
+++ b/js/app/filters/weeksfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('weeksFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'week', 'weeks', count);
+ };
+});
diff --git a/js/app/filters/yearsfilter.js b/js/app/filters/yearsfilter.js
new file mode 100644
index 0000000..ca62c5e
--- /dev/null
+++ b/js/app/filters/yearsfilter.js
@@ -0,0 +1,30 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.filter('yearsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'year', 'years', count);
+ };
+});
diff --git a/js/app/service/localizationservice.js b/js/app/service/localizationservice.js
new file mode 100644
index 0000000..ce618ad
--- /dev/null
+++ b/js/app/service/localizationservice.js
@@ -0,0 +1,61 @@
+/**
+ * ownCloud - Calendar App
+ *
+ * @author Raghu Nayyar
+ * @author Georg Ehrke
+ * @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
+ * @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+app.service('LocalizationService', function() {
+ 'use strict';
+
+ var monthNames = [];
+ var monthNamesShort = [];
+ for (var i = 0; i < 12; i++) {
+ monthNames.push(moment.localeData().months(moment([0, i]), ''));
+ monthNamesShort.push(moment.localeData().monthsShort(moment([0, i]), ''));
+ }
+
+ var dayNames = [];
+ var dayNamesShort = [];
+ var momentWeekHelper = moment().startOf('week');
+ momentWeekHelper.subtract(momentWeekHelper.format('d'));
+ for (var j = 0; j < 7; j++) {
+ dayNames.push(moment.localeData().weekdays(momentWeekHelper));
+ dayNamesShort.push(moment.localeData().weekdaysShort(momentWeekHelper));
+ momentWeekHelper.add(1, 'days');
+ }
+
+ return {
+ monthNames: function() {
+ return monthNames;
+ },
+ shortMonthNames: function() {
+ return monthNamesShort;
+ },
+ weekdayNames: function() {
+ return dayNames;
+ },
+ shortWeekdayNames: function() {
+ return dayNamesShort;
+ },
+ firstDayOfWeek: function() {
+ return parseInt(moment().startOf('week').format('d'));
+ }
+ };
+});
\ No newline at end of file
diff --git a/js/app/service/objectConverter.js b/js/app/service/objectConverter.js
index 9eba44f..93aed1c 100644
--- a/js/app/service/objectConverter.js
+++ b/js/app/service/objectConverter.js
@@ -401,12 +401,27 @@ app.factory('objectConverter', function () {
},
repeating: function(data, vevent) {
var iCalEvent = new ICAL.Event(vevent);
-
data.repeating = iCalEvent.isRecurring();
- simpleParser.dates(data, vevent, 'rdate');
- simpleParser.string(data, vevent, 'rrule');
- simpleParser.dates(data, vevent, 'exdate');
+ var rrule = vevent.getFirstPropertyValue('rrule');
+ if (rrule) {
+ data.rrule = {
+ count: rrule.count,
+ freq: rrule.freq,
+ interval: rrule.interval,
+ parameters: rrule.parts,
+ until: null
+ };
+
+ // TODO - handle until properly
+ //if (rrule.until) {
+ // simpleParser.date(data.rrule, rrule, 'until');
+ //}
+ } else {
+ data.rrule = {
+ freq: 'NONE'
+ };
+ }
}
};
@@ -514,16 +529,17 @@ app.factory('objectConverter', function () {
repeating: function(vevent, oldSimpleData, newSimpleData) {
// We won't support exrule, because it's deprecated and barely used in the wild
if (newSimpleData.repeating === false) {
- delete vevent.rdate;
- delete vevent.rrule;
- delete vevent.exdate;
+ vevent.removeAllProperties('rdate');
+ vevent.removeAllProperties('rrule');
+ vevent.removeAllProperties('exdate');
return;
}
- simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
- simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
- simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
+ //TODO - parse rrule
+ //simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
+ //simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
+ //simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
}
};
@@ -581,7 +597,7 @@ app.factory('objectConverter', function () {
parameters = simpleProperties[key].parameters;
if (oldSimpleData[key] !== newSimpleData[key]) {
if (newSimpleData === null) {
- delete vevent[key];
+ vevent.removeAllProperties(key);
} else {
reader(vevent, oldSimpleData, newSimpleData, key, parameters);
}
diff --git a/js/public/app.js b/js/public/app.js
index 70dd455..a155222 100644
--- a/js/public/app.js
+++ b/js/public/app.js
@@ -46,8 +46,8 @@ app.run(['$rootScope', '$window',
* Description: The fullcalendar controller.
*/
-app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'uiCalendarConfig', '$uibModal',
- function ($scope, $rootScope, $window, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, uiCalendarConfig, $uibModal) {
+app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'uiCalendarConfig', '$uibModal', 'LocalizationService',
+ function ($scope, $rootScope, $window, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, uiCalendarConfig, $uibModal, LocalizationService) {
'use strict';
is.loading = true;
@@ -354,39 +354,20 @@ app.controller('CalController', ['$scope', '$rootScope', '$window', 'CalendarSer
/**
* Calendar UI Configuration.
*/
- var i;
-
- var monthNames = [];
- var monthNamesShort = [];
- for (i = 0; i < 12; i++) {
- monthNames.push(moment.localeData().months(moment([0, i]), ''));
- monthNamesShort.push(moment.localeData().monthsShort(moment([0, i]), ''));
- }
-
- var dayNames = [];
- var dayNamesShort = [];
- var momentWeekHelper = moment().startOf('week');
- momentWeekHelper.subtract(momentWeekHelper.format('d'));
- for (i = 0; i < 7; i++) {
- dayNames.push(moment.localeData().weekdays(momentWeekHelper));
- dayNamesShort.push(moment.localeData().weekdaysShort(momentWeekHelper));
- momentWeekHelper.add(1, 'days');
- }
-
$scope.uiConfig = {
calendar: {
height: w.height() - angular.element('#header').height(),
editable: true,
selectable: true,
lang: moment.locale(),
- monthNames: monthNames,
- monthNamesShort: monthNamesShort,
- dayNames: dayNames,
- dayNamesShort: dayNamesShort,
+ monthNames: LocalizationService.monthNames(),
+ monthNamesShort: LocalizationService.shortMonthNames(),
+ dayNames: LocalizationService.weekdayNames(),
+ dayNamesShort: LocalizationService.shortWeekdayNames(),
timezone: $scope.defaulttimezone,
defaultView: angular.element('#fullcalendar').attr('data-defaultView'),
header: false,
- firstDay: moment().startOf('week').format('d'),
+ firstDay: LocalizationService.firstDayOfWeek(),
select: $scope.newEvent,
eventLimit: true,
eventClick: function(fcEvent, jsEvent, view) {
@@ -834,6 +815,8 @@ app.controller('EventsPopoverEditorController', ['$scope', 'TimezoneService', 'e
$scope.showTimezone = true;
}
+ console.log($scope.properties);
+
$scope.close = function(action) {
$scope.properties.dtstart.value = moment(angular.element('#from').datepicker('getDate'));
$scope.properties.dtend.value = moment(angular.element('#to').datepicker('getDate'));
@@ -973,8 +956,8 @@ app.controller('EventsPopoverEditorController', ['$scope', 'TimezoneService', 'e
* Description: Takes care of anything inside the Events Modal.
*/
-app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'AutoCompletionService', 'eventEditorHelper', '$window', '$uibModalInstance', 'vevent', 'recurrenceId', 'isNew', 'properties', 'emailAddress',
- function($scope, TimezoneService, AutoCompletionService, eventEditorHelper, $window, $uibModalInstance, vevent, recurrenceId, isNew, properties, emailAddress) {
+app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'AutoCompletionService', 'eventEditorHelper', '$window', '$uibModalInstance', 'LocalizationService', 'vevent', 'recurrenceId', 'isNew', 'properties', 'emailAddress',
+ function($scope, TimezoneService, AutoCompletionService, eventEditorHelper, $window, $uibModalInstance, LocalizationService, vevent, recurrenceId, isNew, properties, emailAddress) {
'use strict';
$scope.properties = properties;
@@ -985,6 +968,9 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.selected = 1;
$scope.timezones = [];
$scope.emailAddress = emailAddress;
+ $scope.rruleUnsupported = false;
+
+ console.log($scope.properties);
$scope.previousDtStartDate = null;
$scope.previousDtStartHour = null;
@@ -1188,13 +1174,79 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.previousDtStartMinute = angular.element('#advanced_fromtime').timepicker('getMinute');
$scope.tabopener(1);
+
+ if ($scope.properties.rrule.freq !== 'NONE') {
+ var partIds;
+ if (typeof $scope.properties.rrule.parameters !== 'undefined') {
+ partIds = Object.getOwnPropertyNames($scope.properties.rrule.parameters);
+ } else {
+ partIds = [];
+ }
+
+ var unsupportedFREQs = ['SECONDLY', 'MINUTELY', 'HOURLY'];
+ if (unsupportedFREQs.indexOf($scope.properties.rrule.freq) !== -1) {
+ $scope.rruleUnsupported = true;
+ }
+
+ var unsupportedParts = ['BYSECOND', 'BYMINUTE', 'BYHOUR', 'BYYEARDAY', 'BYWEEKNO'];
+ angular.forEach(unsupportedParts, function(unsupportedPart) {
+ if (partIds.indexOf(unsupportedPart) !== -1) {
+ $scope.rruleUnsupported = true;
+ }
+ });
+
+ if ($scope.rruleUnsupported) {
+ return;
+ }
+
+ if (typeof $scope.properties.rrule.interval === 'number') {
+ $scope.repeat.interval = $scope.properties.rrule.interval;
+ } else {
+ $scope.repeat.interval = 1;
+ }
+
+ $scope.repeat.freq = $scope.properties.rrule.freq;
+ if (partIds.length === 0 && $scope.repeat.interval === 1) {
+ $scope.repeat.simple = $scope.properties.rrule.freq;
+ } else {
+ $scope.repeat.simple = 'CUSTOM';
+
+ if ($scope.repeat.freq === 'DAILY' && partIds.length > 0) {
+ $scope.rruleUnsupported = true;
+ } else if ($scope.repeat.freq === 'WEEKLY') {
+ if (partIds.length === 0) {
+ $scope.repeat.weekly.weekdays = {
+ SU: true,
+ MO: true,
+ TU: true,
+ WE: true,
+ TH: true,
+ FR: true,
+ SA: true
+ };
+ } else if(partIds.length === 1 && partIds.indexOf('BYDAY') !== -1 || partIds.length === 2 && partIds.indexOf('BYDAY') !== -1 && partIds.indexOf('WKST') !== -1) {
+ angular.forEach($scope.properties.rrule.parameters.BYDAY, function (day) {
+ $scope.repeat.weekly.weekdays[day] = true;
+ });
+ } else {
+ $scope.rruleUnsupported = true;
+ }
+ } else if ($scope.repeat.freq === 'MONTHLY') {
+ console.log(partIds);
+
+ } else if ($scope.repeat.freq === 'YEARLY') {
+ console.log(partIds);
+
+ }
+ }
+ }
});
- $scope.tabs = [{
- title: t('Calendar', 'Attendees'), value: 1
- }, {
- title: t('Calendar', 'Reminders'), value: 2
- }];
+ $scope.tabs = [
+ {title: t('Calendar', 'Attendees'), value: 1},
+ {title: t('Calendar', 'Reminders'), value: 2},
+ {title: t('Calendar', 'Repeating'), value: 3}
+ ];
$scope.tabopener = function (val) {
$scope.selected = val;
@@ -1221,74 +1273,147 @@ app.controller('EventsSidebarEditorController', ['$scope', 'TimezoneService', 'A
$scope.properties.location.value = item.label;
};
- $scope.repeater = [
- { val: 'doesnotrepeat' , displayname: t('Calendar', 'Does not repeat')},
- { val: 'daily' , displayname: t('Calendar', 'Daily')},
- { val: 'weekly' , displayname: t('Calendar', 'Weekly')},
- { val: 'weekday' , displayname: t('Calendar', 'Every Weekday')},
- { val: 'biweekly' , displayname: t('Calendar', 'Bi-weekly')},
- { val: 'monthly' , displayname: t('Calendar', 'Monthly')},
- { val: 'yearly' , displayname: t('Calendar', 'Yearly')},
+ $scope.repeat = {
+ simple: 'NONE',
+ interval: 1,
+ freq: 'DAILY',
+ end: 'NEVER',
+ count: 1,
+ weekly: {
+ weekdays: {}
+ },
+ monthly: {
+ monthdays: {},
+ radio: 'on-days',
+ onthe_ordinal: 1,
+ onthe_day: 'DAY'
+ },
+ yearly: {
+ months: {},
+ onthe_ordinal: 1,
+ onthe_day: 'DAY'
+ },
+ //We will not support all crazy RRULEs in the first version
+ //when we can't deal with the RRULE, we'll show a warning in the editor
+ //instead of breaking the RRULE
+ unsupported: false
+ };
+ $scope.repeat_options_simple = [
+ {val: 'NONE', displayname: t('Calendar', 'None')},
+ {val: 'DAILY', displayname: t('Calendar', 'Every day')},
+ {val: 'WEEKLY', displayname: t('Calendar', 'Every week')},
+ {val: 'MONTHLY', displayname: t('Calendar', 'Every month')},
+ {val: 'YEARLY', displayname: t('Calendar', 'Every year')},
+ {val: 'CUSTOM', displayname: t('calendar', 'Custom')}
];
- $scope.repeatmodel = $scope.repeater[0].val;
- $scope.ender = [
- { val: 'never', displayname: t('Calendar','never')},
- { val: 'count', displayname: t('Calendar','by occurances')},
- { val: 'date', displayname: t('Calendar','by date')},
+ // This is not localized on purpose, because we'll pipe it thru a filter
+ $scope.repeat_options = [
+ {val: 'DAILY', displayname: 'days'},
+ {val: 'WEEKLY', displayname: 'weeks'},
+ {val: 'MONTHLY', displayname: 'months'},
+ {val: 'YEARLY', displayname: 'years'}
];
- $scope.monthdays = [
- { val: 'monthday', displayname: t('Calendar','by monthday')},
- { val: 'weekday', displayname: t('Calendar','by weekday')}
+ $scope.selected_repeat_end = 'NEVER';
+ $scope.repeat_end = [
+ {val: 'NEVER', displayname: t('Calendar', 'never')},
+ {val: 'COUNT', displayname: t('Calendar', 'after')},
+ {val: 'UNTIL', displayname: t('Calendar', 'on date')}
];
- $scope.monthdaymodel = $scope.monthdays[0].val;
- $scope.years = [
- { val: 'bydate', displayname: t('Calendar','by events date')},
- { val: 'byyearday', displayname: t('Calendar','by yearday(s)')},
- { val: 'byweekno', displayname: t('Calendar','by week no(s)')},
- { val: 'bydaymonth', displayname: t('Calendar','by day and month')}
- ];
+ $scope.repeat_weekdays = [];
+ $scope.repeat_short_weekdays = [];
+ var weekdayNames = LocalizationService.weekdayNames();
+ var shortWeekdayNames = LocalizationService.shortWeekdayNames();
+ var firstDayOfWeek = LocalizationService.firstDayOfWeek();
+ var icalDays = ['SU', 'MO', 'TU', 'WE' , 'TH', 'FR', 'SA'];
+ for (var i=0; i < 7; i++) {
+ $scope.repeat_weekdays.push({
+ val: icalDays[(i + firstDayOfWeek) % 7],
+ displayname: weekdayNames[(i + firstDayOfWeek) % 7]
+ });
+ $scope.repeat_short_weekdays.push({
+ val: icalDays[(i + firstDayOfWeek) % 7],
+ displayname: shortWeekdayNames[(i + firstDayOfWeek) % 7]
+ });
+ }
- $scope.weeks = [
- { val: 'mon', displayname: t('Calendar','Monday')},
- { val: 'tue', displayname: t('Calendar','Tuesday')},
- { val: 'wed', displayname: t('Calendar','Wednesday')},
- { val: 'thu', displayname: t('Calendar','Thursday')},
- { val: 'fri', displayname: t('Calendar','Friday')},
- { val: 'sat', displayname: t('Calendar','Saturday')},
- { val: 'sun', displayname: t('Calendar','Sunday')}
+ $scope.repeat_weekdays.push({
+ val: 'DAY',
+ displayname: t('Calendar', 'day')
+ });
+ $scope.repeat_weekdays.push({
+ val: 'DAY_OF_WEEK',
+ displayname: t('Calendar', 'day of week')
+ });
+ $scope.repeat_weekdays.push({
+ val: 'DAY_OF_WEEKEND',
+ displayname: t('Calendar', 'day of weekend')
+ });
+
+ $scope.repeat_ordinal = [
+ {val: 0, displayname: t('calendar', 'each')},
+ {val: 1, displayname: t('calendar', 'first')},
+ {val: 2, displayname: t('calendar', 'second')},
+ {val: 3, displayname: t('calendar', 'third')},
+ {val: 4, displayname: t('calendar', 'fourth')},
+ {val: 5, displayname: t('calendar', 'fifth')},
+ {val: -1, displayname: t('calendar', 'last')}
];
- $scope.changerepeater = function (repeat) {
- if (repeat.val === 'monthly') {
- $scope.monthday = false;
- $scope.yearly = true;
- $scope.weekly = true;
- } else if (repeat.val === 'yearly') {
- $scope.yearly = false;
- $scope.monthday = true;
- $scope.weekly = true;
- } else if (repeat.val === 'weekly') {
- $scope.weekly = false;
- $scope.monthday = true;
- $scope.yearly = true;
- } else {
- $scope.weekly = true;
- $scope.monthday = true;
- $scope.yearly = true;
+ $scope.repeat_month_groups = [];
+ var shortMonthNames = LocalizationService.shortMonthNames();
+ for (var j=0; j < 12; j++) {
+ var group = Math.floor(j/6);
+ var index = j % 6;
+
+ $scope.repeat_month_groups[group] = $scope.repeat_month_groups[group] || [];
+ $scope.repeat_month_groups[group][index] = {
+ val: j + 1,
+ displayname: shortMonthNames[j]
+ };
+ }
+
+ $scope.repeat_keep_one_checkbox_active = function(group, key) {
+ var isTrue = false;
+ angular.forEach(group, function(value) {
+ if (value) {
+ isTrue = true;
+ }
+ });
+
+ if (!isTrue) {
+ group[key] = true;
}
};
- // First Day Dropdown
- $scope.recurrenceSelect = [
- { val: t('calendar', 'Daily'), id: '0' },
- { val: t('calendar', 'Weekly'), id: '1' },
- { val: t('calendar', 'Monthly'), id: '2' },
- { val: t('calendar', 'Yearly'), id: '3' },
- { val: t('calendar', 'Other'), id: '4' }
- ];
+ $scope.repeat_did_prefill_already = {
+ DAILY: false,
+ WEEKLY: false,
+ MONTHLY: false,
+ YEARLY: false
+ };
+ $scope.repeat_change_freq = function(freq) {
+ if ($scope.repeat_did_prefill_already[freq]) {
+ //Don't prefill more than once
+ return;
+ }
+
+ $scope.repeat_did_prefill_already[freq] = true;
+ var date = angular.element('#advanced_from').datepicker('getDate');
+ if (freq === 'WEEKLY') {
+ var days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
+ $scope.repeat.weekly.weekdays[days[date.getDay()]] = true;
+ } else if (freq === 'MONTHLY') {
+ $scope.repeat.monthly.monthdays[date.getDate()] = true;
+ } else if (freq === 'YEARLY') {
+ $scope.repeat.yearly.months[date.getMonth() + 1] = true;
+ }
+ };
+
+
+
$scope.cutstats = [
{ displayname: t('Calendar', 'Individual'), val : 'INDIVIDUAL' },
@@ -1961,6 +2086,22 @@ app.filter('datepickerFilter',
}
);
+app.filter('daysFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'day', 'days', count);
+ };
+});
+
+app.filter('hoursFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'hour', 'hours', count);
+ };
+});
+
app.filter('importCalendarFilter',
function () {
'use strict';
@@ -2011,6 +2152,30 @@ app.filter('importErrorFilter',
}
);
+app.filter('minutesFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'minute', 'minutes', count);
+ };
+});
+
+app.filter('monthsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'month', 'months', count);
+ };
+});
+
+app.filter('periodsFilter', ['$filter', function($filter) {
+ 'use strict';
+
+ return function(label, count) {
+ return $filter(label + 'Filter')(count);
+ };
+}]);
+
app.filter('simpleReminderDescription', function() {
'use strict';
var actionMapper = {
@@ -2060,6 +2225,14 @@ app.filter('simpleReminderDescription', function() {
};
});
+app.filter('secondsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'second', 'seconds', count);
+ };
+});
+
app.filter('subscriptionFilter',
[ function () {
'use strict';
@@ -2079,6 +2252,14 @@ app.filter('subscriptionFilter',
}
]);
+app.filter('timesFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'time', 'times', count);
+ };
+});
+
app.filter('timezoneFilter', ['$filter', function($filter) {
'use strict';
@@ -2108,6 +2289,22 @@ app.filter('timezoneWithoutContinentFilter', function() {
};
});
+app.filter('weeksFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'week', 'weeks', count);
+ };
+});
+
+app.filter('yearsFilter', function() {
+ 'use strict';
+
+ return function(count) {
+ return n('calendar', 'year', 'years', count);
+ };
+});
+
app.factory('Calendar', ['$rootScope', '$filter', 'VEventService', 'TimezoneService', 'RandomStringService', function($rootScope, $filter, VEventService, TimezoneService, RandomStringService) {
'use strict';
@@ -3452,6 +3649,44 @@ app.factory('is', function () {
};
});
+app.service('LocalizationService', function() {
+ 'use strict';
+
+ var monthNames = [];
+ var monthNamesShort = [];
+ for (var i = 0; i < 12; i++) {
+ monthNames.push(moment.localeData().months(moment([0, i]), ''));
+ monthNamesShort.push(moment.localeData().monthsShort(moment([0, i]), ''));
+ }
+
+ var dayNames = [];
+ var dayNamesShort = [];
+ var momentWeekHelper = moment().startOf('week');
+ momentWeekHelper.subtract(momentWeekHelper.format('d'));
+ for (var j = 0; j < 7; j++) {
+ dayNames.push(moment.localeData().weekdays(momentWeekHelper));
+ dayNamesShort.push(moment.localeData().weekdaysShort(momentWeekHelper));
+ momentWeekHelper.add(1, 'days');
+ }
+
+ return {
+ monthNames: function() {
+ return monthNames;
+ },
+ shortMonthNames: function() {
+ return monthNamesShort;
+ },
+ weekdayNames: function() {
+ return dayNames;
+ },
+ shortWeekdayNames: function() {
+ return dayNamesShort;
+ },
+ firstDayOfWeek: function() {
+ return parseInt(moment().startOf('week').format('d'));
+ }
+ };
+});
app.factory('objectConverter', function () {
'use strict';
@@ -3833,12 +4068,27 @@ app.factory('objectConverter', function () {
},
repeating: function(data, vevent) {
var iCalEvent = new ICAL.Event(vevent);
-
data.repeating = iCalEvent.isRecurring();
- simpleParser.dates(data, vevent, 'rdate');
- simpleParser.string(data, vevent, 'rrule');
- simpleParser.dates(data, vevent, 'exdate');
+ var rrule = vevent.getFirstPropertyValue('rrule');
+ if (rrule) {
+ data.rrule = {
+ count: rrule.count,
+ freq: rrule.freq,
+ interval: rrule.interval,
+ parameters: rrule.parts,
+ until: null
+ };
+
+ // TODO - handle until properly
+ //if (rrule.until) {
+ // simpleParser.date(data.rrule, rrule, 'until');
+ //}
+ } else {
+ data.rrule = {
+ freq: 'NONE'
+ };
+ }
}
};
@@ -3946,16 +4196,17 @@ app.factory('objectConverter', function () {
repeating: function(vevent, oldSimpleData, newSimpleData) {
// We won't support exrule, because it's deprecated and barely used in the wild
if (newSimpleData.repeating === false) {
- delete vevent.rdate;
- delete vevent.rrule;
- delete vevent.exdate;
+ vevent.removeAllProperties('rdate');
+ vevent.removeAllProperties('rrule');
+ vevent.removeAllProperties('exdate');
return;
}
- simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
- simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
- simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
+ //TODO - parse rrule
+ //simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
+ //simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
+ //simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
}
};
@@ -4013,7 +4264,7 @@ app.factory('objectConverter', function () {
parameters = simpleProperties[key].parameters;
if (oldSimpleData[key] !== newSimpleData[key]) {
if (newSimpleData === null) {
- delete vevent[key];
+ vevent.removeAllProperties(key);
} else {
reader(vevent, oldSimpleData, newSimpleData, key, parameters);
}
diff --git a/templates/part.eventsrepeat.php b/templates/part.eventsrepeat.php
index 070db4d..09e1da8 100644
--- a/templates/part.eventsrepeat.php
+++ b/templates/part.eventsrepeat.php
@@ -22,33 +22,125 @@
*
*/
?>
-
-<fieldset class="event-fieldset">
- <label class="label"><?php p($l->t('Repeat'))?></label>
- <select ng-model="repeatmodel" ng-options="repeat as repeat.displayname for repeat in repeater" ng-change="changerepeater(repeatmodel)">
- </select>
- <select ng-hide="monthday" ng-model="monthdaymodel" ng-options="day as day.displayname for day in monthdays" ng-change="changemonthday(monthdaymodel)">
- </select>
- <select ng-hide="yearly" ng-model="yearmodel" ng-options="year as year.displayname for year in years" ng-change="changeyear(yearmodel)">
- </select>
- <select ng-hide="weekly" ng-model="weekmodel" id="weeklyselect" ng-change="changeweek(weekmodel)" multiple="multiple" data-placeholder="yoll" title="yol">
- <option ng-repeat="week in weeks" value="{{ week.val }}"> {{ week.displayname }}</option>
+<fieldset>
+ <select
+ id="frequency_select"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_options_simple"
+ ng-model="repeat.simple">
</select>
</fieldset>
+<div ng-show="repeat.simple === 'CUSTOM'">
+ <fieldset>
+ <label class="pull-left inline-label"><?php p($l->t('every')); ?></label>
+ <select
+ class="pull-half pull-right"
+ id="frequency_select"
+ ng-options="repeat_option.val as repeat_option.displayname | periodsFilter: repeat.interval for repeat_option in repeat_options"
+ ng-model="repeat.freq"
+ ng-change="repeat_change_freq(repeat.freq)">
+ </select>
+ <input
+ class="pull-right"
+ type="number"
+ min="1"
+ ng-model="repeat.interval">
+ <div class="clear-both"></div>
+ </fieldset>
+ <fieldset ng-show="repeat.freq === 'WEEKLY'">
+ <label><?php p($l->t('on weekdays')); ?>:</label>
+ <table class="event-checkbox-table">
+ <tr>
+ <td ng-repeat="weekday in repeat_short_weekdays" class="seven-rows">
+ <input type="checkbox" id="repeat_weekdays_{{$index}}" class="repeat_checkboxtable_checkbox" value="{{ weekday.val }}"
+ ng-model="repeat.weekly.weekdays[weekday.val]" ng-change="repeat_keep_one_checkbox_active(repeat.weekly.weekdays, weekday.val)">
+ <label for="repeat_weekdays_{{$index}}" class="repeat_checkboxtable_label">
+ {{ weekday.displayname }}
+ </label>
+ </td>
+ </tr>
+ </table>
+ </fieldset>
+ <fieldset class="event-fieldset" ng-show="repeat.freq === 'MONTHLY'">
+ <label><input type="radio" name="repeat_monthly_radio" value="on-days" ng-model="repeat.monthly.radio"><?php p($l->t('on days')); ?>:</label>
+ <div ng-show="repeat.monthly.radio === 'on-days'">
+ <table class="event-checkbox-table">
+ <tr ng-repeat="day_group in [[1,2,3,4,5,6,7], [8,9,10,11,12,13,14], [15,16,17,18,19,20,21], [22,23,24,25,26,27,28], [29,30,31]]">
+ <td ng-repeat="day in day_group" class="seven-rows">
+ <input type="checkbox" id="repeat_monthdays_{{day}}" class="repeat_checkboxtable_checkbox" value="{{ day }}"
+ ng-model="repeat.monthly.monthdays[day]" ng-change="repeat_keep_one_checkbox_active(repeat.monthly.monthdays, day)">
+ <label for="repeat_monthdays_{{day}}" class="repeat_checkboxtable_label">
+ {{ day }}
+ </label>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div class="clear-both"></div>
+ <label><input type="radio" name="repeat_monthly_radio" value="on-the" ng-model="repeat.monthly.radio"><?php p($l->t('on the')); ?>:</label>
+ <div class="clear-both"></div>
+ <div ng-show="repeat.monthly.radio === 'on-the'">
+ <select
+ class="pull-half pull-left"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_ordinal"
+ ng-model="repeat.monthly.onthe_ordinal">
+ </select>
+ <select
+ class="pull-half pull-right"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_weekdays"
+ ng-model="repeat.monthly.onthe_day">
+ </select>
+ </div>
+ </fieldset>
-<fieldset class="event-fieldset">
- <label class="label"><?php p($l->t('Interval'))?></label>
- <input type="number" min="1" max="1000" value="1" name="interval" ng-model="intervalmodel">
-</fieldset>
-
+ <fieldset class="event-fieldset" ng-show="repeat.freq === 'YEARLY'">
+ <table class="event-checkbox-table">
+ <tr ng-repeat="month_group in repeat_month_groups">
+ <td ng-repeat="month in month_group" class="seven-rows">
+ <input type="checkbox" id="repeat_month_{{month.val}}" class="repeat_checkboxtable_checkbox" value="{{ month.val }}"
+ ng-model="repeat.yearly.months[month.val]" ng-change="repeat_keep_one_checkbox_active(repeat.yearly.months, month.val)">
+ <label for="repeat_month_{{month.val}}" class="repeat_checkboxtable_label">
+ {{ month.displayname }}
+ </label>
+ </td>
+ </tr>
+ </table>
+ <div class="clear-both"></div>
+ <label><input type="checkbox" ng-model="repeat.yearly.onthe_checkbox"><?php p($l->t('on the')); ?>:</label>
+ <div ng-show="repeat.yearly.onthe_checkbox">
+ <select
+ class="pull-half pull-left"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_ordinal"
+ ng-model="repeat.yearly.onthe_ordinal">
+ </select>
+ <select
+ class="pull-half pull-right"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_weekdays"
+ ng-model="repeat.yearly.onthe_day">
+ </select>
+ </div>
+ </fieldset>
+</div>
-<fieldset class="event-fieldset">
- <label class="label"><?php p($l->t('End'))?></label>
- <select>
- <option ng-repeat="end in ender" value="end.val" ng-model="end.val">{{ end.displayname }}</option>
- </select>
+<fieldset class="event-fieldset" ng-hide="repeat.simple === 'NONE'">
+ <label class="pull-left inline-label">
+ <?php p($l->t('end repeat ...')); ?>
+ </label>
+ <div class="pull-right pull-half">
+ <select id="frequency_select"
+ ng-options="repeat.val as repeat.displayname for repeat in repeat_end"
+ ng-model="repeat.end">
+ </select>
+ </div>
+ <div class="clear-both"></div>
+ <div class="pull-right pull-half" ng-show="repeat.end === 'COUNT'">
+ <input type="number" min="1" ng-model="repeat.count">
+ {{ repeat.count | timesFilter }}
+ </div>
+ <div class="pull-right pull-half" ng-show="repeat.end === 'UNTIL'">
+ <input type="text">
+ </div>
</fieldset>
diff --git a/templates/part.eventssidebareditor.php b/templates/part.eventssidebareditor.php
index b83dc07..7cdfa22 100644
--- a/templates/part.eventssidebareditor.php
+++ b/templates/part.eventssidebareditor.php
@@ -72,6 +72,10 @@ class="advanced--input h2"
<fieldset ng-show="eventsalarmview" class="advanced--fieldset" ng-disabled="readOnly">
<?php print_unescaped($this->inc('part.eventsalarms')); ?>
</fieldset>
+
+ <fieldset ng-show="eventsrepeatview" class="advanced--fieldset">
+ <?php print_unescaped($this->inc('part.eventsrepeat')); ?>
+ </fieldset>
</div>
<div class="advanced--button-area" ng-show="!readOnly">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment