UNPKG

@nextcloud/calendar-js

Version:

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

1,771 lines 246 kB
import ICAL from "ical.js"; import { Timezone, getTimezoneManager } from "@nextcloud/timezones"; /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class AbstractParser { /** * @class * * @param {object=} options Object of options * @param {boolean=} options.extractGlobalProperties Whether or not to preserve properties from the VCALENDAR component (defaults to false) * @param {boolean=} options.removeRSVPForAttendees Whether or not to remove RSVP from attendees (defaults to false) * @param {boolean=} options.includeTimezones Whether or not to include timezones (defaults to false) * @param {boolean=} options.preserveMethod Whether or not to preserve the iCalendar method (defaults to false) * @param {boolean=} options.processFreeBusy Whether or not to process VFreeBusy components (defaults to false) */ constructor(options = {}) { if (new.target === AbstractParser) { throw new TypeError("Cannot instantiate abstract class AbstractParser"); } this._options = Object.assign({}, options); this._name = null; this._color = null; this._sourceURL = null; this._refreshInterval = null; this._calendarTimezone = null; this._errors = []; } /** * Gets the name extracted from the calendar-data * * @return {string | null} */ getName() { return this._name; } /** * Gets the color extracted from the calendar-data * * @return {string | null} */ getColor() { return this._color; } /** * Gets whether this import can be converted into a webcal subscription * * @return {boolean} */ offersWebcalFeed() { return this._sourceURL !== null; } /** * Gets the url pointing to the webcal source * * @return {string | null} */ getSourceURL() { return this._sourceURL; } /** * Gets the recommended refresh rate to update this subscription * * @return {string | null} */ getRefreshInterval() { return this._refreshInterval; } /** * Gets the default timezone of this calendar * * @return {string} */ getCalendarTimezone() { return this._calendarTimezone; } /** * {String|Object} data * * @param {any} data The data to parse * @throws TypeError */ parse(data) { throw new TypeError("Abstract method not implemented by subclass"); } /** * Returns one CalendarComponent at a time */ *getItemIterator() { throw new TypeError("Abstract method not implemented by subclass"); } /** * Get an array of all items * * @return {CalendarComponent[]} */ getAllItems() { return Array.from(this.getItemIterator()); } /** * Returns a boolean whether or not the parsed data contains vevents * * @return {boolean} */ containsVEvents() { return false; } /** * Returns a boolean whether or not the parsed data contains vjournals * * @return {boolean} */ containsVJournals() { return false; } /** * Returns a boolean whether or not the parsed data contains vtodos * * @return {boolean} */ containsVTodos() { return false; } /** * Returns a boolean whether or not the parsed data contains vfreebusys * * @return {boolean} */ containsVFreeBusy() { return false; } /** * Returns a boolean whether * * @return {boolean} */ hasErrors() { return this._errors.length !== 0; } /** * Get a list of all errors that occurred * * @return {*[]} */ getErrorList() { return this._errors.slice(); } /** * Returns the number of calendar-objects in parser * * @return {number} */ getItemCount() { return 0; } /** * Gets an option provided * * @param {string} name The name of the option to get * @param {*} defaultValue The default value to return if option not provided * @return {any} * @protected */ _getOption(name, defaultValue) { return Object.prototype.hasOwnProperty.call(this._options, name) ? this._options[name] : defaultValue; } /** * Return list of supported mime types * * @static */ static getMimeTypes() { throw new TypeError("Abstract method not implemented by subclass"); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class ModificationNotAllowedError extends Error { } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ function lockableTrait(baseClass) { return class extends baseClass { /** * Constructor * * @param {...any} args */ constructor(...args) { super(...args); this._mutable = true; } /** * Returns whether or not this object is locked * * @return {boolean} */ isLocked() { return !this._mutable; } /** * Marks this object is immutable * locks it against further modification */ lock() { this._mutable = false; } /** * Marks this object as mutable * allowing further modification */ unlock() { this._mutable = true; } /** * Check if modifications are allowed * * @throws {ModificationNotAllowedError} if this object is locked for modification * @protected */ _modify() { if (!this._mutable) { throw new ModificationNotAllowedError(); } } /** * Check if modification of content is allowed * * @throws {ModificationNotAllowedError} if this object is locked for modification * @protected */ _modifyContent() { this._modify(); } }; } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class ExpectedICalJSError extends Error { } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ function lc(str) { return str.toLowerCase(); } function uc(str) { return str.toUpperCase(); } function ucFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function startStringWith(str, startWith) { if (!str.startsWith(startWith)) { str = startWith + str; } return str; } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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 GLOBAL_CONFIG = /* @__PURE__ */ new Map(); function setConfig(key, value) { GLOBAL_CONFIG.set(key, value); } function getConfig(key, defaultValue) { return GLOBAL_CONFIG.get(key) || defaultValue; } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ function createComponent(componentName) { return new ICAL.Component(lc(componentName)); } function createProperty(propertyName) { return new ICAL.Property(lc(propertyName)); } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ function observerTrait(baseClass) { return class extends baseClass { /** * Constructor * * @param {...any} args */ constructor(...args) { super(...args); this._subscribers = []; } /** * Adds a new subscriber * * @param {Function} handler - Handler to be called when modification happens */ subscribe(handler) { this._subscribers.push(handler); } /** * Removes a subscriber * * @param {Function} handler - Handler to be no longer called when modification happens */ unsubscribe(handler) { const index = this._subscribers.indexOf(handler); if (index === -1) { return; } this._subscribers.splice(index, 1); } /** * Notify all subscribed handlers * * @param {...any} args * @protected */ _notifySubscribers(...args) { for (const handler of this._subscribers) { handler(...args); } } }; } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class Parameter extends observerTrait(lockableTrait(class { })) { /** * Constructor * * @param {string} name The name of the parameter * @param {string|Array|null} value The value of the parameter */ constructor(name, value = null) { super(); this._name = uc(name); this._value = value; } /** * Get parameter name * * @readonly * @return {string} */ get name() { return this._name; } /** * Get parameter value * * @return {string | Array} */ get value() { return this._value; } /** * Set new parameter value * * @throws {ModificationNotAllowedError} if parameter is locked for modification * @param {string | Array} value The new value to set */ set value(value) { this._modifyContent(); this._value = value; } /** * Gets the first value of this parameter * * @return {string | null} */ getFirstValue() { if (!this.isMultiValue()) { return this.value; } else { if (this.value.length > 0) { return this.value[0]; } } return null; } /** * Gets an iterator for all values */ *getValueIterator() { if (this.isMultiValue()) { yield* this.value.slice()[Symbol.iterator](); } else { yield this.value; } } /** * Returns whether or not the value is a multivalue * * @return {boolean} */ isMultiValue() { return Array.isArray(this._value); } /** * Creates a copy of this parameter * * @return {Parameter} */ clone() { const parameter = new this.constructor(this._name); if (this.isMultiValue()) { parameter.value = this._value.slice(); } else { parameter.value = this._value; } return parameter; } /** * @inheritDoc */ _modifyContent() { super._modifyContent(); this._notifySubscribers(); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class AbstractValue extends observerTrait(lockableTrait(class { })) { /** * Constructor * * @param {ICAL.Binary|ICAL.Duration|ICAL.Period|ICAL.Recur|ICAL.Time|ICAL.UtcOffset} icalValue The ICAL.JS object to wrap */ constructor(icalValue) { if (new.target === AbstractValue) { throw new TypeError("Cannot instantiate abstract class AbstractValue"); } super(); this._innerValue = icalValue; } /** * Gets wrapped ICAL.JS object * * @return {*} */ toICALJs() { return this._innerValue; } /** * @inheritDoc */ _modifyContent() { super._modifyContent(); this._notifySubscribers(); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class BinaryValue extends AbstractValue { /** * Sets the raw b64 encoded value * * @return {string} */ get rawValue() { return this._innerValue.value; } /** * Gets the raw b64 encoded value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {string} value - The new raw value */ set rawValue(value) { this._modifyContent(); this._innerValue.value = value; } /** * Gets the decoded value * * @return {string} */ get value() { return this._innerValue.decodeValue(); } /** * Sets the decoded Value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {string} decodedValue - The new encoded value */ set value(decodedValue) { this._modifyContent(); this._innerValue.setEncodedValue(decodedValue); } /** * clones this value * * @return {BinaryValue} */ clone() { return BinaryValue.fromRawValue(this._innerValue.value); } /** * Create a new BinaryValue object from an ICAL.Binary object * * @param {ICAL.Binary} icalValue - The ICAL.Binary object * @return {BinaryValue} */ static fromICALJs(icalValue) { return new BinaryValue(icalValue); } /** * Create a new BinaryValue object from a raw b64 encoded value * * @param {string} rawValue - The raw value * @return {BinaryValue} */ static fromRawValue(rawValue) { const icalBinary = new ICAL.Binary(rawValue); return BinaryValue.fromICALJs(icalBinary); } /** * Create a new BinaryValue object from decoded value * * @param {string} decodedValue - The encoded value * @return {BinaryValue} */ static fromDecodedValue(decodedValue) { const icalBinary = new ICAL.Binary(); icalBinary.setEncodedValue(decodedValue); return BinaryValue.fromICALJs(icalBinary); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class DurationValue extends AbstractValue { /** * Gets the weeks of the stored duration-value * * @return {number} */ get weeks() { return this._innerValue.weeks; } /** * Sets the weeks of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if value is negative * @param {number} weeks Amount of weeks */ set weeks(weeks) { this._modifyContent(); if (weeks < 0) { throw new TypeError("Weeks cannot be negative, use isNegative instead"); } this._innerValue.weeks = weeks; } /** * Gets the days of the stored duration-value * * @return {number} */ get days() { return this._innerValue.days; } /** * Sets the days of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if value is negative * @param {number} days Amount of days */ set days(days) { this._modifyContent(); if (days < 0) { throw new TypeError("Days cannot be negative, use isNegative instead"); } this._innerValue.days = days; } /** * Gets the hours of the stored duration-value * * @return {number} */ get hours() { return this._innerValue.hours; } /** * Sets the weeks of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if value is negative * @param {number} hours Amount of hours */ set hours(hours) { this._modifyContent(); if (hours < 0) { throw new TypeError("Hours cannot be negative, use isNegative instead"); } this._innerValue.hours = hours; } /** * Gets the minutes of the stored duration-value * * @return {number} */ get minutes() { return this._innerValue.minutes; } /** * Sets the minutes of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if value is negative * @param {number} minutes Amount of minutes */ set minutes(minutes) { this._modifyContent(); if (minutes < 0) { throw new TypeError("Minutes cannot be negative, use isNegative instead"); } this._innerValue.minutes = minutes; } /** * Gets the seconds of the stored duration-value * * @return {number} */ get seconds() { return this._innerValue.seconds; } /** * Sets the seconds of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if value is negative * @param {number} seconds Amount of seconds */ set seconds(seconds) { this._modifyContent(); if (seconds < 0) { throw new TypeError("Seconds cannot be negative, use isNegative instead"); } this._innerValue.seconds = seconds; } /** * Gets the negative-indicator of the stored duration-value * * @return {boolean} */ get isNegative() { return this._innerValue.isNegative; } /** * Gets the negative-indicator of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {boolean} isNegative Whether or not the duration is negative */ set isNegative(isNegative) { this._modifyContent(); this._innerValue.isNegative = !!isNegative; } /** * Gets the amount of total seconds of the stored duration-value * * @return {* | number} */ get totalSeconds() { return this._innerValue.toSeconds(); } /** * Sets the amount of total seconds of the stored duration-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {number} totalSeconds The total amounts of seconds to set */ set totalSeconds(totalSeconds) { this._modifyContent(); this._innerValue.fromSeconds(totalSeconds); } /** * Compares this duration to another one * * @param {DurationValue} otherDuration The duration to compare to * @return {number} -1, 0 or 1 for less/equal/greater */ compare(otherDuration) { return this._innerValue.compare(otherDuration.toICALJs()); } /** * Adds the value of another duration to this one * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DurationValue} otherDuration The duration to add */ addDuration(otherDuration) { this._modifyContent(); this.totalSeconds += otherDuration.totalSeconds; this._innerValue.normalize(); } /** * Subtract the value of another duration from this one * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DurationValue} otherDuration The duration to subtract */ subtractDuration(otherDuration) { this._modifyContent(); this.totalSeconds -= otherDuration.totalSeconds; this._innerValue.normalize(); } /** * clones this value * * @return {DurationValue} */ clone() { return DurationValue.fromICALJs(this._innerValue.clone()); } /** * Create a new DurationValue object from an ICAL.Duration object * * @param {ICAL.Duration} icalValue The ical.js duration value * @return {DurationValue} */ static fromICALJs(icalValue) { return new DurationValue(icalValue); } /** * Create a new DurationValue object from a number of seconds * * @param {number} seconds Total amount of seconds * @return {DurationValue} */ static fromSeconds(seconds) { const icalDuration = ICAL.Duration.fromSeconds(seconds); return new DurationValue(icalDuration); } /** * Create a new DurationValue object from data * * @param {object} data The destructuring object * @param {number=} data.weeks Number of weeks to set * @param {number=} data.days Number of days to set * @param {number=} data.hours Number of hours to set * @param {number=} data.minutes Number of minutes to set * @param {number=} data.seconds Number of seconds to set * @param {boolean=} data.isNegative Whether or not duration is negative * @return {DurationValue} */ static fromData(data) { const icalDuration = ICAL.Duration.fromData(data); return new DurationValue(icalDuration); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class DateTimeValue extends AbstractValue { /** * Gets the year of the stored date-time-value * * @return {number} */ get year() { return this._innerValue.year; } /** * Sets the year of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {number} year Number of years to set */ set year(year) { this._modifyContent(); this._innerValue.year = year; } /** * Gets the month of the stored date-time-value * * @return {number} */ get month() { return this._innerValue.month; } /** * Sets the month of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {number} month Number of months to set */ set month(month) { this._modifyContent(); if (month < 1 || month > 12) { throw new TypeError("Month out of range"); } this._innerValue.month = month; } /** * Gets the day of the stored date-time-value * * @return {number} */ get day() { return this._innerValue.day; } /** * Sets the day of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if out of range * @param {number} day Number of days to set */ set day(day) { this._modifyContent(); if (day < 1 || day > 31) { throw new TypeError("Day out of range"); } this._innerValue.day = day; } /** * Gets the hour of the stored date-time-value * * @return {number} */ get hour() { return this._innerValue.hour; } /** * Sets the hour of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if out of range * @param {number} hour Number of hours to set */ set hour(hour) { this._modifyContent(); if (hour < 0 || hour > 23) { throw new TypeError("Hour out of range"); } this._innerValue.hour = hour; } /** * Gets the minute of the stored date-time-value * * @return {number} */ get minute() { return this._innerValue.minute; } /** * Sets the minute of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if out of range * @param {number} minute Number of minutes to set */ set minute(minute) { this._modifyContent(); if (minute < 0 || minute > 59) { throw new TypeError("Minute out of range"); } this._innerValue.minute = minute; } /** * Gets the second of the stored date-time-value * * @return {number} */ get second() { return this._innerValue.second; } /** * Sets the second of the stored date-time-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if out of range * @param {number} second Number of seconds to set */ set second(second) { this._modifyContent(); if (second < 0 || second > 59) { throw new TypeError("Second out of range"); } this._innerValue.second = second; } /** * Gets the timezone of this date-time-value * * @return {string | null} */ get timezoneId() { if (this._innerValue.zone.tzid && this._innerValue.zone.tzid !== "floating" && this._innerValue.zone.tzid === "UTC") { return this._innerValue.zone.tzid; } if (this._innerValue.timezone) { return this._innerValue.timezone; } return this._innerValue.zone.tzid || null; } /** * Gets whether this date-time-value is a date or date-time * * @return {boolean} */ get isDate() { return this._innerValue.isDate; } /** * Sets whether this date-time-value is a date or date-time * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {boolean} isDate Whether this is a date or date-time value */ set isDate(isDate) { this._modifyContent(); this._innerValue.isDate = !!isDate; if (isDate) { this._innerValue.hour = 0; this._innerValue.minute = 0; this._innerValue.second = 0; } } /** * Gets the unix-time * * @return {number} */ get unixTime() { return this._innerValue.toUnixTime(); } /** * returns vanilla javascript date object * * @return {Date} */ get jsDate() { return this._innerValue.toJSDate(); } /** * Adds a duration to this date-time-value * * @param {DurationValue} duration The duration to ad */ addDuration(duration) { this._innerValue.addDuration(duration.toICALJs()); } /** * Subtract another date excluding timezones * * @param {DateTimeValue} other The date-time value to subtract * @return {DurationValue} */ subtractDateWithoutTimezone(other) { const icalDuration = this._innerValue.subtractDate(other.toICALJs()); return DurationValue.fromICALJs(icalDuration); } /** * Subtract another date, taking timezones into account * * @param {DateTimeValue} other The date-time value to subtract * @return {DurationValue} */ subtractDateWithTimezone(other) { const icalDuration = this._innerValue.subtractDateTz(other.toICALJs()); return DurationValue.fromICALJs(icalDuration); } /** * Compares this DateTimeValue object with another one * * @param {DateTimeValue} other The date-time to compare to * @return {number} -1, 0 or 1 for less/equal/greater */ compare(other) { return this._innerValue.compare(other.toICALJs()); } /** * Compares only the date part in a given timezone * * @param {DateTimeValue} other The date-time to compare to * @param {Timezone} timezone The timezone to compare in * @return {number} -1, 0 or 1 for less/equal/greater */ compareDateOnlyInGivenTimezone(other, timezone) { return this._innerValue.compareDateOnlyTz(other.toICALJs(), timezone.toICALTimezone()); } /** * Returns a clone of this object which was converted to a different timezone * * @param {Timezone} timezone TimezoneId to convert to * @return {DateTimeValue} */ getInTimezone(timezone) { const clonedICALTime = this._innerValue.convertToZone(timezone.toICALTimezone()); return DateTimeValue.fromICALJs(clonedICALTime); } /** * Get the inner ICAL.Timezone * * @return {ICAL.Timezone} * @package */ getICALTimezone() { return this._innerValue.zone; } /** * Returns a clone of this object which was converted to a different timezone * * @param {ICAL.Timezone} timezone TimezoneId to convert to * @return {DateTimeValue} * @package */ getInICALTimezone(timezone) { const clonedICALTime = this._innerValue.convertToZone(timezone); return DateTimeValue.fromICALJs(clonedICALTime); } /** * Returns a clone of this object which was converted to UTC * * @return {DateTimeValue} */ getInUTC() { const clonedICALTime = this._innerValue.convertToZone(ICAL.Timezone.utcTimezone); return DateTimeValue.fromICALJs(clonedICALTime); } /** * This silently replaces the inner timezone without converting the actual time * * @param {ICAL.Timezone} timezone The timezone to replace with * @package */ silentlyReplaceTimezone(timezone) { this._modify(); this._innerValue = new ICAL.Time({ year: this.year, month: this.month, day: this.day, hour: this.hour, minute: this.minute, second: this.second, isDate: this.isDate, timezone }); } /** * Replaces the inner timezone without converting the actual time * * @param {Timezone} timezone The timezone to replace with */ replaceTimezone(timezone) { this._modifyContent(); this._innerValue = ICAL.Time.fromData({ year: this.year, month: this.month, day: this.day, hour: this.hour, minute: this.minute, second: this.second, isDate: this.isDate }, timezone.toICALTimezone()); } /** * Calculates the UTC offset of the date-time-value in its timezone * * @return {number} */ utcOffset() { return this._innerValue.utcOffset(); } /** * Check if this is an event with floating time * * @return {boolean} */ isFloatingTime() { return this._innerValue.zone.tzid === "floating"; } /** * clones this value * * @return {DateTimeValue} */ clone() { return DateTimeValue.fromICALJs(this._innerValue.clone()); } /** * Create a new DateTimeValue object from an ICAL.Time object * * @param {ICAL.Time} icalValue The ical.js Date value to initialise from * @return {DateTimeValue} */ static fromICALJs(icalValue) { return new DateTimeValue(icalValue); } /** * Creates a new DateTimeValue object based on a vanilla javascript object * * @param {Date} jsDate The JavaScript date to initialise from * @param {boolean=} useUTC Whether or not to treat it as UTC * @return {DateTimeValue} */ static fromJSDate(jsDate, useUTC = false) { const icalValue = ICAL.Time.fromJSDate(jsDate, useUTC); return DateTimeValue.fromICALJs(icalValue); } /** * Creates a new DateTimeValue object based on simple parameters * * @param {object} data The destructuring object * @param {number=} data.year Amount of years to set * @param {number=} data.month Amount of month to set (1-based) * @param {number=} data.day Amount of days to set * @param {number=} data.hour Amount of hours to set * @param {number=} data.minute Amount of minutes to set * @param {number=} data.second Amount of seconds to set * @param {boolean=} data.isDate Whether this is a date or date-time * @param {Timezone=} timezone The timezone of the DateTimeValue * @return {DateTimeValue} */ static fromData(data, timezone) { const icalValue = ICAL.Time.fromData(data, timezone ? timezone.toICALTimezone() : void 0); return DateTimeValue.fromICALJs(icalValue); } } DateTimeValue.SUNDAY = ICAL.Time.SUNDAY; DateTimeValue.MONDAY = ICAL.Time.MONDAY; DateTimeValue.TUESDAY = ICAL.Time.TUESDAY; DateTimeValue.WEDNESDAY = ICAL.Time.WEDNESDAY; DateTimeValue.THURSDAY = ICAL.Time.THURSDAY; DateTimeValue.FRIDAY = ICAL.Time.FRIDAY; DateTimeValue.SATURDAY = ICAL.Time.SATURDAY; DateTimeValue.DEFAULT_WEEK_START = DateTimeValue.MONDAY; /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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/>. * */ class PeriodValue extends AbstractValue { /** * @inheritDoc */ constructor(...args) { super(...args); this._start = DateTimeValue.fromICALJs(this._innerValue.start); this._end = null; this._duration = null; } /** * Gets the start of the period-value * * @return {DateTimeValue} */ get start() { return this._start; } /** * Sets the start of the period-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DateTimeValue} start The start of the period */ set start(start) { this._modifyContent(); this._start = start; this._innerValue.start = start.toICALJs(); } /** * Gets the end of the period-value * * @return {DateTimeValue} */ get end() { if (!this._end) { if (this._duration) { this._duration.lock(); this._duration = null; } this._innerValue.end = this._innerValue.getEnd(); this._end = DateTimeValue.fromICALJs(this._innerValue.end); this._innerValue.duration = null; if (this.isLocked()) { this._end.lock(); } } return this._end; } /** * Sets the end of the period-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DateTimeValue} end The end of the period */ set end(end) { this._modifyContent(); this._innerValue.duration = null; this._innerValue.end = end.toICALJs(); this._end = end; } /** * Gets the duration of the period-value * The value is automatically locked. * If you want to edit the value, clone it and it as new duration * * @return {DurationValue} */ get duration() { if (!this._duration) { if (this._end) { this._end.lock(); this._end = null; } this._innerValue.duration = this._innerValue.getDuration(); this._duration = DurationValue.fromICALJs(this._innerValue.duration); this._innerValue.end = null; if (this.isLocked()) { this._duration.lock(); } } return this._duration; } /** * Sets the duration of the period-value * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DurationValue} duration The duration to set */ set duration(duration) { this._modifyContent(); this._innerValue.end = null; this._innerValue.duration = duration.toICALJs(); this._duration = duration; } /** * @inheritDoc */ lock() { super.lock(); this.start.lock(); if (this._end) { this._end.lock(); } if (this._duration) { this._duration.lock(); } } /** * @inheritDoc */ unlock() { super.unlock(); this.start.unlock(); if (this._end) { this._end.unlock(); } if (this._duration) { this._duration.unlock(); } } /** * clones this value * * @return {PeriodValue} */ clone() { return PeriodValue.fromICALJs(this._innerValue.clone()); } /** * Create a new PeriodValue object from a ICAL.Period object * * @param {ICAL.Period} icalValue The ical.js period value to initialise from * @return {PeriodValue} */ static fromICALJs(icalValue) { return new PeriodValue(icalValue); } /** * Create a new PeriodValue object from start and end * * @param {object} data The destructuring object * @param {DateTimeValue} data.start The start of the period * @param {DateTimeValue} data.end The end of the period * @return {PeriodValue} */ static fromDataWithEnd(data) { const icalPeriod = ICAL.Period.fromData({ start: data.start.toICALJs(), end: data.end.toICALJs() }); return PeriodValue.fromICALJs(icalPeriod); } /** * Create a new PeriodValue object from start and duration * * @param {object} data The destructuring object * @param {DateTimeValue} data.start The start of the period * @param {DurationValue} data.duration The duration of the period * @return {PeriodValue} */ static fromDataWithDuration(data) { const icalPeriod = ICAL.Period.fromData({ start: data.start.toICALJs(), duration: data.duration.toICALJs() }); return PeriodValue.fromICALJs(icalPeriod); } } /** * @copyright Copyright (c) 2019 Georg Ehrke * * @author Georg Ehrke <georg-nextcloud@ehrke.email> * * @license AGPL-3.0-or-later * * 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 ALLOWED_FREQ = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"]; class RecurValue extends AbstractValue { /** * Constructor * * @param {ICAL.Recur} icalValue The ical.js rrule value * @param {DateTimeValue?} until The Until date */ constructor(icalValue, until) { super(icalValue); this._until = until; } /** * Gets the stored interval of this recurrence rule * * @return {number} */ get interval() { return this._innerValue.interval; } /** * Sets the stored interval of this recurrence rule * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {number} interval New Interval to set */ set interval(interval) { this._modifyContent(); this._innerValue.interval = parseInt(interval, 10); } /** * Gets the weekstart used to calculate the recurrence expansion * * @return {number} */ get weekStart() { return this._innerValue.wkst; } /** * Sets the weekstart used to calculate the recurrence expansion * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if weekstart out of range * @param {number} weekStart New start of week to set */ set weekStart(weekStart) { this._modifyContent(); if (weekStart < DateTimeValue.SUNDAY || weekStart > DateTimeValue.SATURDAY) { throw new TypeError("Weekstart out of range"); } this._innerValue.wkst = weekStart; } /** * Gets the until value if set * The value is automatically locked. * If you want to edit the value, clone it and it as new until * * @return {null|DateTimeValue} */ get until() { if (!this._until && this._innerValue.until) { this._until = DateTimeValue.fromICALJs(this._innerValue.until); } return this._until; } /** * Sets the until value, automatically removes count * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {DateTimeValue} until New until date to set */ set until(until) { this._modifyContent(); if (this._until) { this._until.lock(); } this._until = until; this._innerValue.count = null; this._innerValue.until = until.toICALJs(); } /** * Gets the count value if set * * @return {null | number} */ get count() { return this._innerValue.count; } /** * Sets the count value, automatically removes until * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {number} count New occurrence limit to set */ set count(count) { this._modifyContent(); if (this._until) { this._until.lock(); this._until = null; } this._innerValue.until = null; this._innerValue.count = parseInt(count, 10); } /** * Gets the frequency of the recurrence rule * * @return {string} see */ get frequency() { return this._innerValue.freq; } /** * Sets the frequency of the recurrence rule * * @throws {ModificationNotAllowedError} if value is locked for modification * @throws {TypeError} if frequency is unknown * @param {string} freq New frequency to set */ set frequency(freq) { this._modifyContent(); if (!ALLOWED_FREQ.includes(freq)) { throw new TypeError("Unknown frequency"); } this._innerValue.freq = freq; } /** * Modifies this recurrence-value to unset count and until */ setToInfinite() { this._modifyContent(); if (this._until) { this._until.lock(); this._until = null; } this._innerValue.until = null; this._innerValue.count = null; } /** * Checks whether the stored rule is finite * * @return {boolean} */ isFinite() { return this._innerValue.isFinite(); } /** * Checks whether the recurrence rule is limited by count * * @return {boolean} */ isByCount() { return this._innerValue.isByCount(); } /** * Adds a part to a component to the recurrence-rule * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {string} componentName The name of the recurrence-component to add * @param {string | number} value The value to add */ addComponent(componentName, value) { this._modifyContent(); this._innerValue.addComponent(componentName, value); } /** * Sets / overwrites a component to the recurrence-rule * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {string} componentName The name of the component to set * @param {number[] | string[]} value The value to set */ setComponent(componentName, value) { this._modifyContent(); if (value.length === 0) { delete this._innerValue.parts[componentName.toUpperCase()]; } else { this._innerValue.setComponent(componentName, value); } } /** * Removes all parts of a component * * @throws {ModificationNotAllowedError} if value is locked for modification * @param {string} componentName The name of the component to remove */ removeComponent(componentName) { delete this._innerValu