remove legacy code
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
parent
d949d73a13
commit
683a2e34cc
45
js/.jshintrc
45
js/.jshintrc
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 4,
|
||||
"latedef": true,
|
||||
"noarg": true,
|
||||
"noempty": true,
|
||||
"nonew": true,
|
||||
"plusplus": false,
|
||||
"node": true,
|
||||
"undef": true,
|
||||
"unused": false,
|
||||
"strict": true,
|
||||
"maxparams": false,
|
||||
"maxdepth": 4,
|
||||
"esversion": 6,
|
||||
"browser": true,
|
||||
"devel": true,
|
||||
"jquery": true,
|
||||
"jasmine": true,
|
||||
"globals": {
|
||||
"jQuery": true,
|
||||
"ICAL": true,
|
||||
"jstz": true,
|
||||
"moment": true,
|
||||
"angular": true,
|
||||
"app": true,
|
||||
"OC": true,
|
||||
"oc_current_user":true,
|
||||
"oc_requesttoken": true,
|
||||
"requestToken": true,
|
||||
"inject": true,
|
||||
"module": true,
|
||||
"t": true,
|
||||
"it": true,
|
||||
"exports": true,
|
||||
"escapeHTML": true,
|
||||
"possible": true,
|
||||
"dav": true,
|
||||
"hslToRgb": true,
|
||||
"autosize": true,
|
||||
"_": true
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"indentation": "tab",
|
||||
"number-leading-zero": "never",
|
||||
"no-descending-specificity": null,
|
||||
"no-duplicate-selectors": null,
|
||||
"comment-empty-line-before": ["always", {
|
||||
"except": ["first-nested"]
|
||||
}]
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.controller('AttendeeController', function($scope, AutoCompletionService) {
|
||||
'use strict';
|
||||
|
||||
$scope.newAttendeeGroup = -1;
|
||||
$scope.nameofattendee = '';
|
||||
|
||||
$scope.cutstats = [
|
||||
{displayname: t('calendar', 'Individual'), val: 'INDIVIDUAL'},
|
||||
{displayname: t('calendar', 'Group'), val: 'GROUP'},
|
||||
{displayname: t('calendar', 'Resource'), val: 'RESOURCE'},
|
||||
{displayname: t('calendar', 'Room'), val: 'ROOM'},
|
||||
{displayname: t('calendar', 'Unknown'), val: 'UNKNOWN'}
|
||||
];
|
||||
|
||||
$scope.partstats = [
|
||||
{displayname: t('calendar', 'Required'), val: 'REQ-PARTICIPANT'},
|
||||
{displayname: t('calendar', 'Optional'), val: 'OPT-PARTICIPANT'},
|
||||
{displayname: t('calendar', 'Does not attend'), val: 'NON-PARTICIPANT'}
|
||||
];
|
||||
|
||||
$scope.$parent.registerPostHook(function() {
|
||||
$scope.properties.attendee = $scope.properties.attendee || [];
|
||||
if ($scope.properties.attendee.length > 0 && $scope.properties.organizer === null) {
|
||||
$scope.properties.organizer = {
|
||||
value: 'mailto:' + $scope.$parent.emailAddress,
|
||||
parameters: {
|
||||
cn: OC.getCurrentUser().displayName
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$scope.add = function (email) {
|
||||
if (email !== '') {
|
||||
$scope.properties.attendee = $scope.properties.attendee || [];
|
||||
$scope.properties.attendee.push({
|
||||
value: 'mailto:' + email,
|
||||
group: $scope.newAttendeeGroup--,
|
||||
parameters: {
|
||||
'role': 'REQ-PARTICIPANT',
|
||||
'rsvp': 'TRUE',
|
||||
'partstat': 'NEEDS-ACTION',
|
||||
'cutype': 'INDIVIDUAL'
|
||||
}
|
||||
});
|
||||
}
|
||||
$scope.attendeeoptions = false;
|
||||
$scope.nameofattendee = '';
|
||||
};
|
||||
|
||||
$scope.$on('save-contents', function() {
|
||||
$scope.add($scope.nameofattendee);
|
||||
});
|
||||
|
||||
$scope.remove = function (attendee) {
|
||||
$scope.properties.attendee = $scope.properties.attendee.filter(function(elem) {
|
||||
return elem.group !== attendee.group;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.search = function (value) {
|
||||
return AutoCompletionService.searchAttendee(value).then((attendees) => {
|
||||
const arr = [];
|
||||
|
||||
attendees.forEach((attendee) => {
|
||||
const emailCount = attendee.email.length;
|
||||
attendee.email.forEach((email) => {
|
||||
let displayname;
|
||||
if (emailCount === 1) {
|
||||
displayname = _.escape(attendee.name);
|
||||
} else {
|
||||
displayname = t('calendar', '{name} ({email})', {
|
||||
name: attendee.name,
|
||||
email: email
|
||||
});
|
||||
}
|
||||
|
||||
arr.push({
|
||||
displayname: displayname,
|
||||
email: email,
|
||||
name: attendee.name
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return arr;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectFromTypeahead = function (item) {
|
||||
$scope.properties.attendee = $scope.properties.attendee || [];
|
||||
$scope.properties.attendee.push({
|
||||
value: 'mailto:' + item.email,
|
||||
parameters: {
|
||||
cn: item.name,
|
||||
role: 'REQ-PARTICIPANT',
|
||||
rsvp: 'TRUE',
|
||||
partstat: 'NEEDS-ACTION',
|
||||
cutype: 'INDIVIDUAL'
|
||||
}
|
||||
});
|
||||
$scope.nameofattendee = '';
|
||||
};
|
||||
});
|
|
@ -1,306 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: CalController
|
||||
* Description: The fullcalendar controller.
|
||||
*/
|
||||
|
||||
app.controller('CalController', ['$scope', 'Calendar', 'CalendarService', 'VEventService', 'SettingsService', 'TimezoneService', 'VEvent', 'is', 'fc', 'EventsEditorDialogService', 'PopoverPositioningUtility', '$window', 'isPublic', 'constants', 'settings',
|
||||
function ($scope, Calendar, CalendarService, VEventService, SettingsService, TimezoneService, VEvent, is, fc, EventsEditorDialogService, PopoverPositioningUtility, $window, isPublic, constants, settings) {
|
||||
'use strict';
|
||||
|
||||
is.loading = true;
|
||||
|
||||
$scope.calendars = [];
|
||||
$scope.eventSource = {};
|
||||
$scope.eventModal = null;
|
||||
var switcher = [];
|
||||
|
||||
if (settings.timezone === 'automatic') {
|
||||
$scope.defaulttimezone = TimezoneService.getDetected();
|
||||
} else {
|
||||
$scope.defaulttimezone = settings.timezone;
|
||||
}
|
||||
|
||||
function showCalendar(url) {
|
||||
if (switcher.indexOf(url) === -1 && $scope.eventSource[url].isRendering === false) {
|
||||
switcher.push(url);
|
||||
fc.elm.fullCalendar(
|
||||
'removeEventSource',
|
||||
$scope.eventSource[url]);
|
||||
fc.elm.fullCalendar(
|
||||
'addEventSource',
|
||||
$scope.eventSource[url]);
|
||||
}
|
||||
}
|
||||
|
||||
function hideCalendar(url) {
|
||||
fc.elm.fullCalendar(
|
||||
'removeEventSource',
|
||||
$scope.eventSource[url]);
|
||||
if (switcher.indexOf(url) !== -1) {
|
||||
switcher.splice(switcher.indexOf(url), 1);
|
||||
}
|
||||
}
|
||||
|
||||
function createAndRenderEvent(calendar, data, start, end, tz) {
|
||||
VEventService.create(calendar, data).then(function(vevent) {
|
||||
if (calendar.enabled) {
|
||||
fc.elm.fullCalendar('refetchEventSources', calendar.fcEventSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAndRemoveEvent(vevent, fcEvent) {
|
||||
VEventService.delete(vevent).then(function() {
|
||||
fc.elm.fullCalendar('removeEvents', fcEvent.id);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watchCollection('calendars', function(newCalendars, oldCalendars) {
|
||||
newCalendars.filter(function(calendar) {
|
||||
return oldCalendars.indexOf(calendar) === -1;
|
||||
}).forEach(function(calendar) {
|
||||
$scope.eventSource[calendar.url] = calendar.fcEventSource;
|
||||
if (calendar.enabled) {
|
||||
showCalendar(calendar.url);
|
||||
}
|
||||
|
||||
calendar.register(Calendar.hookEnabledChanged, function(enabled) {
|
||||
if (enabled) {
|
||||
showCalendar(calendar.url);
|
||||
} else {
|
||||
hideCalendar(calendar.url);
|
||||
//calendar.list.loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
calendar.register(Calendar.hookColorChanged, function() {
|
||||
if (calendar.enabled) {
|
||||
hideCalendar(calendar.url);
|
||||
showCalendar(calendar.url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
oldCalendars.filter(function(calendar) {
|
||||
return newCalendars.indexOf(calendar) === -1;
|
||||
}).forEach(function(calendar) {
|
||||
var url = calendar.url;
|
||||
hideCalendar(calendar.url);
|
||||
|
||||
delete $scope.eventSource[url];
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
TimezoneService.get($scope.defaulttimezone).then(function(timezone) {
|
||||
if (timezone) {
|
||||
ICAL.TimezoneService.register($scope.defaulttimezone, timezone.jCal);
|
||||
}
|
||||
}).catch(function() {
|
||||
OC.Notification.showTemporary(
|
||||
t('calendar', 'You are in an unknown timezone ({tz}), falling back to UTC', {
|
||||
tz: $scope.defaulttimezone
|
||||
})
|
||||
);
|
||||
|
||||
$scope.defaulttimezone = 'UTC';
|
||||
$scope.fcConfig.timezone = 'UTC';
|
||||
fc.elm.fullCalendar('option', 'timezone', 'UTC');
|
||||
});
|
||||
|
||||
if (!isPublic) {
|
||||
$scope.calendarsPromise = CalendarService.getAll().then(function (calendars) {
|
||||
$scope.calendars = calendars;
|
||||
is.loading = false;
|
||||
// TODO - scope.apply should not be necessary here
|
||||
$scope.$apply();
|
||||
});
|
||||
} else {
|
||||
$scope.calendarsPromise = CalendarService.getPublicCalendar(constants.publicSharingToken).then(function(calendar) {
|
||||
$scope.calendars = [calendar];
|
||||
is.loading = false;
|
||||
// TODO - scope.apply should not be necessary here
|
||||
$scope.$apply();
|
||||
}).catch((reason) => {
|
||||
angular.element('#header-right').css('display', 'none');
|
||||
angular.element('#emptycontent-container').css('display', 'block');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calendar UI Configuration.
|
||||
*/
|
||||
$scope.fcConfig = {
|
||||
timezone: $scope.defaulttimezone,
|
||||
select: function (start, end, jsEvent, view) {
|
||||
var writableCalendars = $scope.calendars.filter(function(elem) {
|
||||
return elem.isWritable();
|
||||
});
|
||||
|
||||
if (writableCalendars.length === 0) {
|
||||
if (!isPublic) {
|
||||
OC.Notification.showTemporary(t('calendar', 'Please create a calendar first.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
start.add(start.toDate().getTimezoneOffset(), 'minutes');
|
||||
end.add(end.toDate().getTimezoneOffset(), 'minutes');
|
||||
|
||||
var vevent = VEvent.fromStartEnd(start, end, $scope.defaulttimezone);
|
||||
vevent.calendar = writableCalendars[0];
|
||||
|
||||
var timestamp = Date.now();
|
||||
var fcEventClass = 'new-event-dummy-' + timestamp;
|
||||
|
||||
vevent.getFcEvent(view.start, view.end, $scope.defaulttimezone).then((fcEvents) => {
|
||||
const fcEvent = fcEvents[0];
|
||||
|
||||
fcEvent.title = t('calendar', 'New event');
|
||||
fcEvent.className.push(fcEventClass);
|
||||
fcEvent.editable = false;
|
||||
fc.elm.fullCalendar('renderEvent', fcEvent);
|
||||
|
||||
EventsEditorDialogService.open($scope, fcEvent, function() {
|
||||
const elements = angular.element('.' + fcEventClass);
|
||||
const isHidden = angular.element(elements[0]).parents('.fc-limited').length !== 0;
|
||||
if (isHidden) {
|
||||
return PopoverPositioningUtility.calculate(jsEvent.clientX, jsEvent.clientY, jsEvent.clientX, jsEvent.clientY, view);
|
||||
} else {
|
||||
return PopoverPositioningUtility.calculateByTarget(elements[0], view);
|
||||
}
|
||||
}, function() {
|
||||
return null;
|
||||
}, function() {
|
||||
fc.elm.fullCalendar('removeEvents', function(fcEventToCheck) {
|
||||
if (Array.isArray(fcEventToCheck.className)) {
|
||||
return (fcEventToCheck.className.indexOf(fcEventClass) !== -1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}).then(function(result) {
|
||||
createAndRenderEvent(result.calendar, result.vevent.data, view.start, view.end, $scope.defaulttimezone);
|
||||
}).catch(function(reason) {
|
||||
//fcEvent is removed by unlock callback
|
||||
//no need to anything
|
||||
return null;
|
||||
});
|
||||
});
|
||||
},
|
||||
eventClick: function(fcEvent, jsEvent, view) {
|
||||
var vevent = fcEvent.vevent;
|
||||
var oldCalendar = vevent.calendar;
|
||||
var fcEvt = fcEvent;
|
||||
|
||||
EventsEditorDialogService.open($scope, fcEvent, function() {
|
||||
return PopoverPositioningUtility.calculateByTarget(jsEvent.currentTarget, view);
|
||||
}, function() {
|
||||
fcEvt.editable = false;
|
||||
fc.elm.fullCalendar('updateEvent', fcEvt);
|
||||
}, function() {
|
||||
fcEvt.editable = fcEvent.calendar.writable;
|
||||
fc.elm.fullCalendar('updateEvent', fcEvt);
|
||||
}).then(function(result) {
|
||||
// was the event moved to another calendar?
|
||||
if (result.calendar === oldCalendar) {
|
||||
VEventService.update(vevent).then(function() {
|
||||
fc.elm.fullCalendar('removeEvents', fcEvent.id);
|
||||
|
||||
if (result.calendar.enabled) {
|
||||
fc.elm.fullCalendar('refetchEventSources', result.calendar.fcEventSource);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
deleteAndRemoveEvent(vevent, fcEvent);
|
||||
createAndRenderEvent(result.calendar, result.vevent.data, view.start, view.end, $scope.defaulttimezone);
|
||||
}
|
||||
}).catch(function(reason) {
|
||||
if (reason === 'delete') {
|
||||
deleteAndRemoveEvent(vevent, fcEvent);
|
||||
}
|
||||
});
|
||||
},
|
||||
eventResize: function (fcEvent, delta, revertFunc) {
|
||||
fcEvent.resize(delta);
|
||||
VEventService.update(fcEvent.vevent).catch(function() {
|
||||
revertFunc();
|
||||
});
|
||||
},
|
||||
eventDrop: function (fcEvent, delta, revertFunc) {
|
||||
const isAllDay = !fcEvent.start.hasTime();
|
||||
|
||||
const defaultAllDayEventDuration = fc.elm.fullCalendar('option', 'defaultAllDayEventDuration');
|
||||
const defaultAllDayEventMomentDuration = moment.duration(defaultAllDayEventDuration);
|
||||
|
||||
const defaultTimedEventDuration = fc.elm.fullCalendar('option', 'defaultTimedEventDuration');
|
||||
const defaultTimedEventMomentDuration = moment.duration(defaultTimedEventDuration);
|
||||
|
||||
const timezone = $scope.defaulttimezone;
|
||||
|
||||
fcEvent.drop(delta, isAllDay, timezone, defaultTimedEventMomentDuration, defaultAllDayEventMomentDuration);
|
||||
VEventService.update(fcEvent.vevent).catch(function() {
|
||||
revertFunc();
|
||||
});
|
||||
},
|
||||
viewRender: function (view, element) {
|
||||
angular.element('#firstrow').find('.datepicker_current').html(view.title).text();
|
||||
angular.element('#datecontrol_date').datepicker('setDate', element.fullCalendar('getDate'));
|
||||
var newView = view.name;
|
||||
if (newView !== $scope.defaultView && !isPublic) {
|
||||
SettingsService.setView(newView);
|
||||
$scope.defaultView = newView;
|
||||
}
|
||||
if (newView === 'agendaDay') {
|
||||
angular.element('td.fc-state-highlight').css('background-color', '#ffffff');
|
||||
} else {
|
||||
angular.element('.fc-bg td.fc-state-highlight').css('background-color', '#ffa');
|
||||
}
|
||||
if (newView ==='agendaWeek') {
|
||||
element.fullCalendar('option', 'aspectRatio', 0.1);
|
||||
} else {
|
||||
element.fullCalendar('option', 'aspectRatio', 1.35);
|
||||
}
|
||||
},
|
||||
eventRender: function(event, element) {
|
||||
var status = event.getSimpleEvent().status;
|
||||
if (status !== null) {
|
||||
if (status.value === 'TENTATIVE') {
|
||||
element.css({'opacity': 0.5});
|
||||
}
|
||||
else if (status.value === 'CANCELLED') {
|
||||
element.css({
|
||||
'text-decoration': 'line-through',
|
||||
'opacity': 0.5
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,409 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author Vinicius Cubas Brand
|
||||
* @author Daniel Tygel
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2017 Vinicius Cubas Brand <vinicius@eita.org.br>
|
||||
* @copyright 2017 Daniel Tygel <dtygel@eita.org.br>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: CalendarListController
|
||||
* Description: Takes care of CalendarList in App Navigation.
|
||||
*/
|
||||
|
||||
app.controller('CalendarListController', ['$scope', '$rootScope', '$window', 'HashService', 'CalendarService', 'WebCalService', 'is', 'CalendarListItem', 'Calendar', 'MailerService', 'ColorUtility', 'isSharingAPI', 'constants',
|
||||
function ($scope, $rootScope, $window, HashService, CalendarService, WebCalService, is, CalendarListItem, Calendar, MailerService, ColorUtility, isSharingAPI, constants) {
|
||||
'use strict';
|
||||
|
||||
$scope.calendarListItems = [];
|
||||
$scope.is = is;
|
||||
$scope.newCalendarInputVal = '';
|
||||
$scope.newCalendarColorVal = '';
|
||||
$scope.addingCal = false;
|
||||
$scope.addingCalRequest = false;
|
||||
$scope.addingSub = false;
|
||||
$scope.addingSubRequest = false;
|
||||
|
||||
$scope.subscription = {};
|
||||
$scope.subscription.newSubscriptionUrl = '';
|
||||
$scope.subscription.newSubscriptionLocked = false;
|
||||
$scope.publicdav = 'CalDAV';
|
||||
$scope.publicdavdesc = t('calendar', 'CalDAV address for clients');
|
||||
$scope.warningLabel = t('calendar', 'Some events in this calendar are broken. Please check the JS console for more info.');
|
||||
$scope.shareLabel = t('calendar', 'Share Calendar');
|
||||
$scope.sharedLabel = t('calendar', 'Shared');
|
||||
|
||||
$scope.isSharingAPI = isSharingAPI;
|
||||
$scope.canSharePublicLink = constants.canSharePublicLink;
|
||||
|
||||
$scope.$watchCollection('calendars', function(newCalendars, oldCalendars) {
|
||||
newCalendars = newCalendars || [];
|
||||
oldCalendars = oldCalendars || [];
|
||||
|
||||
newCalendars.filter(function(calendar) {
|
||||
return oldCalendars.indexOf(calendar) === -1;
|
||||
}).forEach(function(calendar) {
|
||||
const item = CalendarListItem(calendar);
|
||||
if (item) {
|
||||
$scope.calendarListItems.push(item);
|
||||
$scope.publicdavurl = $scope.$parent.calendars[0].caldav;
|
||||
calendar.register(Calendar.hookFinishedRendering, function() {
|
||||
if (!$scope.$$phase) {
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
oldCalendars.filter(function(calendar) {
|
||||
return newCalendars.indexOf(calendar) === -1;
|
||||
}).forEach(function(calendar) {
|
||||
$scope.calendarListItems = $scope.calendarListItems.filter(function(itemToCheck) {
|
||||
return itemToCheck.calendar !== calendar;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$scope.openNewCalendarForm = () => {
|
||||
$scope.addingCal = true;
|
||||
};
|
||||
|
||||
$scope.dismissNewCalendar = () => {
|
||||
$scope.newCalendarInputVal = '';
|
||||
$scope.newCalendarColorVal = '';
|
||||
$scope.addingCal = false;
|
||||
};
|
||||
|
||||
$scope.create = function (name) {
|
||||
$scope.addingCalRequest = true;
|
||||
const color = ColorUtility.randomColor();
|
||||
CalendarService.create(name, color).then(function(calendar) {
|
||||
$scope.calendars.push(calendar);
|
||||
$rootScope.$broadcast('createdCalendar', calendar);
|
||||
|
||||
$scope.newCalendarInputVal = '';
|
||||
$scope.newCalendarColorVal = '';
|
||||
$scope.addingCal = false;
|
||||
$scope.addingCalRequest = false;
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openNewSubscriptionForm = () => {
|
||||
$scope.addingSub = true;
|
||||
};
|
||||
|
||||
$scope.dismissNewSubscription = () => {
|
||||
$scope.subscription.newSubscriptionUrl = '';
|
||||
$scope.addingSub = false;
|
||||
};
|
||||
|
||||
$scope.createSubscription = function(url) {
|
||||
$scope.subscription.newSubscriptionLocked = true;
|
||||
WebCalService.get(url).then(function(splittedICal) {
|
||||
const color = splittedICal.color || ColorUtility.randomColor();
|
||||
let name = splittedICal.name || url;
|
||||
|
||||
if (name.length > 100) {
|
||||
name = name.substr(0, 100);
|
||||
}
|
||||
|
||||
CalendarService.createWebCal(name, color, url)
|
||||
.then(function(calendar) {
|
||||
angular.element('#new-subscription-button').click();
|
||||
$scope.calendars.push(calendar);
|
||||
$scope.subscription.newSubscriptionUrl = '';
|
||||
$scope.$digest();
|
||||
$scope.$parent.$digest();
|
||||
$scope.subscription.newSubscriptionLocked = false;
|
||||
$scope.addingSub = false;
|
||||
})
|
||||
.catch(function() {
|
||||
OC.Notification.showTemporary(t('calendar', 'Could not save WebCal-calendar'));
|
||||
$scope.subscription.newSubscriptionLocked = false;
|
||||
});
|
||||
}).catch(function(reason) {
|
||||
if (reason.error) {
|
||||
OC.Notification.showTemporary(reason.message);
|
||||
$scope.subscription.newSubscriptionLocked = false;
|
||||
} else if(reason.redirect) {
|
||||
$scope.createSubscription(reason.new_url);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.download = function (item) {
|
||||
$window.open(item.calendar.downloadUrl);
|
||||
};
|
||||
|
||||
$scope.integration = function (item) {
|
||||
return '<iframe width="400" height="215" src="' + item.publicEmbedURL + '"></iframe>';
|
||||
};
|
||||
|
||||
$scope.$watch('publicdav', function (newvalue) {
|
||||
if ($scope.$parent.calendars[0]) {
|
||||
if (newvalue === 'CalDAV') { // CalDAV address
|
||||
$scope.publicdavurl = $scope.$parent.calendars[0].caldav;
|
||||
$scope.publicdavdesc = t('calendar', 'CalDAV address for clients');
|
||||
} else { // WebDAV address
|
||||
var url = $scope.$parent.calendars[0].url;
|
||||
// cut off last slash to have a fancy name for the ics
|
||||
if (url.slice(url.length - 1) === '/') {
|
||||
url = url.slice(0, url.length - 1);
|
||||
}
|
||||
url += '?export';
|
||||
$scope.publicdavurl = $window.location.origin + url;
|
||||
$scope.publicdavdesc = t('calendar', 'WebDAV address for subscriptions');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.sendMail = function (item) {
|
||||
item.toggleSendingMail();
|
||||
MailerService.sendMail(item.email, item.publicSharingURL, item.calendar.displayname).then(function (response) {
|
||||
if (response.status === 200) {
|
||||
item.email = '';
|
||||
OC.Notification.showTemporary(t('calendar', 'Email sent.'));
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('calendar', 'Could not send your email.'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goPublic = function (item) {
|
||||
$window.open(item.publicSharingURL);
|
||||
};
|
||||
|
||||
$scope.toggleSharesEditor = function (calendar) {
|
||||
calendar.toggleSharesEditor();
|
||||
};
|
||||
|
||||
$scope.togglePublish = function(item) {
|
||||
if (item.calendar.published) {
|
||||
item.calendar.publish().then(function (response) {
|
||||
if (response) {
|
||||
CalendarService.get(item.calendar.url).then(function (calendar) {
|
||||
item.calendar.publicToken = calendar.publicToken;
|
||||
item.calendar.published = true;
|
||||
});
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
} else {
|
||||
item.calendar.unpublish().then(function (response) {
|
||||
if (response) {
|
||||
item.calendar.published = false;
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.prepareUpdate = function (calendar) {
|
||||
calendar.prepareUpdate();
|
||||
};
|
||||
|
||||
$scope.onSelectSharee = function (item, model, label, calendarItem) {
|
||||
const calendar = calendarItem.calendar;
|
||||
// Create a default share with the user/group/circle, read only
|
||||
calendar.share(item.type, item.identifier, item.displayname, false, false).then(function() {
|
||||
// Remove content from text box
|
||||
calendarItem.selectedSharee = '';
|
||||
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateExistingUserShare = function(calendar, userId, displayname, writable) {
|
||||
calendar.share(constants.SHARE_TYPE_USER, userId, displayname, writable, true).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateExistingGroupShare = function(calendar, groupId, displayname, writable) {
|
||||
calendar.share(constants.SHARE_TYPE_GROUP, groupId, displayname, writable, true).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateExistingCircleShare = function(calendar, circleId, displayname, writable) {
|
||||
calendar.share(constants.SHARE_TYPE_CIRCLE, circleId, displayname, writable, true).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unshareFromUser = function(calendar, userId) {
|
||||
calendar.unshare(constants.SHARE_TYPE_USER, userId).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unshareFromGroup = function(calendar, groupId) {
|
||||
calendar.unshare(constants.SHARE_TYPE_GROUP, groupId).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unshareFromCircle = function(calendar, circleId) {
|
||||
calendar.unshare(constants.SHARE_TYPE_CIRCLE, circleId).then(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.findSharee = function (val, calendar) {
|
||||
return $.get(
|
||||
OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
|
||||
{
|
||||
format: 'json',
|
||||
search: val.trim(),
|
||||
perPage: 200,
|
||||
itemType: 'principals'
|
||||
}
|
||||
).then(function(result) {
|
||||
var users = result.ocs.data.exact.users.concat(result.ocs.data.users);
|
||||
var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups);
|
||||
var circles = result.ocs.data.exact.circles.concat(result.ocs.data.circles);
|
||||
|
||||
var userShares = calendar.shares.users;
|
||||
var groupShares = calendar.shares.groups;
|
||||
var circleShares = calendar.shares.circles;
|
||||
var userSharesLength = userShares.length;
|
||||
var groupSharesLength = groupShares.length;
|
||||
var circleSharesLength = circleShares.length;
|
||||
var i, j;
|
||||
|
||||
// Filter out current user
|
||||
var usersLength = users.length;
|
||||
for (i = 0 ; i < usersLength; i++) {
|
||||
if (users[i].value.shareWith === OC.currentUser) {
|
||||
users.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now filter out all sharees that are already shared with
|
||||
for (i = 0; i < userSharesLength; i++) {
|
||||
var share = userShares[i];
|
||||
usersLength = users.length;
|
||||
for (j = 0; j < usersLength; j++) {
|
||||
if (users[j].value.shareWith === share.id) {
|
||||
users.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine users, groups and circles
|
||||
users = users.map(function(item){
|
||||
return {
|
||||
display: _.escape(item.label),
|
||||
displayname: item.label,
|
||||
type: constants.SHARE_TYPE_USER,
|
||||
identifier: item.value.shareWith
|
||||
};
|
||||
});
|
||||
|
||||
groups = groups.map(function(item){
|
||||
return {
|
||||
display: _.escape(item.label + ' (' + t('calendar', 'group') + ')'),
|
||||
displayname: item.label,
|
||||
type: constants.SHARE_TYPE_GROUP,
|
||||
identifier: item.value.shareWith
|
||||
};
|
||||
});
|
||||
|
||||
circles = circles.map(function(item){
|
||||
return {
|
||||
display: item.label + ' (' + t('calendar', 'circle') + ')',
|
||||
displayname: item.label,
|
||||
type: constants.SHARE_TYPE_CIRCLE,
|
||||
identifier: item.value.shareWith
|
||||
};
|
||||
});
|
||||
|
||||
return circles.concat(groups).concat(users);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.performUpdate = function (item) {
|
||||
item.saveEditor();
|
||||
item.calendar.update().then(function() {
|
||||
$rootScope.$broadcast('updatedCalendar', item.calendar);
|
||||
$rootScope.$broadcast('reloadCalendarList');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the shares of the calendar
|
||||
*/
|
||||
$scope.performUpdateShares = function (calendar) {
|
||||
calendar.update().then(function() {
|
||||
calendar.dropPreviousState();
|
||||
calendar.list.edit = false;
|
||||
$rootScope.$broadcast('updatedCalendar', calendar);
|
||||
$rootScope.$broadcast('reloadCalendarList');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.triggerEnable = function(item) {
|
||||
item.calendar.toggleEnabled();
|
||||
|
||||
item.calendar.update().then(function() {
|
||||
$rootScope.$broadcast('updatedCalendarsVisibility', item.calendar);
|
||||
$rootScope.$broadcast('reloadCalendarList');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.remove = function (item) {
|
||||
item.calendar.delete().then(function() {
|
||||
$scope.$parent.calendars = $scope.$parent.calendars.filter(function(elem) {
|
||||
return elem !== item.calendar;
|
||||
});
|
||||
if (!$scope.$$phase) {
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rootScope.$on('reloadCalendarList', function() {
|
||||
if (!$scope.$$phase) {
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
|
||||
HashService.runIfApplicable('subscribe_to_webcal', (map) => {
|
||||
if (map.has('url')) {
|
||||
const url = map.get('url');
|
||||
|
||||
$scope.subscription.newSubscriptionUrl = url;
|
||||
$scope.subscription.newSubscriptionLocked = true;
|
||||
angular.element('#new-subscription-button').click();
|
||||
|
||||
// wait for calendars to be initialized
|
||||
// needed for creating a proper URL
|
||||
$scope.calendarsPromise.then(() => {
|
||||
$scope.createSubscription(url);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
|
@ -1,113 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: Date Picker Controller
|
||||
* Description: Takes care for pushing dates from app navigation date picker and fullcalendar.
|
||||
*/
|
||||
app.controller('DatePickerController', ['$scope', 'fc', 'uibDatepickerConfig', 'constants',
|
||||
function ($scope, fc, uibDatepickerConfig, constants) {
|
||||
'use strict';
|
||||
|
||||
function getDayClass(data) {
|
||||
if (moment(data.date).isSame(new Date(), 'day')) {
|
||||
return 'highlight-today';
|
||||
}
|
||||
|
||||
if (data.date.getDay() === 0 || data.date.getDay() === 6) {
|
||||
return 'highlight-weekend';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$scope.datepickerOptions = {
|
||||
formatDay: 'd',
|
||||
customClass: getDayClass
|
||||
};
|
||||
|
||||
$scope.dt = new Date();
|
||||
$scope.visibility = false;
|
||||
|
||||
$scope.selectedView = constants.initialView;
|
||||
|
||||
angular.extend(uibDatepickerConfig, {
|
||||
showWeeks: false,
|
||||
startingDay: parseInt(moment().startOf('week').format('d'))
|
||||
});
|
||||
|
||||
$scope.today = function () {
|
||||
$scope.dt = new Date();
|
||||
};
|
||||
|
||||
function changeView(index) {
|
||||
switch($scope.selectedView) {
|
||||
case 'agendaDay':
|
||||
return moment($scope.dt)
|
||||
.add(index, 'day')
|
||||
.toDate();
|
||||
|
||||
case 'agendaWeek':
|
||||
return moment($scope.dt)
|
||||
.add(index, 'week')
|
||||
.startOf('week')
|
||||
.toDate();
|
||||
|
||||
case 'month':
|
||||
return moment($scope.dt)
|
||||
.add(index, 'month')
|
||||
.startOf('month')
|
||||
.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.prev = function() {
|
||||
$scope.dt = changeView(-1);
|
||||
};
|
||||
|
||||
$scope.next = function() {
|
||||
$scope.dt = changeView(1);
|
||||
};
|
||||
|
||||
$scope.toggle = function() {
|
||||
$scope.visibility = !$scope.visibility;
|
||||
};
|
||||
|
||||
$scope.$watch('dt', function(newValue) {
|
||||
if (fc) {
|
||||
fc.elm.fullCalendar(
|
||||
'gotoDate',
|
||||
newValue
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('selectedView', function(newValue) {
|
||||
if (fc) {
|
||||
fc.elm.fullCalendar(
|
||||
'changeView',
|
||||
newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
|
@ -1,337 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: Events Dialog Controller
|
||||
* Description: Takes care of anything inside the Events Modal.
|
||||
*/
|
||||
|
||||
app.controller('EditorController', ['$scope', 'TimezoneService', 'AutoCompletionService', '$timeout', '$window', '$uibModalInstance', 'vevent', 'simpleEvent', 'calendar', 'isNew', 'emailAddress',
|
||||
function($scope, TimezoneService, AutoCompletionService, $timeout, $window, $uibModalInstance, vevent, simpleEvent, calendar, isNew, emailAddress) {
|
||||
'use strict';
|
||||
|
||||
$scope.properties = simpleEvent;
|
||||
$scope.is_new = isNew;
|
||||
$scope.calendar = calendar;
|
||||
$scope.oldCalendar = isNew ? calendar : vevent.calendar;
|
||||
$scope.readOnly = !vevent.calendar.isWritable();
|
||||
$scope.accessibleViaCalDAV = vevent.calendar.eventsAccessibleViaCalDAV();
|
||||
$scope.selected = 0;
|
||||
$scope.timezones = [];
|
||||
$scope.emailAddress = emailAddress;
|
||||
$scope.edittimezone = ((
|
||||
$scope.properties.dtstart.parameters.zone !== 'floating' &&
|
||||
$scope.properties.dtstart.parameters.zone !== $scope.defaulttimezone) || (
|
||||
$scope.properties.dtend.parameters.zone !== 'floating' &&
|
||||
$scope.properties.dtend.parameters.zone !== $scope.defaulttimezone
|
||||
));
|
||||
|
||||
$scope.preEditingHooks = [];
|
||||
$scope.postEditingHooks = [];
|
||||
|
||||
$scope.tabs = [
|
||||
{title: t('calendar', 'Details'), value: 0},
|
||||
{title: t('calendar', 'Attendees'), value: 1},
|
||||
{title: t('calendar', 'Reminders'), value: 2},
|
||||
{title: t('calendar', 'Repeating'), value: 3}
|
||||
];
|
||||
|
||||
$scope.classSelect = [
|
||||
{displayname: t('calendar', 'When shared show full event'), type: 'PUBLIC'},
|
||||
{displayname: t('calendar', 'When shared show only busy'), type: 'CONFIDENTIAL'},
|
||||
{displayname: t('calendar', 'When shared hide this event'), type: 'PRIVATE'}
|
||||
];
|
||||
|
||||
$scope.statusSelect = [
|
||||
{displayname: t('calendar', 'Confirmed'), type: 'CONFIRMED'},
|
||||
{displayname: t('calendar', 'Tentative'), type: 'TENTATIVE'},
|
||||
{displayname: t('calendar', 'Cancelled'), type: 'CANCELLED'}
|
||||
];
|
||||
|
||||
$scope.registerPreHook = function(callback) {
|
||||
$scope.preEditingHooks.push(callback);
|
||||
};
|
||||
|
||||
$uibModalInstance.rendered.then(function() {
|
||||
if ($scope.properties.allDay) {
|
||||
$scope.properties.dtend.value = moment($scope.properties.dtend.value.subtract(1, 'days'));
|
||||
}
|
||||
|
||||
autosize($('.advanced--textarea'));
|
||||
autosize($('.events--textarea'));
|
||||
|
||||
$timeout(() => {
|
||||
autosize.update($('.advanced--textarea'));
|
||||
autosize.update($('.events--textarea'));
|
||||
}, 50);
|
||||
|
||||
angular.forEach($scope.preEditingHooks, function(callback) {
|
||||
callback();
|
||||
});
|
||||
|
||||
$scope.tabopener(0);
|
||||
});
|
||||
|
||||
$scope.registerPostHook = function(callback) {
|
||||
$scope.postEditingHooks.push(callback);
|
||||
};
|
||||
|
||||
$scope.proceed = function() {
|
||||
$scope.prepareClose();
|
||||
$uibModalInstance.close({
|
||||
action: 'proceed',
|
||||
calendar: $scope.calendar,
|
||||
simple: $scope.properties,
|
||||
vevent: vevent
|
||||
});
|
||||
};
|
||||
|
||||
$scope.save = function() {
|
||||
if (!$scope.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.prepareClose();
|
||||
$scope.properties.patch();
|
||||
$uibModalInstance.close({
|
||||
action: 'save',
|
||||
calendar: $scope.calendar,
|
||||
simple: $scope.properties,
|
||||
vevent: vevent
|
||||
});
|
||||
};
|
||||
|
||||
$scope.keypress = function(event) {
|
||||
var code = event.keyCode || event.which;
|
||||
if((event.metaKey === true || event.ctrlKey === true) && code === 13) {
|
||||
$scope.$broadcast('save-contents');
|
||||
$scope.save();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.validate = function() {
|
||||
var error = false;
|
||||
if ($scope.properties.summary === null || $scope.properties.summary.value.trim() === '') {
|
||||
OC.Notification.showTemporary(t('calendar', 'Please add a title.'));
|
||||
error = true;
|
||||
}
|
||||
if ($scope.calendar === null || typeof $scope.calendar === 'undefined') {
|
||||
OC.Notification.showTemporary(t('calendar', 'Please select a calendar.'));
|
||||
error = true;
|
||||
}
|
||||
if (!$scope.properties.checkDtStartBeforeDtEnd()) {
|
||||
OC.Notification.showTemporary(t('calendar', 'The event can not end before it starts.'));
|
||||
error = true;
|
||||
}
|
||||
|
||||
return !error;
|
||||
};
|
||||
|
||||
$scope.prepareClose = function() {
|
||||
if ($scope.properties.allDay) {
|
||||
$scope.properties.dtend.value.add(1, 'days');
|
||||
}
|
||||
|
||||
angular.forEach($scope.postEditingHooks, function(callback) {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
$scope.delete = function() {
|
||||
$uibModalInstance.dismiss('delete');
|
||||
};
|
||||
|
||||
$scope.export = function() {
|
||||
$window.open($scope.oldCalendar.url + vevent.uri);
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything tabs
|
||||
*/
|
||||
$scope.tabopener = function (val) {
|
||||
$scope.selected = val;
|
||||
if (val === 0) {
|
||||
$scope.eventsdetailsview = true;
|
||||
$scope.eventsattendeeview = false;
|
||||
$scope.eventsalarmview = false;
|
||||
$scope.eventsrepeatview = false;
|
||||
} else if (val === 1) {
|
||||
$scope.eventsdetailsview = false;
|
||||
$scope.eventsattendeeview = true;
|
||||
$scope.eventsalarmview = false;
|
||||
$scope.eventsrepeatview = false;
|
||||
} else if (val === 2) {
|
||||
$scope.eventsdetailsview = false;
|
||||
$scope.eventsattendeeview = false;
|
||||
$scope.eventsalarmview = true;
|
||||
$scope.eventsrepeatview = false;
|
||||
} else if (val === 3) {
|
||||
$scope.eventsdetailsview = false;
|
||||
$scope.eventsattendeeview = false;
|
||||
$scope.eventsalarmview = false;
|
||||
$scope.eventsrepeatview = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything calendar select
|
||||
*/
|
||||
$scope.selectedCalendarChanged = () => {
|
||||
if ($scope.calendar.enabled === false) {
|
||||
$scope.calendar.enabled = true;
|
||||
$scope.calendar.update();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showCalendarSelection = function() {
|
||||
// always show selection if it's a readonly calendar
|
||||
if (!$scope.calendar.isWritable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const writableCalendars = $scope.calendars.filter(function (c) {
|
||||
return c.isWritable();
|
||||
});
|
||||
|
||||
return writableCalendars.length > 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything date and time
|
||||
*/
|
||||
$scope.$watch('properties.dtstart.value', function(nv, ov) {
|
||||
var diff = nv.diff(ov, 'seconds');
|
||||
if (diff !== 0) {
|
||||
$scope.properties.dtend.value = moment($scope.properties.dtend.value.add(diff, 'seconds'));
|
||||
}
|
||||
});
|
||||
|
||||
$scope.toggledAllDay = function() {
|
||||
if ($scope.properties.allDay) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.properties.dtstart.value.isSame($scope.properties.dtend.value)) {
|
||||
$scope.properties.dtend.value = moment($scope.properties.dtend.value.add(1, 'hours'));
|
||||
}
|
||||
|
||||
if ($scope.properties.dtstart.parameters.zone === 'floating' &&
|
||||
$scope.properties.dtend.parameters.zone === 'floating') {
|
||||
$scope.properties.dtstart.parameters.zone = $scope.defaulttimezone;
|
||||
$scope.properties.dtend.parameters.zone = $scope.defaulttimezone;
|
||||
}
|
||||
};
|
||||
$scope.$watch('properties.allDay', $scope.toggledAllDay);
|
||||
|
||||
/**
|
||||
* Everything timezones
|
||||
*/
|
||||
TimezoneService.listAll().then(function(list) {
|
||||
if ($scope.properties.dtstart.parameters.zone !== 'floating' &&
|
||||
list.indexOf($scope.properties.dtstart.parameters.zone) === -1) {
|
||||
list.push($scope.properties.dtstart.parameters.zone);
|
||||
}
|
||||
if ($scope.properties.dtend.parameters.zone !== 'floating' &&
|
||||
list.indexOf($scope.properties.dtend.parameters.zone) === -1) {
|
||||
list.push($scope.properties.dtend.parameters.zone);
|
||||
}
|
||||
|
||||
angular.forEach(list, function(timezone) {
|
||||
if(timezone === 'GMT' || timezone === 'Z') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timezone.split('/').length === 1) {
|
||||
$scope.timezones.push({
|
||||
displayname: timezone,
|
||||
group: t('calendar', 'Global'),
|
||||
value: timezone
|
||||
});
|
||||
} else {
|
||||
$scope.timezones.push({
|
||||
displayname: timezone.split('/').slice(1).join('/'),
|
||||
group: timezone.split('/', 1),
|
||||
value: timezone
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.timezones.push({
|
||||
displayname: t('calendar', 'None'),
|
||||
group: t('calendar', 'Global'),
|
||||
value: 'floating'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.loadTimezone = function(tzId) {
|
||||
TimezoneService.get(tzId).then(function(timezone) {
|
||||
ICAL.TimezoneService.register(tzId, timezone.jCal);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything location
|
||||
*/
|
||||
$scope.searchLocation = function(value) {
|
||||
return AutoCompletionService.searchLocation(value).then(function(locations) {
|
||||
locations = locations.map(function(location){
|
||||
return {
|
||||
label: _.escape(location.name) + "\n" + location.label,
|
||||
name: _.escape(location.name)
|
||||
};
|
||||
});
|
||||
|
||||
return locations;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectLocationFromTypeahead = function(item) {
|
||||
$scope.properties.location.value = item.label;
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything access class
|
||||
*/
|
||||
$scope.setClassToDefault = function() {
|
||||
if ($scope.properties.class === null) {
|
||||
$scope.properties.class = {
|
||||
type: 'string',
|
||||
value: 'PUBLIC'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setStatusToDefault = function() {
|
||||
if ($scope.properties.status === null) {
|
||||
$scope.properties.status = {
|
||||
type: 'string',
|
||||
value: 'CONFIRMED'
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,183 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: ImportController
|
||||
* Description: Takes care of importing calendars
|
||||
*/
|
||||
|
||||
app.controller('ImportController', ['$scope', '$filter', 'CalendarService', 'VEventService', '$uibModalInstance', 'files', 'ImportFileWrapper', 'ColorUtility',
|
||||
function($scope, $filter, CalendarService, VEventService, $uibModalInstance, files, ImportFileWrapper, ColorUtility) {
|
||||
'use strict';
|
||||
|
||||
$scope.nameSize = 25;
|
||||
|
||||
$scope.rawFiles = files;
|
||||
$scope.files = [];
|
||||
|
||||
$scope.showCloseButton = false;
|
||||
$scope.writableCalendars = $scope.calendars.filter(function(elem) {
|
||||
return elem.isWritable();
|
||||
});
|
||||
|
||||
$scope.import = function (fileWrapper) {
|
||||
fileWrapper.state = ImportFileWrapper.stateScheduled;
|
||||
|
||||
var importCalendar = function(calendar) {
|
||||
const objects = fileWrapper.splittedICal.objects;
|
||||
|
||||
angular.forEach(objects, function(object) {
|
||||
VEventService.create(calendar, object, false).then(function(response) {
|
||||
fileWrapper.state = ImportFileWrapper.stateImporting;
|
||||
fileWrapper.progress++;
|
||||
|
||||
if (!response) {
|
||||
fileWrapper.errors++;
|
||||
}
|
||||
}).catch(function(reason) {
|
||||
if (reason.status === 400) {
|
||||
const xml = reason.xhr.responseXML;
|
||||
const error = xml.children[0];
|
||||
|
||||
if (error) {
|
||||
const message = error.children[1].textContent;
|
||||
if (message === 'Calendar object with uid already exists in this calendar collection.') {
|
||||
fileWrapper.duplicates++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileWrapper.state = ImportFileWrapper.stateImporting;
|
||||
fileWrapper.errors++;
|
||||
fileWrapper.progress++;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (fileWrapper.selectedCalendar === 'new') {
|
||||
var name = fileWrapper.splittedICal.name || fileWrapper.file.name;
|
||||
var color = fileWrapper.splittedICal.color || ColorUtility.randomColor();
|
||||
|
||||
var components = [];
|
||||
if (fileWrapper.splittedICal.vevents.length > 0) {
|
||||
components.push('vevent');
|
||||
components.push('vtodo');
|
||||
}
|
||||
if (fileWrapper.splittedICal.vjournals.length > 0) {
|
||||
components.push('vjournal');
|
||||
}
|
||||
if (fileWrapper.splittedICal.vtodos.length > 0 && components.indexOf('vtodo') === -1) {
|
||||
components.push('vtodo');
|
||||
}
|
||||
|
||||
CalendarService.create(name, color, components).then(function(calendar) {
|
||||
if (calendar.components.vevent) {
|
||||
$scope.calendars.push(calendar);
|
||||
$scope.writableCalendars.push(calendar);
|
||||
}
|
||||
importCalendar(calendar);
|
||||
fileWrapper.selectedCalendar = calendar.url;
|
||||
});
|
||||
} else {
|
||||
var calendar = $scope.calendars.filter(function (element) {
|
||||
return element.url === fileWrapper.selectedCalendar;
|
||||
})[0];
|
||||
importCalendar(calendar);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
$scope.preselectCalendar = function(fileWrapper) {
|
||||
var possibleCalendars = $filter('importCalendarFilter')($scope.writableCalendars, fileWrapper);
|
||||
if (possibleCalendars.length === 0) {
|
||||
fileWrapper.selectedCalendar = 'new';
|
||||
} else {
|
||||
fileWrapper.selectedCalendar = possibleCalendars[0].url;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeCalendar = function(fileWrapper) {
|
||||
if (fileWrapper.selectedCalendar === 'new') {
|
||||
fileWrapper.incompatibleObjectsWarning = false;
|
||||
} else {
|
||||
var possibleCalendars = $filter('importCalendarFilter')($scope.writableCalendars, fileWrapper);
|
||||
fileWrapper.incompatibleObjectsWarning = (possibleCalendars.indexOf(fileWrapper.selectedCalendar) === -1);
|
||||
}
|
||||
};
|
||||
|
||||
angular.forEach($scope.rawFiles, function(rawFile) {
|
||||
var fileWrapper = ImportFileWrapper(rawFile);
|
||||
fileWrapper.read(function() {
|
||||
$scope.preselectCalendar(fileWrapper);
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
fileWrapper.register(ImportFileWrapper.hookProgressChanged, function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
fileWrapper.register(ImportFileWrapper.hookDone, function() {
|
||||
$scope.$apply();
|
||||
$scope.closeIfNecessary();
|
||||
|
||||
const calendar = $scope.calendars.find(function (element) {
|
||||
return element.url === fileWrapper.selectedCalendar;
|
||||
});
|
||||
if (calendar && calendar.enabled) {
|
||||
calendar.enabled = false;
|
||||
calendar.enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
fileWrapper.register(ImportFileWrapper.hookErrorsChanged, function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
$scope.files.push(fileWrapper);
|
||||
});
|
||||
|
||||
|
||||
$scope.closeIfNecessary = function() {
|
||||
const unfinishedFiles = $scope.files.filter((file) => (!file.wasCanceled() && !file.isDone() && !file.isEmpty() && !file.hasParsingErrors()));
|
||||
const filesEncounteredErrors = $scope.files.filter((file) => ((file.isDone() && file.hasErrors()) || file.hasParsingErrors()));
|
||||
const emptyFiles = $scope.files.filter((file) => file.isEmpty());
|
||||
|
||||
if (unfinishedFiles.length === 0 && filesEncounteredErrors.length === 0 && emptyFiles.length === 0) {
|
||||
$uibModalInstance.close();
|
||||
} else if (unfinishedFiles.length === 0 && (filesEncounteredErrors.length !== 0 || emptyFiles.length !== 0)) {
|
||||
$scope.showCloseButton = true;
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.close = function() {
|
||||
$uibModalInstance.close();
|
||||
};
|
||||
|
||||
$scope.cancelFile = function(fileWrapper) {
|
||||
fileWrapper.state = ImportFileWrapper.stateCanceled;
|
||||
$scope.closeIfNecessary();
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.controller('RecurrenceController', function($scope) {
|
||||
'use strict';
|
||||
|
||||
$scope.rruleNotSupported = 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.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.$parent.registerPreHook(function() {
|
||||
if ($scope.properties.rrule.freq !== 'NONE') {
|
||||
var unsupportedFREQs = ['SECONDLY', 'MINUTELY', 'HOURLY'];
|
||||
if (unsupportedFREQs.indexOf($scope.properties.rrule.freq) !== -1) {
|
||||
$scope.rruleNotSupported = true;
|
||||
}
|
||||
|
||||
if (typeof $scope.properties.rrule.parameters !== 'undefined') {
|
||||
var partIds = Object.getOwnPropertyNames($scope.properties.rrule.parameters);
|
||||
if (partIds.length > 0) {
|
||||
$scope.rruleNotSupported = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.properties.rrule.count !== null) {
|
||||
$scope.selected_repeat_end = 'COUNT';
|
||||
} else if ($scope.properties.rrule.until !== null) {
|
||||
$scope.rruleNotSupported = true;
|
||||
//$scope.selected_repeat_end = 'UNTIL';
|
||||
}
|
||||
|
||||
/*if (!moment.isMoment($scope.properties.rrule.until)) {
|
||||
$scope.properties.rrule.until = moment();
|
||||
}*/
|
||||
|
||||
if ($scope.properties.rrule.interval === null) {
|
||||
$scope.properties.rrule.interval = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$parent.registerPostHook(function() {
|
||||
$scope.properties.rrule.dontTouch = $scope.rruleNotSupported;
|
||||
|
||||
if ($scope.selected_repeat_end === 'NEVER') {
|
||||
$scope.properties.rrule.count = null;
|
||||
$scope.properties.rrule.until = null;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.resetRRule = function() {
|
||||
$scope.selected_repeat_end = 'NEVER';
|
||||
$scope.properties.rrule.freq = 'NONE';
|
||||
$scope.properties.rrule.count = null;
|
||||
//$scope.properties.rrule.until = null;
|
||||
$scope.properties.rrule.interval = 1;
|
||||
$scope.rruleNotSupported = false;
|
||||
$scope.properties.rrule.parameters = {};
|
||||
};
|
||||
|
||||
|
||||
});
|
|
@ -1,138 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: SettingController
|
||||
* Description: Takes care of the Calendar Settings.
|
||||
*/
|
||||
|
||||
app.controller('SettingsController', ['$scope', '$uibModal', '$timeout', 'TimezoneService', 'SettingsService', 'fc', 'isFirstRun', 'settings',
|
||||
function ($scope, $uibModal, $timeout, TimezoneService, SettingsService, fc, isFirstRun, settings) {
|
||||
'use strict';
|
||||
|
||||
$scope.settingsCalDavLink = OC.linkToRemote('dav') + '/';
|
||||
$scope.settingsCalDavPrincipalLink = OC.linkToRemote('dav') + '/principals/users/' + escapeHTML(encodeURIComponent(oc_current_user)) + '/';
|
||||
$scope.skipPopover = settings.skipPopover ? 'yes' : 'no';
|
||||
$scope.settingsShowWeekNr = settings.showWeekNr ? 'yes' : 'no';
|
||||
$scope.timezone = settings.timezone;
|
||||
$scope.timezones = [];
|
||||
|
||||
TimezoneService.listAll().then((timezones) => {
|
||||
$scope.timezones.push({
|
||||
displayname: t('calendar', 'Automatic ({timezone})', {
|
||||
timezone: TimezoneService.getDetected()
|
||||
}),
|
||||
value: 'automatic'
|
||||
});
|
||||
|
||||
timezones.forEach((timezone) => {
|
||||
if (timezone.split('/').length === 1) {
|
||||
$scope.timezones.push({
|
||||
displayname: timezone,
|
||||
group: t('calendar', 'Global'),
|
||||
value: timezone
|
||||
});
|
||||
} else {
|
||||
$scope.timezones.push({
|
||||
displayname: timezone.split('/').slice(1).join('/'),
|
||||
group: timezone.split('/', 1),
|
||||
value: timezone
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.setTimezone = () => {
|
||||
SettingsService.setTimezone($scope.timezone);
|
||||
settings.timezone = $scope.timezone;
|
||||
|
||||
if (fc.elm) {
|
||||
let timezone = settings.timezone;
|
||||
if (settings.timezone === 'automatic') {
|
||||
timezone = TimezoneService.getDetected();
|
||||
}
|
||||
|
||||
fc.elm.fullCalendar('option', 'timezone', timezone);
|
||||
}
|
||||
};
|
||||
|
||||
$timeout(() => {
|
||||
if (isFirstRun) {
|
||||
angular.element('.settings-button').click();
|
||||
angular.element('#import-button-overlay').tooltip({
|
||||
animation: true,
|
||||
placement: 'bottom',
|
||||
title: t('calendar', 'How about getting started by importing some calendars?')
|
||||
});
|
||||
$timeout(() => {
|
||||
angular.element('#import-button-overlay').tooltip('toggle');
|
||||
}, 500);
|
||||
$timeout(() => {
|
||||
angular.element('#import-button-overlay').tooltip('toggle');
|
||||
}, 10500);
|
||||
SettingsService.passedFirstRun();
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
angular.element('#import').on('change', function () {
|
||||
var filesArray = [];
|
||||
for (var i=0; i < this.files.length; i++) {
|
||||
filesArray.push(this.files[i]);
|
||||
}
|
||||
|
||||
if (filesArray.length > 0) {
|
||||
$uibModal.open({
|
||||
templateUrl: 'import.html',
|
||||
controller: 'ImportController',
|
||||
windowClass: 'import',
|
||||
backdropClass: 'import-backdrop',
|
||||
keyboard: false,
|
||||
appendTo: angular.element('#importpopover-container'),
|
||||
resolve: {
|
||||
files: function () {
|
||||
return filesArray;
|
||||
}
|
||||
},
|
||||
scope: $scope
|
||||
});
|
||||
}
|
||||
|
||||
angular.element('#import').val(null);
|
||||
});
|
||||
|
||||
$scope.updateSkipPopover = function() {
|
||||
const newValue = $scope.skipPopover;
|
||||
settings.skipPopover = (newValue === 'yes');
|
||||
SettingsService.setSkipPopover(newValue);
|
||||
};
|
||||
|
||||
$scope.updateShowWeekNr = function() {
|
||||
const newValue = $scope.settingsShowWeekNr;
|
||||
settings.showWeekNr = (newValue === 'yes');
|
||||
SettingsService.setShowWeekNr(newValue);
|
||||
if (fc.elm) {
|
||||
fc.elm.fullCalendar('option', 'weekNumbers', (newValue === 'yes'));
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,289 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.controller('VAlarmController', function($scope) {
|
||||
'use strict';
|
||||
|
||||
$scope.newReminderId = -1;
|
||||
|
||||
$scope.alarmFactors = [
|
||||
60, //seconds
|
||||
60, //minutes
|
||||
24, //hours
|
||||
7 //days
|
||||
];
|
||||
|
||||
$scope.reminderSelect = [
|
||||
{ displayname: t('calendar', 'At time of event'), trigger: 0},
|
||||
{ displayname: t('calendar', '5 minutes before'), trigger: -1 * 5 * 60},
|
||||
{ displayname: t('calendar', '10 minutes before'), trigger: -1 * 10 * 60},
|
||||
{ displayname: t('calendar', '15 minutes before'), trigger: -1 * 15 * 60},
|
||||
{ displayname: t('calendar', '30 minutes before'), trigger: -1 * 30 * 60},
|
||||
{ displayname: t('calendar', '1 hour before'), trigger: -1 * 60 * 60},
|
||||
{ displayname: t('calendar', '2 hours before'), trigger: -1 * 2 * 60 * 60},
|
||||
{ displayname: t('calendar', 'Custom'), trigger: 'custom'}
|
||||
];
|
||||
|
||||
$scope.reminderSelectTriggers = $scope.reminderSelect.map(function(elem) {
|
||||
return elem.trigger;
|
||||
}).filter(function(elem) {
|
||||
return (typeof elem === 'number');
|
||||
});
|
||||
|
||||
$scope.reminderTypeSelect = [
|
||||
{ displayname: t('calendar', 'Audio'), type: 'AUDIO'},
|
||||
{ displayname: t('calendar', 'Email'), type: 'EMAIL'},
|
||||
{ displayname: t('calendar', 'Pop-up'), type: 'DISPLAY'}
|
||||
];
|
||||
|
||||
$scope.timeUnitReminderSelect = [
|
||||
{ displayname: t('calendar', 'sec'), factor: 1},
|
||||
{ displayname: t('calendar', 'min'), factor: 60},
|
||||
{ displayname: t('calendar', 'hours'), factor: 60 * 60},
|
||||
{ displayname: t('calendar', 'days'), factor: 60 * 60 * 24},
|
||||
{ displayname: t('calendar', 'week'), factor: 60 * 60 * 24 * 7}
|
||||
];
|
||||
|
||||
$scope.timePositionReminderSelect = [
|
||||
{ displayname: t('calendar', 'before'), factor: -1},
|
||||
{ displayname: t('calendar', 'after'), factor: 1}
|
||||
];
|
||||
|
||||
$scope.startEndReminderSelect = [
|
||||
{ displayname: t('calendar', 'start'), type: 'start'},
|
||||
{ displayname: t('calendar', 'end'), type: 'end'}
|
||||
];
|
||||
|
||||
$scope.$parent.registerPreHook(function() {
|
||||
angular.forEach($scope.properties.alarm, function(alarm) {
|
||||
$scope._addEditorProps(alarm);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$parent.registerPostHook(function() {
|
||||
angular.forEach($scope.properties.alarm, function(alarm) {
|
||||
if (alarm.editor.triggerType === 'absolute') {
|
||||
alarm.trigger.value = alarm.editor.absMoment;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope._addEditorProps = function(alarm) {
|
||||
angular.extend(alarm, {
|
||||
editor: {
|
||||
triggerValue: 0,
|
||||
triggerBeforeAfter: -1,
|
||||
triggerTimeUnit: 1,
|
||||
absMoment: moment(),
|
||||
editing: false
|
||||
}
|
||||
});
|
||||
|
||||
alarm.editor.reminderSelectValue =
|
||||
($scope.reminderSelectTriggers.indexOf(alarm.trigger.value) !== -1) ?
|
||||
alarm.editor.reminderSelectValue = alarm.trigger.value :
|
||||
alarm.editor.reminderSelectValue = 'custom';
|
||||
|
||||
alarm.editor.triggerType =
|
||||
(alarm.trigger.type === 'duration') ?
|
||||
'relative' :
|
||||
'absolute';
|
||||
|
||||
if (alarm.editor.triggerType === 'relative') {
|
||||
$scope._prepareRelativeVAlarm(alarm);
|
||||
} else {
|
||||
$scope._prepareAbsoluteVAlarm(alarm);
|
||||
}
|
||||
|
||||
$scope._prepareRepeat(alarm);
|
||||
};
|
||||
|
||||
$scope._prepareRelativeVAlarm = function(alarm) {
|
||||
var unitAndValue = $scope._getUnitAndValue(Math.abs(alarm.trigger.value));
|
||||
|
||||
angular.extend(alarm.editor, {
|
||||
triggerBeforeAfter: (alarm.trigger.value < 0) ? -1 : 1,
|
||||
triggerTimeUnit: unitAndValue[0],
|
||||
triggerValue: unitAndValue[1]
|
||||
});
|
||||
};
|
||||
|
||||
$scope._prepareAbsoluteVAlarm = function(alarm) {
|
||||
alarm.editor.absMoment = alarm.trigger.value;
|
||||
};
|
||||
|
||||
$scope._prepareRepeat = function(alarm) {
|
||||
var unitAndValue = $scope._getUnitAndValue((alarm.duration && alarm.duration.value) ? alarm.duration.value : 0);
|
||||
|
||||
angular.extend(alarm.editor, {
|
||||
repeat: !(!alarm.repeat.value || alarm.repeat.value === 0),
|
||||
repeatNTimes: (alarm.editor.repeat) ? alarm.repeat.value : 0,
|
||||
repeatTimeUnit: unitAndValue[0],
|
||||
repeatNValue: unitAndValue[1]
|
||||
});
|
||||
};
|
||||
|
||||
$scope._getUnitAndValue = function(value) {
|
||||
var unit = 1;
|
||||
|
||||
var alarmFactors = [
|
||||
60,
|
||||
60,
|
||||
24,
|
||||
7
|
||||
];
|
||||
|
||||
for (var i = 0; i < alarmFactors.length && value !== 0; i++) {
|
||||
var mod = value % alarmFactors[i];
|
||||
if (mod !== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
unit *= alarmFactors[i];
|
||||
value /= alarmFactors[i];
|
||||
}
|
||||
|
||||
return [unit, value];
|
||||
};
|
||||
|
||||
$scope.add = function() {
|
||||
var setTriggers = [];
|
||||
angular.forEach($scope.properties.alarm, function(alarm) {
|
||||
if (alarm.trigger && alarm.trigger.type === 'duration') {
|
||||
setTriggers.push(alarm.trigger.value);
|
||||
}
|
||||
});
|
||||
|
||||
var triggersToSuggest = [];
|
||||
angular.forEach($scope.reminderSelect, function(option) {
|
||||
if (typeof option.trigger !== 'number' || option.trigger > -1 * 15 * 60) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggersToSuggest.push(option.trigger);
|
||||
});
|
||||
|
||||
var triggerToSet = null;
|
||||
for (var i=0; i < triggersToSuggest.length; i++) {
|
||||
if (setTriggers.indexOf(triggersToSuggest[i]) === -1) {
|
||||
triggerToSet = triggersToSuggest[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (triggerToSet === null) {
|
||||
triggerToSet = triggersToSuggest[triggersToSuggest.length - 1];
|
||||
}
|
||||
|
||||
var alarm = {
|
||||
id: $scope.newReminderId--,
|
||||
action: {
|
||||
type: 'text',
|
||||
value: 'AUDIO'
|
||||
},
|
||||
trigger: {
|
||||
type: 'duration',
|
||||
value: triggerToSet,
|
||||
related: 'start'
|
||||
},
|
||||
repeat: {},
|
||||
duration: {}
|
||||
};
|
||||
|
||||
$scope._addEditorProps(alarm);
|
||||
$scope.properties.alarm.push(alarm);
|
||||
};
|
||||
|
||||
$scope.remove = function (alarm) {
|
||||
$scope.properties.alarm = $scope.properties.alarm.filter(function(elem) {
|
||||
return elem !== alarm;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.triggerEdit = function(alarm) {
|
||||
if (alarm.editor.editing === true) {
|
||||
alarm.editor.editing = false;
|
||||
} else {
|
||||
if ($scope.isEditingReminderSupported(alarm)) {
|
||||
alarm.editor.editing = true;
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('calendar', 'Editing reminders of unknown type not supported.'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isEditingReminderSupported = function(alarm) {
|
||||
//WE DON'T AIM TO SUPPORT PROCEDURE
|
||||
return (['AUDIO', 'DISPLAY', 'EMAIL'].indexOf(alarm.action.value) !== -1);
|
||||
};
|
||||
|
||||
$scope.updateReminderSelectValue = function(alarm) {
|
||||
var factor = alarm.editor.reminderSelectValue;
|
||||
if (factor !== 'custom') {
|
||||
alarm.duration = {};
|
||||
alarm.repeat = {};
|
||||
alarm.trigger.related = 'start';
|
||||
alarm.trigger.type = 'duration';
|
||||
alarm.trigger.value = parseInt(factor);
|
||||
|
||||
$scope._addEditorProps(alarm);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.updateReminderRelative = function(alarm) {
|
||||
alarm.trigger.value =
|
||||
parseInt(alarm.editor.triggerBeforeAfter) *
|
||||
parseInt(alarm.editor.triggerTimeUnit) *
|
||||
parseInt(alarm.editor.triggerValue);
|
||||
|
||||
alarm.trigger.type = 'duration';
|
||||
};
|
||||
|
||||
$scope.updateReminderAbsolute = function(alarm) {
|
||||
if (!moment.isMoment(alarm.trigger.value)) {
|
||||
alarm.trigger.value = moment();
|
||||
}
|
||||
|
||||
alarm.trigger.type = 'date-time';
|
||||
};
|
||||
|
||||
$scope.updateReminderRepeat = function(alarm) {
|
||||
alarm.repeat.type = 'string';
|
||||
alarm.repeat.value = alarm.editor.repeatNTimes;
|
||||
alarm.duration.type = 'duration';
|
||||
alarm.duration.value =
|
||||
parseInt(alarm.editor.repeatNValue) *
|
||||
parseInt(alarm.editor.repeatTimeUnit);
|
||||
};
|
||||
|
||||
$scope.hasAnyEmailReminders = function() {
|
||||
let hasEmail = false;
|
||||
|
||||
angular.forEach($scope.properties.alarm, function(alarm) {
|
||||
if (alarm.action.value === 'EMAIL') {
|
||||
hasEmail = true;
|
||||
}
|
||||
});
|
||||
|
||||
return hasEmail;
|
||||
};
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2017 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.directive('avatar', function() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {},
|
||||
link: function (scope, elm, attrs) {
|
||||
const size = attrs.size ? parseInt(attrs.size, 10) : 32;
|
||||
$(elm).avatar(attrs.user, size);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2016 John Molakvoæ <fremulon@protonmail.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/>.
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Directive: Colorpicker
|
||||
* Description: Colorpicker for the Calendar app.
|
||||
*/
|
||||
app.directive('colorpicker', function(ColorUtility) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
selected: '=',
|
||||
customizedColors: '=colors'
|
||||
},
|
||||
restrict: 'AE',
|
||||
templateUrl: OC.filePath('calendar', 'templates', 'colorpicker.html'),
|
||||
link: function(scope, element, attr) {
|
||||
scope.colors = scope.customizedColors || ColorUtility.colors;
|
||||
scope.selected = scope.selected || scope.colors[0];
|
||||
scope.random = "#000000";
|
||||
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.setAttribute('type', 'color');
|
||||
scope.supportsColorPicker = (inputElement.type === 'color');
|
||||
|
||||
scope.randomizeColour = function() {
|
||||
scope.random = ColorUtility.randomColor();
|
||||
scope.pick(scope.random);
|
||||
};
|
||||
|
||||
scope.pick = function(color) {
|
||||
scope.selected = color;
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
});
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* Nextcloud - calendar
|
||||
*
|
||||
* @author Raimund Schlüßler
|
||||
* @copyright 2016 Raimund Schlüßler <raimund.schluessler@googlemail.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.directive('confirmation', function() {
|
||||
'use strict';
|
||||
return {
|
||||
priority: -1,
|
||||
restrict: 'A',
|
||||
templateUrl: 'confirmation.html',
|
||||
scope: {
|
||||
confirmationFunction: "&confirmation",
|
||||
confirmationMessage: "&confirmationMessage",
|
||||
|
||||
},
|
||||
controller: 'ConfirmationController'
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('ConfirmationController', [
|
||||
'$scope', '$rootScope', '$element', '$attrs', '$compile', '$document', '$window', '$timeout', function($scope, $rootScope, $element, $attrs, $compile, $document, $window, $timeout) {
|
||||
'use strict';
|
||||
var ConfirmationController = (function() {
|
||||
function ConfirmationController(_$scope, $rootScope, $element, $attrs, $compile, $document, $window, $timeout) {
|
||||
this._$scope = _$scope;
|
||||
this._$scope.countdown = 3;
|
||||
|
||||
$element.bind( 'click', function( e ){
|
||||
_$scope.countdown = 3;
|
||||
$element.removeClass('active');
|
||||
var message = _$scope.confirmationMessage() ? _$scope.confirmationMessage() : "Are you sure?";
|
||||
if ($element.hasClass('confirmed')) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
_$scope.activate();
|
||||
$element.children('.confirmation-confirm')
|
||||
.tooltip({title: message, container: 'body', placement: 'right'});
|
||||
$element.addClass("confirmed");
|
||||
});
|
||||
|
||||
$element.children('.confirmation-confirm').bind( 'click', function (e) {
|
||||
if ($element.hasClass('confirmed active')) {
|
||||
_$scope.confirmationFunction();
|
||||
return;
|
||||
} else{
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
this._$scope.documentClick = function () {
|
||||
$element.removeClass("confirmed");
|
||||
};
|
||||
|
||||
this._$scope.activate = function () {
|
||||
if (_$scope.countdown) {
|
||||
$element.find('.countdown').html(_$scope.countdown+' s');
|
||||
$timeout(function() {
|
||||
_$scope.activate();
|
||||
}, 1000);
|
||||
_$scope.countdown--;
|
||||
} else {
|
||||
$element.addClass('active');
|
||||
}
|
||||
};
|
||||
|
||||
$document.bind('click', _$scope.documentClick);
|
||||
$document.bind('touchend', _$scope.documentClick);
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
$document.unbind('click', _$scope.documentClick);
|
||||
$document.unbind('touchend', _$scope.documentClick);
|
||||
});
|
||||
}
|
||||
return ConfirmationController;
|
||||
})();
|
||||
return new ConfirmationController($scope, $rootScope, $element, $attrs, $compile, $document, $window, $timeout);
|
||||
}
|
||||
]);
|
|
@ -1,120 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.directive('ocdatetimepicker', function($compile, $timeout) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: 'ngModel',
|
||||
scope: {
|
||||
disabletime: '=disabletime',
|
||||
date_tabindex: '=datetabindex',
|
||||
time_tabindex: '=timetabindex',
|
||||
readonly: '=readonly'
|
||||
},
|
||||
link: function (scope, element, attrs, ngModelCtrl) {
|
||||
var templateHTML = '<input type="text" ng-model="date" class="events--date" tabindex="{{ date_tabindex }}"/>';
|
||||
templateHTML += '<span class="events--time--wrapper" ng-click="disableAllDayIfNecessary()"><input type="text" ng-model="time" class="events--time" ng-disabled="disabletime" tabindex="{{ time_tabindex }}"/></span>';
|
||||
var template = angular.element(templateHTML);
|
||||
|
||||
scope.date = null;
|
||||
scope.time = null;
|
||||
scope.disableAllDayIfNecessary = () => {
|
||||
if (scope.disabletime && !scope.readonly) {
|
||||
$timeout(() => {
|
||||
scope.$apply(() => {
|
||||
scope.disabletime = false;
|
||||
});
|
||||
element.find('.events--time').timepicker('show');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$compile(template)(scope);
|
||||
element.append(template);
|
||||
|
||||
function updateFromUserInput() {
|
||||
var date = element.find('.events--date').datepicker('getDate'),
|
||||
hours = 0,
|
||||
minutes = 0;
|
||||
|
||||
if (!scope.disabletime) {
|
||||
hours = element.find('.events--time').timepicker('getHour');
|
||||
minutes = element.find('.events--time').timepicker('getMinute');
|
||||
}
|
||||
|
||||
var m = moment(date);
|
||||
m.hours(hours);
|
||||
m.minutes(minutes);
|
||||
m.seconds(0);
|
||||
|
||||
//Hiding the timepicker is an ugly hack to mitigate a bug in the timepicker
|
||||
//We might want to consider using another timepicker in the future
|
||||
element.find('.events--time').timepicker('hide');
|
||||
ngModelCtrl.$setViewValue(m);
|
||||
}
|
||||
|
||||
var localeData = moment.localeData();
|
||||
function initDatePicker() {
|
||||
element.find('.events--date').datepicker({
|
||||
dateFormat: localeData.longDateFormat('L').toLowerCase().replace('yy', 'y').replace('yyy', 'yy'),
|
||||
monthNames: moment.months(),
|
||||
monthNamesShort: moment.monthsShort(),
|
||||
dayNames: moment.weekdays(),
|
||||
dayNamesMin: moment.weekdaysMin(),
|
||||
dayNamesShort: moment.weekdaysShort(),
|
||||
firstDay: +localeData.firstDayOfWeek(),
|
||||
minDate: null,
|
||||
showOtherMonths: true,
|
||||
selectOtherMonths: true,
|
||||
onClose: updateFromUserInput
|
||||
});
|
||||
}
|
||||
function initTimepicker() {
|
||||
element.find('.events--time').timepicker({
|
||||
showPeriodLabels: (localeData.longDateFormat('LT').toLowerCase().indexOf('a') !== -1),
|
||||
showLeadingZero: true,
|
||||
showPeriod: (localeData.longDateFormat('LT').toLowerCase().indexOf('a') !== -1),
|
||||
duration: 0,
|
||||
onClose: updateFromUserInput
|
||||
});
|
||||
}
|
||||
|
||||
initDatePicker();
|
||||
initTimepicker();
|
||||
|
||||
scope.$watch(function() {
|
||||
return ngModelCtrl.$modelValue;
|
||||
}, function(value) {
|
||||
if (moment.isMoment(value)) {
|
||||
element.find('.events--date').datepicker('setDate', value.toDate());
|
||||
element.find('.events--time').timepicker('setTime', value.toDate());
|
||||
}
|
||||
});
|
||||
element.on('$destroy', function() {
|
||||
element.find('.events--date').datepicker('destroy');
|
||||
element.find('.events--time').timepicker('destroy');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,110 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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.constant('fc', {})
|
||||
.directive('fc', function(fc, $window) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {},
|
||||
link: function(scope, elm, attrs) {
|
||||
const localeData = moment.localeData();
|
||||
const englishFallback = moment.localeData('en');
|
||||
|
||||
const monthNames = [];
|
||||
const monthNamesShort = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const monthName = localeData.months(moment([0, i]), '');
|
||||
const shortMonthName = localeData.monthsShort(moment([0, i]), '');
|
||||
|
||||
if (monthName) {
|
||||
monthNames.push(monthName);
|
||||
} else {
|
||||
monthNames.push(englishFallback.months(moment([0, i]), ''));
|
||||
}
|
||||
|
||||
if (shortMonthName) {
|
||||
monthNamesShort.push(shortMonthName);
|
||||
} else {
|
||||
monthNamesShort.push(englishFallback.monthsShort(moment([0, i]), ''));
|
||||
}
|
||||
}
|
||||
|
||||
const dayNames = [];
|
||||
const dayNamesShort = [];
|
||||
const momentWeekHelper = moment().startOf('week');
|
||||
momentWeekHelper.subtract(momentWeekHelper.format('d'));
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dayName = localeData.weekdays(momentWeekHelper);
|
||||
const shortDayName = localeData.weekdaysShort(momentWeekHelper);
|
||||
|
||||
if (dayName) {
|
||||
dayNames.push(dayName);
|
||||
} else {
|
||||
dayNames.push(englishFallback.weekdays(momentWeekHelper));
|
||||
}
|
||||
|
||||
if (shortDayName) {
|
||||
dayNamesShort.push(shortDayName);
|
||||
} else {
|
||||
dayNamesShort.push(englishFallback.weekdaysShort(momentWeekHelper));
|
||||
}
|
||||
|
||||
momentWeekHelper.add(1, 'days');
|
||||
}
|
||||
|
||||
const firstDay = +moment().startOf('week').format('d');
|
||||
|
||||
const headerSize = angular.element('#header').height();
|
||||
const windowElement = angular.element($window);
|
||||
windowElement.bind('resize', _.debounce(function () {
|
||||
const newHeight = windowElement.height() - headerSize;
|
||||
fc.elm.fullCalendar('option', 'height', newHeight);
|
||||
}, 150));
|
||||
|
||||
const isPublic = (attrs.ispublic === '1');
|
||||
|
||||
const baseConfig = {
|
||||
dayNames: dayNames,
|
||||
dayNamesShort: dayNamesShort,
|
||||
defaultView: attrs.initialView,
|
||||
editable: !isPublic,
|
||||
firstDay: firstDay,
|
||||
forceEventDuration: true,
|
||||
header: false,
|
||||
height: windowElement.height() - headerSize,
|
||||
locale: moment.locale(),
|
||||
monthNames: monthNames,
|
||||
monthNamesShort: monthNamesShort,
|
||||
nowIndicator: true,
|
||||
weekNumbers: (attrs.weeknumbers === 'yes'),
|
||||
weekNumbersWithinDays: true,
|
||||
selectable: !isPublic,
|
||||
agendaEventMinHeight: 21
|
||||
};
|
||||
const controllerConfig = scope.$parent.fcConfig;
|
||||
const config = angular.extend({}, baseConfig, controllerConfig);
|
||||
|
||||
fc.elm = $(elm).fullCalendar(config);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Directive: Loading
|
||||
* Description: Can be used to incorperate loading behavior, anywhere.
|
||||
*/
|
||||
|
||||
app.directive('loading',
|
||||
[ function () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: "<div id='loading' class='icon-loading'></div>",
|
||||
link: function ($scope, element, attr) {
|
||||
$scope.$watch('loading', function (val) {
|
||||
if (val) {
|
||||
$(element).show();
|
||||
}
|
||||
else {
|
||||
$(element).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]
|
||||
);
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller: Modal
|
||||
* Description: The jQuery Model ported to angularJS as a directive.
|
||||
*/
|
||||
|
||||
app.directive('openDialog', function() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attr, ctrl) {
|
||||
var dialogId = '#' + attr.openDialog;
|
||||
elem.bind('click', function(e) {
|
||||
$(dialogId).dialog('open');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author Bernhard Posselt
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2016 Bernhard Posselt <dev@bernhard-posselt.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.directive('onToggleShow', function () {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
'onToggleShow': '@'
|
||||
},
|
||||
link: function (scope, elem) {
|
||||
elem.click(function () {
|
||||
var target = $(scope.onToggleShow);
|
||||
target.toggle();
|
||||
});
|
||||
|
||||
scope.$on('documentClicked', function (s, event) {
|
||||
var target = $(scope.onToggleShow);
|
||||
|
||||
if (event.target !== elem[0]) {
|
||||
target.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,361 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @author Vinicius Cubas Brand
|
||||
* @author Daniel Tygel
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2017 Vinicius Cubas Brand <vinicius@eita.org.br>
|
||||
* @copyright 2017 Daniel Tygel <dtygel@eita.org.br>
|
||||
*
|
||||
* 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('CalendarFactory', function(DavClient, Calendar, WebCal, constants) {
|
||||
'use strict';
|
||||
|
||||
const context = {};
|
||||
|
||||
const SHARE_USER_PREFIX = 'principal:principals/users/';
|
||||
const SHARE_GROUP_PREFIX = 'principal:principals/groups/';
|
||||
const SHARE_CIRCLE_PREFIX = 'principal:principals/circles/';
|
||||
|
||||
context.acl = function(props, userPrincipal) {
|
||||
const acl = props['{' + DavClient.NS_DAV + '}acl'] || [];
|
||||
let canWrite = false;
|
||||
|
||||
acl.forEach(function(rule) {
|
||||
let href = rule.getElementsByTagNameNS(DavClient.NS_DAV, 'href');
|
||||
if (href.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (href[0].textContent !== userPrincipal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const writeNode = rule.getElementsByTagNameNS(DavClient.NS_DAV, 'write');
|
||||
if (writeNode.length > 0) {
|
||||
canWrite = true;
|
||||
}
|
||||
});
|
||||
|
||||
return canWrite;
|
||||
};
|
||||
|
||||
context.color = function(props) {
|
||||
const colorProp = props['{' + DavClient.NS_APPLE + '}calendar-color'];
|
||||
const fallbackColor = constants.fallbackColor;
|
||||
|
||||
if (angular.isString(colorProp) && colorProp.length > 0) {
|
||||
// some stupid clients store an alpha value in the rgb hash (like #rrggbbaa) *cough cough* Apple Calendar *cough cough*
|
||||
// but some browsers can't parse that *cough cough* Safari 9 *cough cough*
|
||||
// Safari 10 seems to support this though
|
||||
if (colorProp.length === 9) {
|
||||
return colorProp.substr(0,7);
|
||||
}
|
||||
return colorProp;
|
||||
} else {
|
||||
return fallbackColor;
|
||||
}
|
||||
};
|
||||
|
||||
context.components = function(props) {
|
||||
const components = props['{' + DavClient.NS_IETF + '}supported-calendar-component-set'] || [];
|
||||
const simpleComponents = {
|
||||
vevent: false,
|
||||
vjournal: false,
|
||||
vtodo: false
|
||||
};
|
||||
|
||||
components.forEach(function(component) {
|
||||
const name = component.attributes.getNamedItem('name').textContent.toLowerCase();
|
||||
|
||||
if (simpleComponents.hasOwnProperty(name)) {
|
||||
simpleComponents[name] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return simpleComponents;
|
||||
};
|
||||
|
||||
context.displayname = function(props) {
|
||||
return props['{' + DavClient.NS_DAV + '}displayname'];
|
||||
};
|
||||
|
||||
context.enabled = function(props, owner, currentUser) {
|
||||
if (!angular.isDefined(props['{' + DavClient.NS_OWNCLOUD + '}calendar-enabled'])) {
|
||||
if (owner) {
|
||||
return owner === currentUser;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return (props['{' + DavClient.NS_OWNCLOUD + '}calendar-enabled'] === '1');
|
||||
}
|
||||
};
|
||||
|
||||
context.order = function(props) {
|
||||
const prop = props['{' + DavClient.NS_APPLE + '}calendar-order'];
|
||||
return prop ? parseInt(prop) : undefined;
|
||||
};
|
||||
|
||||
context.owner = function(props) {
|
||||
const ownerProperty = props['{' + DavClient.NS_DAV + '}owner'];
|
||||
if (Array.isArray(ownerProperty) && ownerProperty.length !== 0) {
|
||||
const owner = ownerProperty[0].textContent.slice(0, -1);
|
||||
const index = owner.indexOf('/remote.php/dav/principals/users/');
|
||||
if (index !== -1) {
|
||||
// '/remote.php/dav/principals/users/'.length === 33
|
||||
return owner.substr(index + 33);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
context.sharesAndOwnerDisplayname = function(props, owner) {
|
||||
const shareProp = props['{' + DavClient.NS_OWNCLOUD + '}invite'];
|
||||
const shares = {
|
||||
users: [],
|
||||
groups: [],
|
||||
circles: []
|
||||
};
|
||||
let ownerDisplayname = null;
|
||||
|
||||
const ownerDisplaynameProp = props['{' + DavClient.NS_NEXTCLOUD + '}owner-displayname'];
|
||||
if (ownerDisplaynameProp) {
|
||||
ownerDisplayname = ownerDisplaynameProp;
|
||||
}
|
||||
|
||||
if (!Array.isArray(shareProp)) {
|
||||
return [shares, ownerDisplayname];
|
||||
}
|
||||
|
||||
shareProp.forEach(function(share) {
|
||||
let href = share.getElementsByTagNameNS(DavClient.NS_DAV, 'href');
|
||||
if (href.length === 0) {
|
||||
return;
|
||||
}
|
||||
href = href[0].textContent;
|
||||
|
||||
let displayName = share.getElementsByTagNameNS(DavClient.NS_OWNCLOUD, 'common-name');
|
||||
if (displayName.length === 0) {
|
||||
if (href.startsWith(SHARE_USER_PREFIX)) {
|
||||
displayName = href.substr(SHARE_USER_PREFIX.length);
|
||||
} else if (href.startsWith(SHARE_GROUP_PREFIX)) {
|
||||
displayName = href.substr(SHARE_GROUP_PREFIX.length);
|
||||
} else {
|
||||
displayName = href.substr(SHARE_CIRCLE_PREFIX.length);
|
||||
}
|
||||
} else {
|
||||
displayName = displayName[0].textContent;
|
||||
}
|
||||
|
||||
let access = share.getElementsByTagNameNS(DavClient.NS_OWNCLOUD, 'access');
|
||||
if (access.length === 0) {
|
||||
return;
|
||||
}
|
||||
access = access[0];
|
||||
|
||||
let writable = access.getElementsByTagNameNS(DavClient.NS_OWNCLOUD, 'read-write');
|
||||
writable = writable.length !== 0;
|
||||
|
||||
if (href.startsWith(SHARE_USER_PREFIX)) {
|
||||
if (href.substr(SHARE_USER_PREFIX.length) === owner) {
|
||||
// don't overwrite already present displayname
|
||||
if (!ownerDisplayname) {
|
||||
ownerDisplayname = displayName;
|
||||
}
|
||||
} else {
|
||||
shares.users.push({
|
||||
id: href.substr(SHARE_USER_PREFIX.length),
|
||||
displayname: displayName,
|
||||
writable: writable
|
||||
});
|
||||
}
|
||||
} else if (href.startsWith(SHARE_GROUP_PREFIX)) {
|
||||
shares.groups.push({
|
||||
id: href.substr(SHARE_GROUP_PREFIX.length),
|
||||
displayname: displayName,
|
||||
writable: writable
|
||||
});
|
||||
} else if (href.startsWith(SHARE_CIRCLE_PREFIX)) {
|
||||
shares.circles.push({
|
||||
id: href.substr(SHARE_CIRCLE_PREFIX.length),
|
||||
displayname: displayName,
|
||||
writable: writable
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return [shares, ownerDisplayname];
|
||||
};
|
||||
|
||||
context.shareableAndPublishable = function(props, writable, publicMode) {
|
||||
let shareable = false;
|
||||
let publishable = false;
|
||||
|
||||
if (publicMode || !writable) {
|
||||
return [shareable, publishable];
|
||||
}
|
||||
|
||||
const sharingModesProp = props['{' + DavClient.NS_CALENDARSERVER + '}allowed-sharing-modes'];
|
||||
if (!Array.isArray(sharingModesProp) || sharingModesProp.length === 0) {
|
||||
// Fallback if allowed-sharing-modes is not provided
|
||||
return [writable, publishable];
|
||||
}
|
||||
|
||||
for (let shareMode of sharingModesProp) {
|
||||
shareable = shareable || shareMode.localName === 'can-be-shared';
|
||||
publishable = publishable || shareMode.localName === 'can-be-published';
|
||||
}
|
||||
|
||||
return [shareable, publishable];
|
||||
};
|
||||
|
||||
context.publishedAndPublicToken = function(props) {
|
||||
let published = false;
|
||||
let publicToken = null;
|
||||
|
||||
if (angular.isDefined(props['{' + DavClient.NS_CALENDARSERVER + '}publish-url'])) {
|
||||
published = true;
|
||||
let publishURL = props['{' + DavClient.NS_CALENDARSERVER + '}publish-url'][0].textContent;
|
||||
if (publishURL.substr(-1) === '/') {
|
||||
publishURL = publishURL.substr(0, publishURL.length - 1);
|
||||
}
|
||||
|
||||
const lastIndexOfSlash = publishURL.lastIndexOf('/');
|
||||
publicToken = publishURL.substr(lastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
return [published, publicToken];
|
||||
};
|
||||
|
||||
|
||||
context.webcal = function(props) {
|
||||
const sourceProp = props['{' + DavClient.NS_CALENDARSERVER + '}source'];
|
||||
|
||||
if (Array.isArray(sourceProp)) {
|
||||
const source = sourceProp.find(function(source) {
|
||||
return (DavClient.getNodesFullName(source) === '{' + DavClient.NS_DAV + '}href');
|
||||
});
|
||||
|
||||
return source ? source.textContent : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
context.calendarSkeleton = function(props, userPrincipal, publicMode) {
|
||||
const simple = {};
|
||||
const currentUser = context.getUserFromUserPrincipal(userPrincipal);
|
||||
|
||||
simple.color = context.color(props);
|
||||
simple.displayname = context.displayname(props);
|
||||
simple.components = context.components(props);
|
||||
simple.order = context.order(props);
|
||||
|
||||
simple.writable = context.acl(props, userPrincipal);
|
||||
simple.owner = context.owner(props);
|
||||
simple.enabled = context.enabled(props, simple.owner, currentUser);
|
||||
|
||||
const [shares, ownerDisplayname] = context.sharesAndOwnerDisplayname(props, simple.owner);
|
||||
simple.shares = shares;
|
||||
simple.ownerDisplayname = ownerDisplayname;
|
||||
|
||||
const [shareable, publishable] = context.shareableAndPublishable(props, simple.writable, publicMode);
|
||||
simple.shareable = shareable;
|
||||
simple.publishable = publishable;
|
||||
|
||||
if (simple.owner !== currentUser && !constants.shareeCanEditShares) {
|
||||
simple.shareable = false;
|
||||
simple.publishable = false;
|
||||
}
|
||||
|
||||
const [published, publicToken] = context.publishedAndPublicToken(props);
|
||||
simple.published = published;
|
||||
simple.publicToken = publicToken;
|
||||
|
||||
// always enabled calendars in public mode
|
||||
if (publicMode) {
|
||||
simple.enabled = true;
|
||||
simple.writable = false;
|
||||
simple.color = constants.fallbackColor;
|
||||
}
|
||||
|
||||
if (publicMode) {
|
||||
simple.writableProperties = false;
|
||||
} else if (simple.owner === currentUser) {
|
||||
simple.writableProperties = simple.writable;
|
||||
} else {
|
||||
simple.writableProperties = constants.shareeCanEditCalendarProperties || false;
|
||||
}
|
||||
|
||||
return simple;
|
||||
};
|
||||
|
||||
context.getUserFromUserPrincipal = function(userPrincipal) {
|
||||
if (userPrincipal.endsWith('/')) {
|
||||
userPrincipal = userPrincipal.slice(0, -1);
|
||||
}
|
||||
|
||||
const slashIndex = userPrincipal.lastIndexOf('/');
|
||||
return userPrincipal.substr(slashIndex + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* get a calendar object from raw xml data
|
||||
* @param {object} CalendarService
|
||||
* @param body
|
||||
* @param {string} userPrincipal
|
||||
* @param {boolean} publicMode
|
||||
* @returns {Calendar}
|
||||
*/
|
||||
this.calendar = function(CalendarService, body, userPrincipal, publicMode=false) {
|
||||
const href = body.href;
|
||||
const props = body.propStat[0].properties;
|
||||
|
||||
const simple = context.calendarSkeleton(props, userPrincipal, publicMode);
|
||||
return Calendar(CalendarService, href, simple);
|
||||
};
|
||||
|
||||
/**
|
||||
* get a webcal object from raw xml data
|
||||
* @param {object} CalendarService
|
||||
* @param body
|
||||
* @param {string} userPrincipal
|
||||
* @param {boolean} publicMode
|
||||
* @returns {WebCal}
|
||||
*/
|
||||
this.webcal = function(CalendarService, body, userPrincipal, publicMode=false) {
|
||||
const href = body.href;
|
||||
const props = body.propStat[0].properties;
|
||||
const currentUser = context.getUserFromUserPrincipal(userPrincipal);
|
||||
|
||||
const simple = context.calendarSkeleton(props, userPrincipal, publicMode);
|
||||
simple.href = context.webcal(props);
|
||||
|
||||
// WebCal is obviously not writable
|
||||
simple.writable = false;
|
||||
simple.writableProperties = (currentUser === simple.owner);
|
||||
|
||||
// WebCal subscriptions are neither publishable nor shareable
|
||||
simple.publishable = false;
|
||||
simple.shareable = false;
|
||||
|
||||
return WebCal(CalendarService, href, simple);
|
||||
};
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('ICalFactory', function(constants) {
|
||||
'use strict';
|
||||
|
||||
const self = this;
|
||||
|
||||
/**
|
||||
* create a new ICAL calendar object
|
||||
* @returns {ICAL.Component}
|
||||
*/
|
||||
this.new = function() {
|
||||
const root = new ICAL.Component(['vcalendar', [], []]);
|
||||
|
||||
root.updatePropertyWithValue('prodid', '-//Nextcloud calendar v' + constants.version);
|
||||
root.updatePropertyWithValue('version', '2.0');
|
||||
root.updatePropertyWithValue('calscale', 'GREGORIAN');
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
/**
|
||||
* create a new ICAL calendar object that contains a calendar
|
||||
* @param uid
|
||||
* @returns ICAL.Component
|
||||
*/
|
||||
this.newEvent = function(uid) {
|
||||
const comp = self.new();
|
||||
|
||||
const event = new ICAL.Component('vevent');
|
||||
comp.addSubcomponent(event);
|
||||
|
||||
event.updatePropertyWithValue('created', ICAL.Time.now());
|
||||
event.updatePropertyWithValue('dtstamp', ICAL.Time.now());
|
||||
event.updatePropertyWithValue('last-modified', ICAL.Time.now());
|
||||
event.updatePropertyWithValue('uid', uid);
|
||||
|
||||
//add a dummy dtstart, so it's a valid ics
|
||||
event.updatePropertyWithValue('dtstart', ICAL.Time.now());
|
||||
|
||||
return comp;
|
||||
};
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('calendarListFilter', function(CalendarListItem) {
|
||||
'use strict';
|
||||
|
||||
return function (calendarListItems) {
|
||||
if (!Array.isArray(calendarListItems)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return calendarListItems.filter(function(item) {
|
||||
if (!CalendarListItem.isCalendarListItem(item)) {
|
||||
return false;
|
||||
}
|
||||
return item.calendar.isWritable();
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('subscriptionListFilter', function(CalendarListItem) {
|
||||
'use strict';
|
||||
|
||||
return function (calendarListItems) {
|
||||
if (!Array.isArray(calendarListItems)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return calendarListItems.filter(function(item) {
|
||||
if (!CalendarListItem.isCalendarListItem(item)) {
|
||||
return false;
|
||||
}
|
||||
return !item.calendar.isWritable();
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('attendeeFilter', function() {
|
||||
'use strict';
|
||||
|
||||
return function(attendee) {
|
||||
if (typeof attendee !== 'object' || !attendee) {
|
||||
return '';
|
||||
} else if (typeof attendee.parameters === 'object' && typeof attendee.parameters.cn === 'string') {
|
||||
return attendee.parameters.cn;
|
||||
} else if (typeof attendee.value === 'string' && attendee.value.toLowerCase().startsWith('mailto:')) {
|
||||
return attendee.value.substr(7);
|
||||
} else {
|
||||
return attendee.value || '';
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('attendeeNotOrganizerFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (attendees, organizer) {
|
||||
if (typeof organizer !== 'string' || organizer === '') {
|
||||
return Array.isArray(attendees) ? attendees : [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(attendees)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var organizerValue = 'mailto:' + organizer.toLowerCase();
|
||||
return attendees.filter(function(element) {
|
||||
if (typeof element !== 'object') {
|
||||
return false;
|
||||
} else {
|
||||
return element.value.toLowerCase() !== organizerValue;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('calendarFilter', function() {
|
||||
'use strict';
|
||||
|
||||
return function (calendars) {
|
||||
if (!Array.isArray(calendars)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return calendars.filter(function(element) {
|
||||
if (typeof element !== 'object') {
|
||||
return false;
|
||||
} else {
|
||||
return element.isWritable();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('calendarSelectorFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (calendars, calendar) {
|
||||
if (!Array.isArray(calendars)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var options = calendars.filter(function (c) {
|
||||
return c.isWritable();
|
||||
});
|
||||
|
||||
if (typeof calendar !== 'object' || !calendar) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (!calendar.isWritable()) {
|
||||
return [calendar];
|
||||
} else {
|
||||
if (options.indexOf(calendar) === -1) {
|
||||
options.push(calendar);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('datepickerFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (datetime, view) {
|
||||
if (!(datetime instanceof Date) || typeof view !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch(view) {
|
||||
case 'agendaDay':
|
||||
return moment(datetime).format('ll');
|
||||
|
||||
case 'agendaWeek':
|
||||
return t('calendar', 'Week {number} of {year}', {
|
||||
number: moment(datetime).week(),
|
||||
year: moment(datetime).year()
|
||||
});
|
||||
|
||||
case 'month':
|
||||
return moment(datetime).format('MMMM YYYY');
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('importCalendarFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (calendars, file) {
|
||||
if (!Array.isArray(calendars) || typeof file !== 'object' || !file || typeof file.splittedICal !== 'object' || !file.splittedICal) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var events = file.splittedICal.vevents.length,
|
||||
journals = file.splittedICal.vjournals.length,
|
||||
todos = file.splittedICal.vtodos.length;
|
||||
|
||||
return calendars.filter(function(calendar) {
|
||||
if (events !== 0 && !calendar.components.vevent) {
|
||||
return false;
|
||||
}
|
||||
if (journals !== 0 && !calendar.components.vjournal) {
|
||||
return false;
|
||||
}
|
||||
if (todos !== 0 && !calendar.components.vtodo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2017 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('importErrorFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (file) {
|
||||
if (typeof file !== 'object' || !file || typeof file.errors !== 'number') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const all = file.progressToReach;
|
||||
const errors = (file.errors - file.duplicates);
|
||||
const duplicates = file.duplicates;
|
||||
const imported = (all - file.errors);
|
||||
|
||||
//TODO - use n instead of t to use proper plurals in all translations
|
||||
if (file.errors === 0) {
|
||||
return t('calendar', 'Successfully imported {imported} objects', {imported});
|
||||
} else if(file.errors === 1) {
|
||||
if (file.duplicates === 1) {
|
||||
return t('calendar', 'Imported {imported} out of {all}, skipped one duplicate', {all, imported});
|
||||
} else {
|
||||
return t('calendar', 'Imported {imported} out of {all}, one failure', {all, imported});
|
||||
}
|
||||
} else {
|
||||
if (file.duplicates === 0) {
|
||||
return t('calendar', 'Imported {imported} out of {all}, {errors} failures', {all, errors, imported});
|
||||
} else if (file.duplicates === file.errors) {
|
||||
return t('calendar', 'Imported {imported} out of {all}, skipped {duplicates} duplicates', {all, duplicates, imported});
|
||||
} else {
|
||||
return t('calendar', 'Imported {imported} out of {all}, {errors} failures, skipped {duplicates} duplicates', {all, duplicates, errors, imported});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('simpleReminderDescription', function() {
|
||||
'use strict';
|
||||
|
||||
return function(alarm) {
|
||||
if (typeof alarm !== 'object' || !alarm || typeof alarm.trigger !== 'object' || !alarm.trigger) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var relative = alarm.trigger.type === 'duration';
|
||||
var relatedToStart = alarm.trigger.related === 'start';
|
||||
if (relative) {
|
||||
var timeString = moment.duration(Math.abs(alarm.trigger.value), 'seconds').humanize();
|
||||
if (alarm.trigger.value < 0) {
|
||||
if (relatedToStart) {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm {time} before the event starts', {time: timeString});
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up {time} before the event starts', {time: timeString});
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email {time} before the event starts', {time: timeString});
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None {time} before the event starts', {time: timeString});
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} {time} before the event starts', {type: alarm.action.value, time: timeString});
|
||||
}
|
||||
} else {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm {time} before the event ends', {time: timeString});
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up {time} before the event ends', {time: timeString});
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email {time} before the event ends', {time: timeString});
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None {time} before the event ends', {time: timeString});
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} {time} before the event ends', {type: alarm.action.value, time: timeString});
|
||||
}
|
||||
}
|
||||
} else if (alarm.trigger.value > 0) {
|
||||
if (relatedToStart) {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm {time} after the event starts', {time: timeString});
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up {time} after the event starts', {time: timeString});
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email {time} after the event starts', {time: timeString});
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None {time} after the event starts', {time: timeString});
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} {time} after the event starts', {type: alarm.action.value, time: timeString});
|
||||
}
|
||||
} else {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm {time} after the event ends', {time: timeString});
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up {time} after the event ends', {time: timeString});
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email {time} after the event ends', {time: timeString});
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None {time} after the event ends', {time: timeString});
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} {time} after the event ends', {type: alarm.action.value, time: timeString});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (relatedToStart) {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm at the event\'s start');
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up at the event\'s start');
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email at the event\'s start');
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None at the event\'s start');
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} at the event\'s start', {type: alarm.action.value});
|
||||
}
|
||||
} else {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm at the event\'s end');
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up at the event\'s end');
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email at the event\'s end');
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None at the event\'s end');
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} at the event\'s end', {type: alarm.action.value});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (alarm.editor && moment.isMoment(alarm.editor.absMoment)) {
|
||||
switch(alarm.action.value) {
|
||||
case 'AUDIO':
|
||||
return t('calendar', 'Audio alarm at {time}', {time: alarm.editor.absMoment.format('LLLL')});
|
||||
|
||||
case 'DISPLAY':
|
||||
return t('calendar', 'Pop-up at {time}', {time: alarm.editor.absMoment.format('LLLL')});
|
||||
|
||||
case 'EMAIL':
|
||||
return t('calendar', 'Email at {time}', {time: alarm.editor.absMoment.format('LLLL')});
|
||||
|
||||
case 'NONE':
|
||||
return t('calendar', 'None at {time}', {time: alarm.editor.absMoment.format('LLLL')});
|
||||
|
||||
default:
|
||||
return t('calendar', '{type} at {time}', {
|
||||
type: alarm.action.value,
|
||||
time: alarm.editor.absMoment.format('LLLL')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('subscriptionFilter', function () {
|
||||
'use strict';
|
||||
|
||||
return function (calendars) {
|
||||
if (!Array.isArray(calendars)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return calendars.filter(function(element) {
|
||||
if (typeof element !== 'object') {
|
||||
return false;
|
||||
} else {
|
||||
return !element.isWritable();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('timezoneFilter', ['$filter', function($filter) {
|
||||
'use strict';
|
||||
|
||||
return function(timezone) {
|
||||
if (typeof timezone !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
timezone = timezone.split('_').join(' ');
|
||||
|
||||
var elements = timezone.split('/');
|
||||
if (elements.length === 1) {
|
||||
return elements[0];
|
||||
} else {
|
||||
var continent = elements[0];
|
||||
var city = $filter('timezoneWithoutContinentFilter')(elements.slice(1).join('/'));
|
||||
|
||||
return city + ' (' + continent + ')';
|
||||
}
|
||||
};
|
||||
}]);
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('timezoneWithoutContinentFilter', function() {
|
||||
'use strict';
|
||||
|
||||
return function(timezone) {
|
||||
timezone = timezone.split('_').join(' ');
|
||||
timezone = timezone.replace('St ', 'St. ');
|
||||
|
||||
return timezone.split('/').join(' - ');
|
||||
};
|
||||
});
|
|
@ -1,180 +0,0 @@
|
|||
app.factory('ImportFileWrapper', function(Hook, ICalSplitterUtility) {
|
||||
'use strict';
|
||||
|
||||
function ImportFileWrapper(file) {
|
||||
const context = {
|
||||
file: file,
|
||||
splittedICal: null,
|
||||
selectedCalendar: null,
|
||||
state: 0,
|
||||
errors: 0,
|
||||
progress: 0,
|
||||
progressToReach: -1
|
||||
};
|
||||
const iface = {
|
||||
duplicates: 0,
|
||||
_isAImportFileWrapperObject: true
|
||||
};
|
||||
|
||||
context.checkIsDone = function() {
|
||||
if (context.progress === context.progressToReach) {
|
||||
context.state = ImportFileWrapper.stateDone;
|
||||
iface.emit(ImportFileWrapper.hookDone);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
file: {
|
||||
get: function() {
|
||||
return context.file;
|
||||
}
|
||||
},
|
||||
splittedICal: {
|
||||
get: function() {
|
||||
return context.splittedICal;
|
||||
}
|
||||
},
|
||||
selectedCalendar: {
|
||||
get: function() {
|
||||
return context.selectedCalendar;
|
||||
},
|
||||
set: function(selectedCalendar) {
|
||||
context.selectedCalendar = selectedCalendar;
|
||||
}
|
||||
},
|
||||
state: {
|
||||
get: function() {
|
||||
return context.state;
|
||||
},
|
||||
set: function(state) {
|
||||
if (typeof state === 'number') {
|
||||
context.state = state;
|
||||
}
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
get: function() {
|
||||
return context.errors;
|
||||
},
|
||||
set: function(errors) {
|
||||
if (typeof errors === 'number') {
|
||||
var oldErrors = context.errors;
|
||||
context.errors = errors;
|
||||
iface.emit(ImportFileWrapper.hookErrorsChanged, errors, oldErrors);
|
||||
}
|
||||
}
|
||||
},
|
||||
progress: {
|
||||
get: function() {
|
||||
return context.progress;
|
||||
},
|
||||
set: function(progress) {
|
||||
if (typeof progress === 'number') {
|
||||
var oldProgress = context.progress;
|
||||
context.progress = progress;
|
||||
iface.emit(ImportFileWrapper.hookProgressChanged, progress, oldProgress);
|
||||
|
||||
context.checkIsDone();
|
||||
}
|
||||
}
|
||||
},
|
||||
progressToReach: {
|
||||
get: function() {
|
||||
return context.progressToReach;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iface.wasCanceled = function() {
|
||||
return context.state === ImportFileWrapper.stateCanceled;
|
||||
};
|
||||
|
||||
iface.isAnalyzing = function() {
|
||||
return context.state === ImportFileWrapper.stateAnalyzing;
|
||||
};
|
||||
|
||||
iface.isAnalyzed = function() {
|
||||
return context.state === ImportFileWrapper.stateAnalyzed;
|
||||
};
|
||||
|
||||
iface.isScheduled = function() {
|
||||
return context.state === ImportFileWrapper.stateScheduled;
|
||||
};
|
||||
|
||||
iface.isImporting = function() {
|
||||
return context.state === ImportFileWrapper.stateImporting;
|
||||
};
|
||||
|
||||
iface.isDone = function() {
|
||||
return context.state === ImportFileWrapper.stateDone;
|
||||
};
|
||||
|
||||
iface.hasErrors = function() {
|
||||
return context.errors > 0;
|
||||
};
|
||||
|
||||
iface.isEmpty = () => iface.state === ImportFileWrapper.stateEmpty;
|
||||
iface.hasParsingErrors = () => iface.state === ImportFileWrapper.stateParsingError;
|
||||
|
||||
iface.read = function(afterReadCallback) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function(event) {
|
||||
try {
|
||||
context.splittedICal = ICalSplitterUtility.split(event.target.result);
|
||||
} catch(e) {
|
||||
context.splittedICal = {
|
||||
vevents: [],
|
||||
vjournals: [],
|
||||
vtodos: []
|
||||
};
|
||||
context.progressToReach = 0;
|
||||
|
||||
iface.state = ImportFileWrapper.stateParsingError;
|
||||
iface.emit(ImportFileWrapper.hookDone);
|
||||
return;
|
||||
}
|
||||
|
||||
context.progressToReach = context.splittedICal.vevents.length +
|
||||
context.splittedICal.vjournals.length +
|
||||
context.splittedICal.vtodos.length;
|
||||
|
||||
if (context.progressToReach === 0) {
|
||||
iface.state = ImportFileWrapper.stateEmpty;
|
||||
iface.emit(ImportFileWrapper.hookDone);
|
||||
} else {
|
||||
iface.state = ImportFileWrapper.stateAnalyzed;
|
||||
afterReadCallback();
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
Object.assign(
|
||||
iface,
|
||||
Hook(context)
|
||||
);
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
ImportFileWrapper.isImportWrapper = function(obj) {
|
||||
return obj instanceof ImportFileWrapper || (typeof obj === 'object' && obj !== null && obj._isAImportFileWrapperObject !== null);
|
||||
};
|
||||
|
||||
ImportFileWrapper.stateParsingError = -3;
|
||||
ImportFileWrapper.stateEmpty = -2;
|
||||
ImportFileWrapper.stateCanceled = -1;
|
||||
ImportFileWrapper.stateAnalyzing = 0;
|
||||
ImportFileWrapper.stateAnalyzed = 1;
|
||||
ImportFileWrapper.stateScheduled = 2;
|
||||
ImportFileWrapper.stateImporting = 3;
|
||||
ImportFileWrapper.stateDone = 4;
|
||||
|
||||
ImportFileWrapper.hookProgressChanged = 1;
|
||||
ImportFileWrapper.hookDone = 2;
|
||||
ImportFileWrapper.hookErrorsChanged = 3;
|
||||
|
||||
return ImportFileWrapper;
|
||||
});
|
|
@ -1,203 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('CalendarListItem', function($rootScope, $window, Calendar, WebCal, isSharingAPI) {
|
||||
'use strict';
|
||||
|
||||
function CalendarListItem(calendar) {
|
||||
const context = {
|
||||
calendar: calendar,
|
||||
isEditingShares: false,
|
||||
isEditingProperties: false,
|
||||
isDisplayingCalDAVUrl: false,
|
||||
isDisplayingWebCalUrl: false,
|
||||
isSendingMail: false
|
||||
};
|
||||
const iface = {
|
||||
_isACalendarListItemObject: true
|
||||
};
|
||||
|
||||
if (!Calendar.isCalendar(calendar)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
calendar: {
|
||||
get: function() {
|
||||
return context.calendar;
|
||||
}
|
||||
},
|
||||
publicSharingURL: {
|
||||
get: () => {
|
||||
let displayname = context.calendar.displayname
|
||||
.replace(/\s+/g, '-').replace(/[^\w\-]+/g, '')
|
||||
.replace(/\-\-+/g, '-').replace(/^-+/, '')
|
||||
.replace(/-+$/, '');
|
||||
|
||||
if (displayname === '') {
|
||||
return $rootScope.root + 'p/' + context.calendar.publicToken;
|
||||
} else {
|
||||
return $rootScope.root + 'p/' + context.calendar.publicToken + '/' + displayname;
|
||||
}
|
||||
}
|
||||
},
|
||||
publicEmbedURL: {
|
||||
get: () => {
|
||||
return $rootScope.root + 'embed/' + context.calendar.publicToken;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iface.displayCalDAVUrl = function() {
|
||||
return context.isDisplayingCalDAVUrl;
|
||||
};
|
||||
|
||||
iface.showCalDAVUrl = function() {
|
||||
context.isDisplayingCalDAVUrl = true;
|
||||
};
|
||||
|
||||
iface.displayWebCalUrl = function() {
|
||||
return context.isDisplayingWebCalUrl;
|
||||
};
|
||||
|
||||
iface.hideCalDAVUrl = function() {
|
||||
context.isDisplayingCalDAVUrl = false;
|
||||
};
|
||||
|
||||
iface.showWebCalUrl = function() {
|
||||
context.isDisplayingWebCalUrl = true;
|
||||
};
|
||||
|
||||
iface.hideWebCalUrl = function() {
|
||||
context.isDisplayingWebCalUrl = false;
|
||||
};
|
||||
|
||||
iface.showSharingIcon = function() {
|
||||
const isCalendarShareable = context.calendar.isShareable();
|
||||
const isCalendarShared = context.calendar.isShared();
|
||||
const isCalendarPublishable = context.calendar.isPublishable();
|
||||
|
||||
// Publishing does not depend on sharing API
|
||||
// always show sharing icon in this case
|
||||
if (isCalendarPublishable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the sharing API was disabled, but the calendar was
|
||||
// previously shared, allow users to edit or remove
|
||||
// existing shares
|
||||
if (!isSharingAPI && isCalendarShared && isCalendarShareable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (isSharingAPI && isCalendarShareable);
|
||||
};
|
||||
|
||||
iface.isEditingShares = function() {
|
||||
return context.isEditingShares;
|
||||
};
|
||||
|
||||
iface.isSendingMail = function() {
|
||||
return context.isSendingMail;
|
||||
};
|
||||
|
||||
iface.toggleEditingShares = function() {
|
||||
context.isEditingShares = !context.isEditingShares;
|
||||
};
|
||||
|
||||
iface.toggleSendingMail = function() {
|
||||
context.isSendingMail = !context.isSendingMail;
|
||||
};
|
||||
|
||||
iface.isEditing = function() {
|
||||
return context.isEditingProperties;
|
||||
};
|
||||
|
||||
iface.displayActions = function() {
|
||||
return !iface.isEditing();
|
||||
};
|
||||
|
||||
iface.displayColorIndicator = function() {
|
||||
return (!iface.isEditing() && !context.calendar.isRendering());
|
||||
};
|
||||
|
||||
iface.displaySpinner = function() {
|
||||
return (!iface.isEditing() && context.calendar.isRendering());
|
||||
};
|
||||
|
||||
iface.openEditor = function() {
|
||||
iface.color = context.calendar.color;
|
||||
iface.displayname = context.calendar.displayname;
|
||||
|
||||
context.isEditingProperties = true;
|
||||
};
|
||||
|
||||
iface.cancelEditor = function() {
|
||||
iface.color = '';
|
||||
iface.displayname = '';
|
||||
|
||||
context.isEditingProperties = false;
|
||||
};
|
||||
|
||||
iface.saveEditor = function() {
|
||||
context.calendar.color = iface.color;
|
||||
context.calendar.displayname = iface.displayname;
|
||||
|
||||
iface.color = '';
|
||||
iface.displayname = '';
|
||||
|
||||
context.isEditingProperties = false;
|
||||
};
|
||||
|
||||
iface.isWebCal = function() {
|
||||
return WebCal.isWebCal(context.calendar);
|
||||
};
|
||||
|
||||
iface.getOwnerName = function() {
|
||||
return context.calendar.ownerDisplayname || context.calendar.owner;
|
||||
};
|
||||
|
||||
iface.getPublicDisplayname = function() {
|
||||
const searchFor = '(' + context.calendar.owner + ')';
|
||||
const lastIndexOf = context.calendar.displayname.lastIndexOf(searchFor);
|
||||
|
||||
return context.calendar.displayname.substr(0, lastIndexOf - 1);
|
||||
};
|
||||
|
||||
//Properties for ng-model of calendar editor
|
||||
iface.color = '';
|
||||
iface.displayname = '';
|
||||
|
||||
iface.order = 0;
|
||||
|
||||
iface.selectedSharee = '';
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
CalendarListItem.isCalendarListItem = function(obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isACalendarListItemObject === true);
|
||||
};
|
||||
|
||||
return CalendarListItem;
|
||||
});
|
|
@ -1,384 +0,0 @@
|
|||
/**
|
||||
* Nextcloud - Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @author Vinicius Cubas Brand
|
||||
* @author Daniel Tygel
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2017 Vinicius Cubas Brand <vinicius@eita.org.br>
|
||||
* @copyright 2017 Daniel Tygel <dtygel@eita.org.br>
|
||||
*
|
||||
* 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.factory('Calendar', function($window, Hook, VEventService, TimezoneService, ColorUtility, StringUtility) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* instantiate a calendar object
|
||||
* @param {object} CalendarService
|
||||
* @param {string} url
|
||||
* @param {object} props
|
||||
* @returns {object}
|
||||
* @constructor
|
||||
*/
|
||||
function Calendar(CalendarService, url, props) {
|
||||
url = url || '';
|
||||
props = props || {};
|
||||
|
||||
const context = {
|
||||
calendarService: CalendarService,
|
||||
fcEventSource: {},
|
||||
components: props.components,
|
||||
mutableProperties: {
|
||||
color: props.color,
|
||||
displayname: props.displayname,
|
||||
enabled: props.enabled,
|
||||
order: props.order,
|
||||
published: props.published
|
||||
},
|
||||
updatedProperties: [],
|
||||
tmpId: StringUtility.uid(),
|
||||
url: url,
|
||||
owner: props.owner,
|
||||
ownerDisplayname: props.ownerDisplayname,
|
||||
shares: props.shares,
|
||||
publicToken: props.publicToken,
|
||||
publishable: props.publishable,
|
||||
warnings: [],
|
||||
shareable: props.shareable,
|
||||
writable: props.writable,
|
||||
writableProperties: props.writableProperties
|
||||
};
|
||||
const iface = {
|
||||
_isACalendarObject: true
|
||||
};
|
||||
|
||||
context.fcEventSource.events = function (start, end, timezone, callback) {
|
||||
const fcAPI = this;
|
||||
context.fcEventSource.isRendering = true;
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
|
||||
start = moment(start.stripZone().format());
|
||||
end = moment(end.stripZone().format());
|
||||
|
||||
const TimezoneServicePromise = TimezoneService.get(timezone);
|
||||
const VEventServicePromise = VEventService.getAll(iface, start, end);
|
||||
Promise.all([TimezoneServicePromise, VEventServicePromise]).then(function(results) {
|
||||
const [tz, events] = results;
|
||||
const promises = [];
|
||||
let vevents = [];
|
||||
|
||||
events.forEach((event) => {
|
||||
const promise = event.getFcEvent(start, end, tz).then((vevent) => {
|
||||
vevents = vevents.concat(vevent);
|
||||
}).catch((reason) => {
|
||||
iface.addWarning(reason);
|
||||
console.log(event, reason);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
callback(vevents);
|
||||
fcAPI.eventManager.currentPeriod.release();
|
||||
context.fcEventSource.isRendering = false;
|
||||
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
});
|
||||
}).catch(function(reason) {
|
||||
if (reason === 'Unknown timezone' && timezone !== 'UTC') {
|
||||
const eventsFn = iface.fcEventSource.events.bind(fcAPI);
|
||||
eventsFn(start, end, 'UTC', callback);
|
||||
}
|
||||
|
||||
callback([]);
|
||||
fcAPI.eventManager.currentPeriod.release();
|
||||
|
||||
iface.addWarning(reason);
|
||||
context.fcEventSource.isRendering = false;
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
|
||||
console.log(context.url, reason);
|
||||
});
|
||||
};
|
||||
context.fcEventSource.editable = context.writable;
|
||||
context.fcEventSource.calendar = iface;
|
||||
context.fcEventSource.isRendering = false;
|
||||
|
||||
context.setUpdated = function(property) {
|
||||
if (context.updatedProperties.indexOf(property) === -1) {
|
||||
context.updatedProperties.push(property);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
color: {
|
||||
get: function() {
|
||||
return context.mutableProperties.color;
|
||||
},
|
||||
set: function(color) {
|
||||
var oldColor = context.mutableProperties.color;
|
||||
if (color === oldColor) {
|
||||
return;
|
||||
}
|
||||
context.mutableProperties.color = color;
|
||||
context.setUpdated('color');
|
||||
iface.emit(Calendar.hookColorChanged, color, oldColor);
|
||||
}
|
||||
},
|
||||
textColor: {
|
||||
get: function() {
|
||||
const colors = ColorUtility.extractRGBFromHexString(context.mutableProperties.color);
|
||||
return ColorUtility.generateTextColorFromRGB(colors.r, colors.g, colors.b);
|
||||
}
|
||||
},
|
||||
displayname: {
|
||||
get: function() {
|
||||
return context.mutableProperties.displayname;
|
||||
},
|
||||
set: function(displayname) {
|
||||
var oldDisplayname = context.mutableProperties.displayname;
|
||||
if (displayname === oldDisplayname) {
|
||||
return;
|
||||
}
|
||||
context.mutableProperties.displayname = displayname;
|
||||
context.setUpdated('displayname');
|
||||
iface.emit(Calendar.hookDisplaynameChanged, displayname, oldDisplayname);
|
||||
}
|
||||
},
|
||||
enabled: {
|
||||
get: function() {
|
||||
return context.mutableProperties.enabled;
|
||||
},
|
||||
set: function(enabled) {
|
||||
var oldEnabled = context.mutableProperties.enabled;
|
||||
if (enabled === oldEnabled) {
|
||||
return;
|
||||
}
|
||||
context.mutableProperties.enabled = enabled;
|
||||
context.setUpdated('enabled');
|
||||
iface.emit(Calendar.hookEnabledChanged, enabled, oldEnabled);
|
||||
}
|
||||
},
|
||||
order: {
|
||||
get: function() {
|
||||
return context.mutableProperties.order;
|
||||
},
|
||||
set: function(order) {
|
||||
var oldOrder = context.mutableProperties.order;
|
||||
if (order === oldOrder) {
|
||||
return;
|
||||
}
|
||||
context.mutableProperties.order = order;
|
||||
context.setUpdated('order');
|
||||
iface.emit(Calendar.hookOrderChanged, order, oldOrder);
|
||||
}
|
||||
|
||||
},
|
||||
components: {
|
||||
get: function() {
|
||||
return context.components;
|
||||
}
|
||||
},
|
||||
url: {
|
||||
get: function() {
|
||||
return context.url;
|
||||
}
|
||||
},
|
||||
downloadUrl: {
|
||||
get: function() {
|
||||
let url = context.url;
|
||||
// cut off last slash to have a fancy name for the ics
|
||||
if (url.slice(url.length - 1) === '/') {
|
||||
url = url.slice(0, url.length - 1);
|
||||
}
|
||||
url += '?export';
|
||||
|
||||
return url;
|
||||
},
|
||||
configurable: true
|
||||
},
|
||||
caldav: {
|
||||
get: function() {
|
||||
return $window.location.origin + context.url;
|
||||
}
|
||||
},
|
||||
publicToken: {
|
||||
get: function() {
|
||||
return context.publicToken;
|
||||
},
|
||||
set: function(publicToken) {
|
||||
context.publicToken = publicToken;
|
||||
}
|
||||
},
|
||||
published: {
|
||||
get: function() {
|
||||
return context.mutableProperties.published;
|
||||
},
|
||||
set: function(published) {
|
||||
context.mutableProperties.published = published;
|
||||
}
|
||||
},
|
||||
publishable: {
|
||||
get: function() {
|
||||
return context.publishable;
|
||||
}
|
||||
},
|
||||
fcEventSource: {
|
||||
get: function() {
|
||||
return context.fcEventSource;
|
||||
}
|
||||
},
|
||||
shares: {
|
||||
get: function() {
|
||||
return context.shares;
|
||||
}
|
||||
},
|
||||
tmpId: {
|
||||
get: function() {
|
||||
return context.tmpId;
|
||||
}
|
||||
},
|
||||
warnings: {
|
||||
get: function() {
|
||||
return context.warnings;
|
||||
}
|
||||
},
|
||||
owner: {
|
||||
get: function() {
|
||||
return context.owner;
|
||||
}
|
||||
},
|
||||
ownerDisplayname: {
|
||||
get: function() {
|
||||
return context.ownerDisplayname;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iface.hasUpdated = function() {
|
||||
return context.updatedProperties.length !== 0;
|
||||
};
|
||||
|
||||
iface.getUpdated = function() {
|
||||
return context.updatedProperties;
|
||||
};
|
||||
|
||||
iface.resetUpdated = function() {
|
||||
context.updatedProperties = [];
|
||||
};
|
||||
|
||||
iface.addWarning = function(msg) {
|
||||
context.warnings.push(msg);
|
||||
};
|
||||
|
||||
iface.hasWarnings = function() {
|
||||
return context.warnings.length > 0;
|
||||
};
|
||||
|
||||
iface.resetWarnings = function() {
|
||||
context.warnings = [];
|
||||
};
|
||||
|
||||
iface.toggleEnabled = function() {
|
||||
context.mutableProperties.enabled = !context.mutableProperties.enabled;
|
||||
context.setUpdated('enabled');
|
||||
iface.emit(Calendar.hookEnabledChanged, context.mutableProperties.enabled, !context.mutableProperties.enabled);
|
||||
};
|
||||
|
||||
iface.isShared = function() {
|
||||
return context.shares.circles.length !== 0 ||
|
||||
context.shares.groups.length !== 0 ||
|
||||
context.shares.users.length !== 0;
|
||||
};
|
||||
|
||||
iface.isPublished = function() {
|
||||
return context.mutableProperties.published;
|
||||
};
|
||||
|
||||
iface.isPublishable = function() {
|
||||
return context.publishable;
|
||||
};
|
||||
|
||||
iface.isShareable = function() {
|
||||
return context.shareable;
|
||||
};
|
||||
|
||||
iface.isRendering = function() {
|
||||
return context.fcEventSource.isRendering;
|
||||
};
|
||||
|
||||
iface.isWritable = function() {
|
||||
return context.writable;
|
||||
};
|
||||
|
||||
iface.arePropertiesWritable = function() {
|
||||
return context.writableProperties;
|
||||
};
|
||||
|
||||
iface.eventsAccessibleViaCalDAV = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
iface.refresh = function() {
|
||||
// TODO in a follow up PR
|
||||
};
|
||||
|
||||
iface.update = function() {
|
||||
return context.calendarService.update(iface);
|
||||
};
|
||||
|
||||
iface.delete = function() {
|
||||
return context.calendarService.delete(iface);
|
||||
};
|
||||
|
||||
iface.share = function(shareType, shareWith, shareWithDisplayname, writable, existingShare) {
|
||||
return context.calendarService.share(iface, shareType, shareWith, shareWithDisplayname, writable, existingShare);
|
||||
};
|
||||
|
||||
iface.unshare = function(shareType, shareWith, writable, existingShare) {
|
||||
return context.calendarService.unshare(iface, shareType, shareWith, writable, existingShare);
|
||||
};
|
||||
|
||||
iface.publish = function() {
|
||||
return context.calendarService.publish(iface);
|
||||
};
|
||||
|
||||
iface.unpublish = function() {
|
||||
return context.calendarService.unpublish(iface);
|
||||
};
|
||||
|
||||
Object.assign(
|
||||
iface,
|
||||
Hook(context)
|
||||
);
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
Calendar.isCalendar = function(obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isACalendarObject === true);
|
||||
};
|
||||
|
||||
Calendar.hookFinishedRendering = 1;
|
||||
Calendar.hookColorChanged = 2;
|
||||
Calendar.hookDisplaynameChanged = 3;
|
||||
Calendar.hookEnabledChanged = 4;
|
||||
Calendar.hookOrderChanged = 5;
|
||||
|
||||
return Calendar;
|
||||
});
|
|
@ -1,216 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('FcEvent', function(SimpleEvent) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {VEvent} vevent
|
||||
* @param {ICAL.Component} event
|
||||
* @param {ICAL.Time} start
|
||||
* @param {ICAL.Time} end
|
||||
*/
|
||||
function FcEvent(vevent, event, start, end) {
|
||||
const context = {vevent, event};
|
||||
context.iCalEvent = new ICAL.Event(event);
|
||||
|
||||
let id = context.vevent.uri;
|
||||
if (event.hasProperty('recurrence-id')) {
|
||||
id += context.event.getFirstPropertyValue('recurrence-id').toICALString();
|
||||
}
|
||||
|
||||
const allDay = (start.icaltype === 'date' && end.icaltype === 'date');
|
||||
context.allDay = allDay;
|
||||
|
||||
const iface = {
|
||||
_isAFcEventObject: true,
|
||||
id: id,
|
||||
allDay: allDay,
|
||||
start: moment(start.toString()),
|
||||
end: moment(end.toString()),
|
||||
repeating: context.iCalEvent.isRecurring(),
|
||||
className: ['fcCalendar-id-' + vevent.calendar.tmpId],
|
||||
editable: vevent.calendar.isWritable(),
|
||||
backgroundColor: vevent.calendar.color,
|
||||
borderColor: vevent.calendar.color,
|
||||
textColor: vevent.calendar.textColor,
|
||||
title: event.getFirstPropertyValue('summary')
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
vevent: {
|
||||
get: function() {
|
||||
return context.vevent;
|
||||
},
|
||||
enumerable: true
|
||||
},
|
||||
event: {
|
||||
get: function() {
|
||||
return context.event;
|
||||
},
|
||||
enumerable: true
|
||||
},
|
||||
calendar: {
|
||||
get: () => context.vevent.calendar,
|
||||
enumerable: true
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* get SimpleEvent for current fcEvent
|
||||
* @returns {SimpleEvent}
|
||||
*/
|
||||
iface.getSimpleEvent = function () {
|
||||
return SimpleEvent(context.event);
|
||||
};
|
||||
|
||||
/**
|
||||
* moves the event to a different position
|
||||
* @param {moment.duration} delta
|
||||
* @param {boolean} isAllDay
|
||||
* @param {string} timezone
|
||||
* @param {moment.duration} defaultTimedEventMomentDuration
|
||||
* @param {moment.duration} defaultAllDayEventMomentDuration
|
||||
*/
|
||||
iface.drop = function (delta, isAllDay, timezone, defaultTimedEventMomentDuration, defaultAllDayEventMomentDuration) {
|
||||
delta = new ICAL.Duration().fromSeconds(delta.asSeconds());
|
||||
|
||||
const timedDuration = new ICAL.Duration().fromSeconds(defaultTimedEventMomentDuration.asSeconds());
|
||||
const allDayDuration = new ICAL.Duration().fromSeconds(defaultAllDayEventMomentDuration.asSeconds());
|
||||
|
||||
const dtstartProp = context.event.getFirstProperty('dtstart');
|
||||
const dtstart = dtstartProp.getFirstValue();
|
||||
dtstart.isDate = isAllDay;
|
||||
dtstart.addDuration(delta);
|
||||
dtstart.zone = isAllDay ? 'floating' : dtstart.zone;
|
||||
|
||||
// event was moved from allDay to grid
|
||||
// we need to add a tzid to the dtstart
|
||||
if (context.allDay && !isAllDay) {
|
||||
const timezoneObject = ICAL.TimezoneService.get(timezone);
|
||||
|
||||
if (timezone === 'UTC') {
|
||||
timezone = 'Z';
|
||||
}
|
||||
|
||||
dtstart.zone = timezoneObject;
|
||||
if (timezone !== 'Z') {
|
||||
dtstartProp.setParameter('tzid', timezone);
|
||||
|
||||
if (context.event.parent) {
|
||||
context.event.parent.addSubcomponent(timezoneObject.component);
|
||||
}
|
||||
}
|
||||
}
|
||||
// event was moved from grid to allDay
|
||||
// remove tzid
|
||||
if (!context.allDay && isAllDay) {
|
||||
dtstartProp.removeParameter('tzid');
|
||||
}
|
||||
context.event.updatePropertyWithValue('dtstart', dtstart);
|
||||
|
||||
// event was moved from allday to grid or vice versa
|
||||
if (context.allDay !== isAllDay) {
|
||||
// No DURATION -> either DTEND or only DTSTART
|
||||
if (!context.event.hasProperty('duration')) {
|
||||
const dtend = dtstart.clone();
|
||||
dtend.addDuration(isAllDay ? allDayDuration : timedDuration);
|
||||
const dtendProp = context.event.updatePropertyWithValue('dtend', dtend);
|
||||
|
||||
const tzid = dtstartProp.getParameter('tzid');
|
||||
if (tzid) {
|
||||
dtendProp.setParameter('tzid', tzid);
|
||||
} else {
|
||||
dtendProp.removeParameter('tzid');
|
||||
}
|
||||
} else {
|
||||
context.event.updatePropertyWithValue('duration', isAllDay ?
|
||||
allDayDuration : timedDuration);
|
||||
}
|
||||
} else {
|
||||
// No DURATION -> either DTEND or only DTSTART
|
||||
if (context.event.hasProperty('dtend')) {
|
||||
const dtend = context.event.getFirstPropertyValue('dtend');
|
||||
dtend.addDuration(delta);
|
||||
context.event.updatePropertyWithValue('dtend', dtend);
|
||||
}
|
||||
}
|
||||
|
||||
context.allDay = isAllDay;
|
||||
context.vevent.touch();
|
||||
};
|
||||
|
||||
/**
|
||||
* resizes the event
|
||||
* @param {moment.duration} delta
|
||||
*/
|
||||
iface.resize = function (delta) {
|
||||
delta = new ICAL.Duration().fromSeconds(delta.asSeconds());
|
||||
|
||||
if (context.event.hasProperty('duration')) {
|
||||
const duration = context.event.getFirstPropertyValue('duration');
|
||||
duration.fromSeconds((delta.toSeconds() + duration.toSeconds()));
|
||||
context.event.updatePropertyWithValue('duration', duration);
|
||||
} else if (context.event.hasProperty('dtend')) {
|
||||
const dtend = context.event.getFirstPropertyValue('dtend');
|
||||
dtend.addDuration(delta);
|
||||
context.event.updatePropertyWithValue('dtend', dtend);
|
||||
} else if (context.event.hasProperty('dtstart')) {
|
||||
const dtstart = event.getFirstProperty('dtstart');
|
||||
const dtend = dtstart.getFirstValue().clone();
|
||||
dtend.addDuration(delta);
|
||||
|
||||
const prop = context.event.addPropertyWithValue('dtend', dtend);
|
||||
|
||||
const tzid = dtstart.getParameter('tzid');
|
||||
if (tzid) {
|
||||
prop.setParameter('tzid', tzid);
|
||||
}
|
||||
}
|
||||
|
||||
context.vevent.touch();
|
||||
};
|
||||
|
||||
/**
|
||||
* lock fc event for editing
|
||||
*/
|
||||
iface.lock = function() {
|
||||
context.lock = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* unlock fc event
|
||||
*/
|
||||
iface.unlock = function() {
|
||||
context.lock = false;
|
||||
};
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
FcEvent.isFcEvent = function(obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isAFcEventObject === true);
|
||||
};
|
||||
|
||||
return FcEvent;
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('Hook', function() {
|
||||
'use strict';
|
||||
|
||||
return function Hook(context) {
|
||||
context.hooks = {};
|
||||
const iface = {};
|
||||
|
||||
iface.emit = function(identifier, newValue, oldValue) {
|
||||
if (Array.isArray(context.hooks[identifier])) {
|
||||
context.hooks[identifier].forEach(function(callback) {
|
||||
callback(newValue, oldValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
iface.register = function(identifier, callback) {
|
||||
context.hooks[identifier] = context.hooks[identifier] || [];
|
||||
context.hooks[identifier].push(callback);
|
||||
};
|
||||
|
||||
return iface;
|
||||
};
|
||||
});
|
|
@ -1,671 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('SimpleEvent', function () {
|
||||
'use strict';
|
||||
|
||||
const defaults = {
|
||||
'summary': null,
|
||||
'location': null,
|
||||
'organizer': null,
|
||||
'class': null,
|
||||
'description': null,
|
||||
//'url': null,
|
||||
'status': null,
|
||||
//'resources': null,
|
||||
'alarm': null,
|
||||
'attendee': null,
|
||||
//'categories': null,
|
||||
'dtstart': null,
|
||||
'dtend': null,
|
||||
'repeating': null,
|
||||
'rdate': null,
|
||||
'rrule': null,
|
||||
'exdate': null
|
||||
};
|
||||
|
||||
const attendeeParameters = [
|
||||
'role',
|
||||
'rsvp',
|
||||
'partstat',
|
||||
'cutype',
|
||||
'cn',
|
||||
'delegated-from',
|
||||
'delegated-to'
|
||||
];
|
||||
|
||||
const organizerParameters = [
|
||||
'cn'
|
||||
];
|
||||
|
||||
function getDtProperty(simple, propName) {
|
||||
if (simple.allDay) {
|
||||
simple[propName].parameters.zone = 'floating';
|
||||
}
|
||||
|
||||
simple[propName].parameters.zone = simple[propName].parameters.zone || 'floating';
|
||||
|
||||
if (simple[propName].parameters.zone !== 'floating' && !ICAL.TimezoneService.has(simple[propName].parameters.zone)) {
|
||||
throw new Error('Requested timezone not found (' + simple[propName].parameters.zone + ')');
|
||||
}
|
||||
|
||||
const iCalTime = ICAL.Time.fromJSDate(simple[propName].value.toDate(), false);
|
||||
iCalTime.isDate = simple.allDay;
|
||||
|
||||
if (simple[propName].parameters.zone !== 'floating') {
|
||||
iCalTime.zone = ICAL.TimezoneService.get(simple[propName].parameters.zone);
|
||||
}
|
||||
|
||||
return iCalTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* parsers of supported properties
|
||||
*/
|
||||
const simpleParser = {
|
||||
date: function (data, vevent, key, parameters) {
|
||||
parameters = (parameters || []).concat(['tzid']);
|
||||
simpleParser._parseSingle(data, vevent, key, parameters, function (p) {
|
||||
const first = p.getFirstValue();
|
||||
return (p.type === 'duration') ? first.toSeconds() : moment(first.toJSDate());
|
||||
});
|
||||
},
|
||||
dates: function (data, vevent, key, parameters) {
|
||||
parameters = (parameters || []).concat(['tzid']);
|
||||
simpleParser._parseMultiple(data, vevent, key, parameters, function (p) {
|
||||
const values = p.getValues(), usableValues = [];
|
||||
|
||||
values.forEach(function (value) {
|
||||
if (p.type === 'duration') {
|
||||
usableValues.push(value.toSeconds());
|
||||
} else {
|
||||
usableValues.push(moment(value.toJSDate()));
|
||||
}
|
||||
});
|
||||
|
||||
return usableValues;
|
||||
});
|
||||
},
|
||||
string: function (data, vevent, key, parameters) {
|
||||
simpleParser._parseSingle(data, vevent, key, parameters, function (p) {
|
||||
return p.isMultiValue ? p.getValues() : p.getFirstValue();
|
||||
});
|
||||
},
|
||||
strings: function (data, vevent, key, parameters) {
|
||||
simpleParser._parseMultiple(data, vevent, key, parameters, function (p) {
|
||||
return p.isMultiValue ? p.getValues() : p.getFirstValue();
|
||||
});
|
||||
},
|
||||
_parseSingle: function (data, vevent, key, parameters, valueParser) {
|
||||
const prop = vevent.getFirstProperty(key);
|
||||
if (!prop) {
|
||||
return;
|
||||
}
|
||||
|
||||
data[key] = {
|
||||
parameters: simpleParser._parseParameters(prop, parameters),
|
||||
type: prop.type
|
||||
};
|
||||
|
||||
if (prop.isMultiValue) {
|
||||
data[key].values = valueParser(prop);
|
||||
} else {
|
||||
data[key].value = valueParser(prop);
|
||||
}
|
||||
},
|
||||
_parseMultiple: function (data, vevent, key, parameters, valueParser) {
|
||||
data[key] = data[key] || [];
|
||||
|
||||
const properties = vevent.getAllProperties(key);
|
||||
let group = 0;
|
||||
|
||||
properties.forEach(function (property) {
|
||||
const currentElement = {
|
||||
group: group,
|
||||
parameters: simpleParser._parseParameters(property, parameters),
|
||||
type: property.type
|
||||
};
|
||||
|
||||
if (property.isMultiValue) {
|
||||
currentElement.values = valueParser(property);
|
||||
} else {
|
||||
currentElement.value = valueParser(property);
|
||||
}
|
||||
|
||||
data[key].push(currentElement);
|
||||
property.setParameter('x-nc-group-id', group.toString());
|
||||
group++;
|
||||
});
|
||||
},
|
||||
_parseParameters: function (prop, para) {
|
||||
const parameters = {};
|
||||
|
||||
if (!para) {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
para.forEach(function (p) {
|
||||
parameters[p] = prop.getParameter(p);
|
||||
});
|
||||
|
||||
return parameters;
|
||||
}
|
||||
};
|
||||
|
||||
const simpleReader = {
|
||||
date: function (vevent, oldSimpleData, newSimpleData, key, parameters) {
|
||||
parameters = (parameters || []).concat(['tzid']);
|
||||
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function (v, isMultiValue) {
|
||||
return (v.type === 'duration') ? ICAL.Duration.fromSeconds(v.value) : ICAL.Time.fromJSDate(v.value.toDate());
|
||||
});
|
||||
},
|
||||
dates: function (vevent, oldSimpleData, newSimpleData, key, parameters) {
|
||||
parameters = (parameters || []).concat(['tzid']);
|
||||
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function (v, isMultiValue) {
|
||||
const values = [];
|
||||
|
||||
v.values.forEach(function (value) {
|
||||
if (v.type === 'duration') {
|
||||
values.push(ICAL.Duration.fromSeconds(value));
|
||||
} else {
|
||||
values.push(ICAL.Time.fromJSDate(value.toDate()));
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
});
|
||||
},
|
||||
string: function (vevent, oldSimpleData, newSimpleData, key, parameters) {
|
||||
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function (v, isMultiValue) {
|
||||
return isMultiValue ? v.values : v.value;
|
||||
});
|
||||
},
|
||||
strings: function (vevent, oldSimpleData, newSimpleData, key, parameters) {
|
||||
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function (v, isMultiValue) {
|
||||
return isMultiValue ? v.values : v.value;
|
||||
});
|
||||
},
|
||||
_readSingle: function (vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
|
||||
if (!newSimpleData[key]) {
|
||||
return;
|
||||
}
|
||||
if (!newSimpleData[key].hasOwnProperty('value') && !newSimpleData[key].hasOwnProperty('values')) {
|
||||
return;
|
||||
}
|
||||
const isMultiValue = newSimpleData[key].hasOwnProperty('values');
|
||||
|
||||
const prop = vevent.updatePropertyWithValue(key, valueReader(newSimpleData[key], isMultiValue));
|
||||
simpleReader._readParameters(prop, newSimpleData[key], parameters);
|
||||
},
|
||||
_readMultiple: function (vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
|
||||
const oldGroups = [];
|
||||
let properties, pKey, groupId;
|
||||
|
||||
oldSimpleData[key] = oldSimpleData[key] || [];
|
||||
oldSimpleData[key].forEach(function (e) {
|
||||
oldGroups.push(e.group);
|
||||
});
|
||||
|
||||
newSimpleData[key] = newSimpleData[key] || [];
|
||||
newSimpleData[key].forEach(function (e) {
|
||||
const isMultiValue = e.hasOwnProperty('values');
|
||||
const value = valueReader(e, isMultiValue);
|
||||
|
||||
if (oldGroups.indexOf(e.group) === -1) {
|
||||
const property = new ICAL.Property(key);
|
||||
simpleReader._setProperty(property, value, isMultiValue);
|
||||
simpleReader._readParameters(property, e, parameters);
|
||||
vevent.addProperty(property);
|
||||
} else {
|
||||
oldGroups.splice(oldGroups.indexOf(e.group), 1);
|
||||
|
||||
properties = vevent.getAllProperties(key);
|
||||
for (pKey in properties) {
|
||||
if (!properties.hasOwnProperty(pKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
groupId = properties[pKey].getParameter('x-nc-group-id');
|
||||
if (groupId === null) {
|
||||
continue;
|
||||
}
|
||||
if (parseInt(groupId) === e.group) {
|
||||
simpleReader._setProperty(properties[pKey], value, isMultiValue);
|
||||
simpleReader._readParameters(properties[pKey], e, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
properties = vevent.getAllProperties(key);
|
||||
properties.forEach(function (property) {
|
||||
groupId = property.getParameter('x-nc-group-id');
|
||||
if (oldGroups.indexOf(parseInt(groupId)) !== -1) {
|
||||
vevent.removeProperty(property);
|
||||
}
|
||||
property.removeParameter('x-nc-group-id');
|
||||
});
|
||||
},
|
||||
_readParameters: function (prop, simple, para) {
|
||||
if (!para) {
|
||||
return;
|
||||
}
|
||||
if (!simple.parameters) {
|
||||
return;
|
||||
}
|
||||
|
||||
para.forEach(function (p) {
|
||||
if (simple.parameters[p]) {
|
||||
prop.setParameter(p, simple.parameters[p]);
|
||||
} else {
|
||||
prop.removeParameter(p);
|
||||
}
|
||||
});
|
||||
},
|
||||
_setProperty: function (prop, value, isMultiValue) {
|
||||
if (isMultiValue) {
|
||||
prop.setValues(value);
|
||||
} else {
|
||||
prop.setValue(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* properties supported by event editor
|
||||
*/
|
||||
const simpleProperties = {
|
||||
//General
|
||||
'summary': {parser: simpleParser.string, reader: simpleReader.string},
|
||||
'location': {parser: simpleParser.string, reader: simpleReader.string},
|
||||
//'categories': {parser: simpleParser.strings, reader: simpleReader.strings},
|
||||
//attendees
|
||||
'attendee': {
|
||||
parser: simpleParser.strings,
|
||||
reader: simpleReader.strings,
|
||||
parameters: attendeeParameters
|
||||
},
|
||||
'organizer': {
|
||||
parser: simpleParser.string,
|
||||
reader: simpleReader.string,
|
||||
parameters: organizerParameters
|
||||
},
|
||||
//sharing
|
||||
'class': {parser: simpleParser.string, reader: simpleReader.string},
|
||||
//other
|
||||
'description': {
|
||||
parser: simpleParser.string,
|
||||
reader: simpleReader.string
|
||||
},
|
||||
//'url': {parser: simpleParser.string, reader: simpleReader.string},
|
||||
'status': {parser: simpleParser.string, reader: simpleReader.string}
|
||||
//'resources': {parser: simpleParser.strings, reader: simpleReader.strings}
|
||||
};
|
||||
|
||||
/**
|
||||
* specific parsers that check only one property
|
||||
*/
|
||||
const specificParser = {
|
||||
alarm: function (data, vevent) {
|
||||
data.alarm = data.alarm || [];
|
||||
|
||||
const alarms = vevent.getAllSubcomponents('valarm');
|
||||
let group = 0;
|
||||
alarms.forEach(function (alarm) {
|
||||
const alarmData = {
|
||||
group: group,
|
||||
action: {},
|
||||
trigger: {},
|
||||
repeat: {},
|
||||
duration: {},
|
||||
attendee: []
|
||||
};
|
||||
|
||||
simpleParser.string(alarmData, alarm, 'action');
|
||||
simpleParser.date(alarmData, alarm, 'trigger');
|
||||
simpleParser.string(alarmData, alarm, 'repeat');
|
||||
simpleParser.date(alarmData, alarm, 'duration');
|
||||
simpleParser.strings(alarmData, alarm, 'attendee', attendeeParameters);
|
||||
|
||||
//alarmData.attendeeCopy = [];
|
||||
//angular.copy(alarmData.attendee, alarmData.attendeeCopy);
|
||||
|
||||
if (alarmData.trigger.type === 'duration' && alarm.hasProperty('trigger')) {
|
||||
const trigger = alarm.getFirstProperty('trigger');
|
||||
const related = trigger.getParameter('related');
|
||||
if (related) {
|
||||
alarmData.trigger.related = related;
|
||||
} else {
|
||||
alarmData.trigger.related = 'start';
|
||||
}
|
||||
}
|
||||
|
||||
data.alarm.push(alarmData);
|
||||
|
||||
alarm.getFirstProperty('action')
|
||||
.setParameter('x-nc-group-id', group.toString());
|
||||
group++;
|
||||
});
|
||||
},
|
||||
date: function (data, vevent) {
|
||||
const dtstart = vevent.getFirstPropertyValue('dtstart');
|
||||
let dtend;
|
||||
|
||||
if (vevent.hasProperty('dtend')) {
|
||||
dtend = vevent.getFirstPropertyValue('dtend');
|
||||
} else if (vevent.hasProperty('duration')) {
|
||||
dtend = dtstart.clone();
|
||||
dtend.addDuration(vevent.getFirstPropertyValue('duration'));
|
||||
} else {
|
||||
dtend = dtstart.clone();
|
||||
|
||||
if (dtend.icaltype === 'date') {
|
||||
dtend.addDuration(ICAL.Duration.fromString('P1D'));
|
||||
}
|
||||
}
|
||||
|
||||
data.dtstart = {
|
||||
parameters: {
|
||||
zone: dtstart.zone.toString()
|
||||
},
|
||||
value: moment({
|
||||
years: dtstart.year,
|
||||
months: dtstart.month - 1,
|
||||
date: dtstart.day,
|
||||
hours: dtstart.hour,
|
||||
minutes: dtstart.minute,
|
||||
seconds: dtstart.seconds
|
||||
})
|
||||
};
|
||||
data.dtend = {
|
||||
parameters: {
|
||||
zone: dtend.zone.toString()
|
||||
},
|
||||
value: moment({
|
||||
years: dtend.year,
|
||||
months: dtend.month - 1,
|
||||
date: dtend.day,
|
||||
hours: dtend.hour,
|
||||
minutes: dtend.minute,
|
||||
seconds: dtend.seconds
|
||||
})
|
||||
};
|
||||
data.allDay = (dtstart.icaltype === 'date' && dtend.icaltype === 'date');
|
||||
},
|
||||
repeating: function (data, vevent) {
|
||||
const iCalEvent = new ICAL.Event(vevent);
|
||||
|
||||
data.repeating = iCalEvent.isRecurring();
|
||||
|
||||
const rrule = vevent.getFirstPropertyValue('rrule');
|
||||
if (rrule) {
|
||||
data.rrule = {
|
||||
count: rrule.count,
|
||||
freq: rrule.freq,
|
||||
interval: rrule.interval,
|
||||
parameters: rrule.parts,
|
||||
until: null
|
||||
};
|
||||
|
||||
/*if (rrule.until) {
|
||||
simpleParser.date(data.rrule, rrule, 'until');
|
||||
}*/
|
||||
} else {
|
||||
data.rrule = {
|
||||
freq: 'NONE'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const specificReader = {
|
||||
alarm: function (vevent, oldSimpleData, newSimpleData) {
|
||||
const components = {}, key = 'alarm';
|
||||
|
||||
function getAlarmGroup (alarmData) {
|
||||
return alarmData.group;
|
||||
}
|
||||
|
||||
oldSimpleData[key] = oldSimpleData[key] || [];
|
||||
const oldGroups = oldSimpleData[key].map(getAlarmGroup);
|
||||
|
||||
newSimpleData[key] = newSimpleData[key] || [];
|
||||
const newGroups = newSimpleData[key].map(getAlarmGroup);
|
||||
|
||||
//check for any alarms that are in the old data,
|
||||
//but have been removed from the new data
|
||||
const removedAlarms = oldGroups.filter(function (group) {
|
||||
return (newGroups.indexOf(group) === -1);
|
||||
});
|
||||
|
||||
//get all of the valarms and save them in an object keyed by their groupId
|
||||
vevent.getAllSubcomponents('valarm').forEach(function (alarm) {
|
||||
const group = alarm.getFirstProperty('action').getParameter('x-nc-group-id');
|
||||
components[group] = alarm;
|
||||
});
|
||||
|
||||
//remove any valarm subcomponents have a groupId that matches one of the removedAlarms
|
||||
removedAlarms.forEach(function (group) {
|
||||
if (components[group]) {
|
||||
vevent.removeSubcomponent(components[group]);
|
||||
delete components[group];
|
||||
}
|
||||
});
|
||||
|
||||
//update and create valarms using the new alarm data
|
||||
newSimpleData[key].forEach(function (alarmData) {
|
||||
let valarm, oldSimpleAlarmData;
|
||||
|
||||
if (oldGroups.indexOf(alarmData.group) === -1) {
|
||||
valarm = new ICAL.Component('VALARM');
|
||||
vevent.addSubcomponent(valarm);
|
||||
oldSimpleAlarmData = {};
|
||||
} else {
|
||||
valarm = components[alarmData.group];
|
||||
oldSimpleAlarmData = oldSimpleData.alarm.find(function(alarm) {
|
||||
return alarm.group === alarmData.group;
|
||||
});
|
||||
}
|
||||
|
||||
simpleReader.string(valarm, oldSimpleAlarmData, alarmData, 'action', []);
|
||||
simpleReader.date(valarm, oldSimpleAlarmData, alarmData, 'trigger', []);
|
||||
simpleReader.string(valarm, oldSimpleAlarmData, alarmData, 'repeat', []);
|
||||
simpleReader.date(valarm, oldSimpleAlarmData, alarmData, 'duration', []);
|
||||
simpleReader.strings(valarm, oldSimpleAlarmData, alarmData, 'attendee', attendeeParameters);
|
||||
|
||||
valarm.getFirstProperty('action').removeParameter('x-nc-group-id');
|
||||
});
|
||||
},
|
||||
date: function (vevent, oldSimpleData, newSimpleData) {
|
||||
vevent.removeAllProperties('dtstart');
|
||||
vevent.removeAllProperties('dtend');
|
||||
vevent.removeAllProperties('duration');
|
||||
|
||||
// remove tzid property from allday events
|
||||
if (newSimpleData.allDay) {
|
||||
newSimpleData.dtstart.parameters.zone = 'floating';
|
||||
newSimpleData.dtend.parameters.zone = 'floating';
|
||||
}
|
||||
|
||||
newSimpleData.dtstart.parameters.zone = newSimpleData.dtstart.parameters.zone || 'floating';
|
||||
newSimpleData.dtend.parameters.zone = newSimpleData.dtend.parameters.zone || 'floating';
|
||||
|
||||
if (newSimpleData.dtstart.parameters.zone !== 'floating' && !ICAL.TimezoneService.has(newSimpleData.dtstart.parameters.zone)) {
|
||||
throw new Error('Requested timezone not found (' + newSimpleData.dtstart.parameters.zone + ')');
|
||||
}
|
||||
if (newSimpleData.dtend.parameters.zone !== 'floating' && !ICAL.TimezoneService.has(newSimpleData.dtend.parameters.zone)) {
|
||||
throw new Error('Requested timezone not found (' + newSimpleData.dtend.parameters.zone + ')');
|
||||
}
|
||||
|
||||
const start = ICAL.Time.fromJSDate(newSimpleData.dtstart.value.toDate(), false);
|
||||
start.isDate = newSimpleData.allDay;
|
||||
const end = ICAL.Time.fromJSDate(newSimpleData.dtend.value.toDate(), false);
|
||||
end.isDate = newSimpleData.allDay;
|
||||
|
||||
const alreadyStoredTimezones = ['UTC'];
|
||||
const vtimezones = vevent.parent.getAllSubcomponents('vtimezone');
|
||||
vtimezones.forEach(function (vtimezone) {
|
||||
alreadyStoredTimezones.push(vtimezone.getFirstPropertyValue('tzid'));
|
||||
});
|
||||
|
||||
const startProp = new ICAL.Property('dtstart', vevent);
|
||||
if (newSimpleData.dtstart.parameters.zone !== 'floating') {
|
||||
if (newSimpleData.dtstart.parameters.zone !== 'UTC') {
|
||||
startProp.setParameter('tzid', newSimpleData.dtstart.parameters.zone);
|
||||
}
|
||||
|
||||
const startTz = ICAL.TimezoneService.get(newSimpleData.dtstart.parameters.zone);
|
||||
start.zone = startTz;
|
||||
if (alreadyStoredTimezones.indexOf(newSimpleData.dtstart.parameters.zone) === -1) {
|
||||
vevent.parent.addSubcomponent(startTz.component);
|
||||
alreadyStoredTimezones.push(newSimpleData.dtstart.parameters.zone);
|
||||
}
|
||||
}
|
||||
startProp.setValue(start);
|
||||
|
||||
const endProp = new ICAL.Property('dtend', vevent);
|
||||
if (newSimpleData.dtend.parameters.zone !== 'floating') {
|
||||
if (newSimpleData.dtend.parameters.zone !== 'UTC') {
|
||||
endProp.setParameter('tzid', newSimpleData.dtend.parameters.zone);
|
||||
}
|
||||
|
||||
const endTz = ICAL.TimezoneService.get(newSimpleData.dtend.parameters.zone);
|
||||
end.zone = endTz;
|
||||
if (alreadyStoredTimezones.indexOf(newSimpleData.dtend.parameters.zone) === -1) {
|
||||
vevent.parent.addSubcomponent(endTz.component);
|
||||
}
|
||||
}
|
||||
endProp.setValue(end);
|
||||
|
||||
vevent.addProperty(startProp);
|
||||
vevent.addProperty(endProp);
|
||||
},
|
||||
repeating: function (vevent, oldSimpleData, newSimpleData) {
|
||||
// We won't support exrule, because it's deprecated and barely used in the wild
|
||||
if (newSimpleData.rrule === null || newSimpleData.rrule.freq === 'NONE') {
|
||||
vevent.removeAllProperties('rdate');
|
||||
vevent.removeAllProperties('rrule');
|
||||
vevent.removeAllProperties('exdate');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (newSimpleData.rrule.dontTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
interval: newSimpleData.rrule.interval,
|
||||
freq: newSimpleData.rrule.freq
|
||||
};
|
||||
|
||||
if (newSimpleData.rrule.count) {
|
||||
params.count = newSimpleData.rrule.count;
|
||||
}
|
||||
|
||||
const rrule = new ICAL.Recur(params);
|
||||
vevent.updatePropertyWithValue('rrule', rrule);
|
||||
}
|
||||
};
|
||||
|
||||
function SimpleEvent (event) {
|
||||
const context = {
|
||||
event,
|
||||
patched: false,
|
||||
oldProperties: {}
|
||||
};
|
||||
|
||||
const iface = {
|
||||
_isASimpleEventObject: true,
|
||||
};
|
||||
angular.extend(iface, defaults);
|
||||
|
||||
context.generateOldProperties = function () {
|
||||
context.oldProperties = {};
|
||||
|
||||
for (let key in defaults) {
|
||||
context.oldProperties[key] = angular.copy(iface[key]);
|
||||
}
|
||||
};
|
||||
|
||||
iface.checkDtStartBeforeDtEnd = function() {
|
||||
const dtStart = getDtProperty(iface, 'dtstart');
|
||||
const dtEnd = getDtProperty(iface, 'dtend');
|
||||
|
||||
// dtend may be at the same time or later, but not before
|
||||
return (dtEnd.compare(dtStart) !== -1);
|
||||
};
|
||||
|
||||
iface.patch = function() {
|
||||
if (context.patched) {
|
||||
throw new Error('SimpleEvent was already patched, patching not possible');
|
||||
}
|
||||
|
||||
for (let simpleKey in simpleProperties) {
|
||||
const simpleProperty = simpleProperties[simpleKey];
|
||||
|
||||
const reader = simpleProperty.reader;
|
||||
const parameters = simpleProperty.parameters;
|
||||
if (context.oldProperties[simpleKey] !== iface[simpleKey]) {
|
||||
if (iface[simpleKey] === null) {
|
||||
context.event.removeAllProperties(simpleKey);
|
||||
} else {
|
||||
reader(context.event, context.oldProperties, iface, simpleKey, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let specificKey in specificReader) {
|
||||
const reader = specificReader[specificKey];
|
||||
reader(context.event, context.oldProperties, iface);
|
||||
}
|
||||
|
||||
context.patched = true;
|
||||
};
|
||||
|
||||
for (let simpleKey in simpleProperties) {
|
||||
const simpleProperty = simpleProperties[simpleKey];
|
||||
|
||||
const parser = simpleProperty.parser;
|
||||
const parameters = simpleProperty.parameters;
|
||||
if (context.event.hasProperty(simpleKey)) {
|
||||
parser(iface, context.event, simpleKey, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
for (let specificKey in specificParser) {
|
||||
const parser = specificParser[specificKey];
|
||||
parser(iface, context.event);
|
||||
}
|
||||
|
||||
context.generateOldProperties();
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
SimpleEvent.isSimpleEvent = function (obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isASimpleEventObject === true);
|
||||
};
|
||||
|
||||
return SimpleEvent;
|
||||
});
|
|
@ -1,79 +0,0 @@
|
|||
app.factory('SplittedICal', function() {
|
||||
'use strict';
|
||||
|
||||
function SplittedICal (name, color) {
|
||||
const context = {
|
||||
name: name,
|
||||
color: color,
|
||||
vevents: [],
|
||||
vjournals: [],
|
||||
vtodos: []
|
||||
};
|
||||
const iface = {
|
||||
_isASplittedICalObject: true
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
name: {
|
||||
get: function() {
|
||||
return context.name;
|
||||
}
|
||||
},
|
||||
color: {
|
||||
get: function() {
|
||||
return context.color;
|
||||
}
|
||||
},
|
||||
vevents: {
|
||||
get: function() {
|
||||
return context.vevents;
|
||||
}
|
||||
},
|
||||
vjournals: {
|
||||
get: function() {
|
||||
return context.vjournals;
|
||||
}
|
||||
},
|
||||
vtodos: {
|
||||
get: function() {
|
||||
return context.vtodos;
|
||||
}
|
||||
},
|
||||
objects: {
|
||||
get: function() {
|
||||
return []
|
||||
.concat(context.vevents)
|
||||
.concat(context.vjournals)
|
||||
.concat(context.vtodos);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iface.addObject = function(componentName, object) {
|
||||
switch(componentName) {
|
||||
case 'vevent':
|
||||
context.vevents.push(object);
|
||||
break;
|
||||
|
||||
case 'vjournal':
|
||||
context.vjournals.push(object);
|
||||
break;
|
||||
|
||||
case 'vtodo':
|
||||
context.vtodos.push(object);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
SplittedICal.isSplittedICal = function(obj) {
|
||||
return obj instanceof SplittedICal || (typeof obj === 'object' && obj !== null && obj._isASplittedICalObject !== null);
|
||||
};
|
||||
|
||||
return SplittedICal;
|
||||
});
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('Timezone',
|
||||
function() {
|
||||
'use strict';
|
||||
|
||||
var timezone = function Timezone(data) {
|
||||
angular.extend(this, {
|
||||
_props: {}
|
||||
});
|
||||
|
||||
if (data instanceof ICAL.Timezone) {
|
||||
this._props.jCal = data;
|
||||
this._props.name = data.tzid;
|
||||
} else if (typeof data === 'string') {
|
||||
var jCal = ICAL.parse(data);
|
||||
var components = new ICAL.Component(jCal);
|
||||
var iCalTimezone = null;
|
||||
if (components.name === 'vtimezone') {
|
||||
iCalTimezone = new ICAL.Timezone(components);
|
||||
} else {
|
||||
iCalTimezone = new ICAL.Timezone(components.getFirstSubcomponent('vtimezone'));
|
||||
}
|
||||
this._props.jCal = iCalTimezone;
|
||||
this._props.name = iCalTimezone.tzid;
|
||||
}
|
||||
};
|
||||
|
||||
//Timezones are immutable
|
||||
timezone.prototype = {
|
||||
get jCal() {
|
||||
return this._props.jCal;
|
||||
},
|
||||
get name() {
|
||||
return this._props.name;
|
||||
}
|
||||
};
|
||||
|
||||
return timezone;
|
||||
}
|
||||
);
|
|
@ -1,439 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.factory('VEvent', function(TimezoneService, FcEvent, SimpleEvent, ICalFactory, StringUtility) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* get a VEvent object
|
||||
* @param {Calendar} calendar
|
||||
* @param {ICAL.Component} comp
|
||||
* @param {string} uri
|
||||
* @param {string} etag
|
||||
*/
|
||||
function VEvent(calendar, comp, uri, etag='') {
|
||||
const context = {calendar, comp, uri, etag};
|
||||
const iface = {
|
||||
_isAVEventObject: true
|
||||
};
|
||||
|
||||
if (!context.comp || !context.comp.jCal || context.comp.jCal.length === 0) {
|
||||
throw new TypeError('Given comp is not a valid calendar');
|
||||
}
|
||||
|
||||
// read all timezones in the comp and register them
|
||||
const vtimezones = comp.getAllSubcomponents('vtimezone');
|
||||
vtimezones.forEach(function(vtimezone) {
|
||||
const timezone = new ICAL.Timezone(vtimezone);
|
||||
ICAL.TimezoneService.register(timezone.tzid, timezone);
|
||||
});
|
||||
|
||||
if (!uri) {
|
||||
const vevent = context.comp.getFirstSubcomponent('vevent');
|
||||
context.uri = vevent.getFirstPropertyValue('uid');
|
||||
}
|
||||
|
||||
/**
|
||||
* get DTEND from vevent
|
||||
* @param {ICAL.Component} vevent
|
||||
* @returns {ICAL.Time}
|
||||
*/
|
||||
context.calculateDTEnd = function(vevent) {
|
||||
if (vevent.hasProperty('dtend')) {
|
||||
return vevent.getFirstPropertyValue('dtend');
|
||||
} else if (vevent.hasProperty('duration')) {
|
||||
const dtstart = vevent.getFirstPropertyValue('dtstart').clone();
|
||||
dtstart.addDuration(vevent.getFirstPropertyValue('duration'));
|
||||
|
||||
return dtstart;
|
||||
} else {
|
||||
/*
|
||||
* RFC 5545 - 3.6.1.
|
||||
*
|
||||
* For cases where a "VEVENT" calendar component
|
||||
* specifies a "DTSTART" property with a DATE value type but no
|
||||
* "DTEND" nor "DURATION" property, the event’s duration is taken to
|
||||
* be one day. For cases where a "VEVENT" calendar component
|
||||
* specifies a "DTSTART" property with a DATE-TIME value type but no
|
||||
* "DTEND" property, the event ends on the same calendar date and
|
||||
* time of day specified by the "DTSTART" property.
|
||||
*/
|
||||
const dtstart = vevent.getFirstPropertyValue('dtstart').clone();
|
||||
|
||||
if (dtstart.icaltype === 'date') {
|
||||
dtstart.addDuration(ICAL.Duration.fromString('P1D'));
|
||||
}
|
||||
|
||||
return dtstart;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* convert a dt's timezone if necessary
|
||||
* @param {ICAL.Time} dt
|
||||
* @param {ICAL.Component} timezone
|
||||
* @returns {ICAL.Time}
|
||||
*/
|
||||
context.convertTz = function(dt, timezone) {
|
||||
if (context.needsTzConversion(dt) && timezone) {
|
||||
dt = dt.convertToZone(timezone);
|
||||
}
|
||||
|
||||
return dt;
|
||||
};
|
||||
|
||||
/**
|
||||
* check if we need to convert the timezone of either dtstart or dtend
|
||||
* @param {ICAL.Time} dt
|
||||
* @returns {boolean}
|
||||
*/
|
||||
context.needsTzConversion = function(dt) {
|
||||
return (dt.icaltype !== 'date' &&
|
||||
dt.zone !== ICAL.Timezone.localTimezone);
|
||||
};
|
||||
|
||||
/**
|
||||
* collect missing timezones
|
||||
* @returns {Array}
|
||||
*/
|
||||
context.getMissingEventTimezones = () => {
|
||||
const missingTimezones = [];
|
||||
const propertiesToSearch = ['dtstart', 'dtend'];
|
||||
const vevents = context.comp.getAllSubcomponents('vevent');
|
||||
vevents.forEach(function (vevent) {
|
||||
propertiesToSearch.forEach((propName) => {
|
||||
if (vevent.hasProperty(propName)) {
|
||||
const prop = vevent.getFirstProperty(propName);
|
||||
const tzid = prop.getParameter('tzid');
|
||||
if (tzid && !ICAL.TimezoneService.has(tzid) && missingTimezones.indexOf(tzid) === -1) {
|
||||
missingTimezones.push(tzid);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return missingTimezones;
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
calendar: {
|
||||
get: function() {
|
||||
return context.calendar;
|
||||
},
|
||||
set: function(calendar) {
|
||||
context.calendar = calendar;
|
||||
}
|
||||
},
|
||||
comp: {
|
||||
get: function() {
|
||||
return context.comp;
|
||||
}
|
||||
},
|
||||
data: {
|
||||
get: function() {
|
||||
return context.comp.toString();
|
||||
}
|
||||
},
|
||||
etag: {
|
||||
get: function() {
|
||||
return context.etag;
|
||||
},
|
||||
set: function(etag) {
|
||||
context.etag = etag;
|
||||
}
|
||||
},
|
||||
uri: {
|
||||
get: function() {
|
||||
return context.uri;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* get fullcalendar event in a defined time-range
|
||||
* @param {moment} start
|
||||
* @param {moment} end
|
||||
* @param {Timezone} timezone
|
||||
* @returns {Promise}
|
||||
*/
|
||||
iface.getFcEvent = function(start, end, timezone) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const iCalStart = ICAL.Time.fromJSDate(start.toDate());
|
||||
const iCalEnd = ICAL.Time.fromJSDate(end.toDate());
|
||||
const fcEvents = [];
|
||||
|
||||
const missingTimezones = context.getMissingEventTimezones();
|
||||
const errorSafeMissingTimezones = [];
|
||||
missingTimezones.forEach((missingTimezone) => {
|
||||
const promise = TimezoneService.get(missingTimezone)
|
||||
.then((tz) => tz)
|
||||
.catch((reason) => null);
|
||||
errorSafeMissingTimezones.push(promise);
|
||||
});
|
||||
|
||||
Promise.all(errorSafeMissingTimezones).then((timezones) => {
|
||||
timezones.forEach((timezone) => {
|
||||
if (!timezone) {
|
||||
return;
|
||||
}
|
||||
|
||||
const icalTimezone = new ICAL.Timezone(timezone.jCal);
|
||||
ICAL.TimezoneService.register(timezone.name, icalTimezone);
|
||||
});
|
||||
}).then(() => {
|
||||
const vevents = context.comp.getAllSubcomponents('vevent');
|
||||
const exceptions = vevents.filter((vevent) => vevent.hasProperty('recurrence-id'));
|
||||
const vevent = vevents.find((vevent) => !vevent.hasProperty('recurrence-id'));
|
||||
|
||||
if (!vevent && exceptions.length === 0) {
|
||||
resolve([]);
|
||||
}
|
||||
|
||||
// is there a main event that's recurring?
|
||||
if (vevent && (vevent.hasProperty('rrule') || vevent.hasProperty('rdate'))) {
|
||||
if (!vevent.hasProperty('dtstart')) {
|
||||
resolve([]);
|
||||
}
|
||||
|
||||
const iCalEvent = new ICAL.Event(vevent, {exceptions});
|
||||
const dtstartProp = vevent.getFirstProperty('dtstart');
|
||||
const rawDtstart = dtstartProp.getFirstValue('dtstart');
|
||||
|
||||
const iterator = new ICAL.RecurExpansion({
|
||||
component: vevent,
|
||||
dtstart: rawDtstart
|
||||
});
|
||||
|
||||
let next;
|
||||
while ((next = iterator.next())) {
|
||||
const occurrence = iCalEvent.getOccurrenceDetails(next);
|
||||
|
||||
if (occurrence.endDate.compare(iCalStart) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (occurrence.startDate.compare(iCalEnd) > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const dtstart = context.convertTz(occurrence.startDate, timezone.jCal);
|
||||
const dtend = context.convertTz(occurrence.endDate, timezone.jCal);
|
||||
const fcEvent = FcEvent(iface, occurrence.item.component, dtstart, dtend);
|
||||
|
||||
fcEvents.push(fcEvent);
|
||||
}
|
||||
} else {
|
||||
if (vevent) {
|
||||
exceptions.push(vevent);
|
||||
}
|
||||
|
||||
exceptions.forEach((singleVEvent) => {
|
||||
if (!singleVEvent.hasProperty('dtstart')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dtstartProp = singleVEvent.getFirstProperty('dtstart');
|
||||
const rawDtstart = dtstartProp.getFirstValue('dtstart');
|
||||
const rawDtend = context.calculateDTEnd(singleVEvent);
|
||||
|
||||
const dtstart = context.convertTz(rawDtstart, timezone.jCal);
|
||||
const dtend = context.convertTz(rawDtend, timezone.jCal);
|
||||
const fcEvent = FcEvent(iface, singleVEvent, dtstart, dtend);
|
||||
|
||||
fcEvents.push(fcEvent);
|
||||
});
|
||||
}
|
||||
|
||||
resolve(fcEvents);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param searchedRecurrenceId
|
||||
* @returns {SimpleEvent}
|
||||
*/
|
||||
iface.getSimpleEvent = function(searchedRecurrenceId) {
|
||||
const vevents = context.comp.getAllSubcomponents('vevent');
|
||||
|
||||
const veventsLength = vevents.length;
|
||||
for (let i=0; i < veventsLength; i++) {
|
||||
const vevent = vevents[i];
|
||||
const hasRecurrenceId = vevent.hasProperty('recurrence-id');
|
||||
let recurrenceId = null;
|
||||
if (hasRecurrenceId) {
|
||||
recurrenceId = vevent.getFirstPropertyValue('recurrence-id').toICALString();
|
||||
}
|
||||
|
||||
if (!hasRecurrenceId && !searchedRecurrenceId ||
|
||||
hasRecurrenceId && searchedRecurrenceId === recurrenceId) {
|
||||
return SimpleEvent(vevent);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Event not found');
|
||||
};
|
||||
|
||||
/**
|
||||
* update events last-modified property to now
|
||||
*/
|
||||
iface.touch = function() {
|
||||
const vevent = context.comp.getFirstSubcomponent('vevent');
|
||||
vevent.updatePropertyWithValue('last-modified', ICAL.Time.now());
|
||||
};
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
VEvent.isVEvent = function(obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isAVEventObject === true);
|
||||
};
|
||||
|
||||
/**
|
||||
* create all-day event from malformed input
|
||||
* @param {string} ics
|
||||
* @returns {string}
|
||||
*/
|
||||
VEvent.sanDate = function(ics) {
|
||||
ics.split("\n").forEach(function(el, i) {
|
||||
|
||||
var findTypes = ['DTSTART', 'DTEND'];
|
||||
var dateType = /[^:]*/.exec(el)[0];
|
||||
var icsDate = null;
|
||||
|
||||
if (findTypes.indexOf(dateType) >= 0 && el.trim().substr(-3) === 'T::') { // is date without time
|
||||
icsDate = el.replace(/[^0-9]/g, '');
|
||||
ics = ics.replace(el, dateType + ';VALUE=DATE:' + icsDate);
|
||||
}
|
||||
});
|
||||
|
||||
return ics;
|
||||
};
|
||||
|
||||
/**
|
||||
* create all-day event from malformed input
|
||||
* @param {string} ics
|
||||
* @returns {string}
|
||||
*/
|
||||
VEvent.sanNoDateValue = (ics) => {
|
||||
ics.split("\n").forEach(function(el, i) {
|
||||
|
||||
if (el.indexOf(';VALUE=DATE') !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const findTypes = ['DTSTART', 'DTEND'];
|
||||
const [dateTypePara, dateValue] = el.split(':');
|
||||
const [dateType, ...dateParameters] = dateTypePara.split(';');
|
||||
|
||||
if (findTypes.indexOf(dateType) >= 0 && dateParameters.indexOf('VALUE=DATE') === -1 && dateValue.length === 8) { // is date without time
|
||||
ics = ics.replace(el, dateTypePara + ';VALUE=DATE:' + dateValue);
|
||||
}
|
||||
});
|
||||
|
||||
return ics;
|
||||
};
|
||||
|
||||
/**
|
||||
* fix incorrectly populated trigger
|
||||
* @param {string} ics
|
||||
* @returns {string}
|
||||
*/
|
||||
VEvent.sanTrigger = function(ics) {
|
||||
const regex = /^TRIGGER:P$/gm;
|
||||
if (ics.match(regex)) {
|
||||
ics = ics.replace(regex, 'TRIGGER:P0D');
|
||||
}
|
||||
|
||||
return ics;
|
||||
};
|
||||
|
||||
/**
|
||||
* create a VEvent object from raw ics data
|
||||
* @param {Calendar} calendar
|
||||
* @param {string} ics
|
||||
* @param {string} uri
|
||||
* @param {string} etag
|
||||
* @returns {VEvent}
|
||||
*/
|
||||
VEvent.fromRawICS = function(calendar, ics, uri, etag='') {
|
||||
let comp;
|
||||
|
||||
if (ics.search('T::') > 0) { // no time
|
||||
ics = VEvent.sanDate(ics);
|
||||
}
|
||||
|
||||
if (ics.search('TRIGGER:P') > 0) {
|
||||
ics = VEvent.sanTrigger(ics);
|
||||
}
|
||||
|
||||
ics = VEvent.sanNoDateValue(ics);
|
||||
|
||||
try {
|
||||
const jCal = ICAL.parse(ics);
|
||||
comp = new ICAL.Component(jCal);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new TypeError('given ics data was not valid');
|
||||
}
|
||||
|
||||
return VEvent(calendar, comp, uri, etag);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* generates a new VEvent based on start and end
|
||||
* @param start
|
||||
* @param end
|
||||
* @param timezone
|
||||
* @returns {VEvent}
|
||||
*/
|
||||
VEvent.fromStartEnd = function(start, end, timezone) {
|
||||
const uid = StringUtility.uid();
|
||||
const comp = ICalFactory.newEvent(uid);
|
||||
const uri = StringUtility.uid('Nextcloud', 'ics');
|
||||
const vevent = VEvent(null, comp, uri);
|
||||
const simple = vevent.getSimpleEvent();
|
||||
|
||||
simple.allDay = !start.hasTime() && !end.hasTime();
|
||||
simple.dtstart = {
|
||||
type: start.hasTime() ? 'datetime' : 'date',
|
||||
value: start,
|
||||
parameters: {
|
||||
zone: timezone
|
||||
}
|
||||
};
|
||||
simple.dtend = {
|
||||
type: end.hasTime() ? 'datetime' : 'date',
|
||||
value: end,
|
||||
parameters: {
|
||||
zone: timezone
|
||||
}
|
||||
};
|
||||
simple.patch();
|
||||
|
||||
return vevent;
|
||||
};
|
||||
|
||||
return VEvent;
|
||||
});
|
|
@ -1,157 +0,0 @@
|
|||
/**
|
||||
* ownCloud - Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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.factory('WebCal', function($http, Calendar, VEvent, TimezoneService, WebCalService, WebCalUtility) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* instantiate a webcal object
|
||||
* @param {object} CalendarService
|
||||
* @param {string} url
|
||||
* @param {object} props
|
||||
* @returns {object}
|
||||
* @constructor
|
||||
*/
|
||||
function WebCal(CalendarService, url, props) {
|
||||
const context = {
|
||||
calendarService: CalendarService,
|
||||
updatedProperties: [],
|
||||
storedUrl: props.href, //URL stored in CalDAV
|
||||
url: WebCalUtility.fixURL(props.href)
|
||||
};
|
||||
|
||||
const iface = Calendar(CalendarService, url, props);
|
||||
iface._isAWebCalObject = true;
|
||||
|
||||
context.setUpdated = function(property) {
|
||||
if (context.updatedProperties.indexOf(property) === -1) {
|
||||
context.updatedProperties.push(property);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(iface, {
|
||||
downloadUrl: {
|
||||
get: function() {
|
||||
return context.url;
|
||||
}
|
||||
},
|
||||
storedUrl: {
|
||||
get: function () {
|
||||
return context.storedUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iface.fcEventSource.events = function (start, end, timezone, callback) {
|
||||
var fcAPI = this;
|
||||
iface.fcEventSource.isRendering = true;
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
|
||||
const allowDowngradeToHttp = !context.storedUrl.startsWith('https://');
|
||||
|
||||
const TimezoneServicePromise = TimezoneService.get(timezone);
|
||||
const WebCalServicePromise = WebCalService.get(context.url, allowDowngradeToHttp);
|
||||
Promise.all([TimezoneServicePromise, WebCalServicePromise]).then(function(results) {
|
||||
const [tz, response] = results;
|
||||
const promises = [];
|
||||
let vevents = [];
|
||||
|
||||
response.vevents.forEach((ics) => {
|
||||
try {
|
||||
const vevent = VEvent.fromRawICS(iface, ics);
|
||||
const promise = vevent.getFcEvent(start, end, tz).then((vevent) => {
|
||||
vevents = vevents.concat(vevent);
|
||||
}).catch((reason) => {
|
||||
iface.addWarning(reason);
|
||||
console.log(event, reason);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
} catch(e) {
|
||||
// catch errors in VEvent.fromRawICS
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
callback(vevents);
|
||||
fcAPI.eventManager.currentPeriod.release();
|
||||
|
||||
iface.fcEventSource.isRendering = false;
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
});
|
||||
}).catch(function(reason) {
|
||||
if (reason === 'Unknown timezone' && timezone !== 'UTC') {
|
||||
const eventsFn = iface.fcEventSource.events.bind(fcAPI);
|
||||
eventsFn(start, end, 'UTC', callback);
|
||||
} else if (reason.redirect === true) {
|
||||
if (context.storedUrl === reason.new_url) {
|
||||
return Promise.reject('Fatal error. Redirected URL matched original URL. Aborting');
|
||||
}
|
||||
|
||||
context.storedUrl = reason.new_url;
|
||||
context.url = reason.new_url;
|
||||
context.setUpdated('storedUrl');
|
||||
iface.update();
|
||||
const eventsFn = iface.fcEventSource.events.bind(fcAPI);
|
||||
eventsFn(start, end, timezone, callback);
|
||||
} else {
|
||||
callback([]);
|
||||
fcAPI.eventManager.currentPeriod.release();
|
||||
|
||||
iface.addWarning(reason);
|
||||
console.log(reason);
|
||||
iface.fcEventSource.isRendering = false;
|
||||
iface.emit(Calendar.hookFinishedRendering);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
iface.eventsAccessibleViaCalDAV = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
const parentGetUpdated = iface.getUpdated;
|
||||
iface.getUpdated = function() {
|
||||
const updated = parentGetUpdated();
|
||||
return updated.concat(context.updatedProperties);
|
||||
};
|
||||
|
||||
const parentResetUpdated = iface.resetUpdated;
|
||||
iface.resetUpdated = function() {
|
||||
parentResetUpdated();
|
||||
context.updatedProperties = [];
|
||||
};
|
||||
|
||||
iface.delete = function() {
|
||||
localStorage.removeItem(iface.storedUrl);
|
||||
return context.calendarService.delete(iface);
|
||||
};
|
||||
|
||||
return iface;
|
||||
}
|
||||
|
||||
WebCal.isWebCal = function(obj) {
|
||||
return (typeof obj === 'object' && obj !== null && obj._isAWebCalObject === true);
|
||||
};
|
||||
|
||||
return WebCal;
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('AutoCompletionService', ['$rootScope', '$http',
|
||||
function ($rootScope, $http) {
|
||||
'use strict';
|
||||
|
||||
this.searchAttendee = function(name) {
|
||||
return $http.post($rootScope.baseUrl + 'autocompletion/attendee', {
|
||||
search: name
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
});
|
||||
};
|
||||
|
||||
this.searchLocation = function(address) {
|
||||
return $http.post($rootScope.baseUrl + 'autocompletion/location', {
|
||||
location: address
|
||||
}).then(function (response) {
|
||||
return response.data;
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,689 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author Vinicius Cubas Brand
|
||||
* @author Daniel Tygel
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2017 Vinicius Cubas Brand <vinicius@eita.org.br>
|
||||
* @copyright 2017 Daniel Tygel <dtygel@eita.org.br>
|
||||
*
|
||||
* 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('CalendarService', function(DavClient, StringUtility, XMLUtility, CalendarFactory, isPublic, constants) {
|
||||
'use strict';
|
||||
|
||||
const context = {
|
||||
self: this,
|
||||
calendarHome: null,
|
||||
userPrincipal: null,
|
||||
usedURLs: []
|
||||
};
|
||||
const privateAPI = {};
|
||||
// this is for testing purposes and testing purposes only
|
||||
// don't you dare to call CalendarService.privateAPI.foo()
|
||||
this.privateAPI = privateAPI;
|
||||
|
||||
const PROPERTIES = [
|
||||
'{' + DavClient.NS_DAV + '}displayname',
|
||||
'{' + DavClient.NS_DAV + '}resourcetype',
|
||||
'{' + DavClient.NS_IETF + '}calendar-description',
|
||||
'{' + DavClient.NS_IETF + '}calendar-timezone',
|
||||
'{' + DavClient.NS_APPLE + '}calendar-order',
|
||||
'{' + DavClient.NS_APPLE + '}calendar-color',
|
||||
'{' + DavClient.NS_IETF + '}supported-calendar-component-set',
|
||||
'{' + DavClient.NS_CALENDARSERVER + '}publish-url',
|
||||
'{' + DavClient.NS_CALENDARSERVER + '}allowed-sharing-modes',
|
||||
'{' + DavClient.NS_OWNCLOUD + '}calendar-enabled',
|
||||
'{' + DavClient.NS_DAV + '}acl',
|
||||
'{' + DavClient.NS_DAV + '}owner',
|
||||
'{' + DavClient.NS_OWNCLOUD + '}invite',
|
||||
'{' + DavClient.NS_CALENDARSERVER + '}source',
|
||||
'{' + DavClient.NS_NEXTCLOUD + '}owner-displayname'
|
||||
];
|
||||
|
||||
const CALENDAR_IDENTIFIER = '{' + DavClient.NS_IETF + '}calendar';
|
||||
const WEBCAL_IDENTIFIER = '{' + DavClient.NS_CALENDARSERVER + '}subscribed';
|
||||
|
||||
const UPDATABLE_PROPERTIES = [
|
||||
'color',
|
||||
'displayname',
|
||||
'enabled',
|
||||
'order',
|
||||
'storedUrl'
|
||||
];
|
||||
|
||||
const UPDATABLE_PROPERTIES_MAP = {
|
||||
color: [DavClient.NS_APPLE, 'a:calendar-color'],
|
||||
displayname: [DavClient.NS_DAV, 'd:displayname'],
|
||||
enabled: [DavClient.NS_OWNCLOUD, 'o:calendar-enabled'],
|
||||
order: [DavClient.NS_APPLE, 'a:calendar-order']
|
||||
};
|
||||
|
||||
const SHARE_USER = constants.SHARE_TYPE_USER;
|
||||
const SHARE_GROUP = constants.SHARE_TYPE_GROUP;
|
||||
const SHARE_CIRCLE = constants.SHARE_TYPE_CIRCLE;
|
||||
|
||||
context.bootPromise = (function() {
|
||||
if (isPublic) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const url = DavClient.buildUrl(OC.linkToRemoteBase('dav'));
|
||||
const properties = [
|
||||
'{' + DavClient.NS_DAV + '}current-user-principal'
|
||||
];
|
||||
const depth = 0;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.propFind(url, properties, depth, headers).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status) || response.body.propStat.length < 1) {
|
||||
throw new Error('current-user-principal could not be determined');
|
||||
}
|
||||
|
||||
const props = response.body.propStat[0].properties;
|
||||
context.userPrincipal = props['{' + DavClient.NS_DAV + '}current-user-principal'][0].textContent;
|
||||
|
||||
const url = context.userPrincipal;
|
||||
const properties = [
|
||||
'{' + DavClient.NS_IETF + '}calendar-home-set'
|
||||
];
|
||||
const depth = 0;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.propFind(url, properties, depth, headers).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status) || response.body.propStat.length < 1) {
|
||||
throw new Error('calendar-home-set could not be determind');
|
||||
}
|
||||
|
||||
const props = response.body.propStat[0].properties;
|
||||
context.calendarHome = props['{' + DavClient.NS_IETF + '}calendar-home-set'][0].textContent;
|
||||
});
|
||||
});
|
||||
}());
|
||||
|
||||
context.getResourceType = function(body) {
|
||||
const resourceTypes = body.propStat[0].properties['{' + DavClient.NS_DAV + '}resourcetype'];
|
||||
if (!resourceTypes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resourceType = resourceTypes.find(function(resourceType) {
|
||||
const name = DavClient.getNodesFullName(resourceType);
|
||||
return [
|
||||
CALENDAR_IDENTIFIER,
|
||||
WEBCAL_IDENTIFIER
|
||||
].indexOf(name) !== -1;
|
||||
});
|
||||
|
||||
if (!resourceType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DavClient.getNodesFullName(resourceType);
|
||||
};
|
||||
|
||||
context.getShareValue = function(shareType, shareWith) {
|
||||
if (shareType !== SHARE_USER && shareType !== SHARE_GROUP && shareType !== SHARE_CIRCLE) {
|
||||
throw new Error('Unknown shareType given');
|
||||
}
|
||||
|
||||
let hrefValue;
|
||||
if (shareType === SHARE_USER) {
|
||||
hrefValue = 'principal:principals/users/';
|
||||
} else if (shareType === SHARE_GROUP) {
|
||||
hrefValue = 'principal:principals/groups/';
|
||||
} else {
|
||||
hrefValue = 'principal:principals/circles/';
|
||||
}
|
||||
hrefValue += shareWith;
|
||||
|
||||
return hrefValue;
|
||||
};
|
||||
|
||||
context.isURIAvailable = function(suggestedUri) {
|
||||
const uriToCheck = context.calendarHome + suggestedUri + '/';
|
||||
return (context.usedURLs.indexOf(uriToCheck) === -1);
|
||||
};
|
||||
|
||||
/**
|
||||
* get all calendars a user has access to
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.getAll = function() {
|
||||
return context.bootPromise.then(function() {
|
||||
const url = DavClient.buildUrl(context.calendarHome);
|
||||
const depth = 1;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.propFind(url, PROPERTIES, depth, headers).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
throw new Error('Loading calendars failed');
|
||||
}
|
||||
const calendars = [];
|
||||
|
||||
response.body.forEach(function(body) {
|
||||
if (body.propStat.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remember that url is already used
|
||||
context.usedURLs.push(body.href);
|
||||
|
||||
const responseCode = DavClient.getResponseCodeFromHTTPResponse(body.propStat[0].status);
|
||||
if (!DavClient.wasRequestSuccessful(responseCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceType = context.getResourceType(body);
|
||||
if (resourceType === CALENDAR_IDENTIFIER) {
|
||||
const calendar = CalendarFactory.calendar(privateAPI, body, context.userPrincipal);
|
||||
calendars.push(calendar);
|
||||
} else if (resourceType === WEBCAL_IDENTIFIER) {
|
||||
const webcal = CalendarFactory.webcal(privateAPI, body, context.userPrincipal);
|
||||
calendars.push(webcal);
|
||||
}
|
||||
});
|
||||
|
||||
return calendars.filter((calendar) => calendar.components.vevent === true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get a certain calendar by its url
|
||||
* @param {string} calendarUrl
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.get = function(calendarUrl) {
|
||||
return context.bootPromise.then(function() {
|
||||
const url = DavClient.buildUrl(calendarUrl);
|
||||
const depth = 0;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.propFind(url, PROPERTIES, depth, headers).then(function(response) {
|
||||
const body = response.body;
|
||||
if (body.propStat.length < 1) {
|
||||
throw new Error('Loading requested calendar failed');
|
||||
}
|
||||
|
||||
const responseCode = DavClient.getResponseCodeFromHTTPResponse(body.propStat[0].status);
|
||||
if (!DavClient.wasRequestSuccessful(responseCode)) {
|
||||
throw new Error('Loading requested calendar failed');
|
||||
}
|
||||
|
||||
const resourceType = context.getResourceType(body);
|
||||
if (resourceType === CALENDAR_IDENTIFIER) {
|
||||
return CalendarFactory.calendar(privateAPI, body, context.userPrincipal);
|
||||
} else if (resourceType === WEBCAL_IDENTIFIER) {
|
||||
return CalendarFactory.webcal(privateAPI, body, context.userPrincipal);
|
||||
}
|
||||
}).then(function(calendar) {
|
||||
if (calendar.components.vevent === false) {
|
||||
throw new Error('Requested calendar exists, but does not qualify for storing events');
|
||||
}
|
||||
|
||||
return calendar;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get a public calendar by its public sharing token
|
||||
* @param {string} token
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.getPublicCalendar = function(token) {
|
||||
const urlPart = OC.linkToRemoteBase('dav') + '/public-calendars/' + token;
|
||||
|
||||
const url = DavClient.buildUrl(urlPart);
|
||||
const depth = 0;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.propFind(url, PROPERTIES, depth, headers).then(function(response) {
|
||||
const body = response.body;
|
||||
if (body.propStat.length < 1) {
|
||||
throw new Error('Loading requested calendar failed');
|
||||
}
|
||||
|
||||
const responseCode = DavClient.getResponseCodeFromHTTPResponse(body.propStat[0].status);
|
||||
if (!DavClient.wasRequestSuccessful(responseCode)) {
|
||||
throw new Error('Loading requested calendar failed');
|
||||
}
|
||||
|
||||
return CalendarFactory.calendar(privateAPI, body, '', true);
|
||||
}).then(function(calendar) {
|
||||
if (calendar.components.vevent === false) {
|
||||
throw new Error('Requested calendar exists, but does not qualify for storing events');
|
||||
}
|
||||
|
||||
return calendar;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* creates a new calendar
|
||||
* @param {string} name
|
||||
* @param {string} color
|
||||
* @param {string[]} components
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.create = function(name, color, components=['vevent', 'vtodo']) {
|
||||
return context.bootPromise.then(function() {
|
||||
const [skeleton, dPropChildren] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_DAV, 'd:mkcol'], [DavClient.NS_DAV, 'd:set'],
|
||||
[DavClient.NS_DAV, 'd:prop']);
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:resourcetype'],
|
||||
children: [{
|
||||
name: [DavClient.NS_DAV, 'd:collection']
|
||||
}, {
|
||||
name: [DavClient.NS_IETF, 'c:calendar']
|
||||
}]
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:displayname'],
|
||||
value: name
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_APPLE, 'a:calendar-color'],
|
||||
value: color
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_OWNCLOUD, 'o:calendar-enabled'],
|
||||
value: '1'
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_IETF, 'c:supported-calendar-component-set'],
|
||||
children: components.map(function(component) {
|
||||
return {
|
||||
name: [DavClient.NS_IETF, 'c:comp'],
|
||||
attributes: [
|
||||
['name', component.toUpperCase()]
|
||||
]
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
const method = 'MKCOL';
|
||||
const uri = StringUtility.uri(name, context.isURIAvailable);
|
||||
const url = context.calendarHome + uri + '/';
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
'requesttoken' : OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (response.status !== 201) {
|
||||
throw new Error('Creating a calendar failed');
|
||||
}
|
||||
|
||||
// remember that url is now used
|
||||
context.usedURLs.push(url);
|
||||
|
||||
// previously we set enabled to true,
|
||||
// because the Nextcloud server doesn't allow
|
||||
// storing custom properties on creation,
|
||||
// but this calendar will be owned by the user
|
||||
// and thereby automatically be visible
|
||||
// no need to send a request
|
||||
return context.self.get(url);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* creates a new subscription
|
||||
* @param {string} name
|
||||
* @param {string} color
|
||||
* @param {string} source
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.createWebCal = function(name, color, source) {
|
||||
return context.bootPromise.then(function() {
|
||||
const [skeleton, dPropChildren] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_DAV, 'd:mkcol'], [DavClient.NS_DAV, 'd:set'],
|
||||
[DavClient.NS_DAV, 'd:prop']);
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:resourcetype'],
|
||||
children: [{
|
||||
name: [DavClient.NS_DAV, 'd:collection']
|
||||
}, {
|
||||
name: [DavClient.NS_CALENDARSERVER, 'cs:subscribed']
|
||||
}]
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:displayname'],
|
||||
value: name
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_APPLE, 'a:calendar-color'],
|
||||
value: color
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_OWNCLOUD, 'o:calendar-enabled'],
|
||||
value: '1'
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_CALENDARSERVER, 'cs:source'],
|
||||
children: [{
|
||||
name: [DavClient.NS_DAV, 'd:href'],
|
||||
value: source
|
||||
}]
|
||||
});
|
||||
|
||||
const method = 'MKCOL';
|
||||
const uri = StringUtility.uri(name, context.isURIAvailable);
|
||||
const url = context.calendarHome + uri + '/';
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
'requesttoken' : OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (response.status !== 201) {
|
||||
throw new Error('Creating a webcal subscription failed');
|
||||
}
|
||||
|
||||
// remember that url is now used
|
||||
context.usedURLs.push(url);
|
||||
|
||||
return context.self.get(url);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get properties for a calendar without instantiating a new calendar/webcal object
|
||||
* @param {Calendar|WebCal} calendar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.get = function(calendar) {
|
||||
// TODO in a follow up PR
|
||||
};
|
||||
|
||||
/**
|
||||
* updates a calendar or a webcal subscription
|
||||
* @param {Calendar|WebCal} calendar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.update = function(calendar) {
|
||||
const updatedProperties = calendar.getUpdated();
|
||||
// nothing changed, so why bother to send a http request?
|
||||
if (updatedProperties.length === 0) {
|
||||
return Promise.resolve(calendar);
|
||||
}
|
||||
|
||||
const [skeleton, dPropChildren] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_DAV, 'd:propertyupdate'], [DavClient.NS_DAV, 'd:set'],
|
||||
[DavClient.NS_DAV, 'd:prop']);
|
||||
updatedProperties.forEach(function(name) {
|
||||
if (UPDATABLE_PROPERTIES.indexOf(name) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = calendar[name];
|
||||
if (name === 'enabled') {
|
||||
value = value ? '1' : '0';
|
||||
}
|
||||
|
||||
if (name === 'storedUrl') {
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_CALENDARSERVER, 'cs:source'],
|
||||
children: [{
|
||||
name: [DavClient.NS_DAV, 'd:href'],
|
||||
value: value
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
dPropChildren.push({
|
||||
name: UPDATABLE_PROPERTIES_MAP[name],
|
||||
value
|
||||
});
|
||||
}
|
||||
});
|
||||
calendar.resetUpdated();
|
||||
|
||||
const method = 'PROPPATCH';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
'requesttoken' : OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
throw new Error('Updating calendar failed');
|
||||
}
|
||||
|
||||
return calendar;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* delete a calendar or a webcal subscription
|
||||
* @param {Calendar|WebCal} calendar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.delete = function(calendar) {
|
||||
const method = 'DELETE';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.request(method, url, headers).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
throw new Error('Deleting calendar failed');
|
||||
}
|
||||
|
||||
// remove deleted calendar's url from usedURLs
|
||||
const index = context.usedURLs.indexOf(url);
|
||||
context.usedURLs.splice(index, 1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* share a calendar or update a calendar share
|
||||
* @param {Calendar|WebCal} calendar
|
||||
* @param {number} shareType
|
||||
* @param {string} shareWith
|
||||
* @param {string} shareWithDisplayname
|
||||
* @param {boolean} writable
|
||||
* @param {boolean} existingShare
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.share = function(calendar, shareType, shareWith, shareWithDisplayname, writable, existingShare) {
|
||||
const [skeleton, oSetChildren] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_OWNCLOUD, 'o:share'], [DavClient.NS_OWNCLOUD, 'o:set']);
|
||||
|
||||
const hrefValue = context.getShareValue(shareType, shareWith);
|
||||
oSetChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:href'],
|
||||
value: hrefValue
|
||||
});
|
||||
oSetChildren.push({
|
||||
name: [DavClient.NS_OWNCLOUD, 'o:summary'],
|
||||
value: t('calendar', '{calendar} shared by {owner}', {
|
||||
calendar: calendar.displayname,
|
||||
owner: calendar.owner
|
||||
})
|
||||
});
|
||||
if (writable) {
|
||||
oSetChildren.push({
|
||||
name: [DavClient.NS_OWNCLOUD, 'o:read-write']
|
||||
});
|
||||
}
|
||||
|
||||
const method = 'POST';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
'requesttoken' : OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
throw new Error('Sharing calendar failed');
|
||||
}
|
||||
|
||||
if (existingShare) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shareType === SHARE_USER) {
|
||||
calendar.shares.users.push({
|
||||
id: shareWith,
|
||||
displayname: shareWithDisplayname,
|
||||
writable: writable
|
||||
});
|
||||
} else if (shareType === SHARE_GROUP) {
|
||||
calendar.shares.groups.push({
|
||||
id: shareWith,
|
||||
displayname: shareWithDisplayname,
|
||||
writable: writable
|
||||
});
|
||||
} else {
|
||||
calendar.shares.circles.push({
|
||||
id: shareWith,
|
||||
displayname: shareWithDisplayname,
|
||||
writable: writable
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* unshare a calendar
|
||||
* @param {Calendar|WebCal} calendar
|
||||
* @param {number} shareType
|
||||
* @param {string} shareWith
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.unshare = function(calendar, shareType, shareWith) {
|
||||
const [skeleton, oRemoveChildren] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_OWNCLOUD, 'o:share'], [DavClient.NS_OWNCLOUD, 'o:remove']);
|
||||
|
||||
const hrefValue = context.getShareValue(shareType, shareWith);
|
||||
oRemoveChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:href'],
|
||||
value: hrefValue
|
||||
});
|
||||
|
||||
const method = 'POST';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
'requesttoken' : OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
throw new Error('Sharing calendar failed');
|
||||
}
|
||||
|
||||
if (shareType === SHARE_USER) {
|
||||
const index = calendar.shares.users.findIndex(function(user) {
|
||||
return user.id === shareWith;
|
||||
});
|
||||
calendar.shares.users.splice(index, 1);
|
||||
} else if (shareType === SHARE_GROUP) {
|
||||
const index = calendar.shares.groups.findIndex(function(group) {
|
||||
return group.id === shareWith;
|
||||
});
|
||||
calendar.shares.groups.splice(index, 1);
|
||||
} else {
|
||||
const index = calendar.shares.circles.findIndex(function(circle) {
|
||||
return circle.id === shareWith;
|
||||
});
|
||||
calendar.shares.circles.splice(index, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* publish a calendar
|
||||
* @param {Calendar} calendar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.publish = function(calendar) {
|
||||
const [skeleton] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_CALENDARSERVER, 'cs:publish-calendar']);
|
||||
|
||||
const method = 'POST';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
requesttoken : oc_requesttoken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
//throw new Error('Publishing calendar failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// eventually remove this return true
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* unpublish a calendar
|
||||
* @param {Calendar} calendar
|
||||
* @returns {Promise}
|
||||
*/
|
||||
privateAPI.unpublish = function(calendar) {
|
||||
const [skeleton] = XMLUtility.getRootSkeleton(
|
||||
[DavClient.NS_CALENDARSERVER, 'cs:unpublish-calendar']);
|
||||
|
||||
const method = 'POST';
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type' : 'application/xml; charset=utf-8',
|
||||
requesttoken : oc_requesttoken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request(method, url, headers, xml).then(function(response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
//throw new Error('Unpublishing calendar failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// eventually remove this return true
|
||||
return true;
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('DavClient', function($window) {
|
||||
'use strict';
|
||||
|
||||
const client = new dav.Client({
|
||||
baseUrl: OC.linkToRemote('dav/calendars'),
|
||||
xmlNamespaces: {
|
||||
'DAV:': 'd',
|
||||
'urn:ietf:params:xml:ns:caldav': 'c',
|
||||
'http://apple.com/ns/ical/': 'aapl',
|
||||
'http://owncloud.org/ns': 'oc',
|
||||
'http://nextcloud.com/ns': 'nc',
|
||||
'http://calendarserver.org/ns/': 'cs'
|
||||
}
|
||||
});
|
||||
|
||||
client.NS_DAV = 'DAV:';
|
||||
client.NS_IETF = 'urn:ietf:params:xml:ns:caldav';
|
||||
client.NS_APPLE = 'http://apple.com/ns/ical/';
|
||||
client.NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
client.NS_NEXTCLOUD = 'http://nextcloud.com/ns';
|
||||
client.NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
|
||||
|
||||
/**
|
||||
* get absolute url for path
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
client.buildUrl = function(path) {
|
||||
if (path.substr(0,1) !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
return $window.location.origin + path;
|
||||
};
|
||||
|
||||
/**
|
||||
* get a nodes full name including its namespace
|
||||
* @param {Node} node
|
||||
* @returns {string}
|
||||
*/
|
||||
client.getNodesFullName = function(node) {
|
||||
return '{' + node.namespaceURI + '}' + node.localName;
|
||||
};
|
||||
|
||||
/**
|
||||
* get response code from http response
|
||||
* @param {string} t
|
||||
* @returns {Number}
|
||||
*/
|
||||
client.getResponseCodeFromHTTPResponse = function(t) {
|
||||
return parseInt(t.split(' ')[1]);
|
||||
};
|
||||
|
||||
/**
|
||||
* check if request was successful
|
||||
* @param {Number} status
|
||||
* @returns {boolean}
|
||||
*/
|
||||
client.wasRequestSuccessful = function(status) {
|
||||
return (status >= 200 && status <= 299);
|
||||
};
|
||||
|
||||
return client;
|
||||
});
|
|
@ -1,195 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('EventsEditorDialogService', function($uibModal, constants, settings) {
|
||||
'use strict';
|
||||
|
||||
const EDITOR_POPOVER = 'eventspopovereditor.html';
|
||||
const EDITOR_SIDEBAR = 'eventssidebareditor.html';
|
||||
const REPEAT_QUESTION = ''; // TODO in followup PR
|
||||
|
||||
const context = {
|
||||
fcEvent: null,
|
||||
promise: null,
|
||||
eventModal: null
|
||||
};
|
||||
|
||||
/**
|
||||
* cleanup context variables after dialog was closed
|
||||
*/
|
||||
context.cleanup = () => {
|
||||
context.fcEvent = null;
|
||||
context.promise = null;
|
||||
context.eventModal = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* checks if popover should be shown based
|
||||
* on viewport dimensions
|
||||
* @returns {boolean}
|
||||
*/
|
||||
context.showPopover = () => {
|
||||
return angular.element(window).width() > 768;
|
||||
};
|
||||
|
||||
/**
|
||||
* set position of popover based on previously calculated position
|
||||
* @param {object[]} position
|
||||
*/
|
||||
context.positionPopover = (position) => {
|
||||
angular.element('#popover-container').css('display', 'none');
|
||||
angular.forEach(position, (v) => {
|
||||
angular.element('.modal').css(v.name, v.value);
|
||||
});
|
||||
angular.element('#popover-container').css('display', 'block');
|
||||
};
|
||||
|
||||
/**
|
||||
* open dialog for editing events
|
||||
* @param {string} template - use EDITOR_POPOVER or EDITOR_SIDEBAR
|
||||
* @param {resolveCallback} resolve
|
||||
* @param {rejectCallback} reject
|
||||
* @param {unlockCallback} unlock
|
||||
* @param {object[]} position
|
||||
* @param {object} scope
|
||||
* @param {FcEvent} fcEvent
|
||||
* @param {SimpleEvent} simpleEvent
|
||||
* @param {Calendar} calendar
|
||||
*/
|
||||
context.openDialog = (template, resolve, reject, unlock, position, scope, fcEvent, simpleEvent, calendar) => {
|
||||
context.fcEvent = fcEvent;
|
||||
context.eventModal = $uibModal.open({
|
||||
appendTo: (template === EDITOR_POPOVER) ?
|
||||
angular.element('#popover-container') :
|
||||
angular.element('#app-content'),
|
||||
controller: 'EditorController',
|
||||
resolve: {
|
||||
vevent: () => fcEvent.vevent,
|
||||
simpleEvent: () => simpleEvent,
|
||||
calendar: () => calendar,
|
||||
isNew: () => (fcEvent.vevent.etag === null || fcEvent.vevent.etag === ''),
|
||||
emailAddress: () => constants.emailAddress
|
||||
},
|
||||
scope: scope,
|
||||
templateUrl: template,
|
||||
windowClass: (template === EDITOR_POPOVER) ? 'popover' : null
|
||||
});
|
||||
|
||||
if (template === EDITOR_SIDEBAR) {
|
||||
angular.element('#app-content').addClass('with-app-sidebar');
|
||||
}
|
||||
|
||||
context.eventModal.rendered.then(() => context.positionPopover(position));
|
||||
context.eventModal.result.then((result) => {
|
||||
if (result.action === 'proceed') {
|
||||
context.openDialog(EDITOR_SIDEBAR, resolve, reject, unlock, [], scope, fcEvent, simpleEvent, result.calendar);
|
||||
} else {
|
||||
if (template === EDITOR_SIDEBAR) {
|
||||
angular.element('#app-content').removeClass('with-app-sidebar');
|
||||
}
|
||||
|
||||
unlock();
|
||||
context.cleanup();
|
||||
resolve({
|
||||
calendar: result.calendar,
|
||||
vevent: result.vevent
|
||||
});
|
||||
}
|
||||
}).catch((reason) => {
|
||||
if (template === EDITOR_SIDEBAR) {
|
||||
angular.element('#app-content').removeClass('with-app-sidebar');
|
||||
}
|
||||
|
||||
if (reason !== 'superseded') {
|
||||
context.cleanup();
|
||||
}
|
||||
|
||||
unlock();
|
||||
reject(reason);
|
||||
});
|
||||
};
|
||||
|
||||
context.openRepeatQuestion = () => {
|
||||
// TODO in followup PR
|
||||
};
|
||||
|
||||
/**
|
||||
* open dialog for editing events
|
||||
* @param {object} scope
|
||||
* @param {FcEvent} fcEvent
|
||||
* @param {positionCallback} calculatePosition
|
||||
* @param {lockCallback} lock
|
||||
* @param {unlockCallback} unlock
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.open = function(scope, fcEvent, calculatePosition, lock, unlock) {
|
||||
// don't reload editor for the same event
|
||||
if (context.fcEvent === fcEvent) {
|
||||
return context.promise;
|
||||
}
|
||||
|
||||
// dismiss existing dialogs
|
||||
if (context.fcEvent) {
|
||||
context.eventModal.dismiss('superseded');
|
||||
}
|
||||
|
||||
context.promise = new Promise(function(resolve, reject) {
|
||||
// calculate position of popover
|
||||
// needs to happen before locking event
|
||||
const position = calculatePosition();
|
||||
|
||||
// lock new fcEvent
|
||||
lock();
|
||||
|
||||
const calendar = (fcEvent.vevent) ? fcEvent.vevent.calendar : null;
|
||||
const simpleEvent = fcEvent.getSimpleEvent();
|
||||
|
||||
// skip popover on small devices
|
||||
if (context.showPopover() && !settings.skipPopover) {
|
||||
context.openDialog(EDITOR_POPOVER, resolve, reject, unlock, position, scope, fcEvent, simpleEvent, calendar);
|
||||
} else {
|
||||
context.openDialog(EDITOR_SIDEBAR, resolve, reject, unlock, [], scope, fcEvent, simpleEvent, calendar);
|
||||
}
|
||||
});
|
||||
|
||||
return context.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* @callback resolveCallback
|
||||
* @param {*} value
|
||||
*/
|
||||
/**
|
||||
* @callback rejectCallback
|
||||
* @param {*} reason
|
||||
*/
|
||||
/**
|
||||
* @callback positionCallback
|
||||
*/
|
||||
/**
|
||||
* @callback lockCallback
|
||||
*/
|
||||
/**
|
||||
* @callback unlockCallback
|
||||
*/
|
||||
});
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('HashService', function ($location) {
|
||||
'use strict';
|
||||
|
||||
const context = {
|
||||
hashId: null,
|
||||
parameters: new Map(),
|
||||
};
|
||||
|
||||
(function() {
|
||||
let hash = $location.url();
|
||||
|
||||
if (!hash || hash === '') {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (hash.startsWith('#')) {
|
||||
hash = hash.substr(1);
|
||||
}
|
||||
if (hash.startsWith('/')) {
|
||||
hash = hash.substr(1);
|
||||
}
|
||||
|
||||
|
||||
// the hashes must comply with the following convention
|
||||
// #id?param1=value1¶m2=value2& ... ¶mN=valueN
|
||||
// e.g.:
|
||||
// #go_to_date?date=2016-12-31
|
||||
// #go_to_event?calendar=work&uri=Nextcloud-23as123asf12.ics
|
||||
// #search?term=foobar
|
||||
// #subscribe_to_webcal?url=https%3A%2F%2Fwww.foo.bar%2F
|
||||
//
|
||||
// hashes without a question mark after the id will be ignored
|
||||
if (!hash.includes('?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const questionMarkPosition = hash.indexOf('?');
|
||||
context.hashId = hash.substr(0, questionMarkPosition);
|
||||
|
||||
const parameters = hash.substr(questionMarkPosition + 1);
|
||||
parameters.split('&').forEach((part) => {
|
||||
const [key, value] = part.split('=');
|
||||
context.parameters.set(key, decodeURIComponent(value));
|
||||
});
|
||||
}());
|
||||
|
||||
/**
|
||||
* register a handler for a certain hash id
|
||||
* @param {string} id
|
||||
* @param {function} callback
|
||||
*/
|
||||
this.runIfApplicable = (id, callback) => {
|
||||
if (id === context.hashId) {
|
||||
callback(context.parameters);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author Bernhard Posselt
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2016 Bernhard Posselt <dev@bernhard-posselt.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.factory('is', function () {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
loading: false
|
||||
};
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* 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('MailerService', ['$rootScope', 'DavClient',
|
||||
function ($rootScope, DavClient) {
|
||||
'use strict';
|
||||
|
||||
this.sendMail = function (dest, url, name) {
|
||||
var headers = {
|
||||
'Content-Type' : 'application/json; charset=utf-8',
|
||||
requesttoken : oc_requesttoken
|
||||
};
|
||||
var mailBody = {
|
||||
'recipient': dest,
|
||||
'url': url,
|
||||
'calendarName': name
|
||||
};
|
||||
return DavClient.request('POST', $rootScope.baseUrl + 'public/sendmail', headers, JSON.stringify(mailBody));
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('SettingsService', ['$rootScope', '$http', function($rootScope, $http) {
|
||||
'use strict';
|
||||
|
||||
this.getView = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
params: {key: 'view'}
|
||||
}).then(function(response) {
|
||||
return response.data.value;
|
||||
});
|
||||
};
|
||||
|
||||
this.setView = function(view) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
data: {
|
||||
key: 'view',
|
||||
value: view
|
||||
}
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
this.getSkipPopover = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
params: {key: 'skipPopover'}
|
||||
}).then(function(response) {
|
||||
return response.data.value;
|
||||
});
|
||||
};
|
||||
|
||||
this.setSkipPopover = function(value) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
data: {
|
||||
key: 'skipPopover',
|
||||
value: value
|
||||
}
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
this.getShowWeekNr = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
params: {key: 'showWeekNr'}
|
||||
}).then(function(response) {
|
||||
return response.data.value;
|
||||
});
|
||||
};
|
||||
|
||||
this.setShowWeekNr = function(value) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
data: {
|
||||
key: 'showWeekNr',
|
||||
value: value
|
||||
}
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
this.passedFirstRun = function() {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
data: {
|
||||
key: 'firstRun'
|
||||
}
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
this.getTimezone = function() {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
params: {key: 'timezone'}
|
||||
}).then(function(response) {
|
||||
return response.data.value;
|
||||
});
|
||||
};
|
||||
|
||||
this.setTimezone = function(value) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: $rootScope.baseUrl + 'config',
|
||||
data: {
|
||||
key: 'timezone',
|
||||
value: value
|
||||
}
|
||||
}).then(function() {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -1,98 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('TimezoneService', function (TimezoneDataProvider, Timezone) {
|
||||
'use strict';
|
||||
|
||||
const context = {};
|
||||
|
||||
// List of available timezones
|
||||
const timezoneList = Object.keys(TimezoneDataProvider.zones);
|
||||
|
||||
context.isOlsonTimezone = (tzName) => {
|
||||
const hasSlash = tzName.indexOf('/') !== -1;
|
||||
const hasSpace = tzName.indexOf(' ') !== -1;
|
||||
const startsWithETC = tzName.startsWith('Etc');
|
||||
const startsWithUS = tzName.startsWith('US/');
|
||||
|
||||
return hasSlash && !hasSpace && !startsWithETC && !startsWithUS;
|
||||
};
|
||||
|
||||
/**
|
||||
* get a timezone object by it's id
|
||||
* @param tzid
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.get = function (tzid) {
|
||||
if (TimezoneDataProvider.aliases[tzid]) {
|
||||
tzid = TimezoneDataProvider.aliases[tzid].aliasTo;
|
||||
}
|
||||
|
||||
// GMT maps to UTC, so only check UTC
|
||||
if (tzid === 'UTC') {
|
||||
return Promise.resolve(new Timezone(ICAL.TimezoneService.get('UTC')));
|
||||
} else if (tzid === 'floating') {
|
||||
return Promise.resolve(new Timezone(ICAL.Timezone.localTimezone));
|
||||
}
|
||||
|
||||
if (!TimezoneDataProvider.zones.hasOwnProperty(tzid)) {
|
||||
return Promise.reject('Unknown timezone');
|
||||
}
|
||||
|
||||
const ics = TimezoneDataProvider.zones[tzid].ics;
|
||||
return Promise.resolve(new Timezone(ics));
|
||||
};
|
||||
|
||||
/**
|
||||
* get timezone detected by jstz
|
||||
* @return {string}
|
||||
*/
|
||||
this.getDetected = () => {
|
||||
const tz = jstz.determine();
|
||||
let tzname = tz ? tz.name() : 'UTC';
|
||||
|
||||
if (TimezoneDataProvider.aliases[tzname]) {
|
||||
return TimezoneDataProvider.aliases[tzname].aliasTo;
|
||||
}
|
||||
|
||||
return tzname;
|
||||
};
|
||||
|
||||
/**
|
||||
* list all timezone ids
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.listAll = function () {
|
||||
const olsonAliases = [];
|
||||
angular.forEach(TimezoneDataProvider.aliases, (value, key) => {
|
||||
if (context.isOlsonTimezone(key)) {
|
||||
olsonAliases.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
const timezones = timezoneList.concat(olsonAliases).concat(['UTC']);
|
||||
timezones.sort();
|
||||
|
||||
return Promise.resolve(timezones);
|
||||
};
|
||||
});
|
|
@ -1,232 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('VEventService', function(DavClient, StringUtility, XMLUtility, VEvent) {
|
||||
'use strict';
|
||||
|
||||
const context = {
|
||||
calendarDataPropName: '{' + DavClient.NS_IETF + '}calendar-data',
|
||||
eTagPropName: '{' + DavClient.NS_DAV + '}getetag',
|
||||
self: this
|
||||
};
|
||||
|
||||
/**
|
||||
* get url for event
|
||||
* @param {VEvent} event
|
||||
* @returns {string}
|
||||
*/
|
||||
context.getEventUrl = function(event) {
|
||||
return event.calendar.url + event.uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* get a time-range string from moment object
|
||||
* @param {moment} momentObject
|
||||
* @returns {string}
|
||||
*/
|
||||
context.getTimeRangeString = function(momentObject) {
|
||||
const utc = momentObject.utc();
|
||||
return utc.format('YYYYMMDD') + 'T' + utc.format('HHmmss') + 'Z';
|
||||
};
|
||||
|
||||
/**
|
||||
* get all events from a calendar within a time-range
|
||||
* @param {Calendar} calendar
|
||||
* @param {moment} start
|
||||
* @param {moment} end
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.getAll = function (calendar, start, end) {
|
||||
const [skeleton, dPropChildren] = XMLUtility.getRootSkeleton([DavClient.NS_IETF, 'c:calendar-query']);
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_DAV, 'd:prop'],
|
||||
children: [{
|
||||
name: [DavClient.NS_DAV, 'd:getetag']
|
||||
}, {
|
||||
name: [DavClient.NS_IETF, 'c:calendar-data']
|
||||
}]
|
||||
});
|
||||
dPropChildren.push({
|
||||
name: [DavClient.NS_IETF, 'c:filter'],
|
||||
children: [{
|
||||
name: [DavClient.NS_IETF, 'c:comp-filter'],
|
||||
attributes: [
|
||||
['name', 'VCALENDAR']
|
||||
],
|
||||
children: [{
|
||||
name: [DavClient.NS_IETF, 'c:comp-filter'],
|
||||
attributes: [
|
||||
['name', 'VEVENT']
|
||||
],
|
||||
children: [{
|
||||
name: [DavClient.NS_IETF, 'c:time-range'],
|
||||
attributes: [
|
||||
['start', context.getTimeRangeString(start)],
|
||||
['end', context.getTimeRangeString(end)]
|
||||
]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
const url = calendar.url;
|
||||
const headers = {
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
'Depth': 1,
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
const xml = XMLUtility.serialize(skeleton);
|
||||
|
||||
return DavClient.request('REPORT', url, headers, xml).then(function (response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
const vevents = [];
|
||||
for (let key in response.body) {
|
||||
if (!response.body.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const obj = response.body[key];
|
||||
const props = obj.propStat[0].properties;
|
||||
const calendarData = props[context.calendarDataPropName];
|
||||
const etag = props[context.eTagPropName];
|
||||
const uri = obj.href.substr(obj.href.lastIndexOf('/') + 1);
|
||||
|
||||
try {
|
||||
const vevent = VEvent.fromRawICS(calendar, calendarData, uri, etag);
|
||||
vevents.push(vevent);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
return vevents;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get an event by uri from a calendar
|
||||
* @param {Calendar} calendar
|
||||
* @param {string} uri
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.get = function (calendar, uri) {
|
||||
const url = calendar.url + uri;
|
||||
const headers = {
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.request('GET', url, headers, '').then(function (response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
const calendarData = response.body;
|
||||
const etag = response.xhr.getResponseHeader('ETag');
|
||||
|
||||
try {
|
||||
return VEvent.fromRawICS(calendar, calendarData, uri, etag);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* create a new event
|
||||
* @param {Calendar} calendar
|
||||
* @param {data} data
|
||||
* @param {boolean} returnEvent
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.create = function (calendar, data, returnEvent=true) {
|
||||
const headers = {
|
||||
'Content-Type': 'text/calendar; charset=utf-8',
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
const uri = StringUtility.uid('Nextcloud', 'ics');
|
||||
const url = calendar.url + uri;
|
||||
|
||||
return DavClient.request('PUT', url, headers, data).then(function (response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
if (returnEvent) {
|
||||
return context.self.get(calendar, uri);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* update an event
|
||||
* @param {VEvent} event
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.update = function (event) {
|
||||
const url = context.getEventUrl(event);
|
||||
const headers = {
|
||||
'Content-Type': 'text/calendar; charset=utf-8',
|
||||
'If-Match': event.etag,
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
const payload = event.data;
|
||||
|
||||
return DavClient.request('PUT', url, headers, payload).then(function (response) {
|
||||
if (!DavClient.wasRequestSuccessful(response.status)) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
// update etag of existing event
|
||||
event.etag = response.xhr.getResponseHeader('ETag');
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* delete an event
|
||||
* @param {VEvent} event
|
||||
* @returns {Promise}
|
||||
*/
|
||||
this.delete = function (event) {
|
||||
const url = context.getEventUrl(event);
|
||||
const headers = {
|
||||
'If-Match': event.etag,
|
||||
'requesttoken': OC.requestToken
|
||||
};
|
||||
|
||||
return DavClient.request('DELETE', url, headers, '').then(function (response) {
|
||||
if (DavClient.wasRequestSuccessful(response.status)) {
|
||||
return true;
|
||||
} else {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,91 +0,0 @@
|
|||
/**
|
||||
* ownCloud - Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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('WebCalService', function ($http, ICalSplitterUtility, WebCalUtility, SplittedICal) {
|
||||
'use strict';
|
||||
|
||||
const self = this;
|
||||
const context = {
|
||||
cachedSplittedICals: {}
|
||||
};
|
||||
|
||||
this.get = function(webcalUrl, allowDowngradeToHttp) {
|
||||
if (context.cachedSplittedICals.hasOwnProperty(webcalUrl)) {
|
||||
return Promise.resolve(context.cachedSplittedICals[webcalUrl]);
|
||||
}
|
||||
|
||||
if (allowDowngradeToHttp === undefined) {
|
||||
allowDowngradeToHttp = WebCalUtility.allowDowngrade(webcalUrl);
|
||||
}
|
||||
|
||||
webcalUrl = WebCalUtility.fixURL(webcalUrl);
|
||||
const url = WebCalUtility.buildProxyURL(webcalUrl);
|
||||
|
||||
let localWebcal = JSON.parse(localStorage.getItem(webcalUrl));
|
||||
if (localWebcal && localWebcal.timestamp > new Date().getTime()) {
|
||||
return Promise.resolve(ICalSplitterUtility.split(localWebcal.value));
|
||||
}
|
||||
|
||||
return $http.get(url).then(function(response) {
|
||||
const splitted = ICalSplitterUtility.split(response.data);
|
||||
|
||||
if (!SplittedICal.isSplittedICal(splitted)) {
|
||||
return Promise.reject(t('calendar', 'Please enter a valid WebCal-URL'));
|
||||
}
|
||||
|
||||
context.cachedSplittedICals[webcalUrl] = splitted;
|
||||
localStorage.setItem(webcalUrl, JSON.stringify({value: response.data, timestamp: new Date().getTime() + 7200000})); // That would be two hours in milliseconds
|
||||
|
||||
return splitted;
|
||||
}).catch(function(e) {
|
||||
if (WebCalUtility.downgradePossible(webcalUrl, allowDowngradeToHttp)) {
|
||||
const httpUrl = WebCalUtility.downgradeURL(webcalUrl);
|
||||
|
||||
return self.get(httpUrl, false).then(function(splitted) {
|
||||
context.cachedSplittedICals[webcalUrl] = splitted;
|
||||
return splitted;
|
||||
});
|
||||
}
|
||||
|
||||
if (e.status === 422) {
|
||||
return Promise.reject({
|
||||
error: true,
|
||||
redirect: false,
|
||||
message: e.data.message
|
||||
});
|
||||
} else if(e.status === 400) {
|
||||
return Promise.reject({
|
||||
error: false,
|
||||
redirect: true,
|
||||
new_url: e.data.new_url
|
||||
});
|
||||
} else {
|
||||
return Promise.reject({
|
||||
error: true,
|
||||
redirect: false,
|
||||
message: t('calendar', 'Severe error in webcal proxy. Please contact administrator for more information.')
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,167 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author John Molakvoæ
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2016 John Molakvoæ <fremulon@protonmail.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('ColorUtility', function() {
|
||||
'use strict';
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* List of default colors
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.colors = [];
|
||||
|
||||
/**
|
||||
* generate an appropriate text color based on background color
|
||||
* @param red
|
||||
* @param green
|
||||
* @param blue
|
||||
* @returns {string}
|
||||
*/
|
||||
this.generateTextColorFromRGB = function(red, green, blue) {
|
||||
var brightness = (((red * 299) + (green * 587) + (blue * 114)) / 1000);
|
||||
return (brightness > 130) ? '#000000' : '#FAFAFA';
|
||||
};
|
||||
|
||||
/**
|
||||
* extract decimal values from hex rgb string
|
||||
* @param colorString
|
||||
* @returns {*}
|
||||
*/
|
||||
this.extractRGBFromHexString = function(colorString) {
|
||||
var fallbackColor = {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255
|
||||
}, matchedString;
|
||||
|
||||
if (typeof colorString !== 'string') {
|
||||
return fallbackColor;
|
||||
}
|
||||
|
||||
switch (colorString.length) {
|
||||
case 4:
|
||||
matchedString = colorString.match(/^#([0-9a-f]{3})$/i);
|
||||
return (Array.isArray(matchedString) && matchedString[1]) ? {
|
||||
r: parseInt(matchedString[1].charAt(0), 16) * 0x11,
|
||||
g: parseInt(matchedString[1].charAt(1), 16) * 0x11,
|
||||
b: parseInt(matchedString[1].charAt(2), 16) * 0x11
|
||||
} : fallbackColor;
|
||||
|
||||
case 7:
|
||||
case 9:
|
||||
var regex = new RegExp('^#([0-9a-f]{' + (colorString.length - 1) + '})$', 'i');
|
||||
matchedString = colorString.match(regex);
|
||||
return (Array.isArray(matchedString) && matchedString[1]) ? {
|
||||
r: parseInt(matchedString[1].substr(0, 2), 16),
|
||||
g: parseInt(matchedString[1].substr(2, 2), 16),
|
||||
b: parseInt(matchedString[1].substr(4, 2), 16)
|
||||
} : fallbackColor;
|
||||
|
||||
default:
|
||||
return fallbackColor;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make sure string for Hex always uses two digits
|
||||
* @param str
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
this._ensureTwoDigits = function(str) {
|
||||
return str.length === 1 ? '0' + str : str;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert three Numbers to rgb hex string
|
||||
* @param r
|
||||
* @param g
|
||||
* @param b
|
||||
* @returns {string}
|
||||
*/
|
||||
this.rgbToHex = function(r, g, b) {
|
||||
if(Array.isArray(r)) {
|
||||
[r, g, b] = r;
|
||||
}
|
||||
|
||||
return '#' + this._ensureTwoDigits(parseInt(r, 10).toString(16)) +
|
||||
this._ensureTwoDigits(parseInt(g, 10).toString(16)) +
|
||||
this._ensureTwoDigits(parseInt(b, 10).toString(16));
|
||||
};
|
||||
|
||||
/**
|
||||
* convert HSL to RGB
|
||||
* @param h
|
||||
* @param s
|
||||
* @param l
|
||||
* @returns array
|
||||
*/
|
||||
this._hslToRgb = function(h, s, l) {
|
||||
if(Array.isArray(h)) {
|
||||
[h, s, l] = h;
|
||||
}
|
||||
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
|
||||
return hslToRgb(h, s, l);
|
||||
};
|
||||
|
||||
/**
|
||||
* generates a random color
|
||||
* @returns {string}
|
||||
*/
|
||||
this.randomColor = function() {
|
||||
if (typeof String.prototype.toHsl === 'function') {
|
||||
var hsl = Math.random().toString().toHsl();
|
||||
return self.rgbToHex(self._hslToRgb(hsl));
|
||||
} else {
|
||||
return self.colors[Math.floor(Math.random() * self.colors.length)];
|
||||
}
|
||||
};
|
||||
|
||||
// initialize default colors
|
||||
if (typeof String.prototype.toHsl === 'function') {
|
||||
//0 40 80 120 160 200 240 280 320
|
||||
var hashValues = ['15', '9', '4', 'b', '6', '11', '74', 'f', '57'];
|
||||
angular.forEach(hashValues, function(hashValue) {
|
||||
var hsl = hashValue.toHsl();
|
||||
self.colors.push(self.rgbToHex(self._hslToRgb(hsl)));
|
||||
});
|
||||
} else {
|
||||
this.colors = [
|
||||
'#31CC7C',
|
||||
'#317CCC',
|
||||
'#FF7A66',
|
||||
'#F1DB50',
|
||||
'#7C31CC',
|
||||
'#CC317C',
|
||||
'#3A3B3D',
|
||||
'#CACBCD'
|
||||
];
|
||||
}
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('ICalSplitterUtility', function (ICalFactory, SplittedICal) {
|
||||
'use strict';
|
||||
|
||||
const calendarColorIdentifier = 'x-apple-calendar-color';
|
||||
const calendarNameIdentifier = 'x-wr-calname';
|
||||
const componentNames = ['vevent', 'vjournal', 'vtodo'];
|
||||
|
||||
/**
|
||||
* split ics strings into a SplittedICal object
|
||||
* @param {string} iCalString
|
||||
* @returns {SplittedICal}
|
||||
*/
|
||||
this.split = function (iCalString) {
|
||||
const jcal = ICAL.parse(iCalString);
|
||||
const components = new ICAL.Component(jcal);
|
||||
|
||||
const objects = {};
|
||||
const timezones = components.getAllSubcomponents('vtimezone');
|
||||
|
||||
componentNames.forEach(function (componentName) {
|
||||
const vobjects = components.getAllSubcomponents(componentName);
|
||||
objects[componentName] = {};
|
||||
|
||||
vobjects.forEach(function (vobject) {
|
||||
const uid = vobject.getFirstPropertyValue('uid');
|
||||
objects[componentName][uid] = objects[componentName][uid] || [];
|
||||
objects[componentName][uid].push(vobject);
|
||||
});
|
||||
});
|
||||
|
||||
const name = components.getFirstPropertyValue(calendarNameIdentifier);
|
||||
const color = components.getFirstPropertyValue(calendarColorIdentifier);
|
||||
|
||||
const split = SplittedICal(name, color);
|
||||
componentNames.forEach(function (componentName) {
|
||||
for(let objectKey in objects[componentName]) {
|
||||
/* jshint loopfunc:true */
|
||||
if (!objects[componentName].hasOwnProperty(objectKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const component = ICalFactory.new();
|
||||
timezones.forEach(function (timezone) {
|
||||
component.addSubcomponent(timezone);
|
||||
});
|
||||
objects[componentName][objectKey].forEach(function (object) {
|
||||
component.addSubcomponent(object);
|
||||
});
|
||||
split.addObject(componentName, component.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return split;
|
||||
};
|
||||
});
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('PopoverPositioningUtility', function($window) {
|
||||
'use strict';
|
||||
|
||||
const context = {
|
||||
popoverHeight: 300,
|
||||
popoverWidth: 450,
|
||||
};
|
||||
|
||||
Object.defineProperties(context, {
|
||||
headerHeight: {
|
||||
get: function() {
|
||||
return angular.element('#header').height();
|
||||
}
|
||||
},
|
||||
navigationWidth: {
|
||||
get: function() {
|
||||
return angular.element('#app-navigation').width();
|
||||
}
|
||||
},
|
||||
windowX: {
|
||||
get: function() {
|
||||
return $window.innerWidth - context.navigationWidth;
|
||||
}
|
||||
},
|
||||
windowY: {
|
||||
get: function() {
|
||||
return $window.innerHeight - context.headerHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.isAgendaDayView = function(view) {
|
||||
return view.name === 'agendaDay';
|
||||
};
|
||||
|
||||
context.isAgendaView = function(view) {
|
||||
return view.name.startsWith('agenda');
|
||||
};
|
||||
|
||||
context.isInTheUpperPart = function(top) {
|
||||
return ((top - context.headerHeight) / context.windowY < 0.5);
|
||||
};
|
||||
|
||||
context.isInTheLeftQuarter = function(left) {
|
||||
return ((left - context.navigationWidth) / context.windowX < 0.25);
|
||||
};
|
||||
|
||||
context.isInTheRightQuarter = function(left) {
|
||||
return ((left - context.navigationWidth) / context.windowX > 0.75);
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the position of a popover
|
||||
* @param {Number} left
|
||||
* @param {Number}top
|
||||
* @param {Number}right
|
||||
* @param {Number}bottom
|
||||
* @param {*} view
|
||||
*/
|
||||
this.calculate = function(left, top, right, bottom, view) {
|
||||
const position = [],
|
||||
eventWidth = right - left;
|
||||
|
||||
if (context.isInTheUpperPart(top)) {
|
||||
if (context.isAgendaView(view)) {
|
||||
position.push({
|
||||
name: 'top',
|
||||
value: top - context.headerHeight + 30
|
||||
});
|
||||
} else {
|
||||
position.push({
|
||||
name: 'top',
|
||||
value: bottom - context.headerHeight + 20
|
||||
});
|
||||
}
|
||||
} else {
|
||||
position.push({
|
||||
name: 'top',
|
||||
value: top - context.headerHeight - context.popoverHeight - 20
|
||||
});
|
||||
}
|
||||
|
||||
if (context.isAgendaDayView(view)) {
|
||||
position.push({
|
||||
name: 'left',
|
||||
value: left - (context.popoverWidth / 2) - 20 + eventWidth / 2
|
||||
});
|
||||
} else {
|
||||
if (context.isInTheLeftQuarter(left)) {
|
||||
position.push({
|
||||
name: 'left',
|
||||
value: left - 20 + eventWidth / 2
|
||||
});
|
||||
} else if (context.isInTheRightQuarter(left)) {
|
||||
position.push({
|
||||
name: 'left',
|
||||
value: left - context.popoverWidth - 20 + eventWidth / 2
|
||||
});
|
||||
} else {
|
||||
position.push({
|
||||
name: 'left',
|
||||
value: left - (context.popoverWidth / 2) - 20 + eventWidth / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the position of a popover by a given target
|
||||
* @param {*} target
|
||||
* @param {*} view
|
||||
*/
|
||||
this.calculateByTarget = function(target, view) {
|
||||
const clientRect = target.getClientRects()[0];
|
||||
|
||||
const left = clientRect.left,
|
||||
top = clientRect.top,
|
||||
right = clientRect.right,
|
||||
bottom = clientRect.bottom;
|
||||
|
||||
return this.calculate(left, top, right, bottom, view);
|
||||
};
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('StringUtility', function () {
|
||||
'use strict';
|
||||
|
||||
this.uid = function(prefix, suffix) {
|
||||
prefix = prefix || '';
|
||||
suffix = suffix || '';
|
||||
|
||||
if (prefix !== '') {
|
||||
prefix += '-';
|
||||
}
|
||||
if (suffix !== '') {
|
||||
suffix = '.' + suffix;
|
||||
}
|
||||
|
||||
return prefix + Math.random().toString(36).substr(2).toUpperCase() +
|
||||
Math.random().toString(36).substr(2).toUpperCase() + suffix;
|
||||
};
|
||||
|
||||
this.uri = function(start, isAvailable) {
|
||||
start = start || '';
|
||||
|
||||
var uri = start.toString().toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
||||
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, ''); // Trim - from end of text
|
||||
|
||||
if (uri === '') {
|
||||
uri = '-';
|
||||
}
|
||||
|
||||
if (isAvailable(uri)) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
if (uri.indexOf('-') === -1) {
|
||||
uri = uri + '-1';
|
||||
if (isAvailable(uri)) {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
// === false because !undefined = true, possible infinite loop
|
||||
do {
|
||||
var positionLastDash = uri.lastIndexOf('-');
|
||||
var firstPart = uri.substr(0, positionLastDash);
|
||||
var lastPart = uri.substr(positionLastDash + 1);
|
||||
|
||||
if (lastPart.match(/^\d+$/)) {
|
||||
lastPart = parseInt(lastPart);
|
||||
lastPart++;
|
||||
|
||||
uri = firstPart + '-' + lastPart;
|
||||
} else {
|
||||
uri = uri + '-1';
|
||||
}
|
||||
} while(isAvailable(uri) === false);
|
||||
|
||||
return uri;
|
||||
};
|
||||
});
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* ownCloud - Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('WebCalUtility', function($rootScope) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* check if downgrading is allowed
|
||||
* @param {string} url
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.allowDowngrade = function(url) {
|
||||
return !url.startsWith('https://');
|
||||
};
|
||||
|
||||
/**
|
||||
* construct proxy url
|
||||
* @param url
|
||||
* @returns {string}
|
||||
*/
|
||||
this.buildProxyURL = function(url) {
|
||||
return $rootScope.baseUrl + 'proxy?url=' + encodeURIComponent(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* check if a downgrade is possible
|
||||
* @param {string} url
|
||||
* @param {boolean} allowDowngradeToHttp
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.downgradePossible = function(url, allowDowngradeToHttp) {
|
||||
return url.startsWith('https://') && allowDowngradeToHttp;
|
||||
};
|
||||
|
||||
/**
|
||||
* downgrade a url from https to insecure http
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
this.downgradeURL = function(url) {
|
||||
if (url.startsWith('https://')) {
|
||||
return 'http://' + url.substr(8);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* replace webcal:// in a url
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
this.fixURL = function(url) {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
} else if (url.startsWith('webcal://')) {
|
||||
return 'https://' + url.substr(9);
|
||||
} else {
|
||||
return 'https://' + url;
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @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('XMLUtility', function() {
|
||||
'use strict';
|
||||
|
||||
const context = {};
|
||||
context.XMLify = function(xmlDoc, parent, json) {
|
||||
const element = xmlDoc.createElementNS(json.name[0], json.name[1]);
|
||||
|
||||
json.attributes = json.attributes || [];
|
||||
json.attributes.forEach((a) => {
|
||||
if (a.length === 2) {
|
||||
element.setAttribute(a[0], a[1]);
|
||||
} else {
|
||||
element.setAttributeNS(a[0], a[1], a[2]);
|
||||
}
|
||||
});
|
||||
|
||||
if (json.value) {
|
||||
element.textContent = json.value;
|
||||
} else if (json.children) {
|
||||
for (let key in json.children) {
|
||||
if (json.children.hasOwnProperty(key)) {
|
||||
context.XMLify(xmlDoc, element, json.children[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent.appendChild(element);
|
||||
};
|
||||
|
||||
const serializer = new XMLSerializer();
|
||||
|
||||
this.getRootSkeleton = function() {
|
||||
if (arguments.length === 0) {
|
||||
return [{}, null];
|
||||
}
|
||||
|
||||
const skeleton = {
|
||||
name: arguments[0],
|
||||
children: []
|
||||
};
|
||||
|
||||
let childrenWrapper = skeleton.children;
|
||||
|
||||
const args = Array.prototype.slice.call(arguments, 1);
|
||||
args.forEach(function(argument) {
|
||||
const level = {
|
||||
name: argument,
|
||||
children: []
|
||||
};
|
||||
childrenWrapper.push(level);
|
||||
childrenWrapper = level.children;
|
||||
});
|
||||
|
||||
return [skeleton, childrenWrapper];
|
||||
};
|
||||
|
||||
this.serialize = function(json) {
|
||||
json = json || {};
|
||||
if (typeof json !== 'object' || !json.hasOwnProperty('name')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const root = document.implementation.createDocument('', '', null);
|
||||
context.XMLify(root, root, json);
|
||||
|
||||
return serializer.serializeToString(root);
|
||||
};
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration / App Initialization File
|
||||
*/
|
||||
|
||||
var app = angular.module('Calendar', [
|
||||
'ui.bootstrap'
|
||||
]);
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.config(['$provide', '$httpProvider',
|
||||
function ($provide, $httpProvider) {
|
||||
'use strict';
|
||||
|
||||
$httpProvider.defaults.headers.common.requesttoken = oc_requesttoken;
|
||||
|
||||
ICAL.design.defaultSet.param['x-oc-group-id'] = {
|
||||
allowXName: true
|
||||
};
|
||||
|
||||
angular.forEach($.fullCalendar.locales, function(obj, locale) {
|
||||
$.fullCalendar.locale(locale, {
|
||||
timeFormat: obj.mediumTimeFormat
|
||||
});
|
||||
|
||||
var propsToCheck = ['extraSmallTimeFormat', 'hourFormat', 'mediumTimeFormat', 'noMeridiemTimeFormat', 'smallTimeFormat'];
|
||||
|
||||
angular.forEach(propsToCheck, function(propToCheck) {
|
||||
if (obj[propToCheck]) {
|
||||
var overwrite = {};
|
||||
overwrite[propToCheck] = obj[propToCheck].replace('HH', 'H');
|
||||
|
||||
$.fullCalendar.locale(locale, overwrite);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const isFirstRun = (angular.element('#fullcalendar').attr('data-firstRun') === 'yes');
|
||||
$provide.constant('isFirstRun', isFirstRun);
|
||||
|
||||
const isPublic = (angular.element('#fullcalendar').attr('data-isPublic') === '1');
|
||||
$provide.constant('isPublic', isPublic);
|
||||
|
||||
const isEmbedded = (angular.element('#fullcalendar').attr('data-isEmbedded') === '1');
|
||||
$provide.constant('isEmbedded', isEmbedded);
|
||||
|
||||
const isSharingAPI = (typeof OC.Share === 'object');
|
||||
$provide.constant('isSharingAPI', isSharingAPI);
|
||||
|
||||
const skipPopover = angular.element('#fullcalendar').attr('data-skipPopover') === 'yes';
|
||||
const showWeekNr = angular.element('#fullcalendar').attr('data-weekNumbers') === 'yes';
|
||||
const timezone = angular.element('#fullcalendar').attr('data-timezone');
|
||||
$provide.constant('settings', {skipPopover, showWeekNr, timezone});
|
||||
|
||||
const initialView = angular.element('#fullcalendar').attr('data-initialView');
|
||||
const emailAddress = angular.element('#fullcalendar').attr('data-emailAddress');
|
||||
const fallbackColor = angular.element('#fullcalendar').attr('data-defaultColor');
|
||||
const version = angular.element('#fullcalendar').attr('data-appVersion');
|
||||
const publicSharingToken = angular.element('#fullcalendar').attr('data-publicSharingToken');
|
||||
const shareeCanEditShares = angular.element('#fullcalendar').attr('data-shareeCanEditShares') === 'yes';
|
||||
const shareeCanEditCalendarProperties = angular.element('#fullcalendar').attr('data-shareeCanEditCalendarProperties') === 'yes';
|
||||
const canSharePublicLink = angular.element('#fullcalendar').attr('data-canSharePublicLink') === 'yes';
|
||||
$provide.constant('constants', {
|
||||
initialView,
|
||||
emailAddress,
|
||||
fallbackColor,
|
||||
version,
|
||||
publicSharingToken,
|
||||
shareeCanEditShares,
|
||||
shareeCanEditCalendarProperties,
|
||||
canSharePublicLink,
|
||||
SHARE_TYPE_USER: 0,
|
||||
SHARE_TYPE_GROUP: 1,
|
||||
SHARE_TYPE_CIRCLE: 7
|
||||
});
|
||||
}
|
||||
]);
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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.run(['$document', '$rootScope', '$window', 'isPublic',
|
||||
function ($document, $rootScope, $window, isPublic) {
|
||||
'use strict';
|
||||
|
||||
const origin = $window.location.origin;
|
||||
$rootScope.root = origin + OC.linkTo('calendar', 'index.php') + '/';
|
||||
$rootScope.baseUrl = $rootScope.root + 'v1/';
|
||||
|
||||
try {
|
||||
if (!isPublic) {
|
||||
const webcalHandler = $rootScope.root + '#subscribe_to_webcal?url=%s';
|
||||
navigator.registerProtocolHandler('webcal', webcalHandler, 'Nextcloud calendar');
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
$document.click(function (event) {
|
||||
$rootScope.$broadcast('documentClicked', event);
|
||||
});
|
||||
}
|
||||
]);
|
221
js/gulpfile.js
221
js/gulpfile.js
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* Nextcloud - Calendar
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
* @copyright Bernhard Posselt 2012, 2014
|
||||
*
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2017 Georg Ehrke <oc.list@georgehrke.com>
|
||||
*/
|
||||
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// get plugins
|
||||
const gulp = require('gulp'),
|
||||
ngAnnotate = require('gulp-ng-annotate'),
|
||||
uglify = require('gulp-uglify'),
|
||||
uglifyCSS = require('gulp-uglifycss'),
|
||||
jshint = require('gulp-jshint'),
|
||||
KarmaServer = require('karma').Server,
|
||||
concat = require('gulp-concat'),
|
||||
insert = require('gulp-insert'),
|
||||
wrap = require('gulp-wrap'),
|
||||
strip = require('gulp-strip-comments'),
|
||||
stripCSS = require('gulp-strip-css-comments'),
|
||||
babel = require('gulp-babel'),
|
||||
stylelint = require('gulp-stylelint'),
|
||||
sourcemaps = require('gulp-sourcemaps'),
|
||||
fs = require('fs');
|
||||
const gulpsync = require('gulp-sync')(gulp);
|
||||
const timezones = fs.readFileSync('./timezones/zones.json', 'UTF-8');
|
||||
|
||||
// configure
|
||||
const buildTarget = 'app.js';
|
||||
const buildTargetMin = 'app.min.js';
|
||||
const cssBuildTarget = 'app.scss';
|
||||
const cssBuildTargetMin = 'app.min.scss';
|
||||
const vendorTarget = 'vendor.js';
|
||||
const vendorTargetMin = 'vendor.min.js';
|
||||
const vendorCssTarget = 'vendor.css';
|
||||
const vendorCssTargetMin = 'vendor.min.css';
|
||||
const karmaConfig = __dirname + '/../tests/js/config/karma.js';
|
||||
const destinationFolder = __dirname + '/public/';
|
||||
const cssDestinationFolder = __dirname + '/../css/public/';
|
||||
|
||||
const jsSources = [
|
||||
'config/*.js',
|
||||
'app/**/*.js'
|
||||
];
|
||||
const cssSources = [
|
||||
'../css/app/*.scss'
|
||||
];
|
||||
const vendorSources = [
|
||||
'node_modules/angular/angular.js',
|
||||
'node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js',
|
||||
'node_modules/fullcalendar/dist/fullcalendar.js',
|
||||
'node_modules/fullcalendar/dist/locale-all.js',
|
||||
'licenses/hsl_rgb_converter.js',
|
||||
'node_modules/hsl_rgb_converter/converter.js',
|
||||
'node_modules/ical.js/build/ical.js',
|
||||
'node_modules/jquery-timepicker/jquery.ui.timepicker.js',
|
||||
'node_modules/jstzdetect/dist/jstz.js',
|
||||
];
|
||||
const vendorCssSources = [
|
||||
'node_modules/fullcalendar/dist/fullcalendar.css',
|
||||
'node_modules/angular/angular-csp.css',
|
||||
'licenses/jquery.timepicker.css',
|
||||
'node_modules/jquery-timepicker/jquery.ui.timepicker.css'
|
||||
];
|
||||
|
||||
const testSources = ['../tests/js/unit/**/*.js'];
|
||||
const lintJsSources = jsSources.concat(testSources).concat(['*.js']);
|
||||
const watchSources = lintJsSources.concat(cssSources);
|
||||
|
||||
// tasks
|
||||
gulp.task('lint', ['jslint', 'csslint']);
|
||||
gulp.task('default', ['lint', '_buildSource', '_buildVendor']);
|
||||
gulp.task('build', ['lint', '_buildSource']);
|
||||
|
||||
gulp.task('_buildAndMinifyCSSSources', gulpsync.sync(['_buildCSSSources', '_minifyCSSSources']));
|
||||
gulp.task('_buildAndMinifyJavaScriptSources', gulpsync.sync(['_buildJavaScriptSources', '_minifyJavaScriptSources']));
|
||||
gulp.task('_buildAndMinifyCSSVendor', gulpsync.sync(['_buildCSSVendor', '_minifyCSSVendor']));
|
||||
gulp.task('_buildAndMinifyJavaScriptVendor', gulpsync.sync(['_buildJavaScriptVendor', '_minifyJavaScriptVendor']));
|
||||
|
||||
gulp.task('_buildSource', ['_buildAndMinifyCSSSources', '_buildAndMinifyJavaScriptSources']);
|
||||
gulp.task('_buildVendor', ['_buildAndMinifyCSSVendor', '_buildAndMinifyJavaScriptVendor']);
|
||||
|
||||
// #############################################################################
|
||||
// ################################ SOURCE CSS #################################
|
||||
// #############################################################################
|
||||
gulp.task('_buildCSSSources', () => {
|
||||
return gulp.src(cssSources)
|
||||
.pipe(stripCSS({
|
||||
preserve: false
|
||||
}))
|
||||
.pipe(concat(cssBuildTarget))
|
||||
.pipe(gulp.dest(cssDestinationFolder));
|
||||
});
|
||||
|
||||
gulp.task('_minifyCSSSources', () => {
|
||||
return gulp.src(cssDestinationFolder + cssBuildTarget)
|
||||
.pipe(sourcemaps.init({identityMap: true, largeFile: true}))
|
||||
.pipe(concat(cssBuildTargetMin))
|
||||
.pipe(uglifyCSS())
|
||||
.pipe(sourcemaps.write('./', {includeContent: false}))
|
||||
.pipe(gulp.dest(cssDestinationFolder));
|
||||
});
|
||||
|
||||
// #############################################################################
|
||||
// ################################ SOURCE JS ##################################
|
||||
// #############################################################################
|
||||
gulp.task('_buildJavaScriptSources', () => {
|
||||
return gulp.src(jsSources)
|
||||
.pipe(babel({
|
||||
presets: ['es2015'],
|
||||
compact: false,
|
||||
babelrc: false,
|
||||
ast: false
|
||||
}))
|
||||
.pipe(ngAnnotate())
|
||||
.pipe(strip())
|
||||
.pipe(concat(buildTarget))
|
||||
.pipe(insert.append('\napp.service(\'TimezoneDataProvider\', function () { return '))
|
||||
.pipe(insert.append(timezones))
|
||||
.pipe(insert.append(';});'))
|
||||
.pipe(wrap(`(function(angular, $, oc_requesttoken, undefined){
|
||||
<%= contents %>
|
||||
})(angular, jQuery, oc_requesttoken);`))
|
||||
.pipe(gulp.dest(destinationFolder));
|
||||
});
|
||||
|
||||
gulp.task('_minifyJavaScriptSources', () => {
|
||||
return gulp.src(destinationFolder + buildTarget)
|
||||
.pipe(sourcemaps.init({identityMap: true, largeFile: true}))
|
||||
.pipe(concat(buildTargetMin))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('./', {includeContent: false}))
|
||||
.pipe(gulp.dest(destinationFolder));
|
||||
});
|
||||
|
||||
// #############################################################################
|
||||
// ################################ VENDOR CSS #################################
|
||||
// #############################################################################
|
||||
gulp.task('_buildCSSVendor', () => {
|
||||
return gulp.src(vendorCssSources)
|
||||
.pipe(concat(vendorCssTarget))
|
||||
.pipe(gulp.dest(cssDestinationFolder));
|
||||
});
|
||||
|
||||
gulp.task('_minifyCSSVendor', () => {
|
||||
return gulp.src([cssDestinationFolder + vendorCssTarget])
|
||||
.pipe(sourcemaps.init({identityMap: true, largeFile: true}))
|
||||
.pipe(concat(vendorCssTargetMin))
|
||||
.pipe(stripCSS({
|
||||
preserve: false
|
||||
}))
|
||||
.pipe(uglifyCSS())
|
||||
.pipe(sourcemaps.write('./', {includeContent: false}))
|
||||
.pipe(gulp.dest(cssDestinationFolder));
|
||||
});
|
||||
|
||||
// #############################################################################
|
||||
// ################################# VENDOR JS #################################
|
||||
// #############################################################################
|
||||
gulp.task('_buildJavaScriptVendor', () => {
|
||||
return gulp.src(vendorSources)
|
||||
.pipe(concat(vendorTarget))
|
||||
.pipe(gulp.dest(destinationFolder));
|
||||
});
|
||||
|
||||
gulp.task('_minifyJavaScriptVendor', () => {
|
||||
return gulp.src([destinationFolder + vendorTarget])
|
||||
.pipe(sourcemaps.init({identityMap: true, largeFile: true}))
|
||||
.pipe(concat(vendorTargetMin))
|
||||
.pipe(strip())
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('./', {includeContent: false}))
|
||||
.pipe(gulp.dest(destinationFolder));
|
||||
});
|
||||
|
||||
gulp.task('jslint', () => {
|
||||
return gulp.src(lintJsSources)
|
||||
.pipe(jshint('.jshintrc'))
|
||||
.pipe(jshint.reporter('default'))
|
||||
.pipe(jshint.reporter('fail'));
|
||||
});
|
||||
|
||||
gulp.task('csslint', () => {
|
||||
return gulp.src(cssSources)
|
||||
.pipe(stylelint ({
|
||||
reporters: [{
|
||||
formatter: 'string',
|
||||
console: true
|
||||
}]
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('watch', () => {
|
||||
gulp.watch(watchSources, ['_buildSource']);
|
||||
});
|
||||
|
||||
gulp.task('karma', (done) => {
|
||||
new KarmaServer({
|
||||
configFile: karmaConfig,
|
||||
singleRun: true,
|
||||
browsers: ['Firefox'],
|
||||
reporters: ['progress', 'coverage']
|
||||
}, done).start();
|
||||
});
|
||||
|
||||
gulp.task('watch-karma', (done) => {
|
||||
new KarmaServer({
|
||||
configFile: karmaConfig,
|
||||
autoWatch: true,
|
||||
browsers: ['Firefox'],
|
||||
reporters: ['progress', 'coverage']
|
||||
}, done).start();
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
HSL RGB Converter
|
||||
author: Keenan Lidral-Porter
|
||||
license: ISC
|
||||
*/
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* jQuery UI Timepicker
|
||||
*
|
||||
* Copyright 2010-2013, Francois Gelinas
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://fgelinas.com/code/timepicker
|
||||
*/
|
|
@ -1,67 +0,0 @@
|
|||
{
|
||||
"name": "calendar",
|
||||
"description": "Integrated Calendar Application",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/nextcloud/calendar",
|
||||
"scripts": {
|
||||
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||
"prebuild": "yarn",
|
||||
"build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:nextcloud/calendar.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextcloud/calendar/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.7.8",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"coveralls": "^3.0.6",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^7.0.1",
|
||||
"gulp-cli": "^2.2.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-insert": "^0.5.0",
|
||||
"gulp-jshint": "^2.1.0",
|
||||
"gulp-ng-annotate": "^2.1.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-strip-comments": "^2.5.2",
|
||||
"gulp-strip-css-comments": "^2.0.0",
|
||||
"gulp-stylelint": "^8.0.0",
|
||||
"gulp-sync": "^0.1.4",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"gulp-uglifycss": "^1.1.0",
|
||||
"gulp-wrap": "^0.15.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"jasmine-core": "^3.4.0",
|
||||
"jshint": "^2.10.2",
|
||||
"jshint-stylish": "^2.2.1",
|
||||
"karma": "^2.0.4",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-firefox-launcher": "^1.2.0",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"phantomjs-prebuilt": "^2.1.16",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-standard": "^18.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular": "^1.7.8",
|
||||
"angular-ui-bootstrap": "^2.5.0",
|
||||
"fullcalendar": "^3.9.0",
|
||||
"hsl-to-rgb": "kayellpeee/hsl_rgb_converter",
|
||||
"hsl_rgb_converter": "kayellpeee/hsl_rgb_converter",
|
||||
"ical.js": "^1.3.0",
|
||||
"jquery-timepicker": "fgelinas/timepicker#883bb2cd94",
|
||||
"jstimezonedetect": "georgehrke/jstimezonedetect",
|
||||
"jstzdetect": "georgehrke/jstimezonedetect",
|
||||
"timepicker": "fgelinas/timepicker#883bb2cd94"
|
||||
}
|
||||
}
|
7438
js/yarn.lock
7438
js/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +0,0 @@
|
|||
<ul class="colorpicker-list">
|
||||
<li ng-repeat="color in colors"
|
||||
ng-class="{ selected: (color === selected) }"
|
||||
ng-click="pick(color)"
|
||||
ng-style="{ 'background-color': color}; "></li>
|
||||
<li class="randomcolour"
|
||||
ng-click="randomizeColour()"
|
||||
ng-show="!supportsColorPicker"
|
||||
ng-style="{ 'background-color': random}; "></li>
|
||||
<label class="color-selector-label"
|
||||
ng-show="supportsColorPicker"
|
||||
ng-style="{ 'background-color': selected}; ">
|
||||
<input type="color"
|
||||
class="color-selector"
|
||||
ng-model="selected" />
|
||||
</label>
|
||||
</ul>
|
|
@ -1,83 +0,0 @@
|
|||
<div>
|
||||
<form class="events" ng-keydown="keypress($event)">
|
||||
<fieldset class="events--fieldset" ng-disabled="readOnly">
|
||||
<textarea
|
||||
rows="1"
|
||||
class="events--input h2 events--textarea"
|
||||
ng-model="properties.summary.value"
|
||||
placeholder="<?php p($l->t('Title of the Event'));?>"
|
||||
name="title" type="text"
|
||||
autofocus="autofocus"
|
||||
tabindex="100"
|
||||
></textarea>
|
||||
<select
|
||||
ng-model="calendar"
|
||||
ng-change="selectedCalendarChanged()"
|
||||
ng-options="c as c.displayname for c in calendars | orderBy:['order'] | calendarSelectorFilter: oldCalendar"
|
||||
ng-show="showCalendarSelection()"
|
||||
tabindex="101"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="event-time events--fieldset" ng-disabled="readOnly">
|
||||
<div class="event-time-interior pull-left">
|
||||
<span><?php p($l->t('Starts')); ?></span>
|
||||
<ocdatetimepicker ng-model="properties.dtstart.value" disabletime="properties.allDay" datetabindex="103" timetabindex="104" readonly="readOnly"></ocdatetimepicker>
|
||||
<span ng-show="edittimezone">{{ properties.dtstart.parameters.zone | timezoneFilter }}</span>
|
||||
</div>
|
||||
<div class="event-time-interior pull-right">
|
||||
<span><?php p($l->t('Ends')); ?></span>
|
||||
<ocdatetimepicker ng-model="properties.dtend.value" disabletime="properties.allDay" datetabindex="105" timetabindex="106" readonly="readOnly"></ocdatetimepicker>
|
||||
<span ng-show="edittimezone">{{ properties.dtend.parameters.zone | timezoneFilter }}</span>
|
||||
</div>
|
||||
<div class="clear-both"></div>
|
||||
<div class="events--checkbox pull-left">
|
||||
<input type="checkbox" name="alldayeventcheckbox"
|
||||
ng-model="properties.allDay"
|
||||
class="checkbox"
|
||||
id="alldayeventcheckbox" class="event-checkbox"
|
||||
tabindex="102" />
|
||||
<label for="alldayeventcheckbox"><?php p($l->t('All day Event'))?></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="events--fieldset pull-left" ng-if="!readOnly">
|
||||
<button ng-click="delete()" ng-if="!is_new" class="events--button button btn delete" type="button" tabindex="110">
|
||||
<?php p($l->t('Delete')); ?>
|
||||
</button>
|
||||
<button ng-click="cancel()" class="events--button button btn" type="button" tabindex="111">
|
||||
<?php p($l->t('Cancel')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="events--fieldset pull-right" ng-if="!readOnly">
|
||||
<button ng-click="proceed()" class="events--button button btn" type="button" tabindex="120">
|
||||
<?php p($l->t('More ...')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="events--button button btn primary"
|
||||
ng-click="save()"
|
||||
ng-if="is_new"
|
||||
type="button"
|
||||
tabindex="121">
|
||||
<?php p($l->t('Create')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn primary"
|
||||
ng-click="save()"
|
||||
ng-if="!is_new"
|
||||
type="button"
|
||||
tabindex="122">
|
||||
<?php p($l->t('Update')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="events--fieldset pull-right" ng-if="readOnly">
|
||||
<button ng-click="proceed()" class="events--button button btn" type="button" tabindex="130">
|
||||
<?php p($l->t('More ...')); ?>
|
||||
</button>
|
||||
<button ng-click="cancel()" class="events--button button btn" type="button" tabindex="131">
|
||||
<?php p($l->t('Close')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
|
@ -1,171 +0,0 @@
|
|||
<div id="app-sidebar" class="advanced">
|
||||
<form ng-keydown="keypress($event)">
|
||||
<div class="sidebar-top" ng-class="{'new': is_new}">
|
||||
<div class="advanced--container">
|
||||
<fieldset class="advanced--fieldset" ng-disabled="readOnly">
|
||||
<textarea
|
||||
class="advanced--input h2 advanced--textarea"
|
||||
ng-model="properties.summary.value"
|
||||
placeholder="<?php p($l->t('Title of the Event'));?>"
|
||||
name="title" type="text"
|
||||
rows="1"
|
||||
autofocus="autofocus"
|
||||
tabindex="200"></textarea>
|
||||
<select
|
||||
ng-model="calendar"
|
||||
ng-change="selectedCalendarChanged()"
|
||||
ng-options="c as c.displayname for c in calendars | orderBy:['order'] | calendarSelectorFilter: oldCalendar"
|
||||
ng-show="showCalendarSelection()"
|
||||
tabindex="201"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="advanced--fieldset start-end-container" ng-disabled="readOnly">
|
||||
<div class="event-time-interior pull-left">
|
||||
<span><?php p($l->t('Starts')); ?></span>
|
||||
<ocdatetimepicker ng-model="properties.dtstart.value" disabletime="properties.allDay" datetabindex="203" timetabindex="204" readonly="readOnly"></ocdatetimepicker>
|
||||
<select ng-options="timezone.value as timezone.displayname | timezoneWithoutContinentFilter group by timezone.group for timezone in timezones" ng-model="properties.dtstart.parameters.zone" ng-show="edittimezone || readOnly" ng-disabled="properties.allDay"
|
||||
ng-change="loadTimezone(properties.dtstart.parameters.zone)" class="timezone-select" tabindex="205"></select>
|
||||
</div>
|
||||
<div class="event-time-interior pull-right">
|
||||
<span><?php p($l->t('Ends')); ?></span>
|
||||
<ocdatetimepicker ng-model="properties.dtend.value" disabletime="properties.allDay" datetabindex="206" timetabindex="207" readonly="readOnly"></ocdatetimepicker>
|
||||
<select ng-options="timezone.value as timezone.displayname | timezoneWithoutContinentFilter group by timezone.group for timezone in timezones" ng-model="properties.dtend.parameters.zone" ng-show="edittimezone || readOnly" ng-disabled="properties.allDay"
|
||||
ng-change="loadTimezone(properties.dtend.parameters.zone)" class="timezone-select" tabindex="208"></select>
|
||||
</div>
|
||||
<div class="advanced--checkbox pull-left pull-half">
|
||||
<input type="checkbox" name="alldayeventcheckbox"
|
||||
class="checkbox"
|
||||
ng-model="properties.allDay"
|
||||
id="alldayeventcheckbox" class="event-checkbox"
|
||||
tabindex="202"/>
|
||||
<label for="alldayeventcheckbox"><?php p($l->t('All day Event'))?></label>
|
||||
</div>
|
||||
<div class="pull-right pull-half timezone-container">
|
||||
<button class="button btn-default btn-timezone" ng-click="edittimezone = !edittimezone" ng-show="!readOnly" type="button" tabindex="209">
|
||||
<span class="icon-timezone"></span>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<ul class="tabHeaders">
|
||||
<li class="tabHeader" ng-repeat="tab in tabs"
|
||||
ng-click="tabopener(tab.value)" ng-class="{selected: tab.value == selected}">
|
||||
<a href="#">{{tab.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="clear-both"></div>
|
||||
|
||||
<fieldset class="advanced--fieldset" ng-disabled="readOnly">
|
||||
<div ng-include="currentTab"></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="eventsdetailsview" class="advanced--fieldset" ng-disabled="readOnly">
|
||||
<textarea ng-model="properties.location.value" type="text" class="advanced--input advanced--textarea" rows="1"
|
||||
placeholder="<?php p($l->t('Location'));?>" name="location"
|
||||
uib-typeahead="location.name for location in searchLocation($viewValue)" typeahead-show-hint="true" typeahead-min-length="3"
|
||||
typeahead-on-select="selectLocationFromTypeahead($item)"
|
||||
autocomplete="off" tabindex="210"></textarea>
|
||||
<textarea ng-model="properties.description.value" type="text" class="advanced--input advanced--textarea" rows="2"
|
||||
placeholder="<?php p($l->t('Description'));?>" name="description" tabindex="210"></textarea>
|
||||
<select id="statusSelector"
|
||||
ng-options="status.type as status.displayname for status in statusSelect"
|
||||
ng-init="setStatusToDefault()"
|
||||
ng-model="properties.status.value"
|
||||
title="<?php p($l->t('Event status')); ?>" tabindex="210"></select>
|
||||
<select id="classSelector"
|
||||
ng-options="class.type as class.displayname for class in classSelect"
|
||||
ng-init="setClassToDefault()"
|
||||
ng-model="properties.class.value"
|
||||
title="<?php p($l->t('Visibility when sharing')); ?>" tabindex="210"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="eventsattendeeview" class="advanced--fieldset" ng-disabled="readOnly" ng-controller="AttendeeController">
|
||||
<?php print_unescaped($this->inc('part.eventsattendees')); ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="eventsalarmview" class="advanced--fieldset" ng-disabled="readOnly" ng-controller="VAlarmController">
|
||||
<?php print_unescaped($this->inc('part.eventsalarms')); ?>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="eventsrepeatview" class="advanced--fieldset" ng-disabled="readOnly" ng-controller="RecurrenceController">
|
||||
<?php print_unescaped($this->inc('part.eventsrepeat')); ?>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="advanced--button-area" ng-if="!readOnly">
|
||||
<fieldset>
|
||||
<button
|
||||
class="events--button button btn delete btn-half pull-left"
|
||||
ng-click="delete()"
|
||||
ng-if="!is_new"
|
||||
type="button"
|
||||
tabindex="280">
|
||||
<?php p($l->t('Delete')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn btn-half pull-right"
|
||||
ng-click="cancel()"
|
||||
ng-if="!is_new"
|
||||
type="button"
|
||||
tabindex="281">
|
||||
<?php p($l->t('Cancel')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn btn-full"
|
||||
ng-click="cancel()"
|
||||
ng-if="is_new"
|
||||
type="button"
|
||||
tabindex="282">
|
||||
<?php p($l->t('Cancel')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn btn-full"
|
||||
ng-click="export()"
|
||||
ng-if="!is_new"
|
||||
type="button"
|
||||
tabindex="283">
|
||||
<?php p($l->t('Export')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="events--button button btn primary btn-full"
|
||||
ng-click="save()"
|
||||
ng-if="is_new"
|
||||
type="button"
|
||||
tabindex="284">
|
||||
<?php p($l->t('Create')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn primary btn-full"
|
||||
ng-click="save()"
|
||||
ng-if="!is_new"
|
||||
type="button"
|
||||
tabindex="285">
|
||||
<?php p($l->t('Update')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="advanced--button-area" ng-if="readOnly">
|
||||
<fieldset>
|
||||
<button
|
||||
class="evens--button button btn btn-full"
|
||||
ng-click="export()"
|
||||
ng-if="accessibleViaCalDAV"
|
||||
type="button"
|
||||
tabindex="290">
|
||||
<?php p($l->t('Export')); ?>
|
||||
</button>
|
||||
<button
|
||||
class="evens--button button btn btn-full"
|
||||
ng-click="cancel()"
|
||||
type="button"
|
||||
tabindex="291">
|
||||
<?php p($l->t('Close')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -1,320 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title><?php p($_['subject']); ?></title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%; }
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%; }
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%; }
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top; }
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%; }
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: auto !important;
|
||||
width: 580px; }
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px; }
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
width: 100%; }
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px; }
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%; }
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center; }
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px; }
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize; }
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px; }
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px; }
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline; }
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%; }
|
||||
.btn > tbody > tr > td {
|
||||
padding-bottom: 15px; }
|
||||
.btn table {
|
||||
width: auto; }
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center; }
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize; }
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db; }
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff; }
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0; }
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none; }
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0; }
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important; }
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important; }
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important; }
|
||||
table[class=body] .content {
|
||||
padding: 0 !important; }
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important; }
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important; }
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important; }
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important; }
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important; }}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%; }
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%; }
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important; }
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important; } }
|
||||
|
||||
/* Fixes for NextCloud */
|
||||
#nojavascript {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader"><?php p($l->t('%s has published the calendar %s', [$_['username'], $_['calendarname']])); ?></span>
|
||||
<table class="main">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p><?php p($l->t('Hello,')); ?></p>
|
||||
<p><?php
|
||||
print_unescaped(str_replace(
|
||||
['{boldstart}', '{boldend}'],
|
||||
['<b>', '</b>'],
|
||||
$l->t("We wanted to inform you that %s has published the calendar {boldstart}%s{boldend}.", [$_['username'], '<b>' . $_['calendarname'] . '</b>'])
|
||||
)); ?></p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <a href="<?php p($_['calendarurl']); ?>" target="_blank"><?php p($l->t('Click here to access it')); ?></a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<?php
|
||||
// TRANSLATORS term at the end of a mail
|
||||
p($l->t('Cheers!'));
|
||||
?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content-block">
|
||||
--<br>
|
||||
<?php p($_['defaults']->getName()); ?> -
|
||||
<?php p($_['defaults']->getSlogan()); ?>
|
||||
<br><a href="<?php p($_['defaults']->getBaseUrl()); ?>"><?php p($_['defaults']->getBaseUrl());?></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER --></div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||
<?php p($l->t('Hello,')); ?>
|
||||
|
||||
|
||||
<?php p($l->t("We wanted to inform you that %s has published the calendar %s.", [$_['username'],$_['calendarname']])); ?>
|
||||
|
||||
<?php p($l->t('Click on the link below to access it')); ?>
|
||||
|
||||
<?php p($_['calendarurl']); ?>
|
||||
|
||||
|
||||
<?php p($l->t('Cheers!')); ?>
|
|
@ -1,157 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* OpenGraph */
|
||||
if($_['isPublic']) {
|
||||
OCP\Util::addHeader('meta', ['property' => "og:title", 'content' => $theme->getName() . ' - ' . $theme->getSlogan()]);
|
||||
OCP\Util::addHeader('meta', ['property' => "og:site_name", 'content' => $theme->getName()]);
|
||||
OCP\Util::addHeader('meta', ['property' => "og:url", 'content' => $_['shareURL']]);
|
||||
OCP\Util::addHeader('meta', ['property' => "og:type", 'content' => "object"]);
|
||||
OCP\Util::addHeader('meta', ['property' => "og:image", 'content' => $_['previewImage']]);
|
||||
}
|
||||
$styles = [
|
||||
'public/vendor.min',
|
||||
'public/app.min'
|
||||
];
|
||||
|
||||
foreach ($styles as $style) {
|
||||
style('calendar', $style);
|
||||
}
|
||||
|
||||
$scripts = [
|
||||
'public/vendor.min',
|
||||
'public/app.min'
|
||||
];
|
||||
|
||||
foreach ($scripts as $script) {
|
||||
script('calendar', $script);
|
||||
}
|
||||
script('contacts', 'contacts');
|
||||
style('contacts', 'contacts');
|
||||
?>
|
||||
<?php if($_['isPublic'] && !$_['isEmbedded']): ?>
|
||||
<style>
|
||||
#body-public.layout-base #content {
|
||||
padding-top: 50px;
|
||||
}
|
||||
@media only screen and (max-width: 768px) {
|
||||
#app-navigation, #app-content {
|
||||
top: 45px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($_['isEmbedded']): ?>
|
||||
<style>
|
||||
#body-public.layout-base #app-navigation {
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
@media only screen and (max-width: 768px) {
|
||||
#app-navigation-toggle {
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php endif; ?>
|
||||
<div class="app" ng-app="Calendar" ng-controller="CalController">
|
||||
|
||||
<!-- The Left Calendar Navigation -->
|
||||
<div id="app-navigation">
|
||||
|
||||
<div ng-controller="DatePickerController" id="datepickercontainer" ng-class="{active: visibility}">
|
||||
<?php print_unescaped($this->inc('part.datepicker')); ?>
|
||||
<?php print_unescaped($this->inc('part.buttonarea')); ?>
|
||||
<div class="clear-both"></div>
|
||||
</div>
|
||||
|
||||
<?php if(!$_['isPublic']): ?>
|
||||
<ul ng-controller="CalendarListController" id="calendarlistcontainer" ng-cloak>
|
||||
<?php print_unescaped($this->inc('part.createcalendar')); ?>
|
||||
<?php print_unescaped($this->inc('part.calendarlist')); ?>
|
||||
<li id="spacer"></li><!-- Creates space between Subscriptionlist and Calendarlist.-->
|
||||
<?php print_unescaped($this->inc('part.createsubscription')); ?>
|
||||
<?php print_unescaped($this->inc('part.subscriptionlist')); ?>
|
||||
</ul>
|
||||
|
||||
<div id="app-settings" ng-controller="SettingsController">
|
||||
<?php print_unescaped($this->inc('part.settings')); ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div ng-controller="CalendarListController" id="publicinformationscontainer">
|
||||
<?php print_unescaped($this->inc('part.publicinformations')); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- The Calendar on the right -->
|
||||
<div id="app-content">
|
||||
<?php print_unescaped($this->inc('part.fullcalendar')); ?>
|
||||
</div>
|
||||
|
||||
<div id="popover-container"></div>
|
||||
<?php if(!$_['isPublic']): ?>
|
||||
<div id="importpopover-container"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="emptycontent-container">
|
||||
<?php print_unescaped($this->inc('part.emptycontent')); ?>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="eventspopovereditor.html">
|
||||
<?php print_unescaped($this->inc('editor.popover')); ?>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="eventssidebareditor.html">
|
||||
<?php print_unescaped($this->inc('editor.sidebar')); ?>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="confirmation.html">
|
||||
<button class="confirmation-default">
|
||||
<span class="icon-delete svg"></span>
|
||||
<span><?php p($l->t('Delete')); ?></span>
|
||||
</button>
|
||||
<span class="confirmation-abort icon-close svg" title="<?php p($l->t('Cancel')); ?>">
|
||||
</span>
|
||||
<span class="confirmation-confirm icon-delete-white svg no-permission">
|
||||
<span class="countdown">3</span>
|
||||
</span>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="customShareMatchTemplate.html">
|
||||
<a tabindex="-1"
|
||||
ng-attr-title="{{ match.label }}">
|
||||
<div class="share-autocomplete-item" title="{{ match.label }}">
|
||||
<div class="avatardiv" data-user="{{ match.model.identifier }}" avatar></div>
|
||||
<div class="autocomplete-item-text" ng-bind-html="match.label | uibTypeaheadHighlight:query"></div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</script>
|
||||
|
||||
<?php if(!$_['isPublic']): ?>
|
||||
<script type="text/ng-template" id="import.html">
|
||||
<?php print_unescaped($this->inc('part.import.dialog')); ?>
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="togglebuttons">
|
||||
<button class="button first" ng-value="agendaDay" ng-model="selectedView" uib-btn-radio="'agendaDay'"><?php p($l->t('Day')); ?></button>
|
||||
<button class="button middle" ng-value="agendaWeek" ng-model="selectedView" uib-btn-radio="'agendaWeek'"><?php p($l->t('Week')); ?></button>
|
||||
<button class="button last" ng-value="month" ng-model="selectedView" uib-btn-radio="'month'"><?php p($l->t('Month')); ?></button>
|
||||
</div>
|
||||
<div class="togglebuttons">
|
||||
<button class="button today" ng-click="today()"><?php p($l->t('Today')); ?></button>
|
||||
</div>
|
|
@ -1,249 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @author Vinicius Cubas Brand
|
||||
* @author Daniel Tygel
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.com>
|
||||
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @copyright 2017 Vinicius Cubas Brand <vinicius@eita.org.br>
|
||||
* @copyright 2017 Daniel Tygel <dtygel@eita.org.br>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<span class="calendarCheckbox app-navigation-entry-bullet"
|
||||
ng-click="triggerEnable(item)"
|
||||
ng-if="item.displayColorIndicator() && !item.calendar.hasWarnings()"
|
||||
ng-style="{
|
||||
'background-color' : item.calendar.enabled == true ? item.calendar.color : 'transparent'
|
||||
}">
|
||||
</span>
|
||||
<a class="action permanent"
|
||||
ng-class="{'icon-error': item.calendar.hasWarnings()}"
|
||||
href="#"
|
||||
ng-click="triggerEnable(item)"
|
||||
title="{{ item.calendar.hasWarnings() ? warningLabel : item.calendar.displayname }}">
|
||||
{{ item.calendar.displayname }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils"
|
||||
ng-show="item.displayActions()">
|
||||
<ul ng-class="{'withitems': item.calendar.isShared() || item.calendar.isPublished() }">
|
||||
<li class="app-navigation-entry-utils-menu-button calendarlist-icon share permanent"
|
||||
ng-click="item.toggleEditingShares()"
|
||||
ng-if="item.showSharingIcon()"
|
||||
role="button">
|
||||
<button ng-class="{
|
||||
'icon-shared shared-style': item.calendar.isShared() && !item.calendar.isPublished(),
|
||||
'icon-public': item.calendar.isPublished(),
|
||||
'icon-shared': !item.calendar.isShared() && !item.calendar.isPublished()}"
|
||||
title="{{item.calendar.isShared() && item.calendar.isShareable() || item.calendar.isPublished() ? sharedLabel : shareLabel}}">
|
||||
</button>
|
||||
</li>
|
||||
<li class="app-navigation-entry-utils-menu-button"
|
||||
href="#"
|
||||
title="<?php p($l->t('More')); ?>"
|
||||
role="button"><button class="icon-more" on-toggle-show="#more-actions-{{ $id }}"></button></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="more-actions-{{ $id }}"
|
||||
class="app-navigation-entry-menu hidden">
|
||||
<ul>
|
||||
<li ng-show="item.calendar.arePropertiesWritable()">
|
||||
<button ng-click="item.openEditor()">
|
||||
<span class="icon-rename svg"></span>
|
||||
<span><?php p($l->t('Edit')); ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li ng-show="item.calendar.eventsAccessibleViaCalDAV()">
|
||||
<button ng-click="item.showCalDAVUrl()">
|
||||
<span class="icon-public svg"></span>
|
||||
<span><?php p($l->t('Link')); ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li ng-show="item.isWebCal()">
|
||||
<button ng-click="item.showWebCalUrl()">
|
||||
<span class="icon-link svg"></span>
|
||||
<span><?php p($l->t('iCal link')); ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button ng-click="download(item)">
|
||||
<span class="icon-download svg"></span>
|
||||
<span><?php p($l->t('Download')); ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li confirmation="remove(item)"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="app-navigation-entry-edit"
|
||||
ng-if="item.isEditing()">
|
||||
<form ng-submit="performUpdate(item)">
|
||||
<input type="text" ng-model="item.displayname">
|
||||
<input type="button" value="" class="btn close-button icon-close" ng-click="item.cancelEditor()">
|
||||
<input type="submit" value="" class="icon-checkmark accept-button">
|
||||
</form>
|
||||
<colorpicker class="colorpicker"
|
||||
selected="item.color">
|
||||
</colorpicker>
|
||||
</div>
|
||||
<div class="app-navigation-entry-edit"
|
||||
ng-if="item.displayCalDAVUrl()">
|
||||
<form ng-submit="performUpdate(item)">
|
||||
<input ng-value="item.calendar.caldav"
|
||||
readonly
|
||||
type="text"/>
|
||||
<input type="button" value="" class="n icon-close button-next-to-input" ng-click="item.hideCalDAVUrl()">
|
||||
</form>
|
||||
</div>
|
||||
<div class="app-navigation-entry-edit"
|
||||
ng-if="item.displayWebCalUrl()">
|
||||
<form ng-submit="performUpdate(item)">
|
||||
<input ng-value="item.calendar.storedUrl"
|
||||
readonly
|
||||
type="text"/>
|
||||
<input type="button" value="" class="n icon-close button-next-to-input" ng-click="item.hideWebCalUrl()">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="calendarShares"
|
||||
ng-show="item.isEditingShares()">
|
||||
<i class="glyphicon glyphicon-refresh refresh-shares"
|
||||
ng-show="loadingSharees">
|
||||
</i>
|
||||
<input class="shareeInput"
|
||||
ng-if="isSharingAPI"
|
||||
ng-model="item.selectedSharee"
|
||||
placeholder="<?php p($l->t('Share with users, groups or circles')); ?>"
|
||||
type="text"
|
||||
typeahead-on-select="onSelectSharee($item, $model, $label, item)"
|
||||
typeahead-loading="loadingSharees"
|
||||
typeahead-template-url="customShareMatchTemplate.html"
|
||||
uib-typeahead="sharee.display for sharee in findSharee($viewValue, item.calendar)">
|
||||
<ul class="calendar-share-list">
|
||||
<li class="calendar-share-item"
|
||||
ng-repeat="userShare in item.calendar.shares.users"
|
||||
title="{{ userShare.displayname }}">
|
||||
{{ userShare.displayname }} -
|
||||
<span>
|
||||
<input id="checkbox_sharedWithUser_{{ $parent.$index }}_{{ $id }}"
|
||||
name="editable"
|
||||
class="checkbox"
|
||||
ng-change="updateExistingUserShare(item.calendar, userShare.id, userShare.displayname, userShare.writable)"
|
||||
ng-model="userShare.writable"
|
||||
type="checkbox"
|
||||
value="edit">
|
||||
<label for="checkbox_sharedWithUser_{{ $parent.$index }}_{{ $id }}">
|
||||
<?php p($l->t('can edit')); ?>
|
||||
</label>
|
||||
</span>
|
||||
<span class="utils hide">
|
||||
<span class="action">
|
||||
<span class="icon-delete"
|
||||
href="#"
|
||||
id="calendarlist-icon delete"
|
||||
ng-click="unshareFromUser(item.calendar, userShare.id)"
|
||||
title="<?php p($l->t('Delete')); ?>">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="calendar-share-item"
|
||||
ng-repeat="groupShare in item.calendar.shares.groups"
|
||||
title="{{ groupShare.displayname }} (<?php p($l->t('group')); ?>)">
|
||||
{{ groupShare.displayname }} (<?php p($l->t('group')); ?>) -
|
||||
<span>
|
||||
<input id="checkbox_sharedWithGroup_{{ $parent.$index }}_{{ $id }}"
|
||||
name="editable"
|
||||
class="checkbox"
|
||||
ng-change="updateExistingGroupShare(item.calendar, groupShare.id, groupShare.displayname, groupShare.writable)"
|
||||
ng-model="groupShare.writable"
|
||||
type="checkbox"
|
||||
value="edit">
|
||||
<label for="checkbox_sharedWithGroup_{{ $parent.$index }}_{{ $id }}">
|
||||
<?php p($l->t('can edit')); ?>
|
||||
</label>
|
||||
</span>
|
||||
<span class="utils hide">
|
||||
<span class="action">
|
||||
<span class="icon-delete"
|
||||
href="#"
|
||||
id="calendarlist-icon delete"
|
||||
ng-click="unshareFromGroup(item.calendar, groupShare.id)"
|
||||
title="<?php p($l->t('Delete')); ?>">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="calendar-share-item"
|
||||
ng-repeat="circleShare in item.calendar.shares.circles"
|
||||
title="{{ circleShare.displayname }} (<?php p($l->t('circle')); ?>)">
|
||||
{{ circleShare.displayname }} (<?php p($l->t('circle')); ?>) -
|
||||
<span>
|
||||
<input id="checkbox_sharedWithCircle_{{ $parent.$index }}_{{ $id }}"
|
||||
name="editable"
|
||||
class="checkbox"
|
||||
ng-change="updateExistingCircleShare(item.calendar, circleShare.id, circleShare.displayname, circleShare.writable)"
|
||||
ng-model="circleShare.writable"
|
||||
type="checkbox"
|
||||
value="edit">
|
||||
<label for="checkbox_sharedWithCircle_{{ $parent.$index }}_{{ $id }}">
|
||||
<?php p($l->t('can edit')); ?>
|
||||
</label>
|
||||
</span>
|
||||
<span class="utils hide">
|
||||
<span class="action">
|
||||
<span class="icon-delete"
|
||||
href="#"
|
||||
id="calendarlist-icon delete"
|
||||
ng-click="unshareFromCircle(item.calendar, circleShare.id)"
|
||||
title="<?php p($l->t('Delete')); ?>">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="publishing" ng-if="item.calendar.isPublishable() && canSharePublicLink">
|
||||
<input type="checkbox" name="publish"
|
||||
class="checkbox"
|
||||
id="checkbox_publish_calendar_{{ $index }}"
|
||||
ng-model="item.calendar.published" value="edit"
|
||||
ng-change="togglePublish(item)">
|
||||
<label for="checkbox_publish_calendar_{{ $index }}">
|
||||
<?php p($l->t('Share link')); ?>
|
||||
</label>
|
||||
<div ng-show="item.calendar.published">
|
||||
<span><?php p($l->t('Public access')); ?></span>
|
||||
<span class="icon-public pull-right svg publication-tools"
|
||||
target="_blank"
|
||||
ng-href="item.publicSharingURL"
|
||||
ng-click="goPublic(item)"></span>
|
||||
<span class="icon-mail pull-right svg publication-tools"
|
||||
target="_blank"
|
||||
ng-click="item.toggleSendingMail()"></span>
|
||||
</div>
|
||||
<form ng-submit="sendMail(item)" ng-show="item.isSendingMail() && item.calendar.published">
|
||||
<input class="mailerInput"
|
||||
ng-model="item.email"
|
||||
placeholder="<?php p($l->t('Email link to person')); ?>"
|
||||
type="text">
|
||||
<button type="submit"><?php p($l->t('Send')); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<li ng-if="is.loading" class="icon-loading-small loader-list"><a></a></li>
|
||||
<li ng-repeat="item in calendarListItems | orderBy: item.calendar.order | calendarListFilter"
|
||||
class="app-navigation-list-item"
|
||||
ng-class="{
|
||||
active: item.calendar.enabled,
|
||||
'icon-loading-small': item.displaySpinner(),
|
||||
editing: item.isEditing() || item.displayCalDAVUrl || item.displayWebCalUrl
|
||||
}">
|
||||
<?php print_unescaped($this->inc('part.calendarlist.item')); ?>
|
||||
</li>
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<li class="new-entity-container" ng-class="{'editing': addingCal}">
|
||||
|
||||
<a class="new-entity icon-add" ng-click="openNewCalendarForm()" id="new-calendar-button">
|
||||
<?php p($l->t('New Calendar')); ?>
|
||||
</a>
|
||||
|
||||
<div class="app-navigation-entry-edit calendarlist-fieldset add-new hide">
|
||||
<form ng-submit="create(newCalendarInputVal)">
|
||||
<input class="app-navigation-input"
|
||||
type="text"
|
||||
ng-model="newCalendarInputVal"
|
||||
autofocus
|
||||
placeholder="<?php p($l->t('Name')); ?>"
|
||||
ng-disabled="addingCalRequest" />
|
||||
<span class="add-new-is-processing icon-loading-small"
|
||||
ng-class="{'hidden': !addingCalRequest}"></span>
|
||||
<input type="button" value=""
|
||||
class="icon-close"
|
||||
ng-click="dismissNewCalendar()"
|
||||
ng-disabled="addingCalRequest" />
|
||||
<input type="submit" value=""
|
||||
class="icon-checkmark accept-button new-accept-button"
|
||||
id="submitnewCalendar"
|
||||
oc-click-slide-toggle="{
|
||||
selector: '.add-new',
|
||||
hideOnFocusLost: false,
|
||||
cssClass: 'closed'
|
||||
}"
|
||||
ng-disabled="addingCalRequest" />
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
<li class="new-entity-container" ng-class="{'editing': addingSub}">
|
||||
|
||||
<a class="new-entity icon-add" ng-click="openNewSubscriptionForm()" id="new-subscription-button">
|
||||
<?php p($l->t('New Subscription')); ?>
|
||||
</a>
|
||||
|
||||
<div class="app-navigation-entry-edit calendarlist-fieldset add-new hide">
|
||||
<form ng-submit="createSubscription(subscription.newSubscriptionUrl)">
|
||||
<input
|
||||
class="app-navigation-input"
|
||||
type="text"
|
||||
ng-disabled="subscription.newSubscriptionLocked"
|
||||
ng-model="subscription.newSubscriptionUrl"
|
||||
placeholder="<?php p($l->t('iCal link')); ?>"
|
||||
autofocus />
|
||||
<span class="add-new-is-processing icon-loading-small"
|
||||
ng-class="{'hidden': !subscription.newSubscriptionLocked}"></span>
|
||||
<input
|
||||
type="button" value=""
|
||||
class="icon-close"
|
||||
ng-click="dismissNewSubscription()"
|
||||
ng-disabled="subscription.newSubscriptionLocked" />
|
||||
<input
|
||||
id="submitnewSubscription"
|
||||
class="accept-button icon-checkmark"
|
||||
ng-disabled="subscription.newSubscriptionLocked"
|
||||
oc-click-slide-toggle="{
|
||||
selector: '.add-new-subscription',
|
||||
hideOnFocusLost: false,
|
||||
cssClass: 'closed'
|
||||
}"
|
||||
type="submit"
|
||||
value="" />
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<div class="datepicker-heading">
|
||||
<a href="#" class="icon-view-previous" ng-click="prev()" aria-label="<?php p($l->t('Go back')) ?>"></a>
|
||||
<button ng-cloak type="button" class="button middle" ng-click="toggle()">
|
||||
{{ dt | datepickerFilter:selectedView }}
|
||||
</button>
|
||||
<a href="#" class="icon-view-next" ng-click="next()" aria-label="<?php p($l->t('Go forward')) ?>"></a>
|
||||
</div>
|
||||
<div id="datepicker-ng-show-container" class="ng-hide" ng-show="visibility">
|
||||
<div
|
||||
ng-model="dt"
|
||||
id="datepicker"
|
||||
uib-datepicker
|
||||
datepicker-options="datepickerOptions">
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
<div id="emptycontent">
|
||||
<div class="icon-calendar-dark"></div>
|
||||
<h2><?php p($l->t('Calendar does not exist')); ?></h2>
|
||||
<p><?php p($l->t('Maybe you got a wrong link or the calendar was unshared?')); ?></p>
|
||||
</div>
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
<div id="events"
|
||||
title="<?php p($l->t("Edit event")); ?>"
|
||||
open="{{dialogOpen}}"
|
||||
ok-button="OK"
|
||||
ok-callback="handleOk"
|
||||
cancel-button="Cancel"
|
||||
cancel-callback="handleCancel"
|
||||
ng-init="monthday = true; yearly = true;">
|
||||
<form id="event_form">
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li class="tab pull-left" ng-repeat="tab in tabs"
|
||||
ng-click="tabopener(tab.value)" ng-class="{active: tab.value == selected}">
|
||||
{{tab.title}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="events-container">
|
||||
<div ng-include="currentTab"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="eventsinfoview">
|
||||
<?php print_unescaped($this->inc('part.eventsinfo')); ?>
|
||||
</div>
|
||||
|
||||
<div ng-show="eventsattendeeview">
|
||||
<?php print_unescaped($this->inc('part.eventsattendees')); ?>
|
||||
</div>
|
||||
|
||||
<div ng-show="eventsalarmview">
|
||||
<?php print_unescaped($this->inc('part.eventsalarms')); ?>
|
||||
</div>
|
||||
|
||||
<div class="events-container">
|
||||
<fieldset class="event-fieldset pull-left">
|
||||
<button ng-click="delete()" class="event-button button btn">
|
||||
<?php p($l->t('Delete Event')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset class="event-fieldset pull-right">
|
||||
<button ng-click="update()" class="event-button button btn primary">
|
||||
<?php p($l->t('Save Event')); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -1,150 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="advanced--fieldset">
|
||||
<ul class="advanced--fieldset-reminderlist">
|
||||
<li class="advanced--list pull-left" ng-repeat="alarm in properties.alarm" ng-class="{ active : reminderoptions }">
|
||||
<div class="advanced--toggler" ng-model="reminderoptions" ng-click="triggerEdit(alarm)">
|
||||
<span class="pull-left margin-class">{{alarm | simpleReminderDescription}}</span>
|
||||
<button class="advanced--button__icon pull-right icon-close" ng-click="remove(alarm)" type="button">
|
||||
</button>
|
||||
</div>
|
||||
<div class="reminderoptions pull-left full-width" ng-show="alarm.editor.editing">
|
||||
<div class="event-fieldset-alarm-editor">
|
||||
<!-- simple reminder settings - should fit >95% if all cases -->
|
||||
<div class="advanced--fieldset-interior">
|
||||
<div class="pull-left pull-half">
|
||||
<span><?php p($l->t('Time')); ?></span>
|
||||
<select class="advanced--select advanced--select__reminder"
|
||||
ng-model="alarm.editor.reminderSelectValue"
|
||||
ng-change="updateReminderSelectValue(alarm)"
|
||||
ng-options="reminder.trigger as reminder.displayname for reminder in reminderSelect">
|
||||
</select>
|
||||
</div>
|
||||
<div class="pull-right pull-half">
|
||||
<span><?php p($l->t('Type')); ?></span>
|
||||
<select class="advanced--select advanced--select__reminder"
|
||||
ng-model="alarm.action.value">
|
||||
<option ng-repeat="reminder in reminderTypeSelect"
|
||||
ng-selected="{{reminder.type == alarm.action.value}}"
|
||||
value="{{reminder.type}}">{{reminder.displayname}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clear-both"></div>
|
||||
</div>
|
||||
<div class="advanced--fieldset-interior" ng-show="alarm.editor.reminderSelectValue == 'custom'">
|
||||
<div class="event-fieldset-custom-interior">
|
||||
<!-- Select between relative and absolute -->
|
||||
<div class="relative-container custom-container">
|
||||
<input type="radio" name="relativeorabsolute_{{$id}}"
|
||||
id="relativereminderradio_{{$id}}" class="event-radio"
|
||||
value="relative" ng-model="alarm.editor.triggerType"
|
||||
ng-change="updateReminderRelative(alarm)" />
|
||||
<label for="relativereminderradio_{{$id}}"><?php p($l->t('Relative')); ?></label>
|
||||
<input type="radio" name="relativeorabsolute_{{$id}}"
|
||||
id="absolutereminderradio_{{$id}}" class="event-radio"
|
||||
value="absolute" ng-model="alarm.editor.triggerType"
|
||||
ng-change="updateReminderAbsolute(alarm)" />
|
||||
<label for="absolutereminderradio_{{$id}}"><?php p($l->t('Absolute')); ?></label>
|
||||
</div>
|
||||
<div class="clear-both"></div>
|
||||
<!-- Relative input -->
|
||||
<div class="custom-container-options" ng-show="alarm.editor.triggerType === 'relative'">
|
||||
<input id="relativealarm_{{$id}}" class="event-input relativealarm pull-quarter" type="number"
|
||||
ng-model="alarm.editor.triggerValue"
|
||||
ng-disabled="alarm.editor.triggerType != 'relative'"
|
||||
ng-change="updateReminderRelative(alarm)"
|
||||
/>
|
||||
<select class="event-select event-select-reminder pull-quarter"
|
||||
ng-disabled="alarm.editor.triggerType != 'relative'"
|
||||
ng-model="alarm.editor.triggerTimeUnit"
|
||||
ng-change="updateReminderRelative(alarm)">
|
||||
<option ng-repeat="reminder in timeUnitReminderSelect"
|
||||
ng-selected="{{reminder.factor == alarm.editor.triggerTimeUnit}}"
|
||||
value="{{reminder.factor}}">{{reminder.displayname}}</option>
|
||||
</select>
|
||||
<select class="event-select event-select-reminder pull-quarter"
|
||||
ng-disabled="alarm.editor.triggerType != 'relative'"
|
||||
ng-model="alarm.editor.triggerBeforeAfter"
|
||||
ng-change="updateReminderRelative(alarm)"
|
||||
ng-options="reminder.factor as reminder.displayname for reminder in timePositionReminderSelect">
|
||||
</select>
|
||||
<select class="event-select event-select-reminder pull-quarter"
|
||||
ng-disabled="alarm.editor.triggerType != 'relative'"
|
||||
ng-model="alarm.trigger.related"
|
||||
ng-change="updateReminderRelative(alarm)">
|
||||
<option ng-repeat="reminder in startEndReminderSelect"
|
||||
ng-selected="{{reminder.type == alarm.trigger.related}}"
|
||||
value="{{reminder.type}}">{{reminder.displayname}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- absolute input -->
|
||||
<div class="custom-container-options" ng-show="alarm.editor.triggerType === 'absolute'">
|
||||
<ocdatetimepicker ng-model="alarm.editor.absMoment"></ocdatetimepicker>
|
||||
</div>
|
||||
<!-- repeat settings -->
|
||||
<!--
|
||||
<div class="custom-container repeat-container">
|
||||
<input type="checkbox" class="event-checkbox"
|
||||
id="repeatabsolutereminder_{{$id}}"
|
||||
ng-model="alarm.editor.repeat"
|
||||
ng-change="updateReminderRepeat(alarm)" />
|
||||
<label for="repeatabsolutereminder_{{$id}}"><?php p($l->t('Repeat')); ?></label>
|
||||
</div>
|
||||
<div class="custom-container-options" ng-show="alarm.editor.repeat == true">
|
||||
<input class="event-input pull-quarter" type="number"
|
||||
ng-model="alarm.editor.repeatNTimes"
|
||||
ng-disabled="alarm.editor.repeat == false"
|
||||
ng-change="updateReminderRepeat(alarm)" />
|
||||
<span class="pull-quarter inline"><?php p($l->t('times every')); ?></span>
|
||||
<input class="event-input pull-quarter" type="number"
|
||||
ng-model="alarm.editor.repeatNValue"
|
||||
ng-disabled="alarm.editor.repeat == false"
|
||||
ng-change="updateReminderRepeat(alarm)" />
|
||||
<select class="event-select event-select-reminder pull-quarter"
|
||||
ng-model="alarm.editor.repeatTimeUnit"
|
||||
ng-disabled="alarm.editor.repeat == false"
|
||||
ng-change="updateReminderRepeat(alarm)">
|
||||
<option ng-repeat="reminder in timeUnitReminderSelect"
|
||||
ng-selected="{{reminder.factor == alarm.editor.repeatTimeUnit}}"
|
||||
value="{{reminder.factor}}">{{reminder.displayname}}</option>
|
||||
</select>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<em class="events-fieldset-interior" ng-show="hasAnyEmailReminders()">
|
||||
<?php p($l->t('Email reminders have not been implemented in the CalDAV server yet, so none will be sent.')); ?>
|
||||
</em>
|
||||
<div class="event-fieldset-interior">
|
||||
<button id="addreminders" ng-click="add()" class="btn event-button button" type="button">
|
||||
<?php p($l->t('Add')); ?>
|
||||
</button>
|
||||
</div>
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<div class="advanced--fieldset" ng-hide="emailAddress === ''">
|
||||
<input type="text" class="advanced--input attendeename" ng-model="nameofattendee"
|
||||
ng-keyup="$event.keyCode == 13 && add(nameofattendee)"
|
||||
placeholder="<?php p($l->t('Email address of attendee'))?>"
|
||||
name="nameofattendee" autocomplete="off"
|
||||
uib-typeahead="contact as contact.displayname for contact in search($viewValue)"
|
||||
typeahead-show-hint="true" typeahead-min-length="3"
|
||||
typeahead-on-select="selectFromTypeahead($item)" />
|
||||
<button id="addmoreattendees" ng-click="add(nameofattendee)" class="btn event-button button" type="button">
|
||||
<?php p($l->t('Add')); ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="advanced--fieldset" ng-show="emailAddress === ''">
|
||||
<span><?php p($l->t('Please add your email address in the personal settings in order to add attendees.')); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="advanced--fieldset">
|
||||
<ul class="advanced--fieldset-attendeelist">
|
||||
<li class="pull-left" ng-class="{ active: attendeeoptions }" ng-repeat="attendee in properties.attendee | attendeeNotOrganizerFilter: $scope.emailAddress">
|
||||
<div class="advanced--toggler" ng-model="attendeeoptions" ng-click="attendeeoptions=!attendeeoptions">
|
||||
<span class="bold pull-left">{{ attendee | attendeeFilter }}</span>
|
||||
<button class="event-button event-delete-button icon-close pull-right" ng-click="remove(attendee)" type="button">
|
||||
</button>
|
||||
</div>
|
||||
<div class="attendeeoptions" ng-show="attendeeoptions">
|
||||
<label class="label" for="attendeecutype_{{$id}}"><?php p($l->t('Type')); ?>:</label>
|
||||
<select class="event-select pull-left"
|
||||
ng-model="attendee.parameters.cutype"
|
||||
ng-selected="attendee.parameters.cutype"
|
||||
ng-options="cutstat.val as cutstat.displayname for cutstat in cutstats"
|
||||
id="attendeecutype_{{$id}}">
|
||||
</select>
|
||||
<div class="attendeeopt pull-right">
|
||||
<input type="checkbox" name="attendeecheckbox" class="checkbox"
|
||||
ng-checked="attendee.parameters.role == 'OPT-PARTICIPANT'"
|
||||
ng-click="attendee.parameters.role == 'OPT-PARTICIPANT' ? attendee.parameters.role = 'REQ-PARTICIPANT' : attendee.parameters.role = 'OPT-PARTICIPANT'"
|
||||
id="attendeeopt_{{$id}}"/>
|
||||
<label class="optionallabel" for="attendeeopt_{{$id}}"><?php p($l->t('Optional'));?></label>
|
||||
<input type="checkbox" name="attendeecheckbox" class="checkbox"
|
||||
ng-checked="attendee.parameters.role == 'NON-PARTICIPANT'"
|
||||
ng-click="attendee.parameters.role == 'NON-PARTICIPANT' ? attendee.parameters.role = 'REQ-PARTICIPANT' : attendee.parameters.role = 'NON-PARTICIPANT'"
|
||||
id="attendeeno_{{$id}}"/>
|
||||
<label class="optionallabel" for="attendeeno_{{$id}}"><?php p($l->t('Does not attend'));?></label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
|
||||
<fieldset class="event-fieldset">
|
||||
<input
|
||||
class="event-input h2"
|
||||
ng-model="properties.summary.value"
|
||||
placeholder="<?php p($l->t('Title of the Event'));?>"
|
||||
name="title" type="text"
|
||||
autofocus="autofocus"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="event-calendarselector">
|
||||
<select
|
||||
ng-model="calendar"
|
||||
ng-init="calendar = oldCalendar || calendars[0]"
|
||||
ng-options="c as c.displayname for c in calendars | orderBy:['order'] | calendarSelectorFilter: oldCalendar"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="event-time event-fieldset">
|
||||
<div class="event-time-interior pull-left">
|
||||
<input type="text" name="from" id="from" ng-model="fromdatemodel" placeholder="<?php p($l->t('from'));?>" />
|
||||
<input type="text" name="fromtime" id="fromtime" ng-model="fromtimemodel" ng-disabled="properties.allDay" />
|
||||
</div>
|
||||
<div class="event-time-interior pull-right">
|
||||
<input type="text" name="to" id="to" ng-model="todatemodel" placeholder="<?php p($l->t('to'));?>" />
|
||||
<input type="text" name="totime" id="totime" ng-model="totimemodel" ng-disabled="properties.allDay" />
|
||||
</div>
|
||||
<div class="event-time-interior event-time-interior-allday pull-left">
|
||||
<input type="checkbox" name="alldayeventcheckbox"
|
||||
ng-model="properties.allDay"
|
||||
id="alldayeventcheckbox" class="event-checkbox" />
|
||||
<label for="alldayeventcheckbox"><?php p($l->t('All day Event'))?></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="event-fieldset">
|
||||
<input ng-model="properties.location.value" type="text" class="event-input"
|
||||
placeholder="<?php p($l->t('Location'));?>" name="location"
|
||||
uib-typeahead="location for location in getLocation($viewValue)"
|
||||
autocomplete="off" />
|
||||
</fieldset>
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
|
||||
<fieldset ng-show="rruleNotSupported">
|
||||
<span><?php p($l->t('This event\'s repeating rule is not supported yet.')); ?></span>
|
||||
<span class="hint"><?php p($l->t('Support for advanced rules will be added with subsequent updates.')); ?></span>
|
||||
<button ng-click="resetRRule()"><?php p($l->t('Reset repeating rule')); ?></button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-hide="rruleNotSupported">
|
||||
<select
|
||||
id="frequency_select"
|
||||
ng-options="repeat.val as repeat.displayname for repeat in repeat_options_simple"
|
||||
ng-model="properties.rrule.freq">
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
|
||||
|
||||
<fieldset class="event-fieldset" ng-hide="properties.rrule.freq === 'NONE' || rruleNotSupported">
|
||||
<label class="pull-left">
|
||||
<?php p($l->t('Repeat every ...')); ?>
|
||||
</label>
|
||||
<input
|
||||
class="pull-right pull-half"
|
||||
type="number"
|
||||
min="1"
|
||||
ng-model="properties.rrule.interval">
|
||||
<div class="clear-both"></div>
|
||||
<label class="pull-left inline">
|
||||
<?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="selected_repeat_end">
|
||||
</select>
|
||||
</div>
|
||||
<div class="clear-both"></div>
|
||||
<div class="pull-right pull-half" ng-show="selected_repeat_end === 'COUNT'">
|
||||
<input type="number" min="1" ng-model="properties.rrule.count">
|
||||
<?php p($l->t('times')); ?>
|
||||
</div>
|
||||
<!--
|
||||
<div class="pull-right pull-half" ng-show="selected_repeat_end === 'UNTIL'">
|
||||
<ocdatetimepicker ng-model="properties.rrule.until"></ocdatetimepicker>
|
||||
</div>
|
||||
-->
|
||||
</fieldset>
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<div
|
||||
class="calendar"
|
||||
data-appVersion="<?php p($_['appVersion']); ?>"
|
||||
data-defaultColor="<?php p($_['defaultColor']); ?>"
|
||||
data-initialView="<?php p($_['initialView']); ?>"
|
||||
data-emailAddress="<?php p($_['emailAddress']); ?>"
|
||||
data-firstRun="<?php p($_['firstRun']); ?>"
|
||||
data-skipPopover="<?php p($_['skipPopover']); ?>"
|
||||
data-weekNumbers="<?php p($_['weekNumbers']); ?>"
|
||||
data-isPublic="<?php p($_['isPublic'] ? '1' : '0'); ?>"
|
||||
data-isEmbedded="<?php p($_['isEmbedded'] ? '1' : '0'); ?>"
|
||||
data-publicSharingToken="<?php p($_['token']); ?>"
|
||||
data-shareeCanEditShares="<?php p($_['shareeCanEditShares']); ?>"
|
||||
data-shareeCanEditCalendarProperties="<?php p($_['shareeCanEditCalendarProperties']); ?>"
|
||||
data-canSharePublicLink="<?php p($_['canSharePublicLink']); ?>"
|
||||
data-timezone="<?php p($_['timezone']); ?>"
|
||||
fc
|
||||
id="fullcalendar">
|
||||
</div>
|
|
@ -1,111 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<div id="importdialog" class="dialog" title="<?php p($l->t("Import Calendars")); ?>">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr ng-repeat="file in files" ng-show="!file.done">
|
||||
<td class="name">
|
||||
<span>{{ file.file.name | limitTo:nameSize }}{{file.file.name.length < nameSize ? '' : '...'}}</span>
|
||||
</td>
|
||||
<td class="calendartype">
|
||||
<span ng-show="file.wasCanceled()">
|
||||
<?php p($l->t('Import canceled')); ?>
|
||||
</span>
|
||||
<span
|
||||
ng-show="file.isAnalyzing()">
|
||||
<?php p($l->t('Analyzing calendar')); ?>
|
||||
</span>
|
||||
<div
|
||||
ng-show="file.isAnalyzed()">
|
||||
<span
|
||||
class="svg icon-error"
|
||||
ng-show="file.incompatibleObjectsWarning"
|
||||
title="<?php p($l->t('The file contains objects incompatible with the selected calendar')); ?>">
|
||||
|
||||
</span>
|
||||
<select
|
||||
class="settings-select"
|
||||
ng-change="changeCalendar(file)"
|
||||
ng-model="file.selectedCalendar"
|
||||
ng-show="file.isAnalyzed()">
|
||||
<option
|
||||
ng-repeat="calendar in calendars | calendarFilter | orderBy:['order']"
|
||||
value="{{ calendar.url }}">
|
||||
{{ calendar.displayname }}
|
||||
</option>
|
||||
<option
|
||||
value="new">
|
||||
<?php p($l->t('New calendar')); ?>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<span
|
||||
ng-show="file.isEmpty()">
|
||||
<?php p($l->t('File is empty')); ?>
|
||||
</span>
|
||||
<span
|
||||
ng-show="file.hasParsingErrors()">
|
||||
<?php p($l->t('File could not be parsed')); ?>
|
||||
</span>
|
||||
<span
|
||||
ng-show="file.isScheduled()">
|
||||
<?php p($l->t('Import scheduled')); ?>
|
||||
</span>
|
||||
<uib-progressbar
|
||||
ng-show="file.isImporting()"
|
||||
animate="false"
|
||||
value="file.progress"
|
||||
max="file.progressToReach">
|
||||
|
||||
</uib-progressbar>
|
||||
<div
|
||||
ng-show="file.isDone()">
|
||||
<span>
|
||||
{{ file | importErrorFilter }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="buttongroup">
|
||||
<div class="pull-right" ng-show="file.isAnalyzed()">
|
||||
<button
|
||||
class="primary btn icon-checkmark-white"
|
||||
ng-click="import(file)">
|
||||
</button>
|
||||
<button
|
||||
class="btn icon-close"
|
||||
ng-click="cancelFile(file)">
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-show="showCloseButton">
|
||||
<div class="spacer" ng-show="didFail"></div>
|
||||
<button class="button btn btn-full" ng-click="close()"><?php p($l->t('Close')); ?></button>
|
||||
</div>
|
||||
</div>
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<div id="scollable" class="settings-fieldset-interior public-left-side" ng-repeat="item in calendarListItems">
|
||||
<div class="avatardiv" data-user="{{ item.calendar.owner }}" data-size="96" avatar></div>
|
||||
<h3 class="action permanent displayname" ng-cloak>
|
||||
<?php print_unescaped($l->t('%s shared the calendar <strong>%s</strong> with you', ['{{ item.getOwnerName() }}', '{{ item.getPublicDisplayname() }}'])); ?>
|
||||
</h3>
|
||||
<div class="icon-loading-small"
|
||||
ng-show="item.displaySpinner()" ng-cloak>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(!$_['isEmbedded']): ?>
|
||||
<div id="app-settings">
|
||||
<div id="app-settings-header">
|
||||
<button name="app settings"
|
||||
class="settings-button icon-embed"
|
||||
data-apps-slide-toggle="#app-settings-content">
|
||||
<?php p($l->t('Embed')); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="app-settings-content" ng-repeat="item in calendarListItems">
|
||||
<fieldset class="settings-fieldset">
|
||||
<ul class="settings-fieldset-interior">
|
||||
<li class="settings-fieldset-interior-item">
|
||||
<label><?php p($l->t('Iframe to integrate')); ?></label>
|
||||
<textarea class="integration-code"
|
||||
type="text"
|
||||
ng-value="integration(item)"
|
||||
placeholder="<?php p($l->t('Publish URL')); ?>">
|
||||
</textarea>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<div id="app-settings-header">
|
||||
<button name="app settings"
|
||||
class="settings-button"
|
||||
data-apps-slide-toggle="#app-settings-content">
|
||||
<?php p($l->t('Settings & import')); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="app-settings-content">
|
||||
<fieldset class="settings-fieldset">
|
||||
<ul class="settings-fieldset-interior">
|
||||
<li class="settings-fieldset-interior-item settings-fieldset-interior-upload">
|
||||
<input type="file" name="file" accept="text/calendar" multiple id="import" />
|
||||
<span href="#" class="button settings-upload svg icon-upload" role="button" id="import-button-overlay"><?php p($l->t('Import calendar')); ?></span>
|
||||
<span ng-show="!files.length" class="hide"><?php p($l->t('No Calendars selected for import')); ?></span>
|
||||
</li>
|
||||
<li class="settings-fieldset-interior-item">
|
||||
<input class="checkbox" type="checkbox" ng-change="updateSkipPopover()" ng-model="skipPopover" ng-true-value="'yes'" ng-false-value="'no'" id="skip_popover_checkbox"/>
|
||||
<label for="skip_popover_checkbox">
|
||||
<?php p($l->t('Skip simple event editor')); ?>
|
||||
</label>
|
||||
</li>
|
||||
<li class="settings-fieldset-interior-item settings-fieldset-interior-weeknumbers">
|
||||
<input class="checkbox" type="checkbox" ng-change="updateShowWeekNr()" ng-model="settingsShowWeekNr" ng-true-value="'yes'" ng-false-value="'no'" id="show_weeknumbers_checkbox"/>
|
||||
<label for="show_weeknumbers_checkbox">
|
||||
<?php p($l->t('Show week numbers')); ?>
|
||||
</label>
|
||||
</li>
|
||||
<li class="settings-fieldset-interior-item">
|
||||
<label class="settings-input"><?php p($l->t('Timezone')); ?></label>
|
||||
<select ng-options="timezone.value as timezone.displayname | timezoneWithoutContinentFilter group by timezone.group for timezone in timezones"
|
||||
ng-model="timezone" ng-change="setTimezone()" class="input settings-input"></select>
|
||||
</li>
|
||||
<li class="settings-fieldset-interior-item">
|
||||
<label class="settings-input"><?php p($l->t('Primary CalDAV address')); ?></label>
|
||||
<input class="input settings-input" type="text" ng-model="settingsCalDavLink" readonly />
|
||||
</li>
|
||||
<li class="settings-fieldset-interior-item">
|
||||
<label class="settings-label"><?php p($l->t('iOS/macOS CalDAV address')); ?></label>
|
||||
<input class="input settings-input" type="text" ng-model="settingsCalDavPrincipalLink" readonly />
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
?>
|
||||
<li ng-if="is.loading" class="icon-loading-small loader-list"><a></a></li>
|
||||
<li ng-repeat="item in calendarListItems | orderBy: item.calendar.order | subscriptionListFilter"
|
||||
class="app-navigation-list-item"
|
||||
ng-class="{
|
||||
active: item.calendar.enabled,
|
||||
'icon-loading-small': item.displaySpinner(),
|
||||
editing: item.isEditing() || item.displayCalDAVUrl || item.displayWebCalUrl
|
||||
}">
|
||||
<?php print_unescaped($this->inc('part.calendarlist.item')); ?>
|
||||
</li>
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = function (config) {
|
||||
'use strict';
|
||||
|
||||
config.set({
|
||||
basePath: '../../../',
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'../../core/vendor/jquery/dist/jquery.js',
|
||||
'../../core/vendor/jquery/jquery.js',
|
||||
'../../core/vendor/moment/min/moment-with-locales.js',
|
||||
'../../core/vendor/moment/min/moment-with-locales.min.js',
|
||||
'../../core/vendor/davclient.js/lib/client.js',
|
||||
'js/node_modules/jstzdetect/dist/jstz.min.js',
|
||||
'js/node_modules/fullcalendar/dist/fullcalendar.min.js',
|
||||
'js/node_modules/angular/angular.js',
|
||||
'js/node_modules/angular-mocks/angular-mocks.js',
|
||||
'js/node_modules/ical.js/build/ical.js',
|
||||
'js/node_modules/hsl_rgb_converter/converter.js',
|
||||
'tests/js/stubs/app.js',
|
||||
'js/app/**/*.js',
|
||||
'tests/js/unit/**/*.js'
|
||||
],
|
||||
exclude: [],
|
||||
reporters: ['progress', 'coverage'],
|
||||
port: 8080,
|
||||
colors: true,
|
||||
autoWatch: false,
|
||||
browsers: ['Firefox'],
|
||||
singleRun: true,
|
||||
preprocessors: { 'js/app/**/*.js': ['coverage'] },
|
||||
coverageReporter: {
|
||||
type: 'lcov',
|
||||
dir: 'coverage/'
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
window.app = angular.module('Calendar', [
|
||||
'ngMock'
|
||||
]);
|
||||
|
||||
window.app.config(['$qProvider', function ($qProvider) {
|
||||
$qProvider.errorOnUnhandledRejections(false);
|
||||
}]);
|
||||
|
||||
window.OC = {
|
||||
|
||||
Share: {
|
||||
SHARE_TYPE_USER: 42,
|
||||
SHARE_TYPE_GROUP: 1337,
|
||||
SHARE_TYPE_CIRCLE: 4932
|
||||
},
|
||||
|
||||
linkToRemote: function (url) {
|
||||
'use strict';
|
||||
|
||||
return '/base' + url;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
escapeHTML = function () {
|
||||
'use strict';
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
oc_current_user = 'user';
|
||||
oc_requesttoken = 'requestToken42';
|
||||
|
||||
|
||||
|
||||
function t(app, text, vars, count, options) {
|
||||
'use strict';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
moment.locale('en');
|
|
@ -1,138 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('CalendarListController', function() {
|
||||
'use strict';
|
||||
|
||||
var controller, $scope, $rootScope, Calendar, CalendarService, ColorUtility, $window, calendar, deferred;
|
||||
|
||||
beforeEach(module('Calendar', function($provide) {
|
||||
$provide.value('Calendar', {});
|
||||
$provide.value('CalendarListItem', {});
|
||||
$provide.value('WebCalService', {});
|
||||
$provide.value('isSharingAPI', true);
|
||||
$provide.value('constants', {});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function ($controller, _$rootScope_, _$window_, $q) {
|
||||
$scope = _$rootScope_.$new();
|
||||
$rootScope = _$rootScope_;
|
||||
$window = _$window_;
|
||||
controller = $controller;
|
||||
|
||||
CalendarService = {
|
||||
getAll: function(){},
|
||||
get: function() {},
|
||||
create: function() {}
|
||||
};
|
||||
Calendar = function() {
|
||||
return {
|
||||
list: {},
|
||||
update: jasmine.createSpy(),
|
||||
delete: jasmine.createSpy(),
|
||||
publish: jasmine.createSpy(),
|
||||
unpublish: jasmine.createSpy()
|
||||
};
|
||||
};
|
||||
ColorUtility = {
|
||||
randomColor: () => {}
|
||||
};
|
||||
|
||||
deferred = $q.defer();
|
||||
deferred.resolve(new Calendar());
|
||||
}
|
||||
));
|
||||
|
||||
it ('should create a calendar', function() {
|
||||
spyOn(CalendarService, 'create').and.returnValue(deferred.promise);
|
||||
spyOn(ColorUtility, 'randomColor').and.returnValue('#ffffff');
|
||||
|
||||
controller = controller('CalendarListController', {
|
||||
$scope: $scope,
|
||||
CalendarService: CalendarService,
|
||||
ColorUtility: ColorUtility
|
||||
});
|
||||
|
||||
$scope.newCalendarInputVal = 'Sample Calendar';
|
||||
|
||||
$scope.create($scope.newCalendarInputVal);
|
||||
expect(CalendarService.create).toHaveBeenCalledWith('Sample Calendar', '#ffffff');
|
||||
});
|
||||
|
||||
it ('should delete the selected calendar', function () {
|
||||
controller = controller('CalendarListController', {
|
||||
$scope: $scope,
|
||||
CalendarService: CalendarService
|
||||
});
|
||||
|
||||
var calendarToDelete = {
|
||||
delete: jasmine.createSpy().and.returnValue(deferred.promise),
|
||||
};
|
||||
var calendarItem = {
|
||||
calendar: calendarToDelete
|
||||
};
|
||||
|
||||
$scope.remove(calendarItem);
|
||||
expect(calendarToDelete.delete).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it ('should publish the selected calendar', function () {
|
||||
controller = controller('CalendarListController', {
|
||||
$scope: $scope,
|
||||
CalendarService: CalendarService
|
||||
});
|
||||
|
||||
var calendarToPublish = {
|
||||
publish: jasmine.createSpy().and.returnValue(deferred.promise),
|
||||
published: true
|
||||
};
|
||||
var calendarItem = {
|
||||
calendar: calendarToPublish
|
||||
};
|
||||
|
||||
$scope.togglePublish(calendarItem);
|
||||
expect(calendarToPublish.publish).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it ('should unpublish the selected calendar', function () {
|
||||
controller = controller('CalendarListController', {
|
||||
$scope: $scope,
|
||||
CalendarService: CalendarService
|
||||
});
|
||||
|
||||
var calendarToUnPublish = {
|
||||
unpublish: jasmine.createSpy().and.returnValue(deferred.promise),
|
||||
published: false
|
||||
};
|
||||
var calendarItem = {
|
||||
calendar: calendarToUnPublish
|
||||
};
|
||||
|
||||
$scope.togglePublish(calendarItem);
|
||||
expect(calendarToUnPublish.unpublish).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
||||
});
|
||||
});
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Thomas Bille
|
||||
* @copyright 2017 Thomas Bille <contact@tbille.fr>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('DatePickerController', function() {
|
||||
'use strict';
|
||||
|
||||
var controller, $scope, fc, uibDatepickerConfig, constants;
|
||||
|
||||
beforeEach(module('Calendar', function($provide) {
|
||||
$provide.value('fc', {});
|
||||
$provide.value('uibDatepickerConfig', {});
|
||||
$provide.value('constants', {});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function ($controller, _$rootScope_) {
|
||||
$scope = _$rootScope_.$new();
|
||||
fc = {
|
||||
elm : {
|
||||
fullCalendar: jasmine.createSpy()
|
||||
}
|
||||
};
|
||||
uibDatepickerConfig = {};
|
||||
constants = {
|
||||
initialView: 'initialView'
|
||||
};
|
||||
controller = $controller('DatePickerController', {
|
||||
$scope: $scope,
|
||||
uibDatepickerConfig: uibDatepickerConfig,
|
||||
fc: fc,
|
||||
constants: constants
|
||||
});
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
it ('should initiate the controller with the right values', function() {
|
||||
var today = new Date();
|
||||
expect($scope.dt.getFullYear()).toBe(today.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(today.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(today.getDate());
|
||||
|
||||
expect($scope.visibility).toBe(false);
|
||||
expect($scope.selectedView).toBe(constants.initialView);
|
||||
|
||||
expect(uibDatepickerConfig.showWeeks).toBe(false);
|
||||
expect(uibDatepickerConfig.startingDay).toBe(0);
|
||||
|
||||
});
|
||||
|
||||
it ('should set the date dt to today', function () {
|
||||
var today = new Date();
|
||||
$scope.dt = new Date(1970, 5, 19);
|
||||
|
||||
$scope.today();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(today.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(today.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(today.getDate());
|
||||
});
|
||||
|
||||
it ('should set $scope.visibility to true', function () {
|
||||
$scope.visibility = false;
|
||||
$scope.toggle();
|
||||
|
||||
expect($scope.visibility).toBe(true);
|
||||
});
|
||||
|
||||
it ('should set $scope.visibility to false', function () {
|
||||
$scope.visibility = true;
|
||||
$scope.toggle();
|
||||
|
||||
expect($scope.visibility).toBe(false);
|
||||
});
|
||||
|
||||
it ('should call fullcalendar on dt modification', function() {
|
||||
$scope.dt = new Date(1970, 5, 19);
|
||||
$scope.$digest();
|
||||
|
||||
expect(fc.elm.fullCalendar).toHaveBeenCalledWith('gotoDate', $scope.dt);
|
||||
});
|
||||
|
||||
it ('should call fullcalendar on dt modification', function() {
|
||||
$scope.selectedView = 'newView';
|
||||
$scope.$digest();
|
||||
|
||||
expect(fc.elm.fullCalendar).toHaveBeenCalledWith('changeView', $scope.selectedView);
|
||||
});
|
||||
|
||||
it ('should add a day to dt', function() {
|
||||
$scope.selectedView = 'agendaDay';
|
||||
$scope.dt = new Date(1970, 4, 19);
|
||||
var expectedDate = new Date(1970, 4, 20);
|
||||
|
||||
$scope.next();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should add a week to dt and focus on the first day of the week', function() {
|
||||
$scope.selectedView = 'agendaWeek';
|
||||
$scope.dt = new Date(2017, 6, 4);
|
||||
var expectedDate = new Date(2017, 6, 9);
|
||||
|
||||
$scope.next();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should add a month to dt and focus on the first day of the month', function() {
|
||||
$scope.selectedView = 'month';
|
||||
$scope.dt = new Date(2017, 6, 4);
|
||||
var expectedDate = new Date(2017, 7, 1);
|
||||
|
||||
$scope.next();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should remove a day to dt', function() {
|
||||
$scope.selectedView = 'agendaDay';
|
||||
$scope.dt = new Date(1970, 4, 19);
|
||||
var expectedDate = new Date(1970, 4, 18);
|
||||
|
||||
$scope.prev();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should remove a week to dt and focus on the first day of the week', function() {
|
||||
$scope.selectedView = 'agendaWeek';
|
||||
$scope.dt = new Date(2017, 6, 4);
|
||||
var expectedDate = new Date(2017, 5, 25);
|
||||
|
||||
$scope.prev();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should remove a month to dt and focus on the first day of the month', function() {
|
||||
$scope.selectedView = 'month';
|
||||
$scope.dt = new Date(2017, 6, 4);
|
||||
var expectedDate = new Date(2017, 5, 1);
|
||||
|
||||
$scope.prev();
|
||||
|
||||
expect($scope.dt.getFullYear()).toBe(expectedDate.getFullYear());
|
||||
expect($scope.dt.getMonth()).toBe(expectedDate.getMonth());
|
||||
expect($scope.dt.getDate()).toBe(expectedDate.getDate());
|
||||
});
|
||||
|
||||
it ('should return highlight-today if the day is today date', function() {
|
||||
var data = {
|
||||
date: new Date()
|
||||
};
|
||||
|
||||
expect($scope.datepickerOptions.customClass(data)).toBe('highlight-today');
|
||||
});
|
||||
|
||||
it ('should return highlight-weekend if the day is weekendDay', function() {
|
||||
var data = {
|
||||
date: new Date(2017, 6, 9)
|
||||
};
|
||||
|
||||
expect($scope.datepickerOptions.customClass(data)).toBe('highlight-weekend');
|
||||
});
|
||||
|
||||
it ('should return an empty string if the day is not today\'s date', function() {
|
||||
var data = {
|
||||
date: new Date(1970, 4, 18)
|
||||
};
|
||||
|
||||
expect($scope.datepickerOptions.customClass(data)).toBe('');
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('SettingsController', function() {
|
||||
'use strict';
|
||||
|
||||
var controller, scope, model, http;
|
||||
|
||||
beforeEach(module('Calendar'));
|
||||
|
||||
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
|
||||
http = $httpBackend;
|
||||
scope = $rootScope.$new();
|
||||
controller = $controller;
|
||||
}
|
||||
));
|
||||
|
||||
it ('should enable the calendar', function() {
|
||||
|
||||
});
|
||||
|
||||
it ('should remove the calendar', function () {
|
||||
|
||||
});
|
||||
|
||||
it ('should upload the calendar', function () {
|
||||
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
http.verifyNoOutstandingExpectation();
|
||||
http.verifyNoOutstandingRequest();
|
||||
});
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Calendar App
|
||||
*
|
||||
* @author Raghu Nayyar
|
||||
* @author Georg Ehrke
|
||||
* @copyright 2016 Raghu Nayyar <hey@raghunayyar.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('openDialog', function() {
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -1,50 +0,0 @@
|
|||
describe('ICalFactory tests', function () {
|
||||
'use strict';
|
||||
|
||||
var ICalFactory, constants;
|
||||
|
||||
beforeEach(module('Calendar', function($provide) {
|
||||
|
||||
constants = {
|
||||
version: '42.2.4'
|
||||
};
|
||||
|
||||
$provide.value('constants', constants);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function (_ICalFactory_) {
|
||||
ICalFactory = _ICalFactory_;
|
||||
}));
|
||||
|
||||
it ('should return an ICAL object', function() {
|
||||
const ical = ICalFactory.new();
|
||||
|
||||
expect(ical.getFirstPropertyValue('version')).toEqual('2.0');
|
||||
expect(ical.getFirstPropertyValue('calscale')).toEqual('GREGORIAN');
|
||||
expect(ical.getFirstPropertyValue('prodid')).toEqual('-//Nextcloud calendar v42.2.4');
|
||||
});
|
||||
|
||||
it ('should return an ICAL object with an event in it', function() {
|
||||
const baseTime = new Date(2016, 0, 1);
|
||||
jasmine.clock().mockDate(baseTime);
|
||||
|
||||
const uid = 'foobar';
|
||||
|
||||
const ical = ICalFactory.newEvent(uid);
|
||||
expect(ical.getFirstPropertyValue('version')).toEqual('2.0');
|
||||
expect(ical.getFirstPropertyValue('calscale')).toEqual('GREGORIAN');
|
||||
expect(ical.getFirstPropertyValue('prodid')).toEqual('-//Nextcloud calendar v42.2.4');
|
||||
|
||||
const components = ical.getAllSubcomponents();
|
||||
expect(components.length).toEqual(1);
|
||||
|
||||
expect(components[0].name).toEqual('vevent');
|
||||
expect(components[0].getAllProperties().length).toEqual(5);
|
||||
|
||||
expect(components[0].getFirstPropertyValue('created').toString()).toEqual('2016-01-01T00:00:00');
|
||||
expect(components[0].getFirstPropertyValue('dtstamp').toString()).toEqual('2016-01-01T00:00:00');
|
||||
expect(components[0].getFirstPropertyValue('last-modified').toString()).toEqual('2016-01-01T00:00:00');
|
||||
expect(components[0].getFirstPropertyValue('uid')).toEqual('foobar');
|
||||
expect(components[0].getFirstPropertyValue('dtstart').toString()).toEqual('2016-01-01T00:00:00');
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue