UNPKG

fugitsit

Version:

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

1,462 lines (1,289 loc) 38.8 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 AbstractComponent, { advertiseComponent, advertiseMultipleOccurrenceProperty, advertiseMultiValueStringPropertySeparatedByLang, advertiseSingleOccurrenceProperty, } from '../abstractComponent.js' import DateTimeValue from '../../values/dateTimeValue.js' import DurationValue from '../../values/durationValue.js' import PeriodValue from '../../values/periodValue.js' import { dateFactory } from '../../factories/dateFactory.js' import { uc } from '../../helpers/stringHelper.js' import RecurrenceManager from '../../recurrence/recurrenceManager.js' import { v4 as uuid } from 'uuid' import RelationProperty from '../../properties/relationProperty.js' import AttendeeProperty from '../../properties/attendeeProperty.js' import Timezone from '../../timezones/timezone.js' import RequestStatusProperty from '../../properties/requestStatusProperty.js' import AttachmentProperty from '../../properties/attachmentProperty.js' import ImageProperty from '../../properties/imageProperty.js' import TextProperty from '../../properties/textProperty.js' import AlarmComponent from '../nested/alarmComponent.js' import TriggerProperty from '../../properties/triggerProperty.js' import { getConfig } from '../../config.js' import { getConstructorForComponentName } from '../nested' /** * @class AbstractRecurringComponent * @classdesc AbstractRecurringComponent is the basis for * EventComponent, JournalComponent and TodoComponent. * * It contains all the logic for recurrence-expansion and * recurrence-management plus all management for all * properties and all subcomponents that the three * components mentioned before have in common */ export default class AbstractRecurringComponent extends AbstractComponent { /** * @inheritDoc */ constructor(...args) { super(...args) /** * In case this object is virtual, primary item refers to the master object * that this object was forked from. * * Otherwise primary item is null * * @type {AbstractRecurringComponent} * @private */ this._primaryItem = null /** * Indicator whether this is a direct fork of a primary item, representing * the very same recurrence id * * @type {boolean} * @private */ this._isExactForkOfPrimary = false /** * The original recurrence-id of this occurrence. * Mostly needed for range exceptions with a range * * @type {DateTimeValue|null} * @private */ this._originalRecurrenceId = null /** * Instance of the recurrence manager. * This object is shared among all instances * of a recurrence-set * * @type {RecurrenceManager} * @private */ this._recurrenceManager = null /** * Indicator whether this component was modified * In case it was, the last-modified property * needs to be updated before saving the event * * @type {boolean} * @private */ this._dirty = false /** * Indicator whether there have been significant changes * In case the changes are considered significant, * the sequence needs to be incremented * * @type {boolean} * @private */ this._significantChange = false /** * Id of this AbstractRecurringComponent * * @type {String|null} * @private */ this._cachedId = null } /** * Gets the primary-item of this recurring item * * @returns {AbstractRecurringComponent} */ get primaryItem() { return this._primaryItem } /** * Sets the primary-item of this recurring item * * @param {AbstractRecurringComponent} primaryItem The new primary-item */ set primaryItem(primaryItem) { this._modify() this._primaryItem = primaryItem } /** * Gets whether or not this is a fork of the primary item * for the same recurrence-id * * @returns {boolean} */ get isExactForkOfPrimary() { return this._isExactForkOfPrimary } /** * Sets the isExactForkOfPrimary indicator, see getter for description * * @param {boolean} isExactForkOfPrimary Whether or not this is an exact fork */ set isExactForkOfPrimary(isExactForkOfPrimary) { this._isExactForkOfPrimary = isExactForkOfPrimary } /** * Gets the original recurrence-id * * @returns {DateTimeValue} */ get originalRecurrenceId() { return this._originalRecurrenceId } /** * Sets the original recurrence-id * * @param {DateTimeValue} originalRecurrenceId The new original recurrence-id */ set originalRecurrenceId(originalRecurrenceId) { this._originalRecurrenceId = originalRecurrenceId } /** * Gets the recurrence-manager of this recurrence-set * * @returns {RecurrenceManager} */ get recurrenceManager() { return this._recurrenceManager } /** * Sets the recurrence-manager of this recurrence-set * * @param {RecurrenceManager} recurrenceManager The new recurrence-manager */ set recurrenceManager(recurrenceManager) { this._recurrenceManager = recurrenceManager } /** * Gets the master-item of this recurring item * * @returns {AbstractRecurringComponent} */ get masterItem() { return this.recurrenceManager.masterItem } /** * Returns whether this item is the master item * * @returns {boolean} */ isMasterItem() { return this.masterItem === this } /** * Gets a unique ID for this occurrence of the event * * Please note that if the same event occurs in multiple calendars, * this id will not be unique. Software using this library will have to * manually mix in the calendar id into this id * * @returns {String} */ get id() { if (this._cachedId) { return this._cachedId } if (this.startDate === null) { this._cachedId = encodeURIComponent(this.uid) return this._cachedId } this._cachedId = [ encodeURIComponent(this.uid), encodeURIComponent(this.getReferenceRecurrenceId().unixTime.toString()), ].join('###') return this._cachedId } /** * Gets the UID property * * @returns {String|null} */ get uid() { return this.getFirstPropertyFirstValue('UID') } /** * Sets the UID property and the UID property of all related exceptions * * @param {String} uid The new UID */ set uid(uid) { this._recurrenceManager.updateUID(uid) } /** * Gets the start date of the event * * @returns {DateTimeValue} */ get startDate() { return this.getFirstPropertyFirstValue('dtstart') } /** * Sets the start date of the event * * @param {DateTimeValue} start The new start-date to set */ set startDate(start) { const oldStartDate = this.startDate this.updatePropertyWithValue('dtstart', start) if (this.isMasterItem()) { this._recurrenceManager.updateStartDateOfMasterItem(start, oldStartDate) } } /** * Checks whether this item is part of a recurring set * * @returns {boolean} */ isPartOfRecurrenceSet() { return this.masterItem.isRecurring() } /** * Checks whether this component is recurring * * @returns {boolean} */ isRecurring() { return this.hasProperty('RRULE') || this.hasProperty('RDATE') } /** * Checks whether this component is a recurrence-exception * * @returns {boolean} */ isRecurrenceException() { return this.hasProperty('RECURRENCE-ID') } /** * Checks wether this component is a recurrence-exception * and whether it's modifying the future * * @returns {boolean} */ modifiesFuture() { if (!this.isRecurrenceException()) { return false } const property = this.getFirstProperty('RECURRENCE-ID') return property.getParameterFirstValue('RANGE') === 'THISANDFUTURE' } /** * Creates an occurrence at the given time * * This is an internal function for calendar-js, used by the recurrence-manager * Do not call from outside * * @param {DateTimeValue} recurrenceId The recurrence-Id of the forked item * @param {DurationValue=} startDiff to be used when The start-diff (used for RECURRENCE-ID;RANGE=THISANDFUTURE) * @returns {AbstractRecurringComponent} */ forkItem(recurrenceId, startDiff = null) { const occurrence = this.clone() occurrence.recurrenceManager = this.recurrenceManager occurrence.primaryItem = this // Exact match for master item or recurrence-exception if (occurrence.getReferenceRecurrenceId().compare(recurrenceId) === 0) { occurrence.isExactForkOfPrimary = true } if (!occurrence.hasProperty('DTSTART')) { throw new TypeError('Can\'t fork item without a DTSTART') } if (occurrence.getFirstPropertyFirstValue('DTSTART').timezoneId !== recurrenceId.timezoneId) { const originalTimezone = occurrence.getFirstPropertyFirstValue('DTSTART').getICALTimezone() recurrenceId = recurrenceId.getInICALTimezone(originalTimezone) } occurrence.originalRecurrenceId = recurrenceId.clone() const dtStartValue = occurrence.getFirstPropertyFirstValue('DTSTART') let period = null if (this._recurrenceManager.hasRecurrenceDate(false, recurrenceId)) { const recurrenceDate = this._recurrenceManager.getRecurrenceDate(false, recurrenceId) if (recurrenceDate instanceof PeriodValue) { period = recurrenceDate } } let duration if (occurrence.hasProperty('DTEND')) { const dtEndValue = occurrence.getFirstPropertyFirstValue('DTEND') duration = dtEndValue.subtractDateWithTimezone(dtStartValue) } else if (occurrence.hasProperty('DUE')) { const dueValue = occurrence.getFirstPropertyFirstValue('DUE') duration = dueValue.subtractDateWithTimezone(dtStartValue) } if (!(occurrence.isRecurrenceException() && occurrence.isExactForkOfPrimary)) { occurrence.updatePropertyWithValue('DTSTART', recurrenceId.clone()) if (startDiff) { occurrence.startDate.addDuration(startDiff) } if (occurrence.hasProperty('DTEND')) { const dtEnd = occurrence.startDate.clone() dtEnd.addDuration(duration) occurrence.updatePropertyWithValue('DTEND', dtEnd) } else if (occurrence.hasProperty('DUE')) { const due = occurrence.startDate.clone() due.addDuration(duration) occurrence.updatePropertyWithValue('DUE', due) } if (period) { occurrence.deleteAllProperties('DTEND') occurrence.deleteAllProperties('DURATION') occurrence.updatePropertyWithValue('DTEND', period.end.clone()) } } occurrence.resetDirty() return occurrence } /** * Checks whether it's possible to create a recurrence exception for this event * It is possible * * @returns {boolean} */ canCreateRecurrenceExceptions() { const primaryIsRecurring = this.primaryItem?.isRecurring() ?? false return this.isRecurring() || this.modifiesFuture() || (!this.isRecurring() && primaryIsRecurring) } /** * creates a recurrence exception based on this event * If the parameter thisAndAllFuture is set to true, * it will apply changes to this and all future occurrences * * @param {boolean} thisAndAllFuture Whether to create an exception for this and all future * @returns {AbstractRecurringComponent[]} the AbstractRecurringComponent of the future events. * In case you set `thisAndAllFuture` to true, this will be an * AbstractRecurringComponent inside a entirely new calendar component */ createRecurrenceException(thisAndAllFuture = false) { if (!this.canCreateRecurrenceExceptions()) { throw new Error('Can\'t create recurrence-exceptions for non-recurring items') } const previousPrimaryItem = this.primaryItem /** * The overall support for RANGE=THISANDFUTURE is really bad. * Instead, we have to create a new event/journal/task and * set an until date on the old one. * * Also see: * - https://github.com/nextcloud/calendar/issues/7#issuecomment-292574813 * - https://github.com/nextcloud/calendar/issues/7#issuecomment-299169143 * * Right now, this replaces all future occurrence modifications, * including recurrence-exceptions, RDATES and EXDATES. * This is also how other CUAs handle it, but i would be happy * to put that up for discussion. * * Keeping future RDates + their recurrence-exceptions would be rather easy. * Updating recurrence-exceptions, that are based off normal recurrence rules, * could be very expensive. */ if (thisAndAllFuture) { if (this.isExactForkOfPrimary) { // master item if (this.primaryItem.isMasterItem()) { this._overridePrimaryItem() return [this, this] } } this.removeThisOccurrence(true) this.recurrenceManager = new RecurrenceManager(this) this._originalRecurrenceId = null this.primaryItem = this this.updatePropertyWithValue('UID', uuid()) this._cachedId = null this.addRelation('SIBLING', previousPrimaryItem.uid) previousPrimaryItem.addRelation('SIBLING', this.uid) // delete to make sure all parameters are gone this.deleteAllProperties('RECURRENCE-ID') this.deleteAllProperties('RDATE') this.deleteAllProperties('EXDATE') this.updatePropertyWithValue('CREATED', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('DTSTAMP', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('LAST-MODIFIED', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('SEQUENCE', 0) this._significantChange = false this._dirty = false this.root = this.root.constructor.fromEmpty() this.root.addComponent(this) this.parent = this.root // this is a completely new event, we should set the RSVP of all attendees to true, // so that they receive an invitation to the new event, not only the cancellation of the old one for (const attendee of this.getAttendeeIterator()) { attendee.rsvp = true } } else { // delete to make sure all parameters are gone this.deleteAllProperties('RECURRENCE-ID') this.recurrenceId = this.getReferenceRecurrenceId().clone() this.root.addComponent(this) this.recurrenceManager.relateRecurrenceException(this) this.primaryItem = this this.deleteAllProperties('RDATE') this.deleteAllProperties('RRULE') this.deleteAllProperties('EXDATE') this.updatePropertyWithValue('CREATED', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('DTSTAMP', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('LAST-MODIFIED', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('SEQUENCE', 0) if (this.recurrenceManager.hasRecurrenceDate(false, this.getReferenceRecurrenceId())) { const recurDate = this.recurrenceManager.getRecurrenceDate(false, this.getReferenceRecurrenceId()) if (recurDate instanceof PeriodValue) { const valueDateTimeRecurDate = recurDate.start this.recurrenceManager.removeRecurrenceDate(false, recurDate) this.recurrenceManager.addRecurrenceDate(false, valueDateTimeRecurDate) } } this.originalRecurrenceId = null } return [previousPrimaryItem, this] } /** * Deletes this occurrence from the series of recurring events * If the parameter thisAndAllFuture is set to true, * it will remove this and all future occurrences * * @param {Boolean} thisAndAllFuture Whether to create an exception for this and all future * @throws EmptyRecurrenceSetError Thrown, when deleting an occurrence results in no more events * @returns {Boolean} true if this deleted the last occurrence in set, false if there are occurrences left */ removeThisOccurrence(thisAndAllFuture = false) { if (!this.isPartOfRecurrenceSet()) { // When deleting an object, that's not part of a recurring set, // the calendar-document would be empty. return true } if (thisAndAllFuture) { // To get the UNTIL date, just deduct one second. // That's also how macOS does it, so this should be fairly // well supported among all clients const recurrenceId = this.getReferenceRecurrenceId().clone() const until = recurrenceId.getInTimezone(Timezone.utc) until.addDuration(DurationValue.fromSeconds(-1)) for (const recurValue of this.recurrenceManager.getRecurrenceRuleIterator()) { recurValue.until = until.clone() } for (const recurDate of this.recurrenceManager.getRecurrenceDateIterator()) { let valueToCheck = recurDate if (recurDate instanceof PeriodValue) { valueToCheck = valueToCheck.start } if (recurrenceId.compare(valueToCheck) <= 0) { this.recurrenceManager.removeRecurrenceDate(false, recurDate) } } for (const exceptionDate of this.recurrenceManager.getRecurrenceDateIterator(true)) { if (recurrenceId.compare(exceptionDate) <= 0) { this.recurrenceManager.removeRecurrenceDate(true, exceptionDate) } } for (const exception of this.recurrenceManager.getRecurrenceExceptionList()) { if (recurrenceId.compare(exception.recurrenceId) <= 0) { this.root.deleteComponent(exception) this.recurrenceManager.removeRecurrenceException(exception) } } } else { // Make sure we don't leave orphaned recurrence-exceptions if (this.isRecurrenceException() && !this.modifiesFuture()) { this.root.deleteComponent(this) this.recurrenceManager.removeRecurrenceException(this) } // If this is based on a recurrence-date, simply delete it // otherwise add an exception-date if (this.recurrenceManager.hasRecurrenceDate(false, this.getReferenceRecurrenceId())) { const recurDate = this.recurrenceManager.getRecurrenceDate(false, this.getReferenceRecurrenceId()) this.recurrenceManager.removeRecurrenceDate(false, recurDate) } else { this.recurrenceManager.addRecurrenceDate(true, this.getReferenceRecurrenceId().clone()) } } return this.recurrenceManager.isEmptyRecurrenceSet() } /** * @inheritDoc */ clone() { const comp = super.clone() comp.resetDirty() return comp } /** * Adds a new attendee * * @param {AttendeeProperty} attendee The attendee property to add * @private * @returns {boolean} */ _addAttendee(attendee) { // Check for different Attendee objects with the same uri for (const a of this.getAttendeeIterator()) { if (a.email === attendee.email) { return false } } this.addProperty(attendee) return true } /** * Adds a new attendee based on their name and email-address * * @param {String} name The name of the attendee to add * @param {String} email The email-address of the attendee to add * @returns {boolean} */ addAttendeeFromNameAndEMail(name, email) { const attendeeProperty = AttendeeProperty.fromNameAndEMail(name, email) return this._addAttendee(attendeeProperty) } /** * Adds a new attendee based on their properties * * @param {String} name The name of the attendee to add * @param {String} email The email-address of the attendee to add * @param {String} role The role of the attendee to add * @param {String} userType The type of attendee to add * @param {Boolean} rsvp Whether or not to request a response from the attendee * @returns {boolean} */ addAttendeeFromNameEMailRoleUserTypeAndRSVP(name, email, role, userType, rsvp) { const attendeeProperty = AttendeeProperty.fromNameEMailRoleUserTypeAndRSVP(name, email, role, userType, rsvp, false) return this._addAttendee(attendeeProperty) } /** * Sets the organiser property from common-name and email address * * @param {String} name The name of the organizer * @param {String} email The email-address of the organizer */ setOrganizerFromNameAndEMail(name, email) { this.deleteAllProperties('ORGANIZER') this.addProperty(AttendeeProperty.fromNameAndEMail(name, email, true)) } /** * Adds a new attachment from raw data * * @param {String} data The data of the attachment * @param {String} formatType The mime-type of the attachment */ addAttachmentFromData(data, formatType = null) { this.addProperty(AttachmentProperty.fromData(data, formatType)) } /** * Adds a new attachment from a link * * @param {String} uri The URI of the attachment * @param {String} formatType The mime-type of the attachment */ addAttachmentFromLink(uri, formatType = null) { this.addProperty(AttachmentProperty.fromLink(uri, formatType)) } /** * Adds a new contact * * @url https://tools.ietf.org/html/rfc5545#section-3.8.4.2 * * @param {String} contact The textual contact description to add */ addContact(contact) { this.addProperty(new TextProperty('CONTACT', contact)) } /** * Adds a new comment * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.4 * * @param {String} comment The comment to add */ addComment(comment) { this.addProperty(new TextProperty('COMMENT', comment)) } /** * Adds a new image from raw data * * @param {String} data Data of the image to add * @param {String=} display What display-type the image is optimized for * @param {String=} formatType The mime-type of the image */ addImageFromData(data, display = null, formatType = null) { this.addProperty(ImageProperty.fromData(data, display, formatType)) } /** * Adds a new image from a link * * @param {String} uri The URI of the image to add * @param {String=} display What display-type the image is optimized for * @param {String=} formatType The mime-type of the image */ addImageFromLink(uri, display = null, formatType = null) { this.addProperty(ImageProperty.fromLink(uri, display, formatType)) } /** * Creates a new RELATED-TO property based on a relation-type and id * and adds it to this object * * @param {String} relType The type of relation to add * @param {String} relId The id of the related calendar-document */ addRelation(relType, relId) { this.addProperty(RelationProperty.fromRelTypeAndId(relType, relId)) } /** * Creates a new REQUEST-STATUS property based on code and message * and adds it to this object * * @param {Number} code The status-code of the request status * @param {String} message The message of the request status */ addRequestStatus(code, message) { this.addProperty(RequestStatusProperty.fromCodeAndMessage(code, message)) } /** * Adds a new absolute alarm based on action and trigger time * * @param {String} action The type of alarm Action * @param {DateTimeValue} alarmTime The trigger time of the alarm * @returns {AlarmComponent} */ addAbsoluteAlarm(action, alarmTime) { const alarmComp = new AlarmComponent('VALARM', [ ['action', action], TriggerProperty.fromAbsolute(alarmTime), ]) this.addComponent(alarmComp) return alarmComp } /** * Adds a new relative alarm based on action, trigger time and relativeTo parameter * * @param {String} action The type of alarm Action * @param {DurationValue} alarmOffset The trigger time of the alarm * @param {Boolean=} relatedToStart Whether or not the alarm is related to the event's start * @returns {AlarmComponent} */ addRelativeAlarm(action, alarmOffset, relatedToStart = true) { const alarmComp = new AlarmComponent('VALARM', [ ['action', action], TriggerProperty.fromRelativeAndRelated(alarmOffset, relatedToStart), ]) this.addComponent(alarmComp) return alarmComp } /** * Marks a certain property as edited * * @param {String} propertyName The name of the property */ markPropertyAsDirty(propertyName) { this.markDirty() // Properties that must be considered a significant change // according to RFC 5546 Section 2.1.4 const props = [ 'DTSTART', 'DTEND', 'DURATION', 'RRULE', 'RDATE', 'EXDATE', 'STATUS', ...getConfig('property-list-significant-change', []), ] if (props.includes(uc(propertyName))) { this.markChangesAsSignificant() } } /** * Marks a certain component as edited * * @param {String} componentName The name of the component */ markSubComponentAsDirty(componentName) { this.markDirty() if (getConfig('component-list-significant-change', []).includes(componentName)) { this.markChangesAsSignificant() } } /** * Returns whether or not this component is dirty * * @returns {boolean} */ isDirty() { return this._dirty || this._significantChange } /** * Marks this object as dirty */ markDirty() { this._dirty = true } /** * Marks changes as significant. Can be called by the program using this lib */ markChangesAsSignificant() { this._significantChange = true } /** * Updates the event after modifications. * * @returns {boolean} true if last-modified was updated */ undirtify() { if (!this.isDirty()) { return false } if (!this.hasProperty('SEQUENCE')) { this.sequence = 0 } this.updatePropertyWithValue('DTSTAMP', DateTimeValue.fromJSDate(dateFactory(), true)) this.updatePropertyWithValue('LAST-MODIFIED', DateTimeValue.fromJSDate(dateFactory(), true)) if (this._significantChange) { this.sequence++ } this.resetDirty() return true } /** * Resets the dirty indicators without updating DTSTAMP or LAST-MODIFIED */ resetDirty() { this._dirty = false this._significantChange = false } /** * @inheritDoc */ updatePropertyWithValue(propertyName, value) { super.updatePropertyWithValue(propertyName, value) if (uc(propertyName) === 'UID') { this._cachedId = null } this.markPropertyAsDirty(propertyName) } /** * @inheritDoc */ addProperty(property) { this.markPropertyAsDirty(property.name) property.subscribe(() => this.markPropertyAsDirty(property.name)) return super.addProperty(property) } /** * @inheritDoc */ deleteProperty(property) { this.markPropertyAsDirty(property.name) return super.deleteProperty(property) } /** * @inheritDoc */ deleteAllProperties(propertyName) { this.markPropertyAsDirty(propertyName) return super.deleteAllProperties(propertyName) } /** * @inheritDoc */ addComponent(component) { this.markSubComponentAsDirty(component.name) component.subscribe(() => this.markSubComponentAsDirty(component.name)) return super.addComponent(component) } /** * @inheritDoc */ deleteComponent(component) { this.markSubComponentAsDirty(component.name) return super.deleteComponent(component) } /** * @inheritDoc */ deleteAllComponents(componentName) { this.markSubComponentAsDirty(componentName) return super.deleteAllComponents(componentName) } /** * Gets a recurrence-id that has to be used to refer to this event. * This is used for recurrence-management * * @returns {DateTimeValue} */ getReferenceRecurrenceId() { if (this.originalRecurrenceId) { return this.originalRecurrenceId } else if (this.recurrenceId) { return this.recurrenceId } else { return this.startDate } } /** * Overrides the master item with this one * * @private */ _overridePrimaryItem() { const oldStartDate = this.primaryItem.startDate for (const property of this.primaryItem.getPropertyIterator()) { this.primaryItem.deleteProperty(property) } for (const property of this.getPropertyIterator()) { this.primaryItem.addProperty(property) } this.recurrenceManager.resetCache() if (this.startDate.compare(oldStartDate) !== 0) { this.recurrenceManager.updateStartDateOfMasterItem(this.startDate, oldStartDate) } } /** * @inheritDoc */ static _getConstructorForComponentName(componentName) { return getConstructorForComponentName(componentName) } /** * @inheritDoc */ static fromICALJs(...args) { const comp = super.fromICALJs(...args) comp.resetDirty() return comp } } /** * Date-Time stamp of this object. * It has different meaning, based on whether or not a method is defined * * @url https://tools.ietf.org/html/rfc5545#section-3.8.7.2 * * @name EventComponent#stampTime * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'stampTime', iCalendarName: 'DTSTAMP', }) /** * Recurrence-ID of this object, used for recurrence-exceptions * * @url https://tools.ietf.org/html/rfc5545#section-3.8.4.4 * * @name EventComponent#recurrenceId * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'recurrenceId', iCalendarName: 'RECURRENCE-ID', }) /** * Special color for this event / journal / task * * @url https://tools.ietf.org/html/rfc7986#section-5.9 * * @name EventComponent#color * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, 'color') /** * Creation Time of this event / journal / task * * @url https://tools.ietf.org/html/rfc5545#section-3.8.7.1 * * @name EventComponent#creationTime * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'creationTime', iCalendarName: 'CREATED', }) /** * The time this event / journal / task * * @url https://tools.ietf.org/html/rfc5545#section-3.8.7.3 * * @name EventComponent#modificationTime * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'modificationTime', iCalendarName: 'LAST-MODIFIED', }) /** * Organizer of this event / journal / task * * @url https://tools.ietf.org/html/rfc5545#section-3.8.4.3 * * @name EventComponent#organizer * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, 'organizer') /** * Revision of this this event / journal / task * * @url https://tools.ietf.org/html/rfc5545#section-3.8.7.4 * * @name EventComponent#sequence * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, 'sequence') /** * Status of this event / journal / task * This indicates whether an event is tentative / confirmed / cancelled * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.11 * * @name EventComponent#status * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, 'status') /** * URL of a more dynamic rendition of this event / journal / task * DO NOT use this to simply point to a website merely related. * * @url https://tools.ietf.org/html/rfc5545#section-3.8.4.6 * * @name EventComponent#url * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, 'url') /** * Title of this event / journal / task * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.12 * * @name EventComponent#title * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'title', iCalendarName: 'SUMMARY', }) /** * Access class of this event / journal / task * This determines what other users can see when sharing * * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.3 * * @name EventComponent#accessClass * @type {String} */ advertiseSingleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'accessClass', iCalendarName: 'class', allowedValues: ['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'], defaultValue: 'PUBLIC', unknownValue: 'PRIVATE', }) /** * Returns an iterator over all categories * If the parameter lang is given, it will only * return an iterator for Categories matching that language * * @name AbstractRecurringComponent#getCategoryIterator * @function * @param {String=} lang * @returns {IterableIterator<String>} */ /** * Returns a list of all categories * If the parameter lang is given, it will only * return an iterator for Categories matching that language * * @name AbstractRecurringComponent#getCategoryList * @function * @param {String=} lang * @returns {String[]} */ /** * Adds a new category * * @name AbstractRecurringComponent#addCategory * @function * @param {String} category * @param {String=} lang */ /** * Removes a category * * @name AbstractRecurringComponent#removeCategory * @function * @param {String} category * @param {String=} lang */ /** * Clear all categories of a given language * * @name AbstractRecurringComponent#clearAllCategories * @function */ advertiseMultiValueStringPropertySeparatedByLang(AbstractRecurringComponent.prototype, { name: 'category', pluralName: 'categories', iCalendarName: 'CATEGORIES', }) /** * Returns an iterator over all attendees * * @name AbstractRecurringComponent#getAttendeeIterator * @function * @returns {IterableIterator<AttendeeProperty>} */ /** * Gets a list of all attendees * * @name AbstractRecurringComponent#getAttendeeList * @function * @returns {AttachmentProperty[]} */ /** * Removes an attendee from this event / journal / task * * @name AbstractRecurringComponent#removeAttendee * @function * @param {AttendeeProperty} attendee */ /** * Removes all attendees from this event / journal / task * * @name AbstractRecurringComponent#clearAllAttendees * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'attendee', }) /** * Returns an iterator over all attachments * * @name AbstractRecurringComponent#getAttachmentIterator * @function * @returns {IterableIterator<AttachmentProperty>} */ /** * Gets a list of all attachments * * @name AbstractRecurringComponent#getAttachmentList * @function * @returns {AttachmentProperty[]} */ /** * Removes one attachment from this event / journal / task * * @name AbstractRecurringComponent#removeAttachment * @function * @param {AttachmentProperty} attachment */ /** * Removes all attachments from this event / journal / task * * @name AbstractRecurringComponent#clearAllAttachments * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'attachment', iCalendarName: 'ATTACH', }) /** * Returns an iterator over all relation properties * * @name AbstractRecurringComponent#getRelationIterator * @function * @returns {IterableIterator<RelationProperty>} */ /** * Returns a list of all relation properties * * @name AbstractRecurringComponent#getRelationList * @function * @returns {RelationProperty[]} */ /** * Removes a relation from this event / journal / task * * @name AbstractRecurringComponent#removeRelation * @function * @param {RelationProperty} relation */ /** * Removes all relations from this event / journal / task * * @name AbstractRecurringComponent#clearAllRelations * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'relation', iCalendarName: 'RELATED-TO', }) /** * Returns an iterator over all comments in this event / journal / task * * @name AbstractRecurringComponent#getCommentIterator * @function * @returns {IterableIterator<TextProperty>} */ /** * Returns a list of all comments in this event / journal / task * * @name AbstractRecurringComponent#getCommentList * @function * @returns {TextProperty[]} */ /** * Removes a comment from this event / journal / task * * @name AbstractRecurringComponent#removeComment * @function * @param {TextProperty} comment */ /** * Removes all comments from this event / journal / task * * @name AbstractRecurringComponent#clearAllComments * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, 'comment') /** * Returns an iterator over all contacts referenced in this event / journal / task * * @name AbstractRecurringComponent#getContactIterator * @function * @returns {IterableIterator<TextProperty>} */ /** * Returns a list of all contacts referenced in this event / journal / task * * @name AbstractRecurringComponent#getContactList * @function * @returns {TextProperty[]} */ /** * Removes one contact from this event / journal / task * * @name AbstractRecurringComponent#removeContact * @function * @param {TextProperty} contact */ /** * Removes all contacts from this event / journal / task * * @name AbstractRecurringComponent#clearAllContacts * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, 'contact') /** * Returns an iterator over all image properties * * @name AbstractRecurringComponent#getImageIterator * @function * @returns {IterableIterator<ImageProperty>} */ /** * Returns a list of all image properties * * @name AbstractRecurringComponent#getImageList * @function * @returns {ImageProperty[]} */ /** * Removes one image from this event / journal / task * * @name AbstractRecurringComponent#removeImage * @function * @param {ImageProperty} image */ /** * Removes all images from this event / journal / task * * @name AbstractRecurringComponent#clearAllImages * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, 'image') /** * Returns an iterator over all request status * * @name AbstractRecurringComponent#getRequestStatusIterator * @function * @returns {IterableIterator<RequestStatusProperty>} */ /** * Returns a list of all request status * * @name AbstractRecurringComponent#getRequestStatusList * @function * @returns {RequestStatusProperty[]} */ /** * Removes one request status from this event / journal / task * * @name AbstractRecurringComponent#removeRequestStatus * @function * @param {RequestStatusProperty} requestStatus */ /** * Removes all request status from this event / journal / task * * @name AbstractRecurringComponent#clearAllRequestStatus * @function */ advertiseMultipleOccurrenceProperty(AbstractRecurringComponent.prototype, { name: 'requestStatus', pluralName: 'requestStatus', iCalendarName: 'REQUEST-STATUS', }) /** * Returns an iterator of all alarms * * @name AbstractRecurringComponent#getAlarmIterator * @function * @returns {IterableIterator<AlarmComponent>} */ /** * Returns a list of all alarms * * @name AbstractRecurringComponent#getAlarmList * @function * @returns {AlarmComponent[]} */ /** * Removes an alarm from this event / journal / task * * @name AbstractRecurringComponent#removeAlarm * @function * @param {AlarmComponent} alarm */ /** * Removes all alarms from this event / journal / task * * @name AbstractRecurringComponent#clearAllAlarms * @function */ advertiseComponent(AbstractRecurringComponent.prototype, 'alarm')