remove legacy code

Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
Georg Ehrke 2018-10-17 21:10:03 +02:00
parent d949d73a13
commit 683a2e34cc
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
136 changed files with 2 additions and 32391 deletions

View File

@ -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
}
}

View File

@ -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"]
}]
}
}

View File

@ -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 = '';
};
});

View File

@ -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
});
}
}
}
};
}
]);

View File

@ -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);
});
}
});
}
]);

View File

@ -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);
}
});
}
]);

View File

@ -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'
};
}
};
}
]);

View File

@ -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();
};
}
]);

View File

@ -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 = {};
};
});

View File

@ -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'));
}
};
}
]);

View File

@ -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;
};
});

View File

@ -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);
}
};
});

View File

@ -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;
};
}
};
});

View File

@ -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);
}
]);

View File

@ -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');
});
}
};
});

View File

@ -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);
}
};
});

View File

@ -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();
}
});
}
};
}]
);

View File

@ -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');
});
}
};
});

View File

@ -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();
}
});
}
};
});

View File

@ -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);
};
});

View File

@ -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;
};
});

View File

@ -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();
});
};
});

View File

@ -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();
});
};
});

View File

@ -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 || '';
}
};
});

View File

@ -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;
}
});
};
});

View File

@ -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();
}
});
};
});

View File

@ -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;
}
};
});

View File

@ -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 '';
}
};
});

View File

@ -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;
});
};
});

View File

@ -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});
}
}
};
});

View File

@ -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 '';
}
}
};
});

View File

@ -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();
}
});
};
});

View File

@ -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 + ')';
}
};
}]);

View File

@ -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(' - ');
};
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
};
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
}
);

View File

@ -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 events 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;
});

View File

@ -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;
});

View File

@ -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;
});
};
}
]);

View File

@ -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;
});
};
});

View File

@ -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;
});

View File

@ -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
*/
});

View File

@ -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&param2=value2& ... &paramN=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);
}
};
});

View File

@ -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
};
});

View File

@ -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));
};
}
]);

View File

@ -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;
});
};
}]);

View File

@ -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);
};
});

View File

@ -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);
}
});
};
});

View File

@ -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.')
});
}
});
};
});

View File

@ -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'
];
}
});

View File

@ -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;
};
});

View File

@ -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);
};
});

View File

@ -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;
};
});

View File

@ -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;
}
};
});

View File

@ -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);
};
});

View File

@ -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'
]);

View File

@ -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
});
}
]);

View File

@ -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);
});
}
]);

View File

@ -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();
});

View File

@ -1,5 +0,0 @@
/*!
HSL RGB Converter
author: Keenan Lidral-Porter
license: ISC
*/

View File

@ -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
*/

View File

@ -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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>&nbsp;</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>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -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!')); ?>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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')); ?>">
&nbsp;&nbsp;
</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">
&nbsp;
</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>

View File

@ -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; ?>

View File

@ -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>

View File

@ -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>

View File

@ -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/'
}
});
};

View File

@ -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');

View File

@ -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() {
});
});

View File

@ -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('');
});
});

View File

@ -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();
});
});

View File

@ -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

View File

@ -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