mirror of https://github.com/nextcloud/calendar
Cleanup models and add full unit test coverage
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
parent
53db79f8aa
commit
6f5966a2b4
|
@ -152,7 +152,8 @@
|
||||||
"/node_modules/(?!calendar-js).+\\.js$"
|
"/node_modules/(?!calendar-js).+\\.js$"
|
||||||
],
|
],
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
"./tests/javascript/jest.setup.js"
|
"./tests/javascript/jest.setup.js",
|
||||||
|
"./tests/assets/loadAsset.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
import { getDurationValueFromFullCalendarDuration } from '../fullcalendar/duration'
|
import { getDurationValueFromFullCalendarDuration } from '../fullcalendar/duration'
|
||||||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||||
import logger from '../utils/logger.js'
|
import logger from '../utils/logger.js'
|
||||||
|
import { getObjectAtRecurrenceId } from '../utils/calendarObject.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function to drop an event at a different position
|
* Returns a function to drop an event at a different position
|
||||||
|
@ -60,7 +61,7 @@ export default function(store, fcAPI) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventComponent = calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
|
const eventComponent = getObjectAtRecurrenceId(calendarObject, recurrenceIdDate)
|
||||||
if (!eventComponent) {
|
if (!eventComponent) {
|
||||||
console.debug('Recurrence-id not found')
|
console.debug('Recurrence-id not found')
|
||||||
revert()
|
revert()
|
||||||
|
@ -71,7 +72,9 @@ export default function(store, fcAPI) {
|
||||||
// shiftByDuration may throw exceptions in certain cases
|
// shiftByDuration may throw exceptions in certain cases
|
||||||
eventComponent.shiftByDuration(deltaDuration, event.allDay, timezone, defaultAllDayDuration, defaultTimedDuration)
|
eventComponent.shiftByDuration(deltaDuration, event.allDay, timezone, defaultAllDayDuration, defaultTimedDuration)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
calendarObject.resetToDav()
|
store.commit('resetCalendarObjectToDav', {
|
||||||
|
calendarObject,
|
||||||
|
})
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
revert()
|
revert()
|
||||||
return
|
return
|
||||||
|
@ -86,7 +89,9 @@ export default function(store, fcAPI) {
|
||||||
calendarObject,
|
calendarObject,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
calendarObject.resetToDav()
|
store.commit('resetCalendarObjectToDav', {
|
||||||
|
calendarObject,
|
||||||
|
})
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
revert()
|
revert()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { getDurationValueFromFullCalendarDuration } from './duration'
|
import { getDurationValueFromFullCalendarDuration } from './duration'
|
||||||
|
import { getObjectAtRecurrenceId } from '../utils/calendarObject.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function to resize an event
|
* Returns a function to resize an event
|
||||||
|
@ -50,7 +51,7 @@ export default function(store) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventComponent = calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
|
const eventComponent = getObjectAtRecurrenceId(calendarObject, recurrenceIdDate)
|
||||||
if (!eventComponent) {
|
if (!eventComponent) {
|
||||||
console.debug('Recurrence-id not found')
|
console.debug('Recurrence-id not found')
|
||||||
revert()
|
revert()
|
||||||
|
@ -73,7 +74,9 @@ export default function(store) {
|
||||||
calendarObject,
|
calendarObject,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
calendarObject.resetToDav()
|
store.commit('resetCalendarObjectToDav', {
|
||||||
|
calendarObject,
|
||||||
|
})
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
revert()
|
revert()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
getHexForColorName,
|
getHexForColorName,
|
||||||
} from '../utils/color.js'
|
} from '../utils/color.js'
|
||||||
import logger from '../utils/logger.js'
|
import logger from '../utils/logger.js'
|
||||||
|
import { getAllObjectsInTimeRange } from '../utils/calendarObject.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert an array of calendar-objects to events
|
* convert an array of calendar-objects to events
|
||||||
|
@ -43,7 +44,7 @@ export function eventSourceFunction(calendarObjects, calendar, start, end, timez
|
||||||
for (const calendarObject of calendarObjects) {
|
for (const calendarObject of calendarObjects) {
|
||||||
let allObjectsInTimeRange
|
let allObjectsInTimeRange
|
||||||
try {
|
try {
|
||||||
allObjectsInTimeRange = calendarObject.getAllObjectsInTimeRange(start, end)
|
allObjectsInTimeRange = getAllObjectsInTimeRange(calendarObject, start, end)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -300,7 +300,7 @@ export default {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.calendarObject.existsOnServer()
|
return this.calendarObject.existsOnServer
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the user is allowed to create recurrence exceptions for this event
|
* Returns whether or not the user is allowed to create recurrence exceptions for this event
|
||||||
|
@ -336,7 +336,7 @@ export default {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.calendarObject.existsOnServer()
|
return this.calendarObject.existsOnServer
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Returns the download url as a string or null if event is loading or does not exist on the server (yet)
|
* Returns the download url as a string or null if event is loading or does not exist on the server (yet)
|
||||||
|
@ -385,7 +385,7 @@ export default {
|
||||||
// override the internally stored calendarId. If we did not do this,
|
// override the internally stored calendarId. If we did not do this,
|
||||||
// it would create the event in the default calendar first and move it
|
// it would create the event in the default calendar first and move it
|
||||||
// to the desired calendar as a second step.
|
// to the desired calendar as a second step.
|
||||||
if (this.calendarObject && !this.calendarObject.existsOnServer()) {
|
if (this.calendarObject && !this.calendarObject.existsOnServer) {
|
||||||
this.calendarObject.calendarId = selectedCalendar.id
|
this.calendarObject.calendarId = selectedCalendar.id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -423,7 +423,10 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.$store.dispatch('resetCalendarObjectInstance')
|
this.$store.commit('resetCalendarObjectToDav', {
|
||||||
|
calendarObject: this.calendarObject,
|
||||||
|
})
|
||||||
|
|
||||||
this.requiresActionOnRouteLeave = false
|
this.requiresActionOnRouteLeave = false
|
||||||
this.closeEditor()
|
this.closeEditor()
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getAmountAndUnitForTimedEvents,
|
||||||
|
getAmountHoursMinutesAndUnitForAllDayEvents,
|
||||||
|
} from '../utils/alarms.js'
|
||||||
|
import { getDateFromDateTimeValue } from '../utils/date.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete alarm object based on given props
|
||||||
|
*
|
||||||
|
* @param {Object} props The alarm properties already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultAlarmObject = (props = {}) => Object.assign({}, {
|
||||||
|
// The calendar-js alarm component
|
||||||
|
alarmComponent: null,
|
||||||
|
// Type of alarm: DISPLAY, EMAIL, AUDIO
|
||||||
|
type: null,
|
||||||
|
// Whether or not the alarm is relative
|
||||||
|
isRelative: false,
|
||||||
|
// Date object of an absolute alarm (if it's absolute, it must be DATE-TIME)
|
||||||
|
absoluteDate: null,
|
||||||
|
// Whether or not the relative alarm is before the event,
|
||||||
|
relativeIsBefore: null,
|
||||||
|
// Whether or not the alarm is relative to the event's start
|
||||||
|
relativeIsRelatedToStart: null,
|
||||||
|
// TIMED EVENTS:
|
||||||
|
// Unit (seconds, minutes, hours, ...) if this alarm is inside a timed event
|
||||||
|
relativeUnitTimed: null,
|
||||||
|
// The amount of unit if this alarm is inside a timed event
|
||||||
|
relativeAmountTimed: null,
|
||||||
|
// ALL-DAY EVENTS:
|
||||||
|
// Unit (seconds, minutes, hours, ...) if this alarm is inside an all-day event
|
||||||
|
relativeUnitAllDay: null,
|
||||||
|
// The amount of unit if this alarm is inside a all-day event
|
||||||
|
relativeAmountAllDay: null,
|
||||||
|
// The hours to display alarm for in an all-day event (e.g. 1 day before at 9:00 am)
|
||||||
|
relativeHoursAllDay: null,
|
||||||
|
// The minutes to display alarm for in an all-day event (e.g. 1 day before at 9:30 am)
|
||||||
|
relativeMinutesAllDay: null,
|
||||||
|
// The total amount of seconds for a relative alarm
|
||||||
|
relativeTrigger: null,
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map an alarm component to our alarm object
|
||||||
|
*
|
||||||
|
* @param {AlarmComponent} alarmComponent The calendar-js alarm-component to turn into an alarm object
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapAlarmComponentToAlarmObject = (alarmComponent) => {
|
||||||
|
if (alarmComponent.trigger.isRelative()) {
|
||||||
|
const relativeIsBefore = alarmComponent.trigger.value.isNegative
|
||||||
|
const relativeIsRelatedToStart = alarmComponent.trigger.related === 'START'
|
||||||
|
|
||||||
|
const {
|
||||||
|
amount: relativeAmountTimed,
|
||||||
|
unit: relativeUnitTimed,
|
||||||
|
} = getAmountAndUnitForTimedEvents(alarmComponent.trigger.value.totalSeconds)
|
||||||
|
|
||||||
|
const {
|
||||||
|
unit: relativeUnitAllDay,
|
||||||
|
amount: relativeAmountAllDay,
|
||||||
|
hours: relativeHoursAllDay,
|
||||||
|
minutes: relativeMinutesAllDay,
|
||||||
|
} = getAmountHoursMinutesAndUnitForAllDayEvents(alarmComponent.trigger.value.totalSeconds)
|
||||||
|
|
||||||
|
const relativeTrigger = alarmComponent.trigger.value.totalSeconds
|
||||||
|
|
||||||
|
return getDefaultAlarmObject({
|
||||||
|
alarmComponent,
|
||||||
|
type: alarmComponent.action,
|
||||||
|
isRelative: alarmComponent.trigger.isRelative(),
|
||||||
|
relativeIsBefore,
|
||||||
|
relativeIsRelatedToStart,
|
||||||
|
relativeUnitTimed,
|
||||||
|
relativeAmountTimed,
|
||||||
|
relativeUnitAllDay,
|
||||||
|
relativeAmountAllDay,
|
||||||
|
relativeHoursAllDay,
|
||||||
|
relativeMinutesAllDay,
|
||||||
|
relativeTrigger,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const absoluteDate = getDateFromDateTimeValue(alarmComponent.trigger.value)
|
||||||
|
|
||||||
|
return getDefaultAlarmObject({
|
||||||
|
alarmComponent,
|
||||||
|
type: alarmComponent.action,
|
||||||
|
isRelative: alarmComponent.trigger.isRelative(),
|
||||||
|
absoluteDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultAlarmObject,
|
||||||
|
mapAlarmComponentToAlarmObject,
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete attendee object based on given props
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - we should eventually support delegatedFrom and delegatedTo
|
||||||
|
*
|
||||||
|
* @param {Object} props The attendee properties already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultAttendeeObject = (props = {}) => Object.assign({}, {
|
||||||
|
// The calendar-js attendee property
|
||||||
|
attendeeProperty: null,
|
||||||
|
// The display-name of the attendee
|
||||||
|
commonName: null,
|
||||||
|
// The calendar-user-type of the attendee
|
||||||
|
calendarUserType: 'INDIVIDUAL',
|
||||||
|
// The participation status of the attendee
|
||||||
|
participationStatus: 'NEEDS-ACTION',
|
||||||
|
// The role of the attendee
|
||||||
|
role: 'REQ-PARTICIPANT',
|
||||||
|
// The RSVP for the attendee
|
||||||
|
rsvp: false,
|
||||||
|
// The uri of the attendee
|
||||||
|
uri: null,
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a calendar-js attendee property to our attendee object
|
||||||
|
*
|
||||||
|
* @param {AttendeeProperty} attendeeProperty The calendar-js attendeeProperty to turn into a attendee object
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapAttendeePropertyToAttendeeObject = (attendeeProperty) => {
|
||||||
|
return getDefaultAttendeeObject({
|
||||||
|
attendeeProperty,
|
||||||
|
commonName: attendeeProperty.commonName,
|
||||||
|
calendarUserType: attendeeProperty.userType,
|
||||||
|
participationStatus: attendeeProperty.participationStatus,
|
||||||
|
role: attendeeProperty.role,
|
||||||
|
rsvp: attendeeProperty.rsvp,
|
||||||
|
uri: attendeeProperty.email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultAttendeeObject,
|
||||||
|
mapAttendeePropertyToAttendeeObject,
|
||||||
|
}
|
|
@ -20,6 +20,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { detectColor, uidToHexColor } from '../utils/color.js'
|
import { detectColor, uidToHexColor } from '../utils/color.js'
|
||||||
|
import { mapDavShareeToCalendarShareObject } from './calendarShare.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a complete calendar-object based on given props
|
* Creates a complete calendar-object based on given props
|
||||||
|
@ -27,7 +28,7 @@ import { detectColor, uidToHexColor } from '../utils/color.js'
|
||||||
* @param {Object} props Calendar-props already provided
|
* @param {Object} props Calendar-props already provided
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export const getDefaultCalendarObject = (props = {}) => Object.assign({}, {
|
const getDefaultCalendarObject = (props = {}) => Object.assign({}, {
|
||||||
// Id of the calendar
|
// Id of the calendar
|
||||||
id: '',
|
id: '',
|
||||||
// Visible display name
|
// Visible display name
|
||||||
|
@ -79,7 +80,7 @@ export const getDefaultCalendarObject = (props = {}) => Object.assign({}, {
|
||||||
* @param {Object=} currentUserPrincipal The principal model of the current user principal
|
* @param {Object=} currentUserPrincipal The principal model of the current user principal
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function mapDavCollectionToCalendar(calendar, currentUserPrincipal) {
|
const mapDavCollectionToCalendar = (calendar, currentUserPrincipal) => {
|
||||||
const id = btoa(calendar.url)
|
const id = btoa(calendar.url)
|
||||||
const displayName = calendar.displayname || getCalendarUriFromUrl(calendar.url)
|
const displayName = calendar.displayname || getCalendarUriFromUrl(calendar.url)
|
||||||
|
|
||||||
|
@ -134,11 +135,11 @@ export function mapDavCollectionToCalendar(calendar, currentUserPrincipal) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
shares.push(mapDavShareeToSharee(share))
|
shares.push(mapDavShareeToCalendarShareObject(share))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return getDefaultCalendarObject({
|
||||||
id,
|
id,
|
||||||
displayName,
|
displayName,
|
||||||
color,
|
color,
|
||||||
|
@ -157,43 +158,7 @@ export function mapDavCollectionToCalendar(calendar, currentUserPrincipal) {
|
||||||
shares,
|
shares,
|
||||||
timezone,
|
timezone,
|
||||||
dav: calendar,
|
dav: calendar,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a dav collection to our calendar object model
|
|
||||||
*
|
|
||||||
* @param {Object} sharee The sharee object from the cdav library shares
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
export function mapDavShareeToSharee(sharee) {
|
|
||||||
// sharee.href might contain non-latin characters, so let's uri encode it first
|
|
||||||
const id = btoa(encodeURI(sharee.href))
|
|
||||||
|
|
||||||
let displayName
|
|
||||||
if (sharee['common-name']) {
|
|
||||||
displayName = sharee['common-name']
|
|
||||||
} else if (sharee.href.startsWith('principal:principals/groups/')) {
|
|
||||||
displayName = sharee.href.substr(28)
|
|
||||||
} else if (sharee.href.startsWith('principal:principals/users/')) {
|
|
||||||
displayName = sharee.href.substr(27)
|
|
||||||
} else {
|
|
||||||
displayName = sharee.href
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeable = sharee.access[0].endsWith('read-write')
|
|
||||||
const isGroup = sharee.href.indexOf('principal:principals/groups/') === 0
|
|
||||||
const isCircle = sharee.href.indexOf('principal:principals/circles/') === 0
|
|
||||||
const uri = sharee.href
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
displayName,
|
|
||||||
writeable,
|
|
||||||
isGroup,
|
|
||||||
isCircle,
|
|
||||||
uri,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,3 +174,8 @@ function getCalendarUriFromUrl(url) {
|
||||||
|
|
||||||
return url.substring(url.lastIndexOf('/') + 1)
|
return url.substring(url.lastIndexOf('/') + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultCalendarObject,
|
||||||
|
mapDavCollectionToCalendar,
|
||||||
|
}
|
||||||
|
|
|
@ -19,218 +19,112 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getParserManager } from 'calendar-js'
|
import { getParserManager } from 'calendar-js'
|
||||||
import DateTimeValue from 'calendar-js/src/values/dateTimeValue'
|
import {
|
||||||
import CalendarComponent from 'calendar-js/src/components/calendarComponent'
|
COMPONENT_NAME_EVENT,
|
||||||
|
COMPONENT_NAME_JOURNAL,
|
||||||
|
COMPONENT_NAME_VTODO,
|
||||||
|
} from './consts.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This model represents exactly
|
* Creates a complete calendar-object-object based on given props
|
||||||
*
|
*
|
||||||
* TODO: this should not be a class, but a simple object
|
* @param {Object} props Calendar-object-props already provided
|
||||||
* TODO: all methods should be converted to vuex commits
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export default class CalendarObject {
|
const getDefaultCalendarObjectObject = (props = {}) => Object.assign({}, {
|
||||||
|
// Id of this calendar-object
|
||||||
|
id: null,
|
||||||
|
// Id of the associated calendar
|
||||||
|
calendarId: null,
|
||||||
|
// The cdav-library object storing the calendar-object
|
||||||
|
dav: null,
|
||||||
|
// The parsed calendar-js object
|
||||||
|
calendarComponent: null,
|
||||||
|
// The uid of the calendar-object
|
||||||
|
uid: null,
|
||||||
|
// The uri of the calendar-object
|
||||||
|
uri: null,
|
||||||
|
// The type of calendar-object
|
||||||
|
objectType: null,
|
||||||
|
// Whether or not the calendar-object is an event
|
||||||
|
isEvent: false,
|
||||||
|
// Whether or not the calendar-object is a journal
|
||||||
|
isJournal: false,
|
||||||
|
// Whether or not the calendar-object is a task
|
||||||
|
isTodo: false,
|
||||||
|
// Whether or not the calendar-object exists on the server
|
||||||
|
existsOnServer: false,
|
||||||
|
}, props)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of calendar-object
|
* Maps a calendar-object from c-dav to our calendar-object object
|
||||||
*
|
*
|
||||||
* @param {String|CalendarComponent} calendarData The raw unparsed calendar-data
|
* @param {VObject} dav The c-dav VObject
|
||||||
* @param {String} calendarId Id of the calendar this calendar-object belongs to
|
* @param {String} calendarId The calendar-id this object is associated with
|
||||||
* @param {VObject} dav The dav object
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
constructor(calendarData, calendarId, dav = null) {
|
const mapCDavObjectToCalendarObject = (dav, calendarId) => {
|
||||||
/**
|
const parserManager = getParserManager()
|
||||||
* Id of the calendar this calendar-object is part of
|
const parser = parserManager.getParserForFileType('text/calendar')
|
||||||
*
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
this.calendarId = calendarId
|
|
||||||
|
|
||||||
/**
|
// This should not be the case, but let's just be on the safe side
|
||||||
* Whether or not there has been a conflict with the server version
|
if (typeof dav.data !== 'string' || dav.data.trim() === '') {
|
||||||
*
|
throw new Error('Empty calendar object')
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.conflict = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The dav-object associated with this calendar-object
|
|
||||||
*
|
|
||||||
* @type {Object}|null
|
|
||||||
*/
|
|
||||||
this.dav = dav
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parsed calendar-js object
|
|
||||||
* @type {CalendarComponent}
|
|
||||||
*/
|
|
||||||
this.vcalendar = null
|
|
||||||
this.resetToDav(calendarData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
parser.parse(dav.data)
|
||||||
* ID of the calendar-object
|
const calendarComponentIterator = parser.getItemIterator()
|
||||||
*
|
const calendarComponent = calendarComponentIterator.next().value
|
||||||
* @returns {string}
|
if (!calendarComponent) {
|
||||||
*/
|
throw new Error('Empty calendar object')
|
||||||
get id() {
|
|
||||||
if (this.dav) {
|
|
||||||
return btoa(this.dav.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'new'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const vObjectIterator = calendarComponent.getVObjectIterator()
|
||||||
* @returns {string}
|
const firstVObject = vObjectIterator.next().value
|
||||||
*/
|
|
||||||
getId() {
|
|
||||||
if (this.dav) {
|
|
||||||
return btoa(this.dav.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'new'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UID of the calendar-object
|
|
||||||
*
|
|
||||||
* @returns {null|String}
|
|
||||||
*/
|
|
||||||
get uid() {
|
|
||||||
const iterator = this.vcalendar.getVObjectIterator()
|
|
||||||
const firstVObject = iterator.next().value
|
|
||||||
if (firstVObject) {
|
|
||||||
return firstVObject.uid
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of the calendar-object
|
|
||||||
*
|
|
||||||
* @returns {null|String}
|
|
||||||
*/
|
|
||||||
get objectType() {
|
|
||||||
const iterator = this.vcalendar.getVObjectIterator()
|
|
||||||
const firstVObject = iterator.next().value
|
|
||||||
if (firstVObject) {
|
|
||||||
return firstVObject.name
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not this calendar-object is an event
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isEvent() {
|
|
||||||
return this.objectType === 'vevent'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not this calendar-object is a task
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isTodo() {
|
|
||||||
return this.objectType === 'vtodo'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all recurrence-items in given range
|
|
||||||
*
|
|
||||||
* @param {Date} start Begin of time-range
|
|
||||||
* @param {Date} end End of time-range
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
|
||||||
getAllObjectsInTimeRange(start, end) {
|
|
||||||
const iterator = this.vcalendar.getVObjectIterator()
|
|
||||||
const firstVObject = iterator.next().value
|
|
||||||
if (!firstVObject) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const s = DateTimeValue.fromJSDate(start, true)
|
|
||||||
const e = DateTimeValue.fromJSDate(end, true)
|
|
||||||
return firstVObject.recurrenceManager.getAllOccurrencesBetween(s, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get recurrence-item closest to the given recurrence-Id
|
|
||||||
* This is either the one next in the future or if none exist,
|
|
||||||
* the one closest in the past
|
|
||||||
*
|
|
||||||
* @param {Date} closeTo The time to get the
|
|
||||||
* @returns {AbstractRecurringComponent|null}
|
|
||||||
*/
|
|
||||||
getClosestRecurrence(closeTo) {
|
|
||||||
const iterator = this.vcalendar.getVObjectIterator()
|
|
||||||
const firstVObject = iterator.next().value
|
|
||||||
if (!firstVObject) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const d = DateTimeValue.fromJSDate(closeTo, true)
|
|
||||||
return firstVObject.recurrenceManager.getClosestOccurrence(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get recurrence-item at exactly a given recurrence-Id
|
|
||||||
*
|
|
||||||
* @param {Date} recurrenceId RecurrenceId to retrieve
|
|
||||||
* @returns {AbstractRecurringComponent|null}
|
|
||||||
*/
|
|
||||||
getObjectAtRecurrenceId(recurrenceId) {
|
|
||||||
const iterator = this.vcalendar.getVObjectIterator()
|
|
||||||
const firstVObject = iterator.next().value
|
|
||||||
if (!firstVObject) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const d = DateTimeValue.fromJSDate(recurrenceId, true)
|
|
||||||
return firstVObject.recurrenceManager.getOccurrenceAtExactly(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* resets the inter vcalendar to the dav data
|
|
||||||
*
|
|
||||||
* @param {CalendarComponent|String} data Data to reset to
|
|
||||||
*/
|
|
||||||
resetToDav(data = null) {
|
|
||||||
if (data instanceof CalendarComponent) {
|
|
||||||
this.vcalendar = data
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data === null && this.dav === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const parserManager = getParserManager()
|
|
||||||
const parser = parserManager.getParserForFileType('text/calendar')
|
|
||||||
|
|
||||||
const calendarData = data || this.dav.data
|
|
||||||
parser.parse(calendarData)
|
|
||||||
|
|
||||||
const itemIterator = parser.getItemIterator()
|
|
||||||
const firstVCalendar = itemIterator.next().value
|
|
||||||
if (firstVCalendar) {
|
|
||||||
this.vcalendar = firstVCalendar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not this objects exists on the server
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
existsOnServer() {
|
|
||||||
return !!this.dav
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return getDefaultCalendarObjectObject({
|
||||||
|
id: btoa(dav.url),
|
||||||
|
calendarId,
|
||||||
|
dav,
|
||||||
|
calendarComponent,
|
||||||
|
uid: firstVObject.uid,
|
||||||
|
uri: dav.url,
|
||||||
|
objectType: firstVObject.name,
|
||||||
|
isEvent: firstVObject.name === COMPONENT_NAME_EVENT,
|
||||||
|
isJournal: firstVObject.name === COMPONENT_NAME_JOURNAL,
|
||||||
|
isTodo: firstVObject.name === COMPONENT_NAME_VTODO,
|
||||||
|
existsOnServer: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a calendar-component from calendar-js to our calendar-object object
|
||||||
|
*
|
||||||
|
* @param {CalendarComponent} calendarComponent The calendarComponent to create the calendarObject from
|
||||||
|
* @param {String=} calendarId The associated calendar if applicable
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapCalendarJsToCalendarObject = (calendarComponent, calendarId = null) => {
|
||||||
|
const vObjectIterator = calendarComponent.getVObjectIterator()
|
||||||
|
const firstVObject = vObjectIterator.next().value
|
||||||
|
if (!firstVObject) {
|
||||||
|
throw new Error('Calendar object without vobjects')
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultCalendarObjectObject({
|
||||||
|
calendarId,
|
||||||
|
calendarComponent,
|
||||||
|
uid: firstVObject.uid,
|
||||||
|
objectType: firstVObject.name,
|
||||||
|
isEvent: firstVObject.name === COMPONENT_NAME_EVENT,
|
||||||
|
isJournal: firstVObject.name === COMPONENT_NAME_JOURNAL,
|
||||||
|
isTodo: firstVObject.name === COMPONENT_NAME_VTODO,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultCalendarObjectObject,
|
||||||
|
mapCDavObjectToCalendarObject,
|
||||||
|
mapCalendarJsToCalendarObject,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,677 +0,0 @@
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2019 Georg Ehrke
|
|
||||||
*
|
|
||||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
import { getDateFromDateTimeValue } from '../utils/date.js'
|
|
||||||
import DurationValue from 'calendar-js/src/values/durationValue.js'
|
|
||||||
import { getWeekDayFromDate } from '../utils/recurrence.js'
|
|
||||||
import { getAmountAndUnitForTimedEvents, getAmountHoursMinutesAndUnitForAllDayEvents } from '../utils/alarms.js'
|
|
||||||
import { getHexForColorName } from '../utils/color.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a complete calendar-object-instance-object based on given props
|
|
||||||
*
|
|
||||||
* @param {Object} props The props already provided
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
export const getDefaultCalendarObjectInstanceObject = (props = {}) => Object.assign({}, {
|
|
||||||
// Title of the event
|
|
||||||
title: null,
|
|
||||||
// Start date of the event
|
|
||||||
startDate: null,
|
|
||||||
// Timezone of the start date
|
|
||||||
startTimezoneId: null,
|
|
||||||
// End date of the event
|
|
||||||
endDate: null,
|
|
||||||
// Timezone of the end date
|
|
||||||
endTimezoneId: null,
|
|
||||||
// Indicator whether or not event is all-day
|
|
||||||
isAllDay: false,
|
|
||||||
// Location that the event takes places in
|
|
||||||
location: null,
|
|
||||||
// description of the event
|
|
||||||
description: null,
|
|
||||||
// Access class of the event (PUBLIC, PRIVATE, CONFIDENTIAL)
|
|
||||||
accessClass: null,
|
|
||||||
// Status of the event (CONFIRMED, TENTATIVE, CANCELLED)
|
|
||||||
status: null,
|
|
||||||
// Whether or not to block this event in Free-Busy reports (TRANSPARENT, OPAQUE)
|
|
||||||
timeTransparency: null,
|
|
||||||
// The recurrence rule of this event. We only support one recurrence-rule
|
|
||||||
recurrenceRule: {
|
|
||||||
frequency: 'NONE',
|
|
||||||
interval: 1,
|
|
||||||
count: null,
|
|
||||||
until: null,
|
|
||||||
byDay: [],
|
|
||||||
byMonth: [],
|
|
||||||
byMonthDay: [],
|
|
||||||
bySetPosition: null,
|
|
||||||
isUnsupported: false,
|
|
||||||
recurrenceRuleValue: null,
|
|
||||||
},
|
|
||||||
// Attendees of this event
|
|
||||||
attendees: [],
|
|
||||||
// Organizer of the event
|
|
||||||
organizer: {
|
|
||||||
// name of the organizer
|
|
||||||
name: null,
|
|
||||||
// email of the organizer
|
|
||||||
uri: null,
|
|
||||||
},
|
|
||||||
// Alarm of the event
|
|
||||||
alarms: [],
|
|
||||||
// Custom color of the event
|
|
||||||
customColor: null,
|
|
||||||
// Categories
|
|
||||||
categories: [],
|
|
||||||
// Whether or not the user is allowed to toggle the all-day checkbox
|
|
||||||
canModifyAllDay: true,
|
|
||||||
// The real event-component coming from calendar-js
|
|
||||||
eventComponent: null,
|
|
||||||
}, props)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map an EventComponent from calendar-js to our calendar object instance object
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The EventComponent object to map to an object
|
|
||||||
* @returns {{color: *, canModifyAllDay: *, timeTransparency: *, description: *, location: *, eventComponent: *, title: *, accessClass: *, status: *}}
|
|
||||||
*/
|
|
||||||
export const mapEventComponentToCalendarObjectInstanceObject = (eventComponent) => {
|
|
||||||
const calendarObjectInstanceObject = {
|
|
||||||
title: eventComponent.title,
|
|
||||||
location: eventComponent.location,
|
|
||||||
description: eventComponent.description,
|
|
||||||
accessClass: eventComponent.accessClass,
|
|
||||||
status: eventComponent.status,
|
|
||||||
timeTransparency: eventComponent.timeTransparency,
|
|
||||||
color: eventComponent.color,
|
|
||||||
canModifyAllDay: eventComponent.canModifyAllDay(),
|
|
||||||
eventComponent,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The end date of an event is non-inclusive. This is rather intuitive for timed-events, but very unintuitive for all-day events.
|
|
||||||
// That's why, when an events is from 2019-10-03 to 2019-10-04, we will show 2019-10-03 to 2019-10-03 in the editor.
|
|
||||||
calendarObjectInstanceObject.isAllDay = eventComponent.isAllDay()
|
|
||||||
calendarObjectInstanceObject.startDate = getDateFromDateTimeValue(eventComponent.startDate)
|
|
||||||
calendarObjectInstanceObject.startTimezoneId = eventComponent.startDate.timezoneId
|
|
||||||
|
|
||||||
if (eventComponent.isAllDay()) {
|
|
||||||
const endDate = eventComponent.endDate.clone()
|
|
||||||
endDate.addDuration(DurationValue.fromSeconds(-1 * 60 * 60 * 24))
|
|
||||||
calendarObjectInstanceObject.endDate = getDateFromDateTimeValue(endDate)
|
|
||||||
} else {
|
|
||||||
calendarObjectInstanceObject.endDate = getDateFromDateTimeValue(eventComponent.endDate)
|
|
||||||
}
|
|
||||||
calendarObjectInstanceObject.endTimezoneId = eventComponent.endDate.timezoneId
|
|
||||||
|
|
||||||
calendarObjectInstanceObject.categories = getCategoriesFromEventComponent(eventComponent)
|
|
||||||
calendarObjectInstanceObject.organizer = getOrganizerFromEventComponent(eventComponent)
|
|
||||||
calendarObjectInstanceObject.recurrenceRule = getRecurrenceRuleFromEventComponent(eventComponent)
|
|
||||||
calendarObjectInstanceObject.hasMultipleRecurrenceRules
|
|
||||||
= Array.from(eventComponent.getPropertyIterator('RRULE')).length > 1
|
|
||||||
calendarObjectInstanceObject.attendees = getAttendeesFromEventComponent(eventComponent)
|
|
||||||
calendarObjectInstanceObject.alarms = getAlarmsFromEventComponent(eventComponent)
|
|
||||||
|
|
||||||
if (eventComponent.hasProperty('COLOR')) {
|
|
||||||
const hexColor = getHexForColorName(eventComponent.getFirstPropertyFirstValue('COLOR'))
|
|
||||||
if (hexColor !== null) {
|
|
||||||
calendarObjectInstanceObject.customColor = hexColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return calendarObjectInstanceObject
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the organizer from the event component
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The event-component representing the instance
|
|
||||||
* @returns {null|{commonName: *, uri: *}}
|
|
||||||
*/
|
|
||||||
function getOrganizerFromEventComponent(eventComponent) {
|
|
||||||
if (eventComponent.organizer) {
|
|
||||||
const organizerProperty = eventComponent.getFirstProperty('ORGANIZER')
|
|
||||||
return {
|
|
||||||
commonName: organizerProperty.commonName,
|
|
||||||
uri: organizerProperty.email,
|
|
||||||
attendeeProperty: organizerProperty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all categories (without a language tag) from the event component
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The event-component representing the instance
|
|
||||||
* @returns {String[]}
|
|
||||||
*/
|
|
||||||
function getCategoriesFromEventComponent(eventComponent) {
|
|
||||||
return Array.from(eventComponent.getCategoryIterator())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first recurrence rule from the event component
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The event-component representing the instance
|
|
||||||
* @returns {{byMonth: [], frequency: null, count: null, byDay: [], interval: number, until: null, bySetPosition: null, byMonthDay: []}|{byMonth: *, frequency: *, count: *, byDay: *, interval: *, until: *, bySetPosition: *, byMonthDay: *}}
|
|
||||||
*/
|
|
||||||
function getRecurrenceRuleFromEventComponent(eventComponent) {
|
|
||||||
/** @type {RecurValue} */
|
|
||||||
const recurrenceRule = eventComponent.getFirstPropertyFirstValue('RRULE')
|
|
||||||
if (recurrenceRule) {
|
|
||||||
const component = {
|
|
||||||
frequency: recurrenceRule.frequency,
|
|
||||||
interval: parseInt(recurrenceRule.interval, 10) || 1,
|
|
||||||
count: recurrenceRule.count,
|
|
||||||
until: null,
|
|
||||||
byDay: [],
|
|
||||||
byMonth: [],
|
|
||||||
byMonthDay: [],
|
|
||||||
bySetPosition: null,
|
|
||||||
isUnsupported: false,
|
|
||||||
recurrenceRuleValue: recurrenceRule,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recurrenceRule.until) {
|
|
||||||
component.until = recurrenceRule.until.jsDate
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (component.frequency) {
|
|
||||||
case 'DAILY':
|
|
||||||
getRecurrenceComponentFromDailyRule(recurrenceRule, component)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'WEEKLY':
|
|
||||||
getRecurrenceComponentFromWeeklyRule(recurrenceRule, component, eventComponent)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'MONTHLY':
|
|
||||||
getRecurrenceComponentFromMonthlyRule(recurrenceRule, component, eventComponent)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'YEARLY':
|
|
||||||
getRecurrenceComponentFromYearlyRule(recurrenceRule, component, eventComponent)
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
component.isUnsupported = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return component
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
frequency: 'NONE',
|
|
||||||
interval: 1,
|
|
||||||
count: null,
|
|
||||||
until: null,
|
|
||||||
byDay: [],
|
|
||||||
byMonth: [],
|
|
||||||
byMonthDay: [],
|
|
||||||
bySetPosition: null,
|
|
||||||
isUnsupported: false,
|
|
||||||
recurrenceRuleValue: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the recurrence-rule contains any of the given components
|
|
||||||
*
|
|
||||||
* @param {RecurValue} recurrenceRule The recurrence-rule value to check for the given components
|
|
||||||
* @param {String[]} components List of components to check for
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
function containsRecurrenceComponent(recurrenceRule, components) {
|
|
||||||
for (const component of components) {
|
|
||||||
const componentValue = recurrenceRule.getComponent(component)
|
|
||||||
if (componentValue.length > 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all attendees from the event component
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The event-component representing the instance
|
|
||||||
* @returns {[]}
|
|
||||||
*/
|
|
||||||
function getAttendeesFromEventComponent(eventComponent) {
|
|
||||||
const attendees = []
|
|
||||||
|
|
||||||
for (const attendee of eventComponent.getAttendeeIterator()) {
|
|
||||||
attendees.push({
|
|
||||||
commonName: attendee.commonName,
|
|
||||||
participationStatus: attendee.participationStatus,
|
|
||||||
role: attendee.role,
|
|
||||||
rsvp: attendee.rsvp,
|
|
||||||
uri: attendee.email,
|
|
||||||
attendeeProperty: attendee,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return attendees
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all alarms from the event Component
|
|
||||||
*
|
|
||||||
* @param {EventComponent} eventComponent The event-component representing the instance
|
|
||||||
* @returns {[]}
|
|
||||||
*/
|
|
||||||
function getAlarmsFromEventComponent(eventComponent) {
|
|
||||||
const alarms = []
|
|
||||||
|
|
||||||
for (const alarm of eventComponent.getAlarmIterator()) {
|
|
||||||
alarms.push(getAlarmFromAlarmComponent(alarm))
|
|
||||||
}
|
|
||||||
|
|
||||||
return alarms
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all numbers between start and end as strings
|
|
||||||
*
|
|
||||||
* @param {Number} start Lower end of range
|
|
||||||
* @param {Number} end Upper end of range
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
function getRangeAsStrings(start, end) {
|
|
||||||
return Array
|
|
||||||
.apply(null, Array((end - start) + 1))
|
|
||||||
.map((_, n) => n + start)
|
|
||||||
.map((s) => s.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the recurrence component from a daily recurrence rule
|
|
||||||
*
|
|
||||||
* @param {RecurValue} recurrenceRule The RecurValue to extract data from
|
|
||||||
* @param {Object} recurrenceComponent The recurrence component to write data into
|
|
||||||
*/
|
|
||||||
function getRecurrenceComponentFromDailyRule(recurrenceRule, recurrenceComponent) {
|
|
||||||
/**
|
|
||||||
* # Daily
|
|
||||||
*
|
|
||||||
* The Nextcloud-editor does not support any BY-parts for the daily rule, hence
|
|
||||||
* we will mark any DAILY rule with BY-parts as unsupported.
|
|
||||||
*/
|
|
||||||
const forbiddenComponents = [
|
|
||||||
'BYSECOND',
|
|
||||||
'BYMINUTE',
|
|
||||||
'BYHOUR',
|
|
||||||
'BYDAY',
|
|
||||||
'BYMONTHDAY',
|
|
||||||
'BYYEARDAY',
|
|
||||||
'BYWEEKNO',
|
|
||||||
'BYMONTH',
|
|
||||||
'BYSETPOS',
|
|
||||||
]
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, forbiddenComponents)) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the recurrence component from a weekly recurrence rule
|
|
||||||
*
|
|
||||||
* @param {RecurValue} recurrenceRule The RecurValue to extract data from
|
|
||||||
* @param {Object} recurrenceComponent The recurrence component to write data into
|
|
||||||
* @param {EventComponent} eventComponent The event component needed for default values
|
|
||||||
*/
|
|
||||||
function getRecurrenceComponentFromWeeklyRule(recurrenceRule, recurrenceComponent, eventComponent) {
|
|
||||||
/**
|
|
||||||
* # Weekly
|
|
||||||
*
|
|
||||||
* The Nextcloud-editor only supports BYDAY in order to expand the weekly rule.
|
|
||||||
* It does not support other BY-parts like BYSETPOS or BYMONTH
|
|
||||||
*
|
|
||||||
* As defined by RFC 5545, the individual BYDAY components may not be preceded
|
|
||||||
* by a positive or negative integer.
|
|
||||||
*/
|
|
||||||
const forbiddenComponents = [
|
|
||||||
'BYSECOND',
|
|
||||||
'BYMINUTE',
|
|
||||||
'BYHOUR',
|
|
||||||
'BYMONTHDAY',
|
|
||||||
'BYYEARDAY',
|
|
||||||
'BYWEEKNO',
|
|
||||||
'BYMONTH',
|
|
||||||
'BYSETPOS',
|
|
||||||
]
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, forbiddenComponents)) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
recurrenceComponent.byDay = recurrenceRule.getComponent('BYDAY')
|
|
||||||
.filter((weekDay) => ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'].includes(weekDay))
|
|
||||||
|
|
||||||
// If the BYDAY is empty, add the day that the event occurs in
|
|
||||||
// E.g. if the event is on a Wednesday, automatically set BYDAY:WE
|
|
||||||
if (recurrenceComponent.byDay.length === 0) {
|
|
||||||
recurrenceComponent.byDay.push(getWeekDayFromDate(eventComponent.startDate.jsDate))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the recurrence component from a monthly recurrence rule
|
|
||||||
*
|
|
||||||
* @param {RecurValue} recurrenceRule The RecurValue to extract data from
|
|
||||||
* @param {Object} recurrenceComponent The recurrence component to write data into
|
|
||||||
* @param {EventComponent} eventComponent The event component needed for default values
|
|
||||||
*/
|
|
||||||
function getRecurrenceComponentFromMonthlyRule(recurrenceRule, recurrenceComponent, eventComponent) {
|
|
||||||
/**
|
|
||||||
* # Monthly
|
|
||||||
*
|
|
||||||
* The Nextcloud-editor only supports BYMONTHDAY, BYDAY, BYSETPOS in order to expand the monthly rule.
|
|
||||||
* It supports either BYMONTHDAY or the combination of BYDAY and BYSETPOS. They have to be used exclusively
|
|
||||||
* and cannot be combined.
|
|
||||||
*
|
|
||||||
* It does not support other BY-parts like BYMONTH
|
|
||||||
*
|
|
||||||
* For monthly recurrence-rules, BYDAY components are allowed to be preceded by positive or negative integers.
|
|
||||||
* The Nextcloud-editor supports at most one BYDAY component with an integer.
|
|
||||||
* If it's presented with such a BYDAY component, it will internally be converted to BYDAY without integer and BYSETPOS.
|
|
||||||
* e.g.
|
|
||||||
* BYDAY=3WE => BYDAY=WE,BYSETPOS=3
|
|
||||||
*
|
|
||||||
* BYSETPOS is limited to -2, -1, 1, 2, 3, 4, 5
|
|
||||||
* Other values are not supported
|
|
||||||
*
|
|
||||||
* BYDAY is limited to "MO", "TU", "WE", "TH", "FR", "SA", "SU",
|
|
||||||
* "MO,TU,WE,TH,FR,SA,SU", "MO,TU,WE,TH,FR", "SA,SU"
|
|
||||||
*
|
|
||||||
* BYMONYHDAY is limited to "1", "2", ..., "31"
|
|
||||||
*/
|
|
||||||
const forbiddenComponents = [
|
|
||||||
'BYSECOND',
|
|
||||||
'BYMINUTE',
|
|
||||||
'BYHOUR',
|
|
||||||
'BYYEARDAY',
|
|
||||||
'BYWEEKNO',
|
|
||||||
'BYMONTH',
|
|
||||||
]
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, forbiddenComponents)) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, ['BYMONYHDAY'])) {
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, ['BYDAY', 'BYSETPOS'])) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowedValues = getRangeAsStrings(1, 31)
|
|
||||||
const byMonthDayComponent = recurrenceRule.getComponent('BYMONYHDAY')
|
|
||||||
recurrenceComponent.byMonthDay = byMonthDayComponent.filter((day) =>
|
|
||||||
allowedValues.includes(day))
|
|
||||||
|
|
||||||
if (byMonthDayComponent.length !== recurrenceComponent.byMonthDay.length) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
// TODO: the following is duplicate code, the same as in the yearly function.
|
|
||||||
} else if (containsRecurrenceComponent(recurrenceRule, ['BYDAY']) && containsRecurrenceComponent(recurrenceRule, ['BYSETPOS'])) {
|
|
||||||
if (isAllowedByDay(recurrenceRule.getComponent('BYDAY'))) {
|
|
||||||
recurrenceComponent.byDay = recurrenceRule.getComponent('BYDAY')
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byDay = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPositionArray = recurrenceRule.getComponent('BYSETPOS')
|
|
||||||
if (setPositionArray.length === 1 && isAllowedBySetPos(setPositionArray[0])) {
|
|
||||||
recurrenceComponent.bySetPosition = setPositionArray[0]
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.bySetPosition = 1
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
} else if (containsRecurrenceComponent(recurrenceRule, ['BYDAY'])) {
|
|
||||||
const byDayArray = recurrenceRule.getComponent('BYDAY')
|
|
||||||
|
|
||||||
if (byDayArray.length > 1) {
|
|
||||||
recurrenceComponent.byMonthDay.push(eventComponent.startDate.day.toString())
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
} else {
|
|
||||||
const firstElement = byDayArray[0]
|
|
||||||
|
|
||||||
const match = /^(-?\d)([A-Z]){2}$/.exec(firstElement)
|
|
||||||
if (match) {
|
|
||||||
const bySetPosition = match[1]
|
|
||||||
const byDay = match[2]
|
|
||||||
|
|
||||||
if (isAllowedBySetPos(bySetPosition)) {
|
|
||||||
recurrenceComponent.byDay = [byDay]
|
|
||||||
recurrenceComponent.bySetPosition = bySetPosition
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byDay = [byDay]
|
|
||||||
recurrenceComponent.bySetPosition = 1
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byMonthDay.push(eventComponent.startDate.day.toString())
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If none of the previous rules are present, automatically set a BYMONTHDAY
|
|
||||||
recurrenceComponent.byMonthDay.push(eventComponent.startDate.day.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the recurrence component from a yearly recurrence rule
|
|
||||||
*
|
|
||||||
* @param {RecurValue} recurrenceRule The RecurValue to extract data from
|
|
||||||
* @param {Object} recurrenceComponent The recurrence component to write data into
|
|
||||||
* @param {EventComponent} eventComponent The event component needed for default values
|
|
||||||
*/
|
|
||||||
function getRecurrenceComponentFromYearlyRule(recurrenceRule, recurrenceComponent, eventComponent) {
|
|
||||||
/**
|
|
||||||
* # YEARLY
|
|
||||||
*
|
|
||||||
* The Nextcloud-editor only supports BYMONTH, BYDAY, BYSETPOS in order to expand the monthly rule.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* BYSETPOS is limited to -2, -1, 1, 2, 3, 4, 5
|
|
||||||
* Other values are not supported
|
|
||||||
*
|
|
||||||
* BYDAY is limited to "MO", "TU", "WE", "TH", "FR", "SA", "SU",
|
|
||||||
* "MO,TU,WE,TH,FR,SA,SU", "MO,TU,WE,TH,FR", "SA,SU"
|
|
||||||
*/
|
|
||||||
const forbiddenComponents = [
|
|
||||||
'BYSECOND',
|
|
||||||
'BYMINUTE',
|
|
||||||
'BYHOUR',
|
|
||||||
'BYMONTHDAY',
|
|
||||||
'BYYEARDAY',
|
|
||||||
'BYWEEKNO',
|
|
||||||
]
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, forbiddenComponents)) {
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, ['BYMONTH'])) {
|
|
||||||
recurrenceComponent.byMonth = recurrenceRule.getComponent('BYMONTH')
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byMonth.push(eventComponent.startDate.month.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: the following is duplicate code, the same as in the month function.
|
|
||||||
if (containsRecurrenceComponent(recurrenceRule, ['BYDAY']) && containsRecurrenceComponent(recurrenceRule, ['BYSETPOS'])) {
|
|
||||||
if (isAllowedByDay(recurrenceRule.getComponent('BYDAY'))) {
|
|
||||||
recurrenceComponent.byDay = recurrenceRule.getComponent('BYDAY')
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byDay = ['MO', 'TU', 'W E', 'TH', 'FR', 'SA', 'SU']
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPositionArray = recurrenceRule.getComponent('BYSETPOS')
|
|
||||||
if (setPositionArray.length === 1 && isAllowedBySetPos(setPositionArray[0])) {
|
|
||||||
recurrenceComponent.bySetPosition = setPositionArray[0]
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.bySetPosition = 1
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
} else if (containsRecurrenceComponent(recurrenceRule, ['BYDAY'])) {
|
|
||||||
const byDayArray = recurrenceRule.getComponent('BYDAY')
|
|
||||||
|
|
||||||
if (byDayArray.length > 1) {
|
|
||||||
recurrenceComponent.byMonthDay.push(eventComponent.startDate.day.toString())
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
} else {
|
|
||||||
const firstElement = byDayArray[0]
|
|
||||||
|
|
||||||
const match = /^(-?\d)([A-Z]){2}$/.exec(firstElement)
|
|
||||||
if (match) {
|
|
||||||
const bySetPosition = match[1]
|
|
||||||
const byDay = match[2]
|
|
||||||
|
|
||||||
if (isAllowedBySetPos(bySetPosition)) {
|
|
||||||
recurrenceComponent.byDay = [byDay]
|
|
||||||
recurrenceComponent.bySetPosition = bySetPosition
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byDay = [byDay]
|
|
||||||
recurrenceComponent.bySetPosition = 1
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recurrenceComponent.byMonthDay.push(eventComponent.startDate.day.toString())
|
|
||||||
recurrenceComponent.isUnsupported = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given parameter is a supported BYDAY value
|
|
||||||
*
|
|
||||||
* @param {String[]} byDay The byDay component to check
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
function isAllowedByDay(byDay) {
|
|
||||||
const allowedByDay = [
|
|
||||||
'MO',
|
|
||||||
'TU',
|
|
||||||
'WE',
|
|
||||||
'TH',
|
|
||||||
'FR',
|
|
||||||
'SA',
|
|
||||||
'SU',
|
|
||||||
'FR,MO,SA,SU,TH,TU,WE',
|
|
||||||
'FR,MO,TH,TU,WE',
|
|
||||||
'SA,SU',
|
|
||||||
]
|
|
||||||
|
|
||||||
return allowedByDay.includes(byDay.slice().sort().join(','))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given parameter is a supported BYSETPOS value
|
|
||||||
*
|
|
||||||
* @param {String} bySetPos The bySetPos component to check
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
function isAllowedBySetPos(bySetPos) {
|
|
||||||
const allowedBySetPos = [
|
|
||||||
'-2',
|
|
||||||
'-1',
|
|
||||||
'1',
|
|
||||||
'2',
|
|
||||||
'3',
|
|
||||||
'4',
|
|
||||||
'5',
|
|
||||||
]
|
|
||||||
|
|
||||||
return allowedBySetPos.includes(bySetPos.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} alarm The alarm to set / update
|
|
||||||
* @param {AlarmComponent} alarmComponent The alarm component to read from
|
|
||||||
*/
|
|
||||||
export function updateAlarmFromAlarmComponent(alarm, alarmComponent) {
|
|
||||||
alarm.type = alarmComponent.action
|
|
||||||
alarm.isRelative = alarmComponent.trigger.isRelative()
|
|
||||||
|
|
||||||
alarm.absoluteDate = null
|
|
||||||
alarm.absoluteTimezoneId = null
|
|
||||||
|
|
||||||
alarm.relativeIsBefore = null
|
|
||||||
alarm.relativeIsRelatedToStart = null
|
|
||||||
|
|
||||||
alarm.relativeUnitTimed = null
|
|
||||||
alarm.relativeAmountTimed = null
|
|
||||||
|
|
||||||
alarm.relativeUnitAllDay = null
|
|
||||||
alarm.relativeAmountAllDay = null
|
|
||||||
alarm.relativeHoursAllDay = null
|
|
||||||
alarm.relativeMinutesAllDay = null
|
|
||||||
|
|
||||||
alarm.relativeTrigger = null
|
|
||||||
|
|
||||||
alarm.alarmComponent = alarmComponent
|
|
||||||
|
|
||||||
if (alarm.isRelative) {
|
|
||||||
alarm.relativeIsBefore = alarmComponent.trigger.value.isNegative
|
|
||||||
alarm.relativeIsRelatedToStart = alarmComponent.trigger.related === 'START'
|
|
||||||
|
|
||||||
const timedData = getAmountAndUnitForTimedEvents(alarmComponent.trigger.value.totalSeconds)
|
|
||||||
alarm.relativeAmountTimed = timedData.amount
|
|
||||||
alarm.relativeUnitTimed = timedData.unit
|
|
||||||
|
|
||||||
const allDayData = getAmountHoursMinutesAndUnitForAllDayEvents(alarmComponent.trigger.value.totalSeconds)
|
|
||||||
alarm.relativeUnitAllDay = allDayData.unit
|
|
||||||
alarm.relativeAmountAllDay = allDayData.amount
|
|
||||||
alarm.relativeHoursAllDay = allDayData.hours
|
|
||||||
alarm.relativeMinutesAllDay = allDayData.minutes
|
|
||||||
|
|
||||||
alarm.relativeTrigger = alarmComponent.trigger.value.totalSeconds
|
|
||||||
} else {
|
|
||||||
alarm.absoluteDate = getDateFromDateTimeValue(alarmComponent.trigger.value)
|
|
||||||
alarm.absoluteTimezoneId = alarmComponent.trigger.value.timezoneId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {AlarmComponent} alarmComponent The alarm component to read from
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
export function getAlarmFromAlarmComponent(alarmComponent) {
|
|
||||||
const alarmObject = {}
|
|
||||||
updateAlarmFromAlarmComponent(alarmObject, alarmComponent)
|
|
||||||
|
|
||||||
return alarmObject
|
|
||||||
}
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
PRINCIPAL_PREFIX_CIRCLE,
|
||||||
|
PRINCIPAL_PREFIX_GROUP,
|
||||||
|
PRINCIPAL_PREFIX_USER,
|
||||||
|
} from './consts.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete calendar-share-object based on given props
|
||||||
|
*
|
||||||
|
* @param {Object} props Calendar-share-props already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultCalendarShareObject = (props = {}) => Object.assign({}, {
|
||||||
|
// Unique identifier
|
||||||
|
id: null,
|
||||||
|
// Displayname of the sharee
|
||||||
|
displayName: null,
|
||||||
|
// Whether or not share is writable
|
||||||
|
writeable: false,
|
||||||
|
// Whether or not sharee is an individual user
|
||||||
|
isUser: false,
|
||||||
|
// Whether or not sharee is an admin-defined group
|
||||||
|
isGroup: false,
|
||||||
|
// Whether or not sharee is a user-defined group
|
||||||
|
isCircle: false,
|
||||||
|
// Uri necessary for deleting / updating share
|
||||||
|
uri: null,
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a dav collection to our calendar object model
|
||||||
|
*
|
||||||
|
* @param {Object} sharee The sharee object from the cdav library shares
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapDavShareeToCalendarShareObject = (sharee) => {
|
||||||
|
// sharee.href might contain non-latin characters, so let's uri encode it first
|
||||||
|
const id = btoa(encodeURI(sharee.href))
|
||||||
|
|
||||||
|
let displayName
|
||||||
|
if (sharee['common-name'] && sharee['common-name'].trim() !== '') {
|
||||||
|
displayName = sharee['common-name']
|
||||||
|
} else if (sharee.href.startsWith(PRINCIPAL_PREFIX_GROUP)) {
|
||||||
|
displayName = sharee.href.substr(28)
|
||||||
|
} else if (sharee.href.startsWith(PRINCIPAL_PREFIX_USER)) {
|
||||||
|
displayName = sharee.href.substr(27)
|
||||||
|
} else {
|
||||||
|
displayName = sharee.href
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeable = sharee.access[0].endsWith('read-write')
|
||||||
|
const isUser = sharee.href.startsWith(PRINCIPAL_PREFIX_USER)
|
||||||
|
const isGroup = sharee.href.startsWith(PRINCIPAL_PREFIX_GROUP)
|
||||||
|
const isCircle = sharee.href.startsWith(PRINCIPAL_PREFIX_CIRCLE)
|
||||||
|
const uri = sharee.href
|
||||||
|
|
||||||
|
return getDefaultCalendarShareObject({
|
||||||
|
id,
|
||||||
|
displayName,
|
||||||
|
writeable,
|
||||||
|
isUser,
|
||||||
|
isGroup,
|
||||||
|
isCircle,
|
||||||
|
uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultCalendarShareObject,
|
||||||
|
mapDavShareeToCalendarShareObject,
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const COMPONENT_NAME_EVENT = 'VEVENT'
|
||||||
|
const COMPONENT_NAME_JOURNAL = 'VJOURNAL'
|
||||||
|
const COMPONENT_NAME_VTODO = 'VTODO'
|
||||||
|
|
||||||
|
const ITIP_MESSAGE_ADD = 'ADD'
|
||||||
|
const ITIP_MESSAGE_CANCEL = 'CANCEL'
|
||||||
|
const ITIP_MESSAGE_COUNTER = 'COUNTER'
|
||||||
|
const ITIP_MESSAGE_DECLINECOUNTER = 'DECLINECOUNTER'
|
||||||
|
const ITIP_MESSAGE_PUBLISH = 'PUBLISH'
|
||||||
|
const ITIP_MESSAGE_REFRESH = 'REFRESH'
|
||||||
|
const ITIP_MESSAGE_REPLY = 'REPLY'
|
||||||
|
const ITIP_MESSAGE_REQUEST = 'REQUEST'
|
||||||
|
|
||||||
|
const PRINCIPAL_PREFIX_USER = 'principal:principals/users/'
|
||||||
|
const PRINCIPAL_PREFIX_GROUP = 'principal:principals/groups/'
|
||||||
|
const PRINCIPAL_PREFIX_CIRCLE = 'principal:principals/circles/'
|
||||||
|
const PRINCIPAL_PREFIX_CALENDAR_RESOURCE = 'principal:principals/calendar-resources/'
|
||||||
|
const PRINCIPAL_PREFIX_CALENDAR_ROOM = 'principal:principals/calendar-rooms/'
|
||||||
|
|
||||||
|
export {
|
||||||
|
COMPONENT_NAME_EVENT,
|
||||||
|
COMPONENT_NAME_JOURNAL,
|
||||||
|
COMPONENT_NAME_VTODO,
|
||||||
|
ITIP_MESSAGE_ADD,
|
||||||
|
ITIP_MESSAGE_CANCEL,
|
||||||
|
ITIP_MESSAGE_COUNTER,
|
||||||
|
ITIP_MESSAGE_DECLINECOUNTER,
|
||||||
|
ITIP_MESSAGE_PUBLISH,
|
||||||
|
ITIP_MESSAGE_REFRESH,
|
||||||
|
ITIP_MESSAGE_REPLY,
|
||||||
|
ITIP_MESSAGE_REQUEST,
|
||||||
|
PRINCIPAL_PREFIX_USER,
|
||||||
|
PRINCIPAL_PREFIX_GROUP,
|
||||||
|
PRINCIPAL_PREFIX_CIRCLE,
|
||||||
|
PRINCIPAL_PREFIX_CALENDAR_RESOURCE,
|
||||||
|
PRINCIPAL_PREFIX_CALENDAR_ROOM,
|
||||||
|
}
|
|
@ -26,9 +26,9 @@
|
||||||
* @param {Object} props Contacts-props already provided
|
* @param {Object} props Contacts-props already provided
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export const getDefaultContactsObject = (props = {}) => Object.assign({}, {
|
const getDefaultContactsObject = (props = {}) => Object.assign({}, {
|
||||||
// The name of the contact
|
// The name of the contact
|
||||||
name: '',
|
name: null,
|
||||||
// Calendar-user-type of the contact
|
// Calendar-user-type of the contact
|
||||||
calendarUserType: 'INDIVIDUAL',
|
calendarUserType: 'INDIVIDUAL',
|
||||||
// Whether or not this is a user
|
// Whether or not this is a user
|
||||||
|
@ -51,3 +51,7 @@ export const getDefaultContactsObject = (props = {}) => Object.assign({}, {
|
||||||
// Timezone of the user
|
// Timezone of the user
|
||||||
timezoneId: null,
|
timezoneId: null,
|
||||||
}, props)
|
}, props)
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultContactsObject,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getDateFromDateTimeValue } from '../utils/date.js'
|
||||||
|
import DurationValue from 'calendar-js/src/values/durationValue.js'
|
||||||
|
import { getHexForColorName } from '../utils/color.js'
|
||||||
|
import { mapAlarmComponentToAlarmObject } from './alarm.js'
|
||||||
|
import { mapAttendeePropertyToAttendeeObject } from './attendee.js'
|
||||||
|
import {
|
||||||
|
getDefaultRecurrenceRuleObject,
|
||||||
|
mapRecurrenceRuleValueToRecurrenceRuleObject,
|
||||||
|
} from './recurrenceRule.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete calendar-object-instance-object based on given props
|
||||||
|
*
|
||||||
|
* @param {Object} props The props already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultEventObject = (props = {}) => Object.assign({}, {
|
||||||
|
// The real event-component coming from calendar-js
|
||||||
|
eventComponent: null,
|
||||||
|
// Title of the event
|
||||||
|
title: null,
|
||||||
|
// Start date of the event
|
||||||
|
startDate: null,
|
||||||
|
// Timezone of the start date
|
||||||
|
startTimezoneId: null,
|
||||||
|
// End date of the event
|
||||||
|
endDate: null,
|
||||||
|
// Timezone of the end date
|
||||||
|
endTimezoneId: null,
|
||||||
|
// Indicator whether or not event is all-day
|
||||||
|
isAllDay: false,
|
||||||
|
// Whether or not the user is allowed to toggle the all-day checkbox
|
||||||
|
canModifyAllDay: true,
|
||||||
|
// Location that the event takes places in
|
||||||
|
location: null,
|
||||||
|
// description of the event
|
||||||
|
description: null,
|
||||||
|
// Access class of the event (PUBLIC, PRIVATE, CONFIDENTIAL)
|
||||||
|
accessClass: null,
|
||||||
|
// Status of the event (CONFIRMED, TENTATIVE, CANCELLED)
|
||||||
|
status: null,
|
||||||
|
// Whether or not to block this event in Free-Busy reports (TRANSPARENT, OPAQUE)
|
||||||
|
timeTransparency: null,
|
||||||
|
// The recurrence rule of this event. We only support one recurrence-rule
|
||||||
|
recurrenceRule: getDefaultRecurrenceRuleObject(),
|
||||||
|
// Whether or not this event has multiple recurrence-rules
|
||||||
|
hasMultipleRRules: false,
|
||||||
|
// Whether or not this is the master item
|
||||||
|
isMasterItem: false,
|
||||||
|
// Whether or not this is a recurrence-exception
|
||||||
|
isRecurrenceException: false,
|
||||||
|
// Whether or not the applied modifications require to update this and all future
|
||||||
|
forceThisAndAllFuture: false,
|
||||||
|
// Whether or not it's possible to create a recurrence-exception for this event
|
||||||
|
canCreateRecurrenceException: false,
|
||||||
|
// Attendees of this event
|
||||||
|
attendees: [],
|
||||||
|
// Organizer of the event
|
||||||
|
organizer: null,
|
||||||
|
// Alarm of the event
|
||||||
|
alarms: [],
|
||||||
|
// Custom color of the event
|
||||||
|
customColor: null,
|
||||||
|
// Categories
|
||||||
|
categories: [],
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {EventComponent} eventComponent The calendar-js eventComponent
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapEventComponentToEventObject = (eventComponent) => {
|
||||||
|
const eventObject = getDefaultEventObject({
|
||||||
|
eventComponent,
|
||||||
|
title: eventComponent.title,
|
||||||
|
isAllDay: eventComponent.isAllDay(),
|
||||||
|
canModifyAllDay: eventComponent.canModifyAllDay(),
|
||||||
|
location: eventComponent.location,
|
||||||
|
description: eventComponent.description,
|
||||||
|
accessClass: eventComponent.accessClass,
|
||||||
|
status: eventComponent.status,
|
||||||
|
timeTransparency: eventComponent.timeTransparency,
|
||||||
|
categories: Array.from(eventComponent.getCategoryIterator()),
|
||||||
|
isMasterItem: eventComponent.isMasterItem(),
|
||||||
|
isRecurrenceException: eventComponent.isRecurrenceException(),
|
||||||
|
canCreateRecurrenceException: eventComponent.canCreateRecurrenceExceptions(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According to RFC5545, DTEND is exclusive. This is rather intuitive for timed-events
|
||||||
|
* but rather unintuitive for all-day events
|
||||||
|
*
|
||||||
|
* That's why, when an event is all-day from 2019-10-03 to 2019-10-04,
|
||||||
|
* it will be displayed as 2019-10-03 to 2019-10-03 in the editor.
|
||||||
|
*/
|
||||||
|
eventObject.startDate = getDateFromDateTimeValue(eventComponent.startDate)
|
||||||
|
eventObject.startTimezoneId = eventComponent.startDate.timezoneId
|
||||||
|
|
||||||
|
if (eventComponent.isAllDay()) {
|
||||||
|
const endDate = eventComponent.endDate.clone()
|
||||||
|
endDate.addDuration(DurationValue.fromSeconds(-1 * 60 * 60 * 24))
|
||||||
|
eventObject.endDate = getDateFromDateTimeValue(endDate)
|
||||||
|
} else {
|
||||||
|
eventObject.endDate = getDateFromDateTimeValue(eventComponent.endDate)
|
||||||
|
}
|
||||||
|
eventObject.endTimezoneId = eventComponent.endDate.timezoneId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract organizer if there is any
|
||||||
|
*/
|
||||||
|
if (eventComponent.organizer) {
|
||||||
|
const organizerProperty = eventComponent.getFirstProperty('ORGANIZER')
|
||||||
|
eventObject.organizer = {
|
||||||
|
commonName: organizerProperty.commonName,
|
||||||
|
uri: organizerProperty.email,
|
||||||
|
attendeeProperty: organizerProperty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract alarms
|
||||||
|
*/
|
||||||
|
for (const alarm of eventComponent.getAlarmIterator()) {
|
||||||
|
eventObject.alarms.push(mapAlarmComponentToAlarmObject(alarm))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract attendees
|
||||||
|
*/
|
||||||
|
for (const attendee of eventComponent.getAttendeeIterator()) {
|
||||||
|
eventObject.attendees.push(mapAttendeePropertyToAttendeeObject(attendee))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract recurrence-rule
|
||||||
|
*/
|
||||||
|
const recurrenceRuleIterator = eventComponent.getPropertyIterator('RRULE')
|
||||||
|
const recurrenceRuleFirstIteration = recurrenceRuleIterator.next()
|
||||||
|
|
||||||
|
const firstRecurrenceRule = recurrenceRuleFirstIteration.value
|
||||||
|
if (firstRecurrenceRule) {
|
||||||
|
eventObject.recurrenceRule = mapRecurrenceRuleValueToRecurrenceRuleObject(firstRecurrenceRule.getFirstValue(), eventComponent.startDate)
|
||||||
|
eventObject.hasMultipleRRules = !recurrenceRuleIterator.next().done
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the CSS 3 color name to a hex color
|
||||||
|
*/
|
||||||
|
if (eventComponent.hasProperty('COLOR')) {
|
||||||
|
const hexColor = getHexForColorName(eventComponent.getFirstPropertyFirstValue('COLOR'))
|
||||||
|
if (hexColor !== null) {
|
||||||
|
eventObject.customColor = hexColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventObject
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultEventObject,
|
||||||
|
mapEventComponentToEventObject,
|
||||||
|
}
|
|
@ -20,46 +20,108 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
PRINCIPAL_PREFIX_CALENDAR_RESOURCE,
|
||||||
|
PRINCIPAL_PREFIX_CALENDAR_ROOM,
|
||||||
|
PRINCIPAL_PREFIX_CIRCLE,
|
||||||
|
PRINCIPAL_PREFIX_GROUP,
|
||||||
|
PRINCIPAL_PREFIX_USER,
|
||||||
|
} from './consts.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a complete principal-object based on given props
|
* Creates a complete principal-object based on given props
|
||||||
*
|
*
|
||||||
* @param {Object} props Principal-props already provided
|
* @param {Object} props Principal-props already provided
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export const getDefaultPrincipalObject = (props) => Object.assign({}, {
|
const getDefaultPrincipalObject = (props) => Object.assign({}, {
|
||||||
// Id of the principal
|
// Id of the principal
|
||||||
id: '',
|
id: null,
|
||||||
// Calendar-user-type. This can be INDIVIDUAL, GROUP, RESOURCE or ROOM
|
// Calendar-user-type. This can be INDIVIDUAL, GROUP, RESOURCE or ROOM
|
||||||
calendarUserType: '',
|
calendarUserType: 'INDIVIDUAL',
|
||||||
// E-Mail address of principal used for scheduling
|
// E-Mail address of principal used for scheduling
|
||||||
emailAddress: '',
|
emailAddress: null,
|
||||||
// The principals display-name
|
// The principals display-name
|
||||||
displayname: '',
|
// TODO: this should be renamed to displayName
|
||||||
|
displayname: null,
|
||||||
// principalScheme
|
// principalScheme
|
||||||
principalScheme: '',
|
principalScheme: null,
|
||||||
// The internal user-id in case it is of type INDIVIDUAL and a user
|
// The internal user-id in case it is of type INDIVIDUAL and a user
|
||||||
userId: '',
|
// TODO: userId is deprecrated, use principalId instead
|
||||||
|
userId: null,
|
||||||
// url to the DAV-principal-resource
|
// url to the DAV-principal-resource
|
||||||
url: '',
|
url: null,
|
||||||
// The cdav-library object
|
// The cdav-library object
|
||||||
dav: false,
|
dav: null,
|
||||||
|
// Whether or not this principal represents a circle
|
||||||
|
isCircle: false,
|
||||||
|
// Whether or not this principal represents a user
|
||||||
|
isUser: false,
|
||||||
|
// Whether or not this principal represents a group
|
||||||
|
isGroup: false,
|
||||||
|
// Whether or not this principal represents a calendar-resource
|
||||||
|
isCalendarResource: false,
|
||||||
|
// Whether or not this principal represents a calendar-room
|
||||||
|
isCalendarRoom: false,
|
||||||
|
// The id of the principal without prefix. e.g. userId / groupId / etc.
|
||||||
|
principalId: null,
|
||||||
}, props)
|
}, props)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts a dav principal into a vuex object
|
* converts a dav principal into a vuex object
|
||||||
*
|
*
|
||||||
* @param {Object} principal cdav-library Principal object
|
* @param {Object} dav cdav-library Principal object
|
||||||
* @returns {{emailAddress: *, displayname: *, dav: *, id: *, calendarUserType: *, userId: *, url: *}}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function mapDavToPrincipal(principal) {
|
const mapDavToPrincipal = (dav) => {
|
||||||
return {
|
const id = btoa(encodeURI(dav.url))
|
||||||
id: btoa(principal.url),
|
const calendarUserType = dav.calendarUserType
|
||||||
calendarUserType: principal.calendarUserType,
|
const principalScheme = dav.principalScheme
|
||||||
principalScheme: principal.principalScheme,
|
const emailAddress = dav.email
|
||||||
emailAddress: principal.email,
|
|
||||||
displayname: principal.displayname,
|
const displayname = dav.displayname
|
||||||
userId: principal.userId,
|
|
||||||
url: principal.principalUrl,
|
const isUser = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_USER)
|
||||||
dav: principal,
|
const isGroup = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_GROUP)
|
||||||
|
const isCircle = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_CIRCLE)
|
||||||
|
const isCalendarResource = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_CALENDAR_RESOURCE)
|
||||||
|
const isCalendarRoom = dav.principalScheme.startsWith(PRINCIPAL_PREFIX_CALENDAR_ROOM)
|
||||||
|
|
||||||
|
let principalId = null
|
||||||
|
if (isUser) {
|
||||||
|
principalId = dav.principalScheme.substring(PRINCIPAL_PREFIX_USER.length)
|
||||||
|
} else if (isGroup) {
|
||||||
|
principalId = dav.principalScheme.substring(PRINCIPAL_PREFIX_GROUP.length)
|
||||||
|
} else if (isCircle) {
|
||||||
|
principalId = dav.principalScheme.substring(PRINCIPAL_PREFIX_CIRCLE.length)
|
||||||
|
} else if (isCalendarResource) {
|
||||||
|
principalId = dav.principalScheme.substring(PRINCIPAL_PREFIX_CALENDAR_RESOURCE.length)
|
||||||
|
} else if (isCalendarRoom) {
|
||||||
|
principalId = dav.principalScheme.substring(PRINCIPAL_PREFIX_CALENDAR_ROOM.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = dav.principalUrl
|
||||||
|
const userId = dav.userId
|
||||||
|
|
||||||
|
return getDefaultPrincipalObject({
|
||||||
|
id,
|
||||||
|
calendarUserType,
|
||||||
|
principalScheme,
|
||||||
|
emailAddress,
|
||||||
|
displayname,
|
||||||
|
url,
|
||||||
|
dav,
|
||||||
|
isUser,
|
||||||
|
isGroup,
|
||||||
|
isCircle,
|
||||||
|
isCalendarResource,
|
||||||
|
isCalendarRoom,
|
||||||
|
principalId,
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultPrincipalObject,
|
||||||
|
mapDavToPrincipal,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,503 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { getWeekDayFromDate } from '../utils/recurrence.js'
|
||||||
|
import { getDateFromDateTimeValue } from '../utils/date.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete recurrence-rule-object based on given props
|
||||||
|
*
|
||||||
|
* @param {Object} props Recurrence-rule-object-props already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultRecurrenceRuleObject = (props = {}) => Object.assign({}, {
|
||||||
|
// The calendar-js recurrence-rule value
|
||||||
|
recurrenceRuleValue: null,
|
||||||
|
// The frequency of the recurrence-rule (DAILY, WEEKLY, ...)
|
||||||
|
frequency: 'NONE',
|
||||||
|
// The interval of the recurrence-rule, must be a positive integer
|
||||||
|
interval: 1,
|
||||||
|
// Positive integer if recurrence-rule limited by count, null otherwise
|
||||||
|
count: null,
|
||||||
|
// Date if recurrence-rule limited by date, null otherwise
|
||||||
|
// We do not store a timezone here, since we only care about the date part
|
||||||
|
until: null,
|
||||||
|
// List of byDay components to limit/expand the recurrence-rule
|
||||||
|
byDay: [],
|
||||||
|
// List of byMonth components to limit/expand the recurrence-rule
|
||||||
|
byMonth: [],
|
||||||
|
// List of byMonthDay components to limit/expand the recurrence-rule
|
||||||
|
byMonthDay: [],
|
||||||
|
// A position to limit the recurrence-rule (e.g. -1 for last Friday)
|
||||||
|
bySetPosition: null,
|
||||||
|
// Whether or not the rule is not supported for editing
|
||||||
|
isUnsupported: false,
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a calendar-js recurrence-rule-value to an recurrence-rule-object
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The calendar-js recurrence rule value
|
||||||
|
* @param {DateTimeValue} baseDate The base-date used to fill unset values
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapRecurrenceRuleValueToRecurrenceRuleObject = (recurrenceRuleValue, baseDate) => {
|
||||||
|
switch (recurrenceRuleValue.frequency) {
|
||||||
|
case 'DAILY':
|
||||||
|
return mapDailyRuleValueToRecurrenceRuleObject(recurrenceRuleValue)
|
||||||
|
|
||||||
|
case 'WEEKLY':
|
||||||
|
return mapWeeklyRuleValueToRecurrenceRuleObject(recurrenceRuleValue, baseDate)
|
||||||
|
|
||||||
|
case 'MONTHLY':
|
||||||
|
return mapMonthlyRuleValueToRecurrenceRuleObject(recurrenceRuleValue, baseDate)
|
||||||
|
|
||||||
|
case 'YEARLY':
|
||||||
|
return mapYearlyRuleValueToRecurrenceRuleObject(recurrenceRuleValue, baseDate)
|
||||||
|
|
||||||
|
default: // SECONDLY, MINUTELY, HOURLY
|
||||||
|
return getDefaultRecurrenceRuleObjectForRecurrenceValue(recurrenceRuleValue, {
|
||||||
|
isUnsupported: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FORBIDDEN_BY_PARTS_DAILY = [
|
||||||
|
'BYSECOND',
|
||||||
|
'BYMINUTE',
|
||||||
|
'BYHOUR',
|
||||||
|
'BYDAY',
|
||||||
|
'BYMONTHDAY',
|
||||||
|
'BYYEARDAY',
|
||||||
|
'BYWEEKNO',
|
||||||
|
'BYMONTH',
|
||||||
|
'BYSETPOS',
|
||||||
|
]
|
||||||
|
const FORBIDDEN_BY_PARTS_WEEKLY = [
|
||||||
|
'BYSECOND',
|
||||||
|
'BYMINUTE',
|
||||||
|
'BYHOUR',
|
||||||
|
'BYMONTHDAY',
|
||||||
|
'BYYEARDAY',
|
||||||
|
'BYWEEKNO',
|
||||||
|
'BYMONTH',
|
||||||
|
'BYSETPOS',
|
||||||
|
]
|
||||||
|
const FORBIDDEN_BY_PARTS_MONTHLY = [
|
||||||
|
'BYSECOND',
|
||||||
|
'BYMINUTE',
|
||||||
|
'BYHOUR',
|
||||||
|
'BYYEARDAY',
|
||||||
|
'BYWEEKNO',
|
||||||
|
'BYMONTH',
|
||||||
|
]
|
||||||
|
const FORBIDDEN_BY_PARTS_YEARLY = [
|
||||||
|
'BYSECOND',
|
||||||
|
'BYMINUTE',
|
||||||
|
'BYHOUR',
|
||||||
|
'BYMONTHDAY',
|
||||||
|
'BYYEARDAY',
|
||||||
|
'BYWEEKNO',
|
||||||
|
]
|
||||||
|
|
||||||
|
const SUPPORTED_BY_DAY_WEEKLY = [
|
||||||
|
'SU',
|
||||||
|
'MO',
|
||||||
|
'TU',
|
||||||
|
'WE',
|
||||||
|
'TH',
|
||||||
|
'FR',
|
||||||
|
'SA',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all numbers between start and end as strings
|
||||||
|
*
|
||||||
|
* @param {Number} start Lower end of range
|
||||||
|
* @param {Number} end Upper end of range
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
const getRangeAsStrings = (start, end) => {
|
||||||
|
return Array
|
||||||
|
.apply(null, Array((end - start) + 1))
|
||||||
|
.map((_, n) => n + start)
|
||||||
|
.map((s) => s.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_BY_MONTHDAY_MONTHLY = getRangeAsStrings(1, 31)
|
||||||
|
|
||||||
|
const SUPPORTED_BY_MONTH_YEARLY = getRangeAsStrings(1, 12)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a daily calendar-js recurrence-rule-value to an recurrence-rule-object
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The calendar-js recurrence rule value
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapDailyRuleValueToRecurrenceRuleObject = (recurrenceRuleValue) => {
|
||||||
|
/**
|
||||||
|
* We only support DAILY rules without any by-parts in the editor.
|
||||||
|
* If the recurrence-rule contains any by-parts, mark it as unsupported.
|
||||||
|
*/
|
||||||
|
const isUnsupported = containsRecurrenceComponent(recurrenceRuleValue, FORBIDDEN_BY_PARTS_DAILY)
|
||||||
|
|
||||||
|
return getDefaultRecurrenceRuleObjectForRecurrenceValue(recurrenceRuleValue, {
|
||||||
|
isUnsupported,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a weekly calendar-js recurrence-rule-value to an recurrence-rule-object
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The calendar-js recurrence rule value
|
||||||
|
* @param {DateTimeValue} baseDate The base-date used to fill unset values
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapWeeklyRuleValueToRecurrenceRuleObject = (recurrenceRuleValue, baseDate) => {
|
||||||
|
/**
|
||||||
|
* For WEEKLY recurrences, our editor only allows BYDAY
|
||||||
|
*
|
||||||
|
* As defined in RFC5545 3.3.10. Recurrence Rule:
|
||||||
|
* > Each BYDAY value can also be preceded by a positive (+n) or
|
||||||
|
* > negative (-n) integer. If present, this indicates the nth
|
||||||
|
* > occurrence of a specific day within the MONTHLY or YEARLY "RRULE".
|
||||||
|
*
|
||||||
|
* RFC 5545 specifies other components, which can be used along WEEKLY.
|
||||||
|
* Among them are BYMONTH and BYSETPOS. We don't support those.
|
||||||
|
*/
|
||||||
|
const containsUnsupportedByParts = containsRecurrenceComponent(recurrenceRuleValue, FORBIDDEN_BY_PARTS_WEEKLY)
|
||||||
|
const containsInvalidByDayPart = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
.some((weekday) => !SUPPORTED_BY_DAY_WEEKLY.includes(weekday))
|
||||||
|
|
||||||
|
const isUnsupported = containsUnsupportedByParts || containsInvalidByDayPart
|
||||||
|
|
||||||
|
const byDay = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
.filter((weekday) => SUPPORTED_BY_DAY_WEEKLY.includes(weekday))
|
||||||
|
|
||||||
|
// If the BYDAY is empty, add the day that the event occurs in
|
||||||
|
// E.g. if the event is on a Wednesday, automatically set BYDAY:WE
|
||||||
|
if (byDay.length === 0) {
|
||||||
|
byDay.push(getWeekDayFromDate(baseDate.jsDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultRecurrenceRuleObjectForRecurrenceValue(recurrenceRuleValue, {
|
||||||
|
byDay,
|
||||||
|
isUnsupported,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a monthly calendar-js recurrence-rule-value to an recurrence-rule-object
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The calendar-js recurrence rule value
|
||||||
|
* @param {DateTimeValue} baseDate The base-date used to fill unset values
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapMonthlyRuleValueToRecurrenceRuleObject = (recurrenceRuleValue, baseDate) => {
|
||||||
|
/**
|
||||||
|
* We only supports BYMONTHDAY, BYDAY, BYSETPOS in order to expand the monthly rule.
|
||||||
|
* It supports either BYMONTHDAY or the combination of BYDAY and BYSETPOS. They have to be used exclusively
|
||||||
|
* and cannot be combined.
|
||||||
|
*
|
||||||
|
* We do not support other BY-parts like BYMONTH
|
||||||
|
*
|
||||||
|
* For monthly recurrence-rules, BYDAY components are allowed to be preceded by positive or negative integers.
|
||||||
|
* The Nextcloud-editor supports at most one BYDAY component with an integer.
|
||||||
|
* If it's presented with such a BYDAY component, it will internally be converted to BYDAY without integer and BYSETPOS.
|
||||||
|
* e.g.
|
||||||
|
* BYDAY=3WE => BYDAY=WE,BYSETPOS=3
|
||||||
|
*
|
||||||
|
* BYSETPOS is limited to -2, -1, 1, 2, 3, 4, 5
|
||||||
|
* Other values are not supported
|
||||||
|
*
|
||||||
|
* BYDAY is limited to "MO", "TU", "WE", "TH", "FR", "SA", "SU",
|
||||||
|
* "MO,TU,WE,TH,FR,SA,SU", "MO,TU,WE,TH,FR", "SA,SU"
|
||||||
|
*
|
||||||
|
* BYMONTHDAY is limited to "1", "2", ..., "31"
|
||||||
|
*/
|
||||||
|
let isUnsupported = containsRecurrenceComponent(recurrenceRuleValue, FORBIDDEN_BY_PARTS_MONTHLY)
|
||||||
|
|
||||||
|
let byDay = []
|
||||||
|
let bySetPosition = null
|
||||||
|
let byMonthDay = []
|
||||||
|
|
||||||
|
// This handles the first case, where we have a BYMONTHDAY rule
|
||||||
|
if (containsRecurrenceComponent(recurrenceRuleValue, ['BYMONTHDAY'])) {
|
||||||
|
// verify there is no BYDAY or BYSETPOS at the same time
|
||||||
|
if (containsRecurrenceComponent(recurrenceRuleValue, ['BYDAY', 'BYSETPOS'])) {
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const containsInvalidByMonthDay = recurrenceRuleValue.getComponent('BYMONTHDAY')
|
||||||
|
.some((monthDay) => !SUPPORTED_BY_MONTHDAY_MONTHLY.includes(monthDay.toString()))
|
||||||
|
isUnsupported = isUnsupported || containsInvalidByMonthDay
|
||||||
|
|
||||||
|
byMonthDay = recurrenceRuleValue.getComponent('BYMONTHDAY')
|
||||||
|
.filter((monthDay) => SUPPORTED_BY_MONTHDAY_MONTHLY.includes(monthDay.toString()))
|
||||||
|
.map((monthDay) => monthDay.toString())
|
||||||
|
|
||||||
|
// This handles cases where we have both BYDAY and BYSETPOS
|
||||||
|
} else if (containsRecurrenceComponent(recurrenceRuleValue, ['BYDAY']) && containsRecurrenceComponent(recurrenceRuleValue, ['BYSETPOS'])) {
|
||||||
|
|
||||||
|
if (isAllowedByDay(recurrenceRuleValue.getComponent('BYDAY'))) {
|
||||||
|
byDay = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
} else {
|
||||||
|
byDay = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPositionArray = recurrenceRuleValue.getComponent('BYSETPOS')
|
||||||
|
if (setPositionArray.length === 1 && isAllowedBySetPos(setPositionArray[0])) {
|
||||||
|
bySetPosition = setPositionArray[0]
|
||||||
|
} else {
|
||||||
|
bySetPosition = 1
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handles cases where we only have a BYDAY
|
||||||
|
} else if (containsRecurrenceComponent(recurrenceRuleValue, ['BYDAY'])) {
|
||||||
|
|
||||||
|
const byDayArray = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
|
||||||
|
if (byDayArray.length > 1) {
|
||||||
|
byMonthDay.push(baseDate.day.toString())
|
||||||
|
isUnsupported = true
|
||||||
|
} else {
|
||||||
|
const firstElement = byDayArray[0]
|
||||||
|
|
||||||
|
const match = /^(-?\d)([A-Z]{2})$/.exec(firstElement)
|
||||||
|
if (match) {
|
||||||
|
const matchedBySetPosition = match[1]
|
||||||
|
const matchedByDay = match[2]
|
||||||
|
|
||||||
|
if (isAllowedBySetPos(matchedBySetPosition)) {
|
||||||
|
byDay = [matchedByDay]
|
||||||
|
bySetPosition = parseInt(matchedBySetPosition, 10)
|
||||||
|
} else {
|
||||||
|
byDay = [matchedByDay]
|
||||||
|
bySetPosition = 1
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byMonthDay.push(baseDate.day.toString())
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a fallback where we just default BYMONTHDAY to the start date of the event
|
||||||
|
} else {
|
||||||
|
byMonthDay.push(baseDate.day.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultRecurrenceRuleObjectForRecurrenceValue(recurrenceRuleValue, {
|
||||||
|
byDay,
|
||||||
|
bySetPosition,
|
||||||
|
byMonthDay,
|
||||||
|
isUnsupported,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a yearly calendar-js recurrence-rule-value to an recurrence-rule-object
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The calendar-js recurrence rule value
|
||||||
|
* @param {DateTimeValue} baseDate The base-date used to fill unset values
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapYearlyRuleValueToRecurrenceRuleObject = (recurrenceRuleValue, baseDate) => {
|
||||||
|
/**
|
||||||
|
* We only supports BYMONTH, BYDAY, BYSETPOS in order to expand the yearly rule.
|
||||||
|
* It supports a combination of them.
|
||||||
|
*
|
||||||
|
* We do not support other BY-parts.
|
||||||
|
*
|
||||||
|
* For yearly recurrence-rules, BYDAY components are allowed to be preceded by positive or negative integers.
|
||||||
|
* The Nextcloud-editor supports at most one BYDAY component with an integer.
|
||||||
|
* If it's presented with such a BYDAY component, it will internally be converted to BYDAY without integer and BYSETPOS.
|
||||||
|
* e.g.
|
||||||
|
* BYDAY=3WE => BYDAY=WE,BYSETPOS=3
|
||||||
|
*
|
||||||
|
* BYSETPOS is limited to -2, -1, 1, 2, 3, 4, 5
|
||||||
|
* Other values are not supported
|
||||||
|
*
|
||||||
|
* BYDAY is limited to "MO", "TU", "WE", "TH", "FR", "SA", "SU",
|
||||||
|
* "MO,TU,WE,TH,FR,SA,SU", "MO,TU,WE,TH,FR", "SA,SU"
|
||||||
|
*/
|
||||||
|
let isUnsupported = containsRecurrenceComponent(recurrenceRuleValue, FORBIDDEN_BY_PARTS_YEARLY)
|
||||||
|
|
||||||
|
let byDay = []
|
||||||
|
let bySetPosition = null
|
||||||
|
let byMonth = []
|
||||||
|
|
||||||
|
if (containsRecurrenceComponent(recurrenceRuleValue, ['BYMONTH'])) {
|
||||||
|
const containsInvalidByMonthDay = recurrenceRuleValue.getComponent('BYMONTH')
|
||||||
|
.some((month) => !SUPPORTED_BY_MONTH_YEARLY.includes(month.toString()))
|
||||||
|
isUnsupported = isUnsupported || containsInvalidByMonthDay
|
||||||
|
|
||||||
|
byMonth = recurrenceRuleValue.getComponent('BYMONTH')
|
||||||
|
.filter((monthDay) => SUPPORTED_BY_MONTH_YEARLY.includes(monthDay.toString()))
|
||||||
|
.map((month) => month.toString())
|
||||||
|
} else {
|
||||||
|
byMonth.push(baseDate.month.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsRecurrenceComponent(recurrenceRuleValue, ['BYDAY']) && containsRecurrenceComponent(recurrenceRuleValue, ['BYSETPOS'])) {
|
||||||
|
|
||||||
|
if (isAllowedByDay(recurrenceRuleValue.getComponent('BYDAY'))) {
|
||||||
|
byDay = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
} else {
|
||||||
|
byDay = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPositionArray = recurrenceRuleValue.getComponent('BYSETPOS')
|
||||||
|
if (setPositionArray.length === 1 && isAllowedBySetPos(setPositionArray[0])) {
|
||||||
|
bySetPosition = setPositionArray[0]
|
||||||
|
} else {
|
||||||
|
bySetPosition = 1
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (containsRecurrenceComponent(recurrenceRuleValue, ['BYDAY'])) {
|
||||||
|
|
||||||
|
const byDayArray = recurrenceRuleValue.getComponent('BYDAY')
|
||||||
|
if (byDayArray.length > 1) {
|
||||||
|
isUnsupported = true
|
||||||
|
} else {
|
||||||
|
const firstElement = byDayArray[0]
|
||||||
|
|
||||||
|
const match = /^(-?\d)([A-Z]{2})$/.exec(firstElement)
|
||||||
|
if (match) {
|
||||||
|
const matchedBySetPosition = match[1]
|
||||||
|
const matchedByDay = match[2]
|
||||||
|
|
||||||
|
if (isAllowedBySetPos(matchedBySetPosition)) {
|
||||||
|
byDay = [matchedByDay]
|
||||||
|
bySetPosition = parseInt(matchedBySetPosition, 10)
|
||||||
|
} else {
|
||||||
|
byDay = [matchedByDay]
|
||||||
|
bySetPosition = 1
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isUnsupported = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultRecurrenceRuleObjectForRecurrenceValue(recurrenceRuleValue, {
|
||||||
|
byDay,
|
||||||
|
bySetPosition,
|
||||||
|
byMonth,
|
||||||
|
isUnsupported,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given parameter is a supported BYDAY value
|
||||||
|
*
|
||||||
|
* @param {String[]} byDay The byDay component to check
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
const isAllowedByDay = (byDay) => {
|
||||||
|
return [
|
||||||
|
'MO',
|
||||||
|
'TU',
|
||||||
|
'WE',
|
||||||
|
'TH',
|
||||||
|
'FR',
|
||||||
|
'SA',
|
||||||
|
'SU',
|
||||||
|
'FR,MO,SA,SU,TH,TU,WE',
|
||||||
|
'FR,MO,TH,TU,WE',
|
||||||
|
'SA,SU',
|
||||||
|
].includes(byDay.slice().sort().join(','))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given parameter is a supported BYSETPOS value
|
||||||
|
*
|
||||||
|
* @param {String} bySetPos The bySetPos component to check
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
const isAllowedBySetPos = (bySetPos) => {
|
||||||
|
return [
|
||||||
|
'-2',
|
||||||
|
'-1',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
].includes(bySetPos.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the recurrence-rule contains any of the given components
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRule The recurrence-rule value to check for the given components
|
||||||
|
* @param {String[]} components List of components to check for
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
const containsRecurrenceComponent = (recurrenceRule, components) => {
|
||||||
|
for (const component of components) {
|
||||||
|
const componentValue = recurrenceRule.getComponent(component)
|
||||||
|
if (componentValue.length > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a full recurrence-rule-object with default values derived from recurrenceRuleValue
|
||||||
|
* and additional props
|
||||||
|
*
|
||||||
|
* @param {RecurValue} recurrenceRuleValue The recurrence-rule value to get default values from
|
||||||
|
* @param {Object} props The properties to provide on top of default one
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultRecurrenceRuleObjectForRecurrenceValue = (recurrenceRuleValue, props) => {
|
||||||
|
const isUnsupported = recurrenceRuleValue.count !== null && recurrenceRuleValue.until !== null
|
||||||
|
let isUnsupportedProps = {}
|
||||||
|
|
||||||
|
if (isUnsupported) {
|
||||||
|
isUnsupportedProps = {
|
||||||
|
isUnsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultRecurrenceRuleObject(Object.assign({}, {
|
||||||
|
recurrenceRuleValue,
|
||||||
|
frequency: recurrenceRuleValue.frequency,
|
||||||
|
interval: parseInt(recurrenceRuleValue.interval, 10) || 1,
|
||||||
|
count: recurrenceRuleValue.count,
|
||||||
|
until: recurrenceRuleValue.until
|
||||||
|
? getDateFromDateTimeValue(recurrenceRuleValue.until)
|
||||||
|
: null,
|
||||||
|
}, props, isUnsupportedProps))
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultRecurrenceRuleObject,
|
||||||
|
mapRecurrenceRuleValueToRecurrenceRuleObject,
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ import { getDefaultCategories } from '../defaults/defaultCategories.js'
|
||||||
*
|
*
|
||||||
* @returns {{color: {readableName: *, icon: string, multiple: boolean, info: *}, timeTransparency: {readableName: *, defaultValue: string, icon: string, multiple: boolean, options: *[], info: *}, description: {readableName: *, icon: string, placeholder: *, defaultNumberOfRows: number}, location: {readableName: *, icon: string, placeholder: *}, categories: {readableName: *, icon: string, multiple: boolean, options: *, tagPlaceholder: *, placeholder: *, info: *}, accessClass: {readableName: *, defaultValue: string, icon: string, options: *[], multiple: boolean, info: *}, status: {readableName: *, defaultValue: string, icon: string, options: *[], multiple: boolean, info: *}}}
|
* @returns {{color: {readableName: *, icon: string, multiple: boolean, info: *}, timeTransparency: {readableName: *, defaultValue: string, icon: string, multiple: boolean, options: *[], info: *}, description: {readableName: *, icon: string, placeholder: *, defaultNumberOfRows: number}, location: {readableName: *, icon: string, placeholder: *}, categories: {readableName: *, icon: string, multiple: boolean, options: *, tagPlaceholder: *, placeholder: *, info: *}, accessClass: {readableName: *, defaultValue: string, icon: string, options: *[], multiple: boolean, info: *}, status: {readableName: *, defaultValue: string, icon: string, options: *[], multiple: boolean, info: *}}}
|
||||||
*/
|
*/
|
||||||
export function getRFCProperties() {
|
const getRFCProperties = () => {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* https://tools.ietf.org/html/rfc5545#section-3.8.1.3
|
* https://tools.ietf.org/html/rfc5545#section-3.8.1.3
|
||||||
|
@ -114,4 +114,6 @@ export function getRFCProperties() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getRFCProperties
|
export {
|
||||||
|
getRFCProperties,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { getParserManager } from 'calendar-js'
|
||||||
|
import { getDateFromDateTimeValue } from '../utils/date.js'
|
||||||
|
import {
|
||||||
|
ITIP_MESSAGE_ADD,
|
||||||
|
ITIP_MESSAGE_CANCEL,
|
||||||
|
ITIP_MESSAGE_COUNTER,
|
||||||
|
ITIP_MESSAGE_DECLINECOUNTER,
|
||||||
|
ITIP_MESSAGE_PUBLISH,
|
||||||
|
ITIP_MESSAGE_REFRESH,
|
||||||
|
ITIP_MESSAGE_REPLY,
|
||||||
|
ITIP_MESSAGE_REQUEST,
|
||||||
|
} from './consts.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a complete scheduling-object-object based on given props
|
||||||
|
*
|
||||||
|
* @param {Object} props Scheduling-object-props already provided
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const getDefaultSchedulingObject = (props = {}) => Object.assign({}, {
|
||||||
|
// Id of the scheduling-object
|
||||||
|
id: null,
|
||||||
|
// The cdav-library object storing the scheduling-object
|
||||||
|
dav: null,
|
||||||
|
// The parsed calendar-js object
|
||||||
|
calendarComponent: null,
|
||||||
|
// The uid of the scheduling-object
|
||||||
|
uid: null,
|
||||||
|
// Recurrence-id of the scheduling-object
|
||||||
|
recurrenceId: null,
|
||||||
|
// The uri of the scheduling-object
|
||||||
|
uri: null,
|
||||||
|
// The scheduling method
|
||||||
|
method: null,
|
||||||
|
// Whether or not the method is PUBLISH
|
||||||
|
isPublish: false,
|
||||||
|
// Whether or not the method is REQUEST
|
||||||
|
isRequest: false,
|
||||||
|
// Whether or not the method is REPLY
|
||||||
|
isReply: false,
|
||||||
|
// Whether or not the method is ADD
|
||||||
|
isAdd: false,
|
||||||
|
// Whether or not the method is CANCEL
|
||||||
|
isCancel: false,
|
||||||
|
// Whether or not the method is REFRESH
|
||||||
|
isRefresh: false,
|
||||||
|
// Whether or not the method is COUNTER
|
||||||
|
isCounter: false,
|
||||||
|
// Whether or not the method is DECLINECOUNTER
|
||||||
|
isDeclineCounter: false,
|
||||||
|
// Whether or not the scheduling-object exists on the server
|
||||||
|
existsOnServer: false,
|
||||||
|
}, props)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a calendar-object from c-dav to our calendar-object object
|
||||||
|
*
|
||||||
|
* @param {VObject} dav The c-dav VObject
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapCDavObjectToSchedulingObject = (dav) => {
|
||||||
|
const parserManager = getParserManager()
|
||||||
|
const parser = parserManager.getParserForFileType('text/calendar', {
|
||||||
|
preserveMethod: true,
|
||||||
|
processFreeBusy: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// This should not be the case, but let's just be on the safe side
|
||||||
|
if (typeof dav.data !== 'string' || dav.data.trim() === '') {
|
||||||
|
throw new Error('Empty scheduling object')
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.parse(dav.data)
|
||||||
|
const calendarComponentIterator = parser.getItemIterator()
|
||||||
|
const calendarComponent = calendarComponentIterator.next().value
|
||||||
|
if (!calendarComponent) {
|
||||||
|
throw new Error('Empty scheduling object')
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstVObject = getFirstObjectFromCalendarComponent(calendarComponent)
|
||||||
|
|
||||||
|
let recurrenceId = null
|
||||||
|
if (firstVObject.recurrenceId) {
|
||||||
|
recurrenceId = getDateFromDateTimeValue(firstVObject.recurrenceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calendarComponent.method) {
|
||||||
|
throw new Error('Scheduling-object does not have method')
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultSchedulingObject({
|
||||||
|
id: btoa(dav.url),
|
||||||
|
dav,
|
||||||
|
calendarComponent,
|
||||||
|
uid: firstVObject.uid,
|
||||||
|
uri: dav.url,
|
||||||
|
recurrenceId,
|
||||||
|
method: calendarComponent.method,
|
||||||
|
isPublish: calendarComponent.method === ITIP_MESSAGE_PUBLISH,
|
||||||
|
isRequest: calendarComponent.method === ITIP_MESSAGE_REQUEST,
|
||||||
|
isReply: calendarComponent.method === ITIP_MESSAGE_REPLY,
|
||||||
|
isAdd: calendarComponent.method === ITIP_MESSAGE_ADD,
|
||||||
|
isCancel: calendarComponent.method === ITIP_MESSAGE_CANCEL,
|
||||||
|
isRefresh: calendarComponent.method === ITIP_MESSAGE_REFRESH,
|
||||||
|
isCounter: calendarComponent.method === ITIP_MESSAGE_COUNTER,
|
||||||
|
isDeclineCounter: calendarComponent.method === ITIP_MESSAGE_DECLINECOUNTER,
|
||||||
|
existsOnServer: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a calendar-component from calendar-js to our calendar-object object
|
||||||
|
*
|
||||||
|
* @param {CalendarComponent} calendarComponent The calendarComponent to create the calendarObject from
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const mapCalendarJsToSchedulingObject = (calendarComponent) => {
|
||||||
|
const firstVObject = getFirstObjectFromCalendarComponent(calendarComponent)
|
||||||
|
|
||||||
|
let recurrenceId = null
|
||||||
|
if (firstVObject.recurrenceId) {
|
||||||
|
recurrenceId = getDateFromDateTimeValue(firstVObject.recurrenceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calendarComponent.method) {
|
||||||
|
throw new Error('Scheduling-object does not have method')
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultSchedulingObject({
|
||||||
|
calendarComponent,
|
||||||
|
uid: firstVObject.uid,
|
||||||
|
recurrenceId,
|
||||||
|
method: calendarComponent.method,
|
||||||
|
isPublish: calendarComponent.method === ITIP_MESSAGE_PUBLISH,
|
||||||
|
isRequest: calendarComponent.method === ITIP_MESSAGE_REQUEST,
|
||||||
|
isReply: calendarComponent.method === ITIP_MESSAGE_REPLY,
|
||||||
|
isAdd: calendarComponent.method === ITIP_MESSAGE_ADD,
|
||||||
|
isCancel: calendarComponent.method === ITIP_MESSAGE_CANCEL,
|
||||||
|
isRefresh: calendarComponent.method === ITIP_MESSAGE_REFRESH,
|
||||||
|
isCounter: calendarComponent.method === ITIP_MESSAGE_COUNTER,
|
||||||
|
isDeclineCounter: calendarComponent.method === ITIP_MESSAGE_DECLINECOUNTER,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the first object from the calendar-component
|
||||||
|
*
|
||||||
|
* @param {CalendarComponent} calendarComponent The calendar-component
|
||||||
|
* @returns {any} First VEvent / VJournal / VTodo / VFreeBusy
|
||||||
|
*/
|
||||||
|
const getFirstObjectFromCalendarComponent = (calendarComponent) => {
|
||||||
|
const vObjectIterator = calendarComponent.getVObjectIterator()
|
||||||
|
const firstVObject = vObjectIterator.next().value
|
||||||
|
if (firstVObject) {
|
||||||
|
return firstVObject
|
||||||
|
}
|
||||||
|
|
||||||
|
const vFreeBusyIterator = calendarComponent.getFreebusyIterator()
|
||||||
|
return vFreeBusyIterator.next().value
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDefaultSchedulingObject,
|
||||||
|
mapCDavObjectToSchedulingObject,
|
||||||
|
mapCalendarJsToSchedulingObject,
|
||||||
|
}
|
|
@ -31,9 +31,9 @@ import RecurValue from 'calendar-js/src/values/recurValue.js'
|
||||||
import Property from 'calendar-js/src/properties/property.js'
|
import Property from 'calendar-js/src/properties/property.js'
|
||||||
import { getBySetPositionAndBySetFromDate, getWeekDayFromDate } from '../utils/recurrence.js'
|
import { getBySetPositionAndBySetFromDate, getWeekDayFromDate } from '../utils/recurrence.js'
|
||||||
import {
|
import {
|
||||||
getAlarmFromAlarmComponent,
|
getDefaultEventObject,
|
||||||
getDefaultCalendarObjectInstanceObject, mapEventComponentToCalendarObjectInstanceObject,
|
mapEventComponentToEventObject,
|
||||||
} from '../models/calendarObjectInstance.js'
|
} from '../models/event.js'
|
||||||
import {
|
import {
|
||||||
getAmountAndUnitForTimedEvents,
|
getAmountAndUnitForTimedEvents,
|
||||||
getAmountHoursMinutesAndUnitForAllDayEvents,
|
getAmountHoursMinutesAndUnitForAllDayEvents,
|
||||||
|
@ -43,6 +43,8 @@ import {
|
||||||
getClosestCSS3ColorNameForHex,
|
getClosestCSS3ColorNameForHex,
|
||||||
getHexForColorName,
|
getHexForColorName,
|
||||||
} from '../utils/color.js'
|
} from '../utils/color.js'
|
||||||
|
import { mapAlarmComponentToAlarmObject } from '../models/alarm.js'
|
||||||
|
import { getObjectAtRecurrenceId } from '../utils/calendarObject.js'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
isNew: null,
|
isNew: null,
|
||||||
|
@ -970,7 +972,7 @@ const mutations = {
|
||||||
removeRecurrenceRuleFromCalendarObjectInstance(state, { calendarObjectInstance, recurrenceRule }) {
|
removeRecurrenceRuleFromCalendarObjectInstance(state, { calendarObjectInstance, recurrenceRule }) {
|
||||||
if (recurrenceRule.recurrenceRuleValue) {
|
if (recurrenceRule.recurrenceRuleValue) {
|
||||||
calendarObjectInstance.eventComponent.deleteAllProperties('RRULE')
|
calendarObjectInstance.eventComponent.deleteAllProperties('RRULE')
|
||||||
Vue.set(calendarObjectInstance, 'recurrenceRule', getDefaultCalendarObjectInstanceObject().recurrenceRule)
|
Vue.set(calendarObjectInstance, 'recurrenceRule', getDefaultEventObject().recurrenceRule)
|
||||||
|
|
||||||
console.debug(calendarObjectInstance)
|
console.debug(calendarObjectInstance)
|
||||||
console.debug(recurrenceRule)
|
console.debug(recurrenceRule)
|
||||||
|
@ -1300,7 +1302,7 @@ const mutations = {
|
||||||
const duration = DurationValue.fromSeconds(totalSeconds)
|
const duration = DurationValue.fromSeconds(totalSeconds)
|
||||||
const alarmComponent = eventComponent.addRelativeAlarm(type, duration)
|
const alarmComponent = eventComponent.addRelativeAlarm(type, duration)
|
||||||
|
|
||||||
const alarmObject = getAlarmFromAlarmComponent(alarmComponent)
|
const alarmObject = mapAlarmComponentToAlarmObject(alarmComponent)
|
||||||
|
|
||||||
calendarObjectInstance.alarms.push(alarmObject)
|
calendarObjectInstance.alarms.push(alarmObject)
|
||||||
|
|
||||||
|
@ -1349,9 +1351,15 @@ const actions = {
|
||||||
*/
|
*/
|
||||||
async resolveClosestRecurrenceIdForCalendarObject({ state, dispatch, commit }, { objectId, closeToDate }) {
|
async resolveClosestRecurrenceIdForCalendarObject({ state, dispatch, commit }, { objectId, closeToDate }) {
|
||||||
const calendarObject = await dispatch('getEventByObjectId', { objectId })
|
const calendarObject = await dispatch('getEventByObjectId', { objectId })
|
||||||
const eventComponent = calendarObject.getClosestRecurrence(closeToDate)
|
const iterator = calendarObject.calendarComponent.getVObjectIterator()
|
||||||
|
const firstVObject = iterator.next().value
|
||||||
|
|
||||||
return eventComponent.getReferenceRecurrenceId().unixTime
|
const d = DateTimeValue.fromJSDate(closeToDate, true)
|
||||||
|
return firstVObject
|
||||||
|
.recurrenceManager
|
||||||
|
.getClosestOccurrence(d)
|
||||||
|
.getReferenceRecurrenceId()
|
||||||
|
.unixTime
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1380,12 +1388,12 @@ const actions = {
|
||||||
|
|
||||||
const recurrenceIdDate = new Date(recurrenceId * 1000)
|
const recurrenceIdDate = new Date(recurrenceId * 1000)
|
||||||
const calendarObject = await dispatch('getEventByObjectId', { objectId })
|
const calendarObject = await dispatch('getEventByObjectId', { objectId })
|
||||||
const eventComponent = calendarObject.getObjectAtRecurrenceId(recurrenceIdDate)
|
const eventComponent = getObjectAtRecurrenceId(calendarObject, recurrenceIdDate)
|
||||||
if (eventComponent === null) {
|
if (eventComponent === null) {
|
||||||
throw new Error('Not a valid recurrence-id')
|
throw new Error('Not a valid recurrence-id')
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(eventComponent)
|
const calendarObjectInstance = mapEventComponentToEventObject(eventComponent)
|
||||||
commit('setCalendarObjectInstanceForExistingEvent', {
|
commit('setCalendarObjectInstanceForExistingEvent', {
|
||||||
calendarObject,
|
calendarObject,
|
||||||
calendarObjectInstance,
|
calendarObjectInstance,
|
||||||
|
@ -1423,8 +1431,8 @@ const actions = {
|
||||||
|
|
||||||
const calendarObject = await dispatch('createNewEvent', { start, end, isAllDay, timezoneId })
|
const calendarObject = await dispatch('createNewEvent', { start, end, isAllDay, timezoneId })
|
||||||
const startDate = new Date(start * 1000)
|
const startDate = new Date(start * 1000)
|
||||||
const eventComponent = calendarObject.getObjectAtRecurrenceId(startDate)
|
const eventComponent = getObjectAtRecurrenceId(calendarObject, startDate)
|
||||||
const calendarObjectInstance = mapEventComponentToCalendarObjectInstanceObject(eventComponent)
|
const calendarObjectInstance = mapEventComponentToEventObject(eventComponent)
|
||||||
|
|
||||||
commit('setCalendarObjectInstanceForNewEvent', {
|
commit('setCalendarObjectInstanceForNewEvent', {
|
||||||
calendarObject,
|
calendarObject,
|
||||||
|
@ -1539,22 +1547,6 @@ const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets a calendar-object-instance to it's original data and
|
|
||||||
* removes all data from the calendar-object-instance store
|
|
||||||
*
|
|
||||||
* @param {Object} vuex The vuex destructuring object
|
|
||||||
* @param {Object} vuex.state The Vuex state
|
|
||||||
* @param {Function} vuex.dispatch The Vuex dispatch function
|
|
||||||
* @param {Function} vuex.commit The Vuex commit function
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async resetCalendarObjectInstance({ state, commit }) {
|
|
||||||
if (state.calendarObject) {
|
|
||||||
state.calendarObject.resetToDav()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} data The destructuring object for Vuex
|
* @param {Object} data The destructuring object for Vuex
|
||||||
|
|
|
@ -22,10 +22,14 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import CalendarObject from '../models/calendarObject'
|
import { mapCalendarJsToCalendarObject } from '../models/calendarObject'
|
||||||
import logger from '../utils/logger.js'
|
import logger from '../utils/logger.js'
|
||||||
import DateTimeValue from 'calendar-js/src/values/dateTimeValue'
|
import DateTimeValue from 'calendar-js/src/values/dateTimeValue'
|
||||||
import { createEvent, getTimezoneManager } from 'calendar-js'
|
import {
|
||||||
|
createEvent,
|
||||||
|
getParserManager,
|
||||||
|
getTimezoneManager,
|
||||||
|
} from 'calendar-js'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
calendarObjects: {},
|
calendarObjects: {},
|
||||||
|
@ -43,12 +47,8 @@ const mutations = {
|
||||||
*/
|
*/
|
||||||
appendCalendarObjects(state, { calendarObjects = [] }) {
|
appendCalendarObjects(state, { calendarObjects = [] }) {
|
||||||
for (const calendarObject of calendarObjects) {
|
for (const calendarObject of calendarObjects) {
|
||||||
if (!state.calendarObjects[calendarObject.getId()]) {
|
if (!state.calendarObjects[calendarObject.id]) {
|
||||||
if (calendarObject instanceof CalendarObject) {
|
Vue.set(state.calendarObjects, calendarObject.id, calendarObject)
|
||||||
Vue.set(state.calendarObjects, calendarObject.getId(), calendarObject)
|
|
||||||
} else {
|
|
||||||
logger.error('Invalid calendarObject object')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -61,12 +61,49 @@ const mutations = {
|
||||||
* @param {Object} data.calendarObject Calendar-object to add
|
* @param {Object} data.calendarObject Calendar-object to add
|
||||||
*/
|
*/
|
||||||
appendCalendarObject(state, { calendarObject }) {
|
appendCalendarObject(state, { calendarObject }) {
|
||||||
if (!state.calendarObjects[calendarObject.getId()]) {
|
if (!state.calendarObjects[calendarObject.id]) {
|
||||||
if (calendarObject instanceof CalendarObject) {
|
Vue.set(state.calendarObjects, calendarObject.id, calendarObject)
|
||||||
Vue.set(state.calendarObjects, calendarObject.getId(), calendarObject)
|
}
|
||||||
} else {
|
},
|
||||||
logger.error('Invalid calendarObject object')
|
|
||||||
}
|
/**
|
||||||
|
* Updates a calendar-object id
|
||||||
|
*
|
||||||
|
* @param {Object} state The store data
|
||||||
|
* @param {Object} data The destructuring object
|
||||||
|
* @param {Object} data.calendarObject Calendar-object to update
|
||||||
|
*/
|
||||||
|
updateCalendarObjectId(state, { calendarObject }) {
|
||||||
|
if (calendarObject.dav === null) {
|
||||||
|
calendarObject.id = null
|
||||||
|
} else {
|
||||||
|
calendarObject.id = btoa(calendarObject.dav.url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets a calendar-object to it's original server state
|
||||||
|
*
|
||||||
|
* @param {Object} state The store data
|
||||||
|
* @param {Object} data The destructuring object
|
||||||
|
* @param {Object} data.calendarObject Calendar-object to reset
|
||||||
|
*/
|
||||||
|
resetCalendarObjectToDav(state, { calendarObject }) {
|
||||||
|
calendarObject = state.calendarObjects[calendarObject.id]
|
||||||
|
|
||||||
|
// If this object does not exist on the server yet, there is nothing to do
|
||||||
|
if (!calendarObject || !calendarObject.existsOnServer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parserManager = getParserManager()
|
||||||
|
const parser = parserManager.getParserForFileType('text/calendar')
|
||||||
|
parser.parse(calendarObject.dav.data)
|
||||||
|
|
||||||
|
const itemIterator = parser.getItemIterator()
|
||||||
|
const firstVCalendar = itemIterator.next().value
|
||||||
|
if (firstVCalendar) {
|
||||||
|
calendarObject.calendarComponent = firstVCalendar
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -78,7 +115,7 @@ const mutations = {
|
||||||
* @param {Object} data.calendarObject Calendar-object to delete
|
* @param {Object} data.calendarObject Calendar-object to delete
|
||||||
*/
|
*/
|
||||||
deleteCalendarObject(state, { calendarObject }) {
|
deleteCalendarObject(state, { calendarObject }) {
|
||||||
Vue.delete(state.calendarObjects, calendarObject.getId())
|
Vue.delete(state.calendarObjects, calendarObject.id)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,7 +151,7 @@ const actions = {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async moveCalendarObject(context, { calendarObject, newCalendarId }) {
|
async moveCalendarObject(context, { calendarObject, newCalendarId }) {
|
||||||
if (!calendarObject.existsOnServer()) {
|
if (!calendarObject.existsOnServer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +209,8 @@ const actions = {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async updateCalendarObject(context, { calendarObject }) {
|
async updateCalendarObject(context, { calendarObject }) {
|
||||||
if (calendarObject.existsOnServer()) {
|
if (calendarObject.existsOnServer) {
|
||||||
calendarObject.dav.data = calendarObject.vcalendar.toICS()
|
calendarObject.dav.data = calendarObject.calendarComponent.toICS()
|
||||||
await calendarObject.dav.update()
|
await calendarObject.dav.update()
|
||||||
|
|
||||||
context.commit('addCalendarObjectIdToAllTimeRangesOfCalendar', {
|
context.commit('addCalendarObjectIdToAllTimeRangesOfCalendar', {
|
||||||
|
@ -188,7 +225,9 @@ const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendar = context.getters.getCalendarById(calendarObject.calendarId)
|
const calendar = context.getters.getCalendarById(calendarObject.calendarId)
|
||||||
calendarObject.dav = await calendar.dav.createVObject(calendarObject.vcalendar.toICS())
|
calendarObject.dav = await calendar.dav.createVObject(calendarObject.calendarComponent.toICS())
|
||||||
|
calendarObject.existsOnServer = true
|
||||||
|
context.commit('updateCalendarObjectId', { calendarObject })
|
||||||
|
|
||||||
context.commit('appendCalendarObject', { calendarObject })
|
context.commit('appendCalendarObject', { calendarObject })
|
||||||
context.commit('addCalendarObjectToCalendar', {
|
context.commit('addCalendarObjectToCalendar', {
|
||||||
|
@ -215,8 +254,10 @@ const actions = {
|
||||||
*/
|
*/
|
||||||
async createCalendarObjectFromFork(context, { eventComponent, calendarId }) {
|
async createCalendarObjectFromFork(context, { eventComponent, calendarId }) {
|
||||||
const calendar = context.getters.getCalendarById(calendarId)
|
const calendar = context.getters.getCalendarById(calendarId)
|
||||||
const calendarObject = new CalendarObject(eventComponent.root, calendar.id)
|
const calendarObject = mapCalendarJsToCalendarObject(eventComponent.root, calendar.id)
|
||||||
calendarObject.dav = await calendar.dav.createVObject(calendarObject.vcalendar.toICS())
|
calendarObject.dav = await calendar.dav.createVObject(calendarObject.calendarComponent.toICS())
|
||||||
|
calendarObject.existsOnServer = true
|
||||||
|
context.commit('updateCalendarObjectId', { calendarObject })
|
||||||
|
|
||||||
context.commit('appendCalendarObject', { calendarObject })
|
context.commit('appendCalendarObject', { calendarObject })
|
||||||
context.commit('addCalendarObjectToCalendar', {
|
context.commit('addCalendarObjectToCalendar', {
|
||||||
|
@ -243,7 +284,7 @@ const actions = {
|
||||||
async deleteCalendarObject(context, { calendarObject }) {
|
async deleteCalendarObject(context, { calendarObject }) {
|
||||||
// If this calendar-object was not created on the server yet,
|
// If this calendar-object was not created on the server yet,
|
||||||
// no need to send requests to the server
|
// no need to send requests to the server
|
||||||
if (calendarObject.existsOnServer()) {
|
if (calendarObject.existsOnServer) {
|
||||||
await calendarObject.dav.delete()
|
await calendarObject.dav.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +337,7 @@ const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstCalendar = context.getters.sortedCalendars[0].id
|
const firstCalendar = context.getters.sortedCalendars[0].id
|
||||||
return Promise.resolve(new CalendarObject(calendar, firstCalendar))
|
return Promise.resolve(mapCalendarJsToCalendarObject(calendar, firstCalendar))
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
*/
|
*/
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import client from '../services/caldavService.js'
|
import client from '../services/caldavService.js'
|
||||||
import CalendarObject from '../models/calendarObject'
|
import { mapCDavObjectToCalendarObject } from '../models/calendarObject'
|
||||||
import { dateFactory, getUnixTimestampFromDate } from '../utils/date.js'
|
import { dateFactory, getUnixTimestampFromDate } from '../utils/date.js'
|
||||||
import { getDefaultCalendarObject, mapDavCollectionToCalendar } from '../models/calendar'
|
import { getDefaultCalendarObject, mapDavCollectionToCalendar } from '../models/calendar'
|
||||||
import pLimit from 'p-limit'
|
import pLimit from 'p-limit'
|
||||||
|
@ -706,7 +706,7 @@ const actions = {
|
||||||
const calendarObjects = []
|
const calendarObjects = []
|
||||||
const calendarObjectIds = []
|
const calendarObjectIds = []
|
||||||
for (const r of response) {
|
for (const r of response) {
|
||||||
const calendarObject = new CalendarObject(r.data, calendar.id, r)
|
const calendarObject = mapCDavObjectToCalendarObject(r, calendar.id)
|
||||||
calendarObjects.push(calendarObject)
|
calendarObjects.push(calendarObject)
|
||||||
calendarObjectIds.push(calendarObject.id)
|
calendarObjectIds.push(calendarObject.id)
|
||||||
}
|
}
|
||||||
|
@ -751,7 +751,7 @@ const actions = {
|
||||||
|
|
||||||
const calendar = context.state.calendarsById[calendarId]
|
const calendar = context.state.calendarsById[calendarId]
|
||||||
const vObject = await calendar.dav.find(objectFileName)
|
const vObject = await calendar.dav.find(objectFileName)
|
||||||
const calendarObject = new CalendarObject(vObject.data, calendar.id, vObject)
|
const calendarObject = mapCDavObjectToCalendarObject(vObject, calendar.id)
|
||||||
context.commit('appendCalendarObject', { calendarObject })
|
context.commit('appendCalendarObject', { calendarObject })
|
||||||
context.commit('addCalendarObjectToCalendar', {
|
context.commit('addCalendarObjectToCalendar', {
|
||||||
calendar: {
|
calendar: {
|
||||||
|
@ -831,7 +831,7 @@ const actions = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarObject = new CalendarObject(davObject.data, calendarId, davObject)
|
const calendarObject = mapCDavObjectToCalendarObject(davObject, calendarId)
|
||||||
context.commit('appendCalendarObject', { calendarObject })
|
context.commit('appendCalendarObject', { calendarObject })
|
||||||
context.commit('addCalendarObjectToCalendar', {
|
context.commit('addCalendarObjectToCalendar', {
|
||||||
calendar,
|
calendar,
|
||||||
|
|
|
@ -21,11 +21,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
/* eslint-disable import/first */
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
Vue.config.devtools = true
|
|
||||||
|
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
import calendarObjectInstance from './calendarObjectInstance'
|
import calendarObjectInstance from './calendarObjectInstance'
|
||||||
import calendarObjects from './calendarObjects'
|
import calendarObjects from './calendarObjects'
|
||||||
import calendars from './calendars.js'
|
import calendars from './calendars.js'
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import DateTimeValue from 'calendar-js/src/values/dateTimeValue.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all recurrence-items in given range
|
||||||
|
*
|
||||||
|
* @param {Object} calendarObject Calendar-object model
|
||||||
|
* @param {Date} start Begin of time-range
|
||||||
|
* @param {Date} end End of time-range
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
const getAllObjectsInTimeRange = (calendarObject, start, end) => {
|
||||||
|
const iterator = calendarObject.calendarComponent.getVObjectIterator()
|
||||||
|
const firstVObject = iterator.next().value
|
||||||
|
if (!firstVObject) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = DateTimeValue.fromJSDate(start, true)
|
||||||
|
const e = DateTimeValue.fromJSDate(end, true)
|
||||||
|
return firstVObject.recurrenceManager.getAllOccurrencesBetween(s, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get recurrence-item at exactly a given recurrence-Id
|
||||||
|
*
|
||||||
|
* @param {Object} calendarObject Calendar-object model
|
||||||
|
* @param {Date} recurrenceId RecurrenceId to retrieve
|
||||||
|
* @returns {AbstractRecurringComponent|null}
|
||||||
|
*/
|
||||||
|
const getObjectAtRecurrenceId = (calendarObject, recurrenceId) => {
|
||||||
|
const iterator = calendarObject.calendarComponent.getVObjectIterator()
|
||||||
|
const firstVObject = iterator.next().value
|
||||||
|
if (!firstVObject) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = DateTimeValue.fromJSDate(recurrenceId, true)
|
||||||
|
return firstVObject.recurrenceManager.getOccurrenceAtExactly(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllObjectsInTimeRange,
|
||||||
|
getObjectAtRecurrenceId,
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;VALUE=DATE-TIME:20200306T083000Z
|
||||||
|
END:VALARM
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=START:P1DT9H
|
||||||
|
END:VALARM
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=START:PT9H
|
||||||
|
END:VALARM
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=START:-PT15H
|
||||||
|
END:VALARM
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=END:-PT15H
|
||||||
|
END:VALARM
|
|
@ -0,0 +1,4 @@
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=START:-P6DT15H
|
||||||
|
END:VALARM
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:jsmith@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;CUTYPE=GROUP:mailto:ietf-calsch@example.org
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;PARTSTAT=DECLINED:mailto:jsmith@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;ROLE=CHAIR:mailto:mrbig@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;DELEGATED-FROM="mailto:iamboss@example.com";CN=Henry Cabot:mailto:hcabot@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;ROLE=NON-PARTICIPANT;PARTSTAT=DELEGATED;DELEGATED-TO="mailto:hcabot@example.com";CN=The Big Cheese:mailto:iamboss@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@example.com
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;UNTIL=20201122T001122Z;COUNT=5
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;COUNT=42
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;UNTIL=20201122T001122Z
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=SECONDLY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYMONTHDAY=1,2,3,30,31
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYMONTHDAY=-1,2,-3,30,31,-31
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYMONTHDAY=1,2,3,30,31;BYDAY=MO;BYSETPOS=-1
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO;BYSETPOS=3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO,TU,WE;BYSETPOS=3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO;BYSETPOS=-3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO;BYSETPOS=1,2,3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYMONTH=1,2,3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MINUTELY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYMONTH=1,2,3,0
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO;BYSETPOS=3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO,TU,WE;BYSETPOS=3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO;BYSETPOS=-3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO;BYSETPOS=1,2,3
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO,TU,WE
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=3MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=-3MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=MONTHLY;BYDAY=MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO,TU,WE
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=HOURLY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=3MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=-3MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=YEARLY;BYDAY=MO
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=DAILY;INTERVAL=5
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=DAILY;INTERVAL=42;BYMONTH=1
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=WEEKLY
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=WEEKLY;BYDAY=MO,TU,WE
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=WEEKLY;BYDAY=MO,2TU,-3WE
|
|
@ -0,0 +1 @@
|
||||||
|
FREQ=WEEKLY;BYDAY=MO,TU,WE;BYMONTH=1,2
|
|
@ -0,0 +1,18 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:ADD
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:123456789@example.com
|
||||||
|
SEQUENCE:2
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:a@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE:mailto:b@example.com
|
||||||
|
SUMMARY:Review Accounts
|
||||||
|
DTSTART:19980315T180000Z
|
||||||
|
DTEND:19980315T200000Z
|
||||||
|
DTSTAMP:19980307T193000Z
|
||||||
|
LOCATION:Conference Room A
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,16 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:CANCEL
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:guid-1@example.com
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:a@example.com
|
||||||
|
ATTENDEE:mailto:b@example.com
|
||||||
|
ATTENDEE:mailto:c@example.com
|
||||||
|
ATTENDEE:mailto:d@example.com
|
||||||
|
DTSTAMP:19970721T103000Z
|
||||||
|
STATUS:CANCELLED
|
||||||
|
SEQUENCE:3
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,23 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:COUNTER
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:guid-1@example.com
|
||||||
|
RECURRENCE-ID:19970715T210000Z
|
||||||
|
SEQUENCE:4
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR;RSVP=TRUE:mailto:a@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE:mailto:b@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE:mailto:c@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE:mailto:d@example.com
|
||||||
|
DESCRIPTION:IETF-C&S Conference Call
|
||||||
|
CLASS:PUBLIC
|
||||||
|
SUMMARY:IETF Calendaring Working Group Meeting
|
||||||
|
DTSTART:19970715T220000Z
|
||||||
|
DTEND:19970715T230000Z
|
||||||
|
LOCATION:Conference Call
|
||||||
|
COMMENT:May we bump this by an hour? I have a conflict
|
||||||
|
DTSTAMP:19970629T094000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,13 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
METHOD:DECLINECOUNTER
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE;CUTYPE=INDIVIDUAL:mailto:b@example.com
|
||||||
|
COMMENT:Sorry, I cannot change this meeting time
|
||||||
|
UID:calsrv.example.com-873970198738777@example.com
|
||||||
|
SEQUENCE:0
|
||||||
|
DTSTAMP:19970614T190000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,14 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
METHOD:REPLY
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VFREEBUSY
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE:mailto:b@example.com
|
||||||
|
DTSTART:19970701T080000Z
|
||||||
|
DTEND:19970701T200000Z
|
||||||
|
UID:calsrv.example.com-873970198738777@example.com
|
||||||
|
FREEBUSY:19970701T090000Z/PT1H,19970701T140000Z/PT30M
|
||||||
|
DTSTAMP:19970613T190030Z
|
||||||
|
END:VFREEBUSY
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,15 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
METHOD:REQUEST
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VFREEBUSY
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR:mailto:a@example.com
|
||||||
|
ATTENDEE:mailto:b@example.com
|
||||||
|
ATTENDEE:mailto:c@example.com
|
||||||
|
DTSTAMP:19970613T190000Z
|
||||||
|
DTSTART:19970701T080000Z
|
||||||
|
DTEND:19970701T200000
|
||||||
|
UID:calsrv.example.com-873970198738777@example.com
|
||||||
|
END:VFREEBUSY
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,18 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
BEGIN:VFREEBUSY
|
||||||
|
DTSTAMP:19980101T124100Z
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
DTSTART:19980101T124200Z
|
||||||
|
DTEND:19980108T124200Z
|
||||||
|
FREEBUSY:19980101T180000Z/19980101T190000Z
|
||||||
|
FREEBUSY:19980103T020000Z/19980103T050000Z
|
||||||
|
FREEBUSY:19980107T020000Z/19980107T050000Z
|
||||||
|
FREEBUSY:19980113T000000Z/19980113T010000Z
|
||||||
|
FREEBUSY:19980115T190000Z/19980115T200000Z
|
||||||
|
FREEBUSY:19980115T220000Z/19980115T230000Z
|
||||||
|
FREEBUSY:19980116T013000Z/19980116T043000Z
|
||||||
|
END:VFREEBUSY
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,14 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
METHOD:REFRESH
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:a@example.com
|
||||||
|
ATTENDEE:mailto:b@example.com
|
||||||
|
ATTENDEE:mailto:c@example.com
|
||||||
|
ATTENDEE:mailto:d@example.com
|
||||||
|
UID:guid-1-12345@example.com
|
||||||
|
DTSTAMP:19970603T094000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,13 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
METHOD:REPLY
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
ATTENDEE;PARTSTAT=ACCEPTED:mailto:b@example.com
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
UID:calsrv.example.com-873970198738777@example.com
|
||||||
|
SEQUENCE:0
|
||||||
|
REQUEST-STATUS:2.0;Success
|
||||||
|
DTSTAMP:19970612T190000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,19 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:REQUEST
|
||||||
|
PRODID:-//Example/ExampleCalendarClient//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:123456789@example.com
|
||||||
|
SEQUENCE:1
|
||||||
|
RECURRENCE-ID:19980311T180000Z
|
||||||
|
ORGANIZER:mailto:a@example.com
|
||||||
|
ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:a@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE:mailto:b@example.com
|
||||||
|
SUMMARY:Review Accounts
|
||||||
|
DTSTART:19980311T160000Z
|
||||||
|
DTEND:19980311T180000Z
|
||||||
|
DTSTAMP:19980306T193000Z
|
||||||
|
LOCATION:The Small conference room
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,5 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,40 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;RELATED=START:P1DT9H
|
||||||
|
END:VALARM
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER;VALUE=DATE-TIME:20200306T083000Z
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,15 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20161004T144433Z
|
||||||
|
UID:85560E76-1B0D-47E1-A735-21625767FCA4
|
||||||
|
DTEND;VALUE=DATE:20161008
|
||||||
|
TRANSP:TRANSPARENT
|
||||||
|
DTSTART;VALUE=DATE:20161005
|
||||||
|
DTSTAMP:20161004T144437Z
|
||||||
|
SUMMARY:allday event
|
||||||
|
SEQUENCE:0
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,37 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
ORGANIZER;CN=John Smith:mailto:jsmith@example.com
|
||||||
|
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:jsmith@example.com
|
||||||
|
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Henry Cabot:mailto:hcabot@example.com
|
||||||
|
ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="mailto:bob@example.com";PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@example.com
|
||||||
|
ATTENDEE;ROLE=NON-PARTICIPANT;PARTSTAT=DELEGATED;DELEGATED-TO="mailto:hcabot@example.com";CN=The Big Cheese:mailto:iamboss@example.com
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,33 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,33 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
COLOR:turquoise
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,32 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,34 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU
|
||||||
|
RRULE:FREQ=DAILY;BYMONTH=1
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,16 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//IDN nextcloud.com//Calendar app 2.0.2//EN
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20200401T142357Z
|
||||||
|
DTSTAMP:20200401T142406Z
|
||||||
|
LAST-MODIFIED:20200401T142406Z
|
||||||
|
SEQUENCE:2
|
||||||
|
UID:4f7a5e63-6ae5-43da-b949-7bad490882c5
|
||||||
|
DTSTART;VALUE=DATE:20200401
|
||||||
|
DTEND;VALUE=DATE:20200402
|
||||||
|
RRULE:FREQ=WEEKLY;BYDAY=WE
|
||||||
|
SUMMARY:Weekly test
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,85 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST
|
||||||
|
RRULE:FREQ=WEEKLY
|
||||||
|
DTSTART;TZID=Europe/Berlin:20200301T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20200301T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST EX 1
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200308T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20200401T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20200401T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST EX 2
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200315T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20201101T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20201101T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST EX 3
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200405T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20200406T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20200406T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST EX 4
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200412T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20201201T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20201201T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:TEST EX 5
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200426T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20200410T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20200410T160000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
DTSTAMP:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
SUMMARY:INVALID RECURRENCE-ID
|
||||||
|
RECURRENCE-ID;TZID=Europe/Berlin:20200427T150000
|
||||||
|
DTSTART;TZID=Europe/Berlin:20200420T150000
|
||||||
|
DTEND;TZID=Europe/Berlin:20200420T160000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,32 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND;TZID=Europe/Berlin:20160816T100000
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART;TZID=Europe/Berlin:20160816T090000
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,32 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20160809T163629Z
|
||||||
|
UID:0AD16F58-01B3-463B-A215-FD09FC729A02
|
||||||
|
DTEND:20160816T100000Z
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
SUMMARY:Test Europe Berlin
|
||||||
|
DTSTART:20160816T090000Z
|
||||||
|
DTSTAMP:20160809T163632Z
|
||||||
|
SEQUENCE:0
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,20 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VJOURNAL
|
||||||
|
UID:19970901T130000Z-123405@example.com
|
||||||
|
DTSTAMP:19970901T130000Z
|
||||||
|
DTSTART;VALUE=DATE:19970317
|
||||||
|
SUMMARY:Staff meeting minutes
|
||||||
|
DESCRIPTION:1. Staff meeting: Participants include Joe\,
|
||||||
|
Lisa\, and Bob. Aurora project plans were reviewed.
|
||||||
|
There is currently no budget reserves for this project.
|
||||||
|
Lisa will escalate to management. Next meeting on Tuesday.\n
|
||||||
|
2. Telephone Conference: ABC Corp. sales representative
|
||||||
|
called to discuss new printer. Promised to get us a demo by
|
||||||
|
Friday.\n3. Henry Miller (Handsoff Insurance): Car was
|
||||||
|
totaled by tree. Is looking into a loaner car. 555-2323
|
||||||
|
(tel).
|
||||||
|
END:VJOURNAL
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,14 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:20070313T123432Z-456553@example.com
|
||||||
|
DTSTAMP:20070313T123432Z
|
||||||
|
DUE;VALUE=DATE:20070501
|
||||||
|
SUMMARY:Submit Quebec Income Tax Return for 2006
|
||||||
|
CLASS:CONFIDENTIAL
|
||||||
|
CATEGORIES:FAMILY,FINANCE
|
||||||
|
STATUS:NEEDS-ACTION
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,22 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Tests//
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
DTSTART:19810329T020000
|
||||||
|
TZNAME:GMT+2
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
DTSTART:19961027T030000
|
||||||
|
TZNAME:GMT+1
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||||
|
*
|
||||||
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import ICAL from 'ical.js'
|
||||||
|
import AlarmComponent from 'calendar-js/src/components/nested/alarmComponent.js'
|
||||||
|
import AttendeeProperty from "calendar-js/src/properties/attendeeProperty.js";
|
||||||
|
import RecurValue from "calendar-js/src/values/recurValue.js";
|
||||||
|
import {getParserManager} from "calendar-js";
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* global helper function to load an ics asset by name
|
||||||
|
*
|
||||||
|
* @param {string} assetName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
global.loadICS = (assetName) => {
|
||||||
|
return fs.readFileSync('tests/assets/ics/' + assetName + '.ics', 'UTF8')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an AlarmComponent from an asset
|
||||||
|
*
|
||||||
|
* @param {String} assetName Name of the asset
|
||||||
|
* @returns {AlarmComponent}
|
||||||
|
*/
|
||||||
|
global.getAlarmComponentFromAsset = (assetName) => {
|
||||||
|
const ics = loadICS(assetName)
|
||||||
|
const jCal = ICAL.parse(ics.trim())
|
||||||
|
const iCalComp = new ICAL.Component(jCal)
|
||||||
|
|
||||||
|
return AlarmComponent.fromICALJs(iCalComp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an AttendeeProperty from an asset
|
||||||
|
*
|
||||||
|
* @param {String} assetName Name of the asset
|
||||||
|
* @returns {AttendeeProperty}
|
||||||
|
*/
|
||||||
|
global.getAttendeePropertyFromAsset = (assetName) => {
|
||||||
|
const ics = loadICS(assetName)
|
||||||
|
const iCalProp = ICAL.Property.fromString(ics.trim())
|
||||||
|
|
||||||
|
return AttendeeProperty.fromICALJs(iCalProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a RecurValue from an asset
|
||||||
|
*
|
||||||
|
* @param {String} assetName Name of the asset
|
||||||
|
* @returns {RecurValue}
|
||||||
|
*/
|
||||||
|
global.getRecurValueFromAsset = (assetName) => {
|
||||||
|
const ics = loadICS(assetName)
|
||||||
|
const iCalValue = ICAL.Recur.fromString(ics.trim())
|
||||||
|
|
||||||
|
return RecurValue.fromICALJs(iCalValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an eventComponent from an asset
|
||||||
|
*
|
||||||
|
* @param {String} assetName Name of the asset
|
||||||
|
* @param {DateTimeValue} recurrenceId RecurrenceId of instance
|
||||||
|
*/
|
||||||
|
global.getEventComponentFromAsset = (assetName, recurrenceId) => {
|
||||||
|
const ics = loadICS(assetName)
|
||||||
|
const parser = getParserManager().getParserForFileType('text/calendar')
|
||||||
|
parser.parse(ics)
|
||||||
|
|
||||||
|
const calendarComponent = parser.getAllItems()[0]
|
||||||
|
const firstVObject = Array.from(calendarComponent.getVObjectIterator())[0]
|
||||||
|
return firstVObject.recurrenceManager.getOccurrenceAtExactly(recurrenceId)
|
||||||
|
}
|
|
@ -22,15 +22,18 @@
|
||||||
import eventDrop from "../../../../src/fullcalendar/eventDrop.js";
|
import eventDrop from "../../../../src/fullcalendar/eventDrop.js";
|
||||||
import { getDurationValueFromFullCalendarDuration} from "../../../../src/fullcalendar/duration.js";
|
import { getDurationValueFromFullCalendarDuration} from "../../../../src/fullcalendar/duration.js";
|
||||||
import getTimezoneManager from '../../../../src/services/timezoneDataProviderService.js'
|
import getTimezoneManager from '../../../../src/services/timezoneDataProviderService.js'
|
||||||
|
import {getObjectAtRecurrenceId} from "../../../../src/utils/calendarObject.js";
|
||||||
|
|
||||||
jest.mock("../../../../src/fullcalendar/duration.js")
|
jest.mock("../../../../src/fullcalendar/duration.js")
|
||||||
jest.mock('../../../../src/services/timezoneDataProviderService.js')
|
jest.mock('../../../../src/services/timezoneDataProviderService.js')
|
||||||
|
jest.mock("../../../../src/utils/calendarObject.js")
|
||||||
|
|
||||||
describe('fullcalendar/eventDrop test suite', () => {
|
describe('fullcalendar/eventDrop test suite', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getDurationValueFromFullCalendarDuration.mockClear()
|
getDurationValueFromFullCalendarDuration.mockClear()
|
||||||
getTimezoneManager.mockClear()
|
getTimezoneManager.mockClear()
|
||||||
|
getObjectAtRecurrenceId.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should properly drop a non-recurring event', async () => {
|
it('should properly drop a non-recurring event', async () => {
|
||||||
|
@ -74,12 +77,14 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
|
||||||
const eventDropFunction = eventDrop(store, fcAPI)
|
const eventDropFunction = eventDrop(store, fcAPI)
|
||||||
await eventDropFunction({ event, delta, revert })
|
await eventDropFunction({ event, delta, revert })
|
||||||
|
@ -104,7 +109,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(0)
|
expect(revert).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -149,13 +153,15 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
canCreateRecurrenceExceptions: jest.fn().mockReturnValue(false),
|
canCreateRecurrenceExceptions: jest.fn().mockReturnValue(false),
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
|
||||||
const eventDropFunction = eventDrop(store, fcAPI)
|
const eventDropFunction = eventDrop(store, fcAPI)
|
||||||
await eventDropFunction({ event, delta, revert })
|
await eventDropFunction({ event, delta, revert })
|
||||||
|
@ -184,7 +190,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(0)
|
expect(revert).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -229,9 +234,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
@ -260,7 +266,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(1)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenNthCalledWith(1)
|
expect(eventComponent.createRecurrenceException).toHaveBeenNthCalledWith(1)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(0)
|
expect(revert).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -305,9 +310,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
@ -331,7 +337,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -376,9 +381,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
@ -402,7 +408,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -447,9 +452,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
@ -473,7 +479,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -518,9 +523,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockRejectedValueOnce({ message: 'error message' }) // getEventByObjectId
|
store.dispatch.mockRejectedValueOnce({ message: 'error message' }) // getEventByObjectId
|
||||||
|
|
||||||
|
@ -544,7 +550,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -584,9 +589,10 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(null),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(null)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
@ -607,7 +613,6 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledTimes(1)
|
expect(store.dispatch).toHaveBeenCalledTimes(1)
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -654,13 +659,16 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||||
|
|
||||||
|
store.commit = jest.fn()
|
||||||
|
|
||||||
const eventDropFunction = eventDrop(store, fcAPI)
|
const eventDropFunction = eventDrop(store, fcAPI)
|
||||||
await eventDropFunction({ event, delta, revert })
|
await eventDropFunction({ event, delta, revert })
|
||||||
|
|
||||||
|
@ -677,13 +685,15 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledTimes(1)
|
expect(store.dispatch).toHaveBeenCalledTimes(1)
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
||||||
|
|
||||||
|
expect(store.commit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(store.commit).toHaveBeenNthCalledWith(1, 'resetCalendarObjectToDav', { calendarObject: calendarObject })
|
||||||
|
|
||||||
expect(eventComponent.shiftByDuration).toHaveBeenCalledTimes(1)
|
expect(eventComponent.shiftByDuration).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.shiftByDuration).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 }, false, { calendarJsTimezone: true, tzid: 'America/New_York' }, { calendarJsDurationValue: true, days: 1 }, { calendarJsDurationValue: true, hours: 2 })
|
expect(eventComponent.shiftByDuration).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 }, false, { calendarJsTimezone: true, tzid: 'America/New_York' }, { calendarJsDurationValue: true, days: 1 }, { calendarJsDurationValue: true, hours: 2 })
|
||||||
|
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(1)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -728,15 +738,18 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
store.dispatch.mockImplementationOnce(() => {
|
store.dispatch.mockImplementationOnce(() => {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}) // updateCalendarObject
|
}) // updateCalendarObject
|
||||||
|
|
||||||
|
store.commit = jest.fn()
|
||||||
|
|
||||||
const eventDropFunction = eventDrop(store, fcAPI)
|
const eventDropFunction = eventDrop(store, fcAPI)
|
||||||
await eventDropFunction({ event, delta, revert })
|
await eventDropFunction({ event, delta, revert })
|
||||||
|
|
||||||
|
@ -754,13 +767,15 @@ describe('fullcalendar/eventDrop test suite', () => {
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'updateCalendarObject', { calendarObject })
|
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'updateCalendarObject', { calendarObject })
|
||||||
|
|
||||||
|
expect(store.commit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(store.commit).toHaveBeenNthCalledWith(1, 'resetCalendarObjectToDav', { calendarObject: calendarObject })
|
||||||
|
|
||||||
expect(eventComponent.shiftByDuration).toHaveBeenCalledTimes(1)
|
expect(eventComponent.shiftByDuration).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.shiftByDuration).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 }, false, { calendarJsTimezone: true, tzid: 'America/New_York' }, { calendarJsDurationValue: true, days: 1 }, { calendarJsDurationValue: true, hours: 2 })
|
expect(eventComponent.shiftByDuration).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 }, false, { calendarJsTimezone: true, tzid: 'America/New_York' }, { calendarJsDurationValue: true, days: 1 }, { calendarJsDurationValue: true, hours: 2 })
|
||||||
|
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(1)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,12 +22,15 @@
|
||||||
import eventResize from "../../../../src/fullcalendar/eventResize.js";
|
import eventResize from "../../../../src/fullcalendar/eventResize.js";
|
||||||
|
|
||||||
import { getDurationValueFromFullCalendarDuration} from '../../../../src/fullcalendar/duration.js'
|
import { getDurationValueFromFullCalendarDuration} from '../../../../src/fullcalendar/duration.js'
|
||||||
|
import {getObjectAtRecurrenceId} from "../../../../src/utils/calendarObject.js";
|
||||||
jest.mock('../../../../src/fullcalendar/duration.js')
|
jest.mock('../../../../src/fullcalendar/duration.js')
|
||||||
|
jest.mock("../../../../src/utils/calendarObject.js")
|
||||||
|
|
||||||
describe('fullcalendar/eventResize test suite', () => {
|
describe('fullcalendar/eventResize test suite', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getDurationValueFromFullCalendarDuration.mockClear()
|
getDurationValueFromFullCalendarDuration.mockClear()
|
||||||
|
getObjectAtRecurrenceId.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should properly resize a non-recurring event', async () => {
|
it('should properly resize a non-recurring event', async () => {
|
||||||
|
@ -57,9 +60,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
@ -84,7 +88,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(0)
|
expect(revert).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -115,9 +118,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
@ -142,7 +146,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(1)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(0)
|
expect(revert).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -171,9 +174,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
@ -194,7 +198,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -225,9 +228,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
|
@ -251,7 +255,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -282,9 +285,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(null),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(null)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
@ -306,7 +310,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(0)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -337,9 +340,10 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
createRecurrenceException: jest.fn(),
|
createRecurrenceException: jest.fn(),
|
||||||
}
|
}
|
||||||
const calendarObject = {
|
const calendarObject = {
|
||||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
_isCalendarObject: true,
|
||||||
resetToDav: jest.fn()
|
|
||||||
}
|
}
|
||||||
|
getObjectAtRecurrenceId
|
||||||
|
.mockReturnValue(eventComponent)
|
||||||
|
|
||||||
store.dispatch
|
store.dispatch
|
||||||
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||||
|
@ -347,6 +351,8 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}) // updateCalendarObject
|
}) // updateCalendarObject
|
||||||
|
|
||||||
|
store.commit = jest.fn()
|
||||||
|
|
||||||
const eventResizeFunction = eventResize(store)
|
const eventResizeFunction = eventResize(store)
|
||||||
await eventResizeFunction({ event, startDelta, endDelta, revert })
|
await eventResizeFunction({ event, startDelta, endDelta, revert })
|
||||||
|
|
||||||
|
@ -358,6 +364,9 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
||||||
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'updateCalendarObject', { calendarObject })
|
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'updateCalendarObject', { calendarObject })
|
||||||
|
|
||||||
|
expect(store.commit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(store.commit).toHaveBeenNthCalledWith(1, 'resetCalendarObjectToDav', { calendarObject: calendarObject })
|
||||||
|
|
||||||
expect(eventComponent.addDurationToStart).toHaveBeenCalledTimes(1)
|
expect(eventComponent.addDurationToStart).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.addDurationToStart).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 })
|
expect(eventComponent.addDurationToStart).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 })
|
||||||
|
|
||||||
|
@ -366,7 +375,6 @@ describe('fullcalendar/eventResize test suite', () => {
|
||||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(1)
|
|
||||||
expect(revert).toHaveBeenCalledTimes(1)
|
expect(revert).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,10 @@ import {
|
||||||
getHexForColorName,
|
getHexForColorName,
|
||||||
} from '../../../../src/utils/color.js'
|
} from '../../../../src/utils/color.js'
|
||||||
import { translate } from '@nextcloud/l10n'
|
import { translate } from '@nextcloud/l10n'
|
||||||
|
import {getAllObjectsInTimeRange} from "../../../../src/utils/calendarObject.js";
|
||||||
jest.mock('@nextcloud/l10n')
|
jest.mock('@nextcloud/l10n')
|
||||||
jest.mock('../../../../src/utils/color.js')
|
jest.mock('../../../../src/utils/color.js')
|
||||||
|
jest.mock("../../../../src/utils/calendarObject.js")
|
||||||
|
|
||||||
describe('fullcalendar/eventSourceFunction test suite', () => {
|
describe('fullcalendar/eventSourceFunction test suite', () => {
|
||||||
|
|
||||||
|
@ -36,6 +38,7 @@ describe('fullcalendar/eventSourceFunction test suite', () => {
|
||||||
translate.mockClear()
|
translate.mockClear()
|
||||||
getHexForColorName.mockClear()
|
getHexForColorName.mockClear()
|
||||||
generateTextColorForHex.mockClear()
|
generateTextColorForHex.mockClear()
|
||||||
|
getAllObjectsInTimeRange.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should provide fc-events', () => {
|
it('should provide fc-events', () => {
|
||||||
|
@ -149,28 +152,26 @@ describe('fullcalendar/eventSourceFunction test suite', () => {
|
||||||
color: 'red',
|
color: 'red',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
getAllObjectsInTimeRange
|
||||||
|
.mockReturnValueOnce(eventComponentSet1)
|
||||||
|
.mockReturnValueOnce(eventComponentSet2)
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error('Error while getting all objects in time-range')
|
||||||
|
})
|
||||||
|
.mockReturnValueOnce(eventComponentSet4)
|
||||||
|
|
||||||
const calendarObjects = [{
|
const calendarObjects = [{
|
||||||
calendarObject: true,
|
calendarObject: true,
|
||||||
id: '1',
|
id: '1',
|
||||||
getAllObjectsInTimeRange: jest.fn()
|
|
||||||
.mockReturnValueOnce(eventComponentSet1),
|
|
||||||
}, {
|
}, {
|
||||||
calendarObject: true,
|
calendarObject: true,
|
||||||
id: '2',
|
id: '2',
|
||||||
getAllObjectsInTimeRange: jest.fn()
|
|
||||||
.mockReturnValueOnce(eventComponentSet2),
|
|
||||||
}, {
|
}, {
|
||||||
calendarObject: true,
|
calendarObject: true,
|
||||||
id: '3',
|
id: '3',
|
||||||
getAllObjectsInTimeRange: jest.fn()
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
throw new Error('Error while getting all objects in time-range')
|
|
||||||
}),
|
|
||||||
}, {
|
}, {
|
||||||
calendarObject: true,
|
calendarObject: true,
|
||||||
id: '4',
|
id: '4',
|
||||||
getAllObjectsInTimeRange: jest.fn()
|
|
||||||
.mockReturnValueOnce(eventComponentSet4),
|
|
||||||
}]
|
}]
|
||||||
const start = new Date(Date.UTC(2019, 0, 1, 0, 0, 0, 0))
|
const start = new Date(Date.UTC(2019, 0, 1, 0, 0, 0, 0))
|
||||||
const end = new Date(Date.UTC(2020, 0, 31, 59, 59, 59, 999))
|
const end = new Date(Date.UTC(2020, 0, 31, 59, 59, 59, 999))
|
||||||
|
@ -304,6 +305,12 @@ describe('fullcalendar/eventSourceFunction test suite', () => {
|
||||||
expect(translate).toHaveBeenNthCalledWith(4, 'calendar', 'Untitled event')
|
expect(translate).toHaveBeenNthCalledWith(4, 'calendar', 'Untitled event')
|
||||||
expect(translate).toHaveBeenNthCalledWith(5, 'calendar', 'Untitled event')
|
expect(translate).toHaveBeenNthCalledWith(5, 'calendar', 'Untitled event')
|
||||||
|
|
||||||
|
expect(getAllObjectsInTimeRange).toHaveBeenCalledTimes(4)
|
||||||
|
expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(1, calendarObjects[0], start, end)
|
||||||
|
expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(2, calendarObjects[1], start, end)
|
||||||
|
expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(3, calendarObjects[2], start, end)
|
||||||
|
expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(4, calendarObjects[3], start, end)
|
||||||
|
|
||||||
expect(getHexForColorName).toHaveBeenCalledTimes(1)
|
expect(getHexForColorName).toHaveBeenCalledTimes(1)
|
||||||
expect(getHexForColorName).toHaveBeenNthCalledWith(1, 'red')
|
expect(getHexForColorName).toHaveBeenNthCalledWith(1, 'red')
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue