UNPKG

fugitsit

Version:

Small library that wraps ICAL.js and provide more convenient means for editing

458 lines (410 loc) 12.1 kB
/** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @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 AbstractRecurringComponent from './abstractRecurringComponent.js' import { advertiseMultipleOccurrenceProperty, advertiseMultiValueStringPropertySeparatedByLang, advertiseSingleOccurrenceProperty, } from '../abstractComponent.js' import { getAgeOfBirthday, getIconForBirthday, getTypeOfBirthdayEvent, } from '../../helpers/birthdayHelper.js' import DurationValue from '../../values/durationValue.js' import GeoProperty from '../../properties/geoProperty.js' import ConferenceProperty from '../../properties/conferenceProperty.js' /** * @class EventComponent * @classdesc * * @url https://tools.ietf.org/html/rfc5545#section-3.6.1 */ export default class EventComponent extends AbstractRecurringComponent { /** * Returns whether this event is an all-day event * * @returns {boolean} */ isAllDay() { return this.startDate.isDate && this.endDate.isDate } /** * Checks whether it's possible to switch from date-time to date or vise-versa * * @returns {boolean} */ canModifyAllDay() { return !this.recurrenceManager.masterItem.isRecurring() } /** * Gets the calculated end-date of the event * * Quote from RFC 5545 3.6.1: * The "DTSTART" property for a "VEVENT" specifies the inclusive * start of the event. For recurring events, it also specifies the * very first instance in the recurrence set. The "DTEND" property * for a "VEVENT" calendar component specifies the non-inclusive end * of the event. For cases where a "VEVENT" calendar component * specifies a "DTSTART" property with a DATE value type but no * "DTEND" nor "DURATION" property, the event's duration is taken to * be one day. For cases where a "VEVENT" calendar component * specifies a "DTSTART" property with a DATE-TIME value type but no * "DTEND" property, the event ends on the same calendar date and * time of day specified by the "DTSTART" property. * * @returns {DateTimeValue} */ get endDate() { if (this.hasProperty('dtend')) { return this.getFirstPropertyFirstValue('dtend') } const dtend = this.startDate.clone() if (this.hasProperty('duration')) { dtend.addDuration(this.getFirstPropertyFirstValue('duration')) } else if (this.startDate.isDate) { dtend.addDuration(DurationValue.fromSeconds(60 * 60 * 24)) } // There is nothing to do when this event is not allday return dtend } /** * Sets the end time of the event * * @param {DateTimeValue} end The end of the event */ set endDate(end) { this.deleteAllProperties('duration') this.updatePropertyWithValue('dtend', end) } /** * Gets the calculated duration of the event * * @returns {DurationValue} */ get duration() { if (this.hasProperty('duration')) { return this.getFirstPropertyFirstValue('duration') } return this.startDate.subtractDateWithTimezone(this.endDate) } /** * Sets the calculated duration of the event * * @param {DurationValue} duration The duration of the event */ set duration(duration) { this.deleteAllProperties('dtend') this.updatePropertyWithValue('duration', duration) } /** * Sets the geographical position based on latitude and longitude * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.6 * * @param {Number} lat - latitude * @param {Number} long - longitude */ setGeographicalPositionFromLatitudeAndLongitude(lat, long) { this.deleteAllProperties('GEO') this.addProperty(GeoProperty.fromPosition(lat, long)) } /** * Adds a new conference property based on URI, label and features * * @url https://tools.ietf.org/html/rfc7986#section-5.11 * * @param {String} uri The URI of the conference system * @param {String=} label The label for the conference system * @param {String[]=} features The features of the conference system */ addConference(uri, label = null, features = null) { this._modify() this.addProperty(ConferenceProperty.fromURILabelAndFeatures(uri, label, features)) } /** * Adds a duration to the start of the event * * @param {DurationValue} duration The duration to add */ addDurationToStart(duration) { this.startDate.addDuration(duration) } /** * Adds a duration to the end of the event * * @param {DurationValue} duration The duration to add */ addDurationToEnd(duration) { const endDate = this.endDate endDate.addDuration(duration) this.endDate = endDate } /** * Shifts the entire event by the given duration * * @param {DurationValue} delta The duration to shift event by * @param {Boolean} allDay Whether the updated event should be all-day or not * @param {Timezone} defaultTimezone The default timezone if moving from all-day to timed event * @param {DurationValue} defaultAllDayDuration The default all-day duration if moving from timed to all-day * @param {DurationValue} defaultTimedDuration The default timed duration if moving from all-day to timed */ shiftByDuration(delta, allDay, defaultTimezone, defaultAllDayDuration, defaultTimedDuration) { const currentAllDay = this.isAllDay() if (currentAllDay !== allDay && !this.canModifyAllDay()) { throw new TypeError('Can\'t modify all-day of this event') } this.startDate.isDate = allDay this.startDate.addDuration(delta) // If this event was moved from the all-day area into the time-grid, // then we have to add a timezone and the default duration if (currentAllDay && !allDay) { this.startDate.replaceTimezone(defaultTimezone) this.endDate = this.startDate.clone() this.endDate.addDuration(defaultTimedDuration) } // If this event was moved from the time-grid into the all-day area, // then we have to change the default duration if (!currentAllDay && allDay) { this.endDate = this.startDate.clone() this.endDate.addDuration(defaultAllDayDuration) } // If this event was only moved inside the time-grid or only inside // the all-day area, then we only have to adjust the end-date if (currentAllDay === allDay) { const endDate = this.endDate endDate.addDuration(delta) this.endDate = endDate } } /** * Checks if this is a birthday event * * @returns {boolean} */ isBirthdayEvent() { return getTypeOfBirthdayEvent(this) === 'BDAY' } /** * Gets the icon to the birthday event * * @returns {string} */ getIconForBirthdayEvent() { return getIconForBirthday(this) } /** * Calculates the age of the birthday * * @returns {number} */ getAgeForBirthdayEvent() { return getAgeOfBirthday(this, this.startDate.year) } /** * Serializes the entire series to ICS * * @returns {string} */ toICSEntireSeries() { return this.root.toICS() } /** * Serializes exactly this recurrence to ICS * It removes all recurrence information * * @returns {string} */ toICSThisOccurrence() { const clone = this.clone() clone.deleteAllProperties('RRULE') clone.deleteAllProperties('EXRULE') clone.deleteAllProperties('RDATE') clone.deleteAllProperties('EXDATE') clone.deleteAllProperties('RECURRENCE-ID') clone.root = clone.root.constructor.fromEmpty() clone.parent = clone.root clone.root.addComponent(clone) return clone.root.toICS() } /** * Checks if this event is in a given time-frame * * @param {DateTimeValue} start Start of time-range to check * @param {DateTimeValue} end End of time-range to check * @returns {boolean} */ isInTimeFrame(start, end) { return start.compare(this.endDate) <= 0 && end.compare(this.startDate) >= 0 } } /** * Time-transparency of this event. * If set to TRANSPARENT, this event will be ignored for FREE/BUSY calculations. * * @url https://tools.ietf.org/html/rfc5545#section-3.8.2.7 * * @name EventComponent#timeTransparency * @type {String} */ advertiseSingleOccurrenceProperty(EventComponent.prototype, { name: 'timeTransparency', iCalendarName: 'TRANSP', allowedValues: ['OPAQUE', 'TRANSPARENT'], defaultValue: 'OPAQUE', }) /** * Description of this event. * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 * * @name EventComponent#description * @type {String} */ advertiseSingleOccurrenceProperty(EventComponent.prototype, 'description') /** * Geographical position of this event * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.6 * * @name EventComponent#geographicalPosition * @type {String} */ advertiseSingleOccurrenceProperty(EventComponent.prototype, { name: 'geographicalPosition', iCalendarName: 'GEO', }) /** * Location that this event takes place in * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.7 * * @name EventComponent#location * @type {String} */ advertiseSingleOccurrenceProperty(EventComponent.prototype, 'location') /** * Priority of this event * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.9 * * @name EventComponent#priority * @type Number */ advertiseSingleOccurrenceProperty(EventComponent.prototype, { name: 'priority', allowedValues: Array(9).keys(), defaultValue: 0, unknownValue: 0, }) /** * Returns an iterator over all resources * If the parameter lang is given, it will only * return an iterator for Resources matching that language * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.10 * * @name EventComponent#getResourceIterator * @function * @param {String=} lang * @returns {IterableIterator<String>} */ /** * Returns a list of all resources * If the parameter lang is given, it will only * return an iterator for resources matching that language * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.10 * * @name EventComponent#getResourceList * @function * @param {String=} lang * @returns {String[]} */ /** * Adds a resource * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.10 * * @name EventComponent#addResource * @function * @param {String} resource * @param {String=} lang */ /** * Removes a resource * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.10 * * @name EventComponent#removeResource * @function * @param {String} resource * @param {String=} lang */ /** * Removes all resources from this event * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.10 * * @name EventComponent#clearAllResources * @function * @param {String=} lang */ advertiseMultiValueStringPropertySeparatedByLang(EventComponent.prototype, { name: 'resource', iCalendarName: 'RESOURCES', }) /** * Gets an iterator over all conference properties * * @url https://tools.ietf.org/html/rfc7986#section-5.11 * * @name EventComponent#getConferenceIterator * @function * @returns {IterableIterator<ConferenceProperty>} */ /** * Gets a list of all conference properties * * @url https://tools.ietf.org/html/rfc7986#section-5.11 * * @name EventComponent#getConferenceList * @function * @returns {ConferenceProperty[]} */ /** * Removes a conference from this event * * @url https://tools.ietf.org/html/rfc7986#section-5.11 * * @name EventComponent#removeConference * @function * @param {ConferenceProperty} conference */ /** * Removes all conferences from this event * * @url https://tools.ietf.org/html/rfc7986#section-5.11 * * @name EventComponent#clearAllConferences * @function */ advertiseMultipleOccurrenceProperty(EventComponent.prototype, 'conference')