ical-generator
Version:
ical-generator is a small piece of code which generates ical calendar files
1 lines • 191 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/tools.ts","../src/attendee.ts","../src/alarm.ts","../src/category.ts","../src/event.ts","../src/calendar.ts"],"sourcesContent":["/**\n * ical-generator entrypoint\n */\n\n'use strict';\n\nimport ICalCalendar, { type ICalCalendarData } from './calendar.ts';\n\n/**\n * Create a new, empty calendar and returns it.\n *\n * ```javascript\n * import ical from 'ical-generator';\n *\n * // or use require:\n * // const { default: ical } = require('ical-generator');\n *\n * const cal = ical();\n * ```\n *\n * You can pass options to setup your calendar or use setters to do this.\n *\n * ```javascript\n * import ical from 'ical-generator';\n *\n * // or use require:\n * // const { default: ical } = require('ical-generator');\n * const cal = ical({domain: 'sebbo.net'});\n *\n * // is the same as\n *\n * const cal = ical().domain('sebbo.net');\n *\n * // is the same as\n *\n * const cal = ical();\n * cal.domain('sebbo.net');\n * ```\n *\n * @param data Calendar data\n */\nfunction ical(data?: ICalCalendarData): ICalCalendar {\n return new ICalCalendar(data);\n}\n\nexport default ical;\n\nexport {\n default as ICalAlarm,\n type ICalAlarmBaseData,\n type ICalAlarmData,\n type ICalAlarmJSONData,\n ICalAlarmRelatesTo,\n type ICalAlarmRepeatData,\n type ICalAlarmTriggerAfterData,\n type ICalAlarmTriggerBeforeData,\n type ICalAlarmTriggerData,\n ICalAlarmType,\n type ICalAlarmTypeValue,\n type ICalAttachment,\n} from './alarm.ts';\n\nexport {\n default as ICalAttendee,\n type ICalAttendeeData,\n type ICalAttendeeJSONData,\n ICalAttendeeRole,\n type ICalAttendeeScheduleAgent,\n ICalAttendeeStatus,\n ICalAttendeeType,\n} from './attendee.ts';\n\nexport {\n default as ICalCalendar,\n type ICalCalendarData,\n type ICalCalendarJSONData,\n ICalCalendarMethod,\n type ICalCalendarProdIdData,\n} from './calendar.ts';\n\nexport {\n default as ICalCategory,\n type ICalCategoryData,\n type ICalCategoryJSONData,\n} from './category.ts';\n\nexport {\n default as ICalEvent,\n ICalEventBusyStatus,\n ICalEventClass,\n type ICalEventData,\n type ICalEventJSONData,\n type ICalEventJSONRepeatingData,\n ICalEventStatus,\n ICalEventTransparency,\n} from './event.ts';\n\nexport { escape, foldLines, formatDate, formatDateTZ } from './tools.ts';\n\nexport {\n type ICalDateTimeValue,\n type ICalDayJsStub,\n type ICalDescription,\n ICalEventRepeatingFreq,\n type ICalGeo,\n type ICalLocation,\n type ICalLocationWithoutTitle,\n type ICalLocationWithTitle,\n type ICalLuxonDateTimeStub,\n type ICalMomentDurationStub,\n type ICalMomentStub,\n type ICalMomentTimezoneStub,\n type ICalOrganizer,\n type ICalRepeatingOptions,\n type ICalRRuleStub,\n type ICalTimezone,\n type ICalTZDateStub,\n ICalWeekday,\n} from './types.ts';\n","export enum ICalEventRepeatingFreq {\n DAILY = 'DAILY',\n HOURLY = 'HOURLY',\n MINUTELY = 'MINUTELY',\n MONTHLY = 'MONTHLY',\n SECONDLY = 'SECONDLY',\n WEEKLY = 'WEEKLY',\n YEARLY = 'YEARLY',\n}\n\nexport enum ICalWeekday {\n FR = 'FR',\n MO = 'MO',\n SA = 'SA',\n SU = 'SU',\n TH = 'TH',\n TU = 'TU',\n WE = 'WE',\n}\n\n/**\n * ical-generator supports [native Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date),\n * [moment.js](https://momentjs.com/) (and [moment-timezone](https://momentjs.com/timezone/), [Day.js](https://day.js.org/en/) and\n * [Luxon](https://moment.github.io/luxon/)'s [DateTime](https://moment.github.io/luxon/docs/class/src/datetime.js~DateTime.html)\n * objects. You can also pass a string which is then passed to javascript's Date internally.\n */\nexport type ICalDateTimeValue =\n | Date\n | ICalDayJsStub\n | ICalLuxonDateTimeStub\n | ICalMomentStub\n | ICalMomentTimezoneStub\n | string;\n\nexport interface ICalDayJsStub {\n format(format?: string): string;\n isValid(): boolean;\n toDate(): Date;\n toJSON(): string;\n tz(zone?: string): ICalDayJsStub;\n utc(): ICalDayJsStub;\n}\n\nexport interface ICalDescription {\n html?: string;\n plain: string;\n}\n\nexport interface ICalGeo {\n lat: number;\n lon: number;\n}\n\nexport type ICalLocation = ICalLocationWithoutTitle | ICalLocationWithTitle;\n\nexport interface ICalLocationWithoutTitle {\n geo: ICalGeo;\n}\n\nexport interface ICalLocationWithTitle {\n address?: string;\n geo?: ICalGeo;\n radius?: number;\n title: string;\n}\n\nexport interface ICalLuxonDateTimeStub {\n get isValid(): boolean;\n setZone(zone?: string): ICalLuxonDateTimeStub;\n toFormat(fmt: string): string;\n toJSDate(): Date;\n toJSON(): null | string;\n zone: { type: string };\n}\n\nexport interface ICalMomentDurationStub {\n asSeconds(): number;\n}\n\nexport interface ICalMomentStub {\n clone(): ICalMomentStub;\n format(format?: string): string;\n isValid(): boolean;\n toDate(): Date;\n toJSON(): string;\n utc(): ICalMomentStub;\n}\n\nexport interface ICalMomentTimezoneStub extends ICalMomentStub {\n clone(): ICalMomentTimezoneStub;\n tz(): string | undefined;\n tz(timezone: string): ICalMomentTimezoneStub;\n utc(): ICalMomentTimezoneStub;\n}\n\nexport interface ICalOrganizer {\n email?: string;\n mailto?: string;\n name: string;\n sentBy?: string;\n}\n\nexport interface ICalRepeatingOptions {\n byDay?: ICalWeekday | ICalWeekday[];\n byMonth?: number | number[];\n byMonthDay?: number | number[];\n bySetPos?: number | number[];\n count?: number;\n exclude?: ICalDateTimeValue | ICalDateTimeValue[];\n freq: ICalEventRepeatingFreq;\n interval?: number;\n startOfWeek?: ICalWeekday;\n until?: ICalDateTimeValue;\n}\n\nexport interface ICalRRuleStub {\n between(\n after: Date,\n before: Date,\n inc?: boolean,\n iterator?: (d: Date, len: number) => boolean,\n ): Date[];\n toString(): string;\n}\n\nexport interface ICalTimezone {\n generator?: (timezone: string) => null | string;\n name: null | string;\n}\n\nexport interface ICalTZDateStub extends Date {\n timeZone?: string;\n withTimeZone(timezone?: null | string): ICalTZDateStub;\n}\n","'use strict';\n\nimport {\n type ICalDateTimeValue,\n type ICalDayJsStub,\n type ICalLuxonDateTimeStub,\n type ICalMomentDurationStub,\n type ICalMomentStub,\n type ICalMomentTimezoneStub,\n type ICalOrganizer,\n type ICalRRuleStub,\n type ICalTZDateStub,\n} from './types.ts';\n\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[],\n): void;\n\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray: string,\n value: string,\n): void;\n\nexport function addOrGetCustomAttributes(data: {\n x: [string, string][];\n}): { key: string; value: string }[];\n\nexport function addOrGetCustomAttributes(\n data: { x: [string, string][] },\n keyOrArray?:\n | [string, string][]\n | Record<string, string>\n | string\n | undefined\n | { key: string; value: string }[],\n value?: string | undefined,\n): void | { key: string; value: string }[] {\n if (Array.isArray(keyOrArray)) {\n data.x = keyOrArray.map(\n (o: [string, string] | { key: string; value: string }) => {\n if (Array.isArray(o)) {\n return o;\n }\n if (typeof o.key !== 'string' || typeof o.value !== 'string') {\n throw new Error('Either key or value is not a string!');\n }\n if (o.key.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n return [o.key, o.value] as [string, string];\n },\n );\n } else if (typeof keyOrArray === 'object') {\n data.x = Object.entries(keyOrArray).map(([key, value]) => {\n if (typeof key !== 'string' || typeof value !== 'string') {\n throw new Error('Either key or value is not a string!');\n }\n if (key.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n return [key, value];\n });\n } else if (typeof keyOrArray === 'string' && typeof value === 'string') {\n if (keyOrArray.substr(0, 2) !== 'X-') {\n throw new Error('Key has to start with `X-`!');\n }\n\n data.x.push([keyOrArray, value]);\n } else {\n return data.x.map((a) => ({\n key: a[0],\n value: a[1],\n }));\n }\n}\n\n/**\n * Checks if the given input is a valid date and\n * returns the internal representation (= moment object)\n */\nexport function checkDate(\n value: ICalDateTimeValue,\n attribute: string,\n): ICalDateTimeValue {\n // Date & String\n if (\n (value instanceof Date && isNaN(value.getTime())) ||\n (typeof value === 'string' && isNaN(new Date(value).getTime()))\n ) {\n throw new Error(`\\`${attribute}\\` has to be a valid date!`);\n }\n if (value instanceof Date || typeof value === 'string') {\n return value;\n }\n\n // Luxon\n if (isLuxonDate(value) && value.isValid === true) {\n return value;\n }\n\n // Moment / Moment Timezone\n if ((isMoment(value) || isDayjs(value)) && value.isValid()) {\n return value;\n }\n\n throw new Error(`\\`${attribute}\\` has to be a valid date!`);\n}\n/**\n * Checks if the given string `value` is a\n * valid one for the type `type`\n */\nexport function checkEnum(\n type: Record<string, string>,\n value: unknown,\n): unknown {\n const allowedValues = Object.values(type);\n const valueStr = String(value).toUpperCase();\n\n if (!valueStr || !allowedValues.includes(valueStr)) {\n throw new Error(\n `Input must be one of the following: ${allowedValues.join(', ')}`,\n );\n }\n\n return valueStr;\n}\n/**\n * Check the given string or ICalOrganizer. Parses\n * the string for name and email address if possible.\n *\n * @param attribute Attribute name for error messages\n * @param value Value to parse name/email from\n */\nexport function checkNameAndMail(\n attribute: string,\n value: ICalOrganizer | string,\n): ICalOrganizer {\n let result: ICalOrganizer | null = null;\n\n if (typeof value === 'string') {\n const match = value.match(/^(.+) ?<([^>]+)>$/);\n if (match) {\n result = {\n email: match[2].trim(),\n name: match[1].trim(),\n };\n } else if (value.includes('@')) {\n result = {\n email: value.trim(),\n name: value.trim(),\n };\n }\n } else if (typeof value === 'object') {\n result = {\n email: value.email,\n mailto: value.mailto,\n name: value.name,\n sentBy: value.sentBy,\n };\n }\n\n if (!result && typeof value === 'string') {\n throw new Error(\n '`' +\n attribute +\n \"` isn't formated correctly. See https://sebbo2002.github.io/ical-generator/develop/\" +\n 'reference/interfaces/ICalOrganizer.html',\n );\n } else if (!result) {\n throw new Error(\n '`' +\n attribute +\n '` needs to be a valid formed string or an object. See https://sebbo2002.github.io/' +\n 'ical-generator/develop/reference/interfaces/ICalOrganizer.html',\n );\n }\n\n if (!result.name) {\n throw new Error('`' + attribute + '.name` is empty!');\n }\n\n return result;\n}\n/**\n * Escapes special characters in the given string\n */\nexport function escape(str: string | unknown, inQuotes: boolean): string {\n return String(str)\n .replace(inQuotes ? /[\\\\\"]/g : /[\\\\;,]/g, function (match) {\n return '\\\\' + match;\n })\n .replace(/(?:\\r\\n|\\r|\\n)/g, '\\\\n');\n}\n\n/**\n * Trim line length of given string\n */\nexport function foldLines(input: string): string {\n return input\n .split('\\r\\n')\n .map(function (line) {\n let result = '';\n let c = 0;\n for (let i = 0; i < line.length; i++) {\n let ch = line.charAt(i);\n\n // surrogate pair, see https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs\n if (ch >= '\\ud800' && ch <= '\\udbff') {\n ch += line.charAt(++i);\n }\n\n // TextEncoder is available in browsers and node.js >= 11.0.0\n const charsize = new TextEncoder().encode(ch).length;\n c += charsize;\n if (c > 74) {\n result += '\\r\\n ';\n c = charsize;\n }\n\n result += ch;\n }\n return result;\n })\n .join('\\r\\n');\n}\n\n/**\n * Converts a valid date/time object supported by this library to a string.\n */\nexport function formatDate(\n timezone: null | string,\n d: ICalDateTimeValue,\n dateonly?: boolean,\n floating?: boolean,\n): string {\n if (timezone?.startsWith('/')) {\n timezone = timezone.substr(1);\n }\n\n if (typeof d === 'string' || d instanceof Date) {\n // TZDate is an extension of the native Date object.\n // @see https://github.com/date-fns/tz for more information.\n const m = isTZDate(d) ? d.withTimeZone(timezone) : new Date(d);\n\n // (!dateonly && !floating) || !timezone => utc\n let s =\n m.getUTCFullYear() +\n String(m.getUTCMonth() + 1).padStart(2, '0') +\n m.getUTCDate().toString().padStart(2, '0');\n\n // (dateonly || floating) && timezone => tz\n if (timezone) {\n s =\n m.getFullYear() +\n String(m.getMonth() + 1).padStart(2, '0') +\n m.getDate().toString().padStart(2, '0');\n }\n\n if (dateonly) {\n return s;\n }\n\n if (timezone) {\n s +=\n 'T' +\n m.getHours().toString().padStart(2, '0') +\n m.getMinutes().toString().padStart(2, '0') +\n m.getSeconds().toString().padStart(2, '0');\n\n return s;\n }\n\n s +=\n 'T' +\n m.getUTCHours().toString().padStart(2, '0') +\n m.getUTCMinutes().toString().padStart(2, '0') +\n m.getUTCSeconds().toString().padStart(2, '0') +\n (floating ? '' : 'Z');\n\n return s;\n } else if (isMoment(d)) {\n // @see https://momentjs.com/timezone/docs/#/using-timezones/parsing-in-zone/\n const m = timezone\n ? isMomentTZ(d) && !d.tz()\n ? d.clone().tz(timezone)\n : d\n : floating || (dateonly && isMomentTZ(d) && d.tz())\n ? d\n : d.utc();\n\n return (\n m.format('YYYYMMDD') +\n (!dateonly\n ? 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n } else if (isLuxonDate(d)) {\n const m = timezone\n ? d.setZone(timezone)\n : floating || (dateonly && d.zone.type !== 'system')\n ? d\n : d.setZone('utc');\n\n return (\n m.toFormat('yyyyLLdd') +\n (!dateonly\n ? 'T' + m.toFormat('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n } else {\n // @see https://day.js.org/docs/en/plugin/utc\n\n let m = d;\n if (timezone) {\n m = typeof d.tz === 'function' ? d.tz(timezone) : d;\n } else if (floating) {\n // m = d;\n } else if (typeof d.utc === 'function') {\n m = d.utc();\n } else {\n throw new Error(\n 'Unable to convert dayjs object to UTC value: UTC plugin is not available!',\n );\n }\n\n return (\n m.format('YYYYMMDD') +\n (!dateonly\n ? 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')\n : '')\n );\n }\n}\n\n/**\n * Converts a valid date/time object supported by this library to a string.\n * For information about this format, see RFC 5545, section 3.3.5\n * https://tools.ietf.org/html/rfc5545#section-3.3.5\n */\nexport function formatDateTZ(\n timezone: null | string,\n property: string,\n date: Date | ICalDateTimeValue | string,\n eventData?: { floating?: boolean | null; timezone?: null | string },\n): string {\n let tzParam = '';\n let floating = eventData?.floating || false;\n\n if (eventData?.timezone) {\n tzParam = ';TZID=' + eventData.timezone;\n\n // This isn't a 'floating' event because it has a timezone;\n // but we use it to omit the 'Z' UTC specifier in formatDate()\n floating = true;\n }\n\n return (\n property + tzParam + ':' + formatDate(timezone, date, false, floating)\n );\n}\n\nexport function generateCustomAttributes(data: {\n x: [string, string][];\n}): string {\n const str = data.x\n .map(([key, value]) => key.toUpperCase() + ':' + escape(value, false))\n .join('\\r\\n');\n return str.length ? str + '\\r\\n' : '';\n}\n\nexport function isDayjs(value: ICalDateTimeValue): value is ICalDayJsStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n !(value instanceof Date) &&\n !isMoment(value) &&\n !isLuxonDate(value)\n );\n}\n\nexport function isLuxonDate(\n value: ICalDateTimeValue,\n): value is ICalLuxonDateTimeStub {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'toJSDate' in value &&\n typeof value.toJSDate === 'function'\n );\n}\nexport function isMoment(value: ICalDateTimeValue): value is ICalMomentStub {\n // @ts-expect-error _isAMomentObject is a private property\n return value != null && value._isAMomentObject != null;\n}\nexport function isMomentDuration(\n value: unknown,\n): value is ICalMomentDurationStub {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'asSeconds' in value &&\n typeof value.asSeconds === 'function'\n );\n}\nexport function isMomentTZ(\n value: ICalDateTimeValue,\n): value is ICalMomentTimezoneStub {\n return isMoment(value) && 'tz' in value && typeof value.tz === 'function';\n}\n\nexport function isRRule(value: unknown): value is ICalRRuleStub {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'between' in value &&\n typeof value.between === 'function' &&\n typeof value.toString === 'function'\n );\n}\n\nexport function isTZDate(value: ICalDateTimeValue): value is ICalTZDateStub {\n return (\n value instanceof Date &&\n 'internal' in value &&\n value.internal instanceof Date &&\n 'withTimeZone' in value &&\n typeof value.withTimeZone === 'function' &&\n 'tzComponents' in value &&\n typeof value.tzComponents === 'function'\n );\n}\n\nexport function toDate(value: ICalDateTimeValue): Date {\n if (typeof value === 'string' || value instanceof Date) {\n return new Date(value);\n }\n\n if (isLuxonDate(value)) {\n return value.toJSDate();\n }\n\n return value.toDate();\n}\n\nexport function toDurationString(seconds: number): string {\n let string = '';\n\n // < 0\n if (seconds < 0) {\n string = '-';\n seconds *= -1;\n }\n\n string += 'P';\n\n // DAYS\n if (seconds >= 86400) {\n string += Math.floor(seconds / 86400) + 'D';\n seconds %= 86400;\n }\n if (!seconds && string.length > 1) {\n return string;\n }\n\n string += 'T';\n\n // HOURS\n if (seconds >= 3600) {\n string += Math.floor(seconds / 3600) + 'H';\n seconds %= 3600;\n }\n\n // MINUTES\n if (seconds >= 60) {\n string += Math.floor(seconds / 60) + 'M';\n seconds %= 60;\n }\n\n // SECONDS\n if (seconds > 0) {\n string += seconds + 'S';\n } else if (string.length <= 2) {\n string += '0S';\n }\n\n return string;\n}\n\nexport function toJSON(\n value: ICalDateTimeValue | null | undefined,\n): null | string | undefined {\n if (!value) {\n return null;\n }\n if (typeof value === 'string') {\n return value;\n }\n\n return value.toJSON();\n}\n","'use strict';\n\nimport type ICalAlarm from './alarm.ts';\nimport type ICalEvent from './event.ts';\n\nimport {\n addOrGetCustomAttributes,\n checkEnum,\n checkNameAndMail,\n escape,\n} from './tools.ts';\n\nexport enum ICalAttendeeRole {\n CHAIR = 'CHAIR',\n NON = 'NON-PARTICIPANT',\n OPT = 'OPT-PARTICIPANT',\n REQ = 'REQ-PARTICIPANT',\n}\n\n// ref: https://datatracker.ietf.org/doc/html/rfc6638#section-7.1\nexport enum ICalAttendeeScheduleAgent {\n CLIENT = 'CLIENT',\n NONE = 'NONE',\n SERVER = 'SERVER',\n}\n\nexport enum ICalAttendeeStatus {\n ACCEPTED = 'ACCEPTED',\n DECLINED = 'DECLINED',\n DELEGATED = 'DELEGATED',\n NEEDSACTION = 'NEEDS-ACTION',\n TENTATIVE = 'TENTATIVE',\n}\n\n// ref: https://tools.ietf.org/html/rfc2445#section-4.2.3\nexport enum ICalAttendeeType {\n GROUP = 'GROUP',\n INDIVIDUAL = 'INDIVIDUAL',\n RESOURCE = 'RESOURCE',\n ROOM = 'ROOM',\n UNKNOWN = 'UNKNOWN',\n}\nexport interface ICalAttendeeData {\n delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string;\n delegatedTo?: ICalAttendee | ICalAttendeeData | null | string;\n delegatesFrom?: ICalAttendee | ICalAttendeeData | null | string;\n delegatesTo?: ICalAttendee | ICalAttendeeData | null | string;\n email: string;\n mailto?: null | string;\n name?: null | string;\n role?: ICalAttendeeRole;\n rsvp?: boolean | null;\n scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null;\n sentBy?: null | string;\n status?: ICalAttendeeStatus | null;\n type?: ICalAttendeeType | null;\n x?:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[];\n}\n\nexport interface ICalAttendeeJSONData {\n delegatedFrom: null | string;\n delegatedTo: null | string;\n email: string;\n mailto: null | string;\n name: null | string;\n role: ICalAttendeeRole;\n rsvp: boolean | null;\n sentBy: null | string;\n status: ICalAttendeeStatus | null;\n type: ICalAttendeeType | null;\n x: { key: string; value: string }[];\n}\n\nexport type ICalXName = `X-${string}`;\n\ninterface ICalInternalAttendeeData {\n delegatedFrom: ICalAttendee | null;\n delegatedTo: ICalAttendee | null;\n email: string;\n mailto: null | string;\n name: null | string;\n role: ICalAttendeeRole;\n rsvp: boolean | null;\n scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null;\n sentBy: null | string;\n status: ICalAttendeeStatus | null;\n type: ICalAttendeeType | null;\n x: [string, string][];\n}\n\n/**\n * Usually you get an {@link ICalAttendee} object like this:\n *\n * ```javascript\n * import ical from 'ical-generator';\n * const calendar = ical();\n * const event = calendar.createEvent();\n * const attendee = event.createAttendee({ email: 'mail@example.com' });\n * ```\n *\n * You can also use the {@link ICalAttendee} object directly:\n *\n * ```javascript\n * import ical, {ICalAttendee} from 'ical-generator';\n * const attendee = new ICalAttendee({ email: 'mail@example.com' });\n * event.attendees([attendee]);\n * ```\n */\nexport default class ICalAttendee {\n private readonly data: ICalInternalAttendeeData;\n private readonly parent: ICalAlarm | ICalEvent;\n\n /**\n * Constructor of {@link ICalAttendee}. The event reference is\n * required to query the calendar's timezone when required.\n *\n * @param data Attendee Data\n * @param parent Reference to ICalEvent object\n */\n constructor(data: ICalAttendeeData, parent: ICalAlarm | ICalEvent) {\n this.data = {\n delegatedFrom: null,\n delegatedTo: null,\n email: '',\n mailto: null,\n name: null,\n role: ICalAttendeeRole.REQ,\n rsvp: null,\n scheduleAgent: null,\n sentBy: null,\n status: null,\n type: null,\n x: [],\n };\n this.parent = parent;\n if (!this.parent) {\n throw new Error('`event` option required!');\n }\n if (!data.email) {\n throw new Error('No value for `email` in ICalAttendee given!');\n }\n\n if (data.name !== undefined) this.name(data.name);\n if (data.email !== undefined) this.email(data.email);\n if (data.mailto !== undefined) this.mailto(data.mailto);\n if (data.sentBy !== undefined) this.sentBy(data.sentBy);\n if (data.status !== undefined) this.status(data.status);\n if (data.role !== undefined) this.role(data.role);\n if (data.rsvp !== undefined) this.rsvp(data.rsvp);\n if (data.type !== undefined) this.type(data.type);\n if (data.delegatedTo !== undefined) this.delegatedTo(data.delegatedTo);\n if (data.delegatedFrom !== undefined)\n this.delegatedFrom(data.delegatedFrom);\n if (data.delegatesTo) this.delegatesTo(data.delegatesTo);\n if (data.delegatesFrom) this.delegatesFrom(data.delegatesFrom);\n if (data.scheduleAgent !== undefined)\n this.scheduleAgent(data.scheduleAgent);\n if (data.x !== undefined) this.x(data.x);\n }\n\n /**\n * Get the attendee's delegated-from field\n * @since 0.2.0\n */\n delegatedFrom(): ICalAttendee | null;\n /**\n * Set the attendee's delegated-from field\n *\n * Creates a new Attendee if the passed object is not already a\n * {@link ICalAttendee} object. Will set the `delegatedTo` and\n * `delegatedFrom` attributes.\n *\n * @param delegatedFrom\n */\n delegatedFrom(\n delegatedFrom: ICalAttendee | ICalAttendeeData | null | string,\n ): this;\n delegatedFrom(\n delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string,\n ): ICalAttendee | null | this {\n if (delegatedFrom === undefined) {\n return this.data.delegatedFrom;\n }\n\n if (!delegatedFrom) {\n this.data.delegatedFrom = null;\n } else if (typeof delegatedFrom === 'string') {\n this.data.delegatedFrom = new ICalAttendee(\n {\n email: delegatedFrom,\n ...checkNameAndMail('delegatedFrom', delegatedFrom),\n },\n this.parent,\n );\n } else if (delegatedFrom instanceof ICalAttendee) {\n this.data.delegatedFrom = delegatedFrom;\n } else {\n this.data.delegatedFrom = new ICalAttendee(\n delegatedFrom,\n this.parent,\n );\n }\n\n return this;\n }\n\n /**\n * Get the attendee's delegated-to value.\n * @since 0.2.0\n */\n delegatedTo(): ICalAttendee | null;\n /**\n * Set the attendee's delegated-to field.\n *\n * Creates a new Attendee if the passed object is not already a\n * {@link ICalAttendee} object. Will set the `delegatedTo` and\n * `delegatedFrom` attributes.\n *\n * Will also set the `status` to `DELEGATED`, if attribute is set.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'});\n ```\n *\n * @since 0.2.0\n */\n delegatedTo(\n delegatedTo: ICalAttendee | ICalAttendeeData | null | string,\n ): this;\n delegatedTo(\n delegatedTo?: ICalAttendee | ICalAttendeeData | null | string,\n ): ICalAttendee | null | this {\n if (delegatedTo === undefined) {\n return this.data.delegatedTo;\n }\n if (!delegatedTo) {\n this.data.delegatedTo = null;\n if (this.data.status === ICalAttendeeStatus.DELEGATED) {\n this.data.status = null;\n }\n return this;\n }\n\n if (typeof delegatedTo === 'string') {\n this.data.delegatedTo = new ICalAttendee(\n {\n email: delegatedTo,\n ...checkNameAndMail('delegatedTo', delegatedTo),\n },\n this.parent,\n );\n } else if (delegatedTo instanceof ICalAttendee) {\n this.data.delegatedTo = delegatedTo;\n } else {\n this.data.delegatedTo = new ICalAttendee(delegatedTo, this.parent);\n }\n\n this.data.status = ICalAttendeeStatus.DELEGATED;\n return this;\n }\n\n /**\n * Create a new attendee this attendee delegates from and returns\n * this new attendee. Creates a new attendee if the passed object\n * is not already an {@link ICalAttendee}.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesFrom({email: 'foo@bar.com', name: 'Foo'});\n * ```\n *\n * @since 0.2.0\n */\n delegatesFrom(\n options: ICalAttendee | ICalAttendeeData | string,\n ): ICalAttendee {\n const a =\n options instanceof ICalAttendee\n ? options\n : this.parent.createAttendee(options);\n this.delegatedFrom(a);\n a.delegatedTo(this);\n return a;\n }\n\n /**\n * Create a new attendee this attendee delegates to and returns\n * this new attendee. Creates a new attendee if the passed object\n * is not already an {@link ICalAttendee}.\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n * const attendee = cal.createAttendee();\n *\n * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'});\n * ```\n *\n * @since 0.2.0\n */\n delegatesTo(\n options: ICalAttendee | ICalAttendeeData | string,\n ): ICalAttendee {\n const a =\n options instanceof ICalAttendee\n ? options\n : this.parent.createAttendee(options);\n this.delegatedTo(a);\n a.delegatedFrom(this);\n return a;\n }\n /**\n * Get the attendee's email address\n * @since 0.2.0\n */\n email(): string;\n /**\n * Set the attendee's email address\n * @since 0.2.0\n */\n email(email: string): this;\n email(email?: string): string | this {\n if (!email) {\n return this.data.email;\n }\n\n this.data.email = email;\n return this;\n }\n /**\n * Get the attendee's email address\n * @since 1.3.0\n */\n mailto(): null | string;\n /**\n * Set the attendee's email address\n * @since 1.3.0\n */\n mailto(mailto: null | string): this;\n mailto(mailto?: null | string): null | string | this {\n if (mailto === undefined) {\n return this.data.mailto;\n }\n\n this.data.mailto = mailto || null;\n return this;\n }\n /**\n * Get the attendee's name\n * @since 0.2.0\n */\n name(): null | string;\n /**\n * Set the attendee's name\n * @since 0.2.0\n */\n name(name: null | string): this;\n name(name?: null | string): null | string | this {\n if (name === undefined) {\n return this.data.name;\n }\n\n this.data.name = name || null;\n return this;\n }\n /**\n * Get attendee's role\n * @since 0.2.0\n */\n role(): ICalAttendeeRole;\n /**\n * Set the attendee's role, defaults to `REQ` / `REQ-PARTICIPANT`.\n * Checkout {@link ICalAttendeeRole} for available roles.\n *\n * @since 0.2.0\n */\n role(role: ICalAttendeeRole): this;\n role(role?: ICalAttendeeRole): ICalAttendeeRole | this {\n if (role === undefined) {\n return this.data.role;\n }\n\n this.data.role = checkEnum(ICalAttendeeRole, role) as ICalAttendeeRole;\n return this;\n }\n /**\n * Get attendee's RSVP expectation\n * @since 0.2.1\n */\n rsvp(): boolean | null;\n /**\n * Set the attendee's RSVP expectation\n * @since 0.2.1\n */\n rsvp(rsvp: boolean | null): this;\n rsvp(rsvp?: boolean | null): boolean | null | this {\n if (rsvp === undefined) {\n return this.data.rsvp;\n }\n if (rsvp === null) {\n this.data.rsvp = null;\n return this;\n }\n\n this.data.rsvp = Boolean(rsvp);\n return this;\n }\n /**\n * Get attendee's schedule agent\n * @since 9.0.0\n */\n scheduleAgent(): ICalAttendeeScheduleAgent | ICalXName;\n /**\n * Set attendee's schedule agent\n * See {@link ICalAttendeeScheduleAgent} for available values.\n * You can also use custom X-* values.\n *\n * @since 9.0.0\n */\n scheduleAgent(\n scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null,\n ): this;\n scheduleAgent(\n scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null,\n ): ICalAttendeeScheduleAgent | ICalXName | null | this {\n if (scheduleAgent === undefined) {\n return this.data.scheduleAgent;\n }\n if (!scheduleAgent) {\n this.data.scheduleAgent = null;\n return this;\n }\n\n if (\n typeof scheduleAgent == 'string' &&\n scheduleAgent.toUpperCase().startsWith('X-')\n ) {\n this.data.scheduleAgent = scheduleAgent as string as ICalXName;\n return this;\n }\n\n this.data.scheduleAgent = checkEnum(\n ICalAttendeeScheduleAgent,\n scheduleAgent,\n ) as ICalAttendeeScheduleAgent;\n return this;\n }\n /**\n * Get the acting user's email adress\n * @since 3.3.0\n */\n sentBy(): null | string;\n /**\n * Set the acting user's email adress\n * @since 3.3.0\n */\n sentBy(email: null | string): this;\n sentBy(email?: null | string): null | string | this {\n if (!email) {\n return this.data.sentBy;\n }\n\n this.data.sentBy = email;\n return this;\n }\n /**\n * Get attendee's status\n * @since 0.2.0\n */\n status(): ICalAttendeeStatus | null;\n /**\n * Set the attendee's status. See {@link ICalAttendeeStatus}\n * for available status options.\n *\n * @since 0.2.0\n */\n status(status: ICalAttendeeStatus | null): this;\n status(\n status?: ICalAttendeeStatus | null,\n ): ICalAttendeeStatus | null | this {\n if (status === undefined) {\n return this.data.status;\n }\n if (!status) {\n this.data.status = null;\n return this;\n }\n\n this.data.status = checkEnum(\n ICalAttendeeStatus,\n status,\n ) as ICalAttendeeStatus;\n return this;\n }\n /**\n * Return a shallow copy of the attendee's options for JSON stringification.\n * Can be used for persistence.\n *\n * @since 0.2.4\n */\n toJSON(): ICalAttendeeJSONData {\n return Object.assign({}, this.data, {\n delegatedFrom: this.data.delegatedFrom?.email() || null,\n delegatedTo: this.data.delegatedTo?.email() || null,\n x: this.x(),\n });\n }\n /**\n * Return generated attendee as a string.\n *\n * ```javascript\n * console.log(attendee.toString()); // → ATTENDEE;ROLE=…\n * ```\n */\n toString(): string {\n let g = 'ATTENDEE';\n\n if (!this.data.email) {\n throw new Error('No value for `email` in ICalAttendee given!');\n }\n\n // ROLE\n g += ';ROLE=' + this.data.role;\n\n // TYPE\n if (this.data.type) {\n g += ';CUTYPE=' + this.data.type;\n }\n\n // PARTSTAT\n if (this.data.status) {\n g += ';PARTSTAT=' + this.data.status;\n }\n\n // RSVP\n if (this.data.rsvp !== null) {\n g += ';RSVP=' + this.data.rsvp.toString().toUpperCase();\n }\n\n // SENT-BY\n if (this.data.sentBy !== null) {\n g += ';SENT-BY=\"mailto:' + this.data.sentBy + '\"';\n }\n\n // DELEGATED-TO\n if (this.data.delegatedTo) {\n g += ';DELEGATED-TO=\"' + this.data.delegatedTo.email() + '\"';\n }\n\n // DELEGATED-FROM\n if (this.data.delegatedFrom) {\n g += ';DELEGATED-FROM=\"' + this.data.delegatedFrom.email() + '\"';\n }\n\n // CN / Name\n if (this.data.name) {\n g += ';CN=\"' + escape(this.data.name, true) + '\"';\n }\n\n // EMAIL\n if (this.data.email && this.data.mailto) {\n g += ';EMAIL=' + escape(this.data.email, false);\n }\n\n // SCHEDULE-AGENT\n if (this.data.scheduleAgent) {\n g += ';SCHEDULE-AGENT=' + this.data.scheduleAgent;\n }\n\n // CUSTOM X ATTRIBUTES\n if (this.data.x.length) {\n g +=\n ';' +\n this.data.x\n .map(\n ([key, value]) =>\n key.toUpperCase() + '=' + escape(value, false),\n )\n .join(';');\n }\n\n g +=\n ':MAILTO:' +\n escape(this.data.mailto || this.data.email, false) +\n '\\r\\n';\n\n return g;\n }\n\n /**\n * Get attendee's type (a.k.a. CUTYPE)\n * @since 0.2.3\n */\n type(): ICalAttendeeType;\n /**\n * Set attendee's type (a.k.a. CUTYPE).\n * See {@link ICalAttendeeType} for available status options.\n *\n * @since 0.2.3\n */\n type(type: ICalAttendeeType | null): this;\n type(type?: ICalAttendeeType | null): ICalAttendeeType | null | this {\n if (type === undefined) {\n return this.data.type;\n }\n if (!type) {\n this.data.type = null;\n return this;\n }\n\n this.data.type = checkEnum(ICalAttendeeType, type) as ICalAttendeeType;\n return this;\n }\n /**\n * Set X-* attributes. Woun't filter double attributes,\n * which are also added by another method (e.g. status),\n * so these attributes may be inserted twice.\n *\n * ```javascript\n * attendee.x([\n * {\n * key: \"X-MY-CUSTOM-ATTR\",\n * value: \"1337!\"\n * }\n * ]);\n *\n * attendee.x([\n * [\"X-MY-CUSTOM-ATTR\", \"1337!\"]\n * ]);\n *\n * attendee.x({\n * \"X-MY-CUSTOM-ATTR\": \"1337!\"\n * });\n * ```\n *\n * @since 1.9.0\n */\n x(\n keyOrArray:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[],\n ): this;\n /**\n * Set a X-* attribute. Woun't filter double attributes,\n * which are also added by another method (e.g. status),\n * so these attributes may be inserted twice.\n *\n * ```javascript\n * attendee.x(\"X-MY-CUSTOM-ATTR\", \"1337!\");\n * ```\n *\n * @since 1.9.0\n */\n x(keyOrArray: string, value: string): this;\n /**\n * Get all custom X-* attributes.\n * @since 1.9.0\n */\n x(): { key: string; value: string }[];\n x(\n keyOrArray?:\n | [string, string][]\n | Record<string, string>\n | string\n | { key: string; value: string }[],\n value?: string,\n ): this | void | { key: string; value: string }[] {\n if (keyOrArray === undefined) {\n return addOrGetCustomAttributes(this.data);\n }\n\n if (typeof keyOrArray === 'string' && typeof value === 'string') {\n addOrGetCustomAttributes(this.data, keyOrArray, value);\n } else if (typeof keyOrArray === 'object') {\n addOrGetCustomAttributes(this.data, keyOrArray);\n } else {\n throw new Error('Either key or value is not a string!');\n }\n\n return this;\n }\n}\n","'use strict';\n\nimport type ICalEvent from './event.ts';\n\nimport ICalAttendee, { type ICalAttendeeData } from './attendee.ts';\nimport {\n addOrGetCustomAttributes,\n checkDate,\n checkNameAndMail,\n escape,\n formatDate,\n generateCustomAttributes,\n toDurationString,\n toJSON,\n} from './tools.ts';\nimport { type ICalDateTimeValue } from './types.ts';\n\nexport enum ICalAlarmType {\n audio = 'audio',\n display = 'display',\n email = 'email',\n}\n\nexport const ICalAlarmRelatesTo = {\n end: 'END',\n start: 'START',\n} as const;\n\nexport interface ICalAlarmBaseData {\n attach?: ICalAttachment | null | string;\n attendees?: ICalAttendee[] | ICalAttendeeData[];\n description?: null | string;\n relatesTo?: ICalAlarmRelatesTo | null;\n repeat?: ICalAlarmRepeatData | null;\n summary?: null | string;\n type?: ICalAlarmType;\n x?:\n | [string, string][]\n | Record<string, string>\n | { key: string; value: string }[];\n}\n\nexport type ICalAlarmData =\n | ICalAlarmBaseData\n | ICalAlarmTriggerAfterData\n | ICalAlarmTriggerBeforeData\n | ICalAlarmTriggerData;\n\nexport interface ICalAlarmJSONData {\n attach: ICalAttachment | null;\n attendees: ICalAttendee[];\n description: null | string;\n interval: null | number;\n relatesTo: ICalAlarmRelatesTo | null;\n repeat: ICalAlarmRepeatData | null;\n summary: null | string;\n trigger: number | string;\n type: ICalAlarmType;\n x: { key: string; value: string }[];\n}\n\nexport type ICalAlarmRelatesTo =\n (typeof ICalAlarmRelatesTo)[keyof typeof ICalAlarmRelatesTo];\n\nexport interface ICalAlarmRepeatData {\n interval: number;\n times: number;\n}\nexport type ICalAlarmTriggerAfterData = ICalAlarmBaseData & {\n triggerAfter: ICalDateTimeValue | number;\n};\nexport type ICalAlarmTriggerBeforeData = ICalAlarmBaseData & {\n triggerBefore: ICalDateTimeValue | number;\n};\n\nexport type ICalAlarmTriggerData = ICalAlarmBaseData & {\n trigger: ICalDateTimeValue | number;\n};\n\nexport type ICalAlarmTypeValue = keyof ICalAlarmType;\n\nexport interface ICalAttachment {\n mime: null | string;\n uri: string;\n}\n\ninterface ICalInternalAlarmData {\n attach: ICalAttachment | null;\n attendees: ICalAttendee[];\n description: null | string;\n interval: null | number;\n relatesTo: ICalAlarmRelatesTo | null;\n repeat: ICalAlarmRepeatData | null;\n summary: null | string;\n trigger: ICalDateTimeValue | number;\n type: ICalAlarmType;\n x: [string, string][];\n}\n\n/**\n * Usually you get an {@link ICalAlarm} object like this:\n *\n * ```javascript\n * import ical from 'ical-generator';\n * const calendar = ical();\n * const event = calendar.createEvent();\n * const alarm = event.createAlarm();\n * ```\n *\n * You can also use the {@link ICalAlarm} object directly:\n *\n * ```javascript\n * import ical, {ICalAlarm} from 'ical-generator';\n * const alarm = new ICalAlarm();\n * event.alarms([alarm]);\n * ```\n */\nexport default class ICalAlarm {\n private readonly data: ICalInternalAlarmData;\n private readonly event: ICalEvent;\n\n /**\n * Constructor of {@link ICalAttendee}. The event reference is required\n * to query the calendar's timezone and summary when required.\n *\n * @param data Alarm Data\n * @param event Reference to ICalEvent object\n */\n constructor(data: ICalAlarmData, event: ICalEvent) {\n this.data = {\n attach: null,\n attendees: [],\n description: null,\n interval: null,\n relatesTo: null,\n repeat: null,\n summary: null,\n trigger: -600,\n type: ICalAlarmType.display,\n x: [],\n };\n\n this.event = event;\n if (!event) {\n throw new Error('`event` option required!');\n }\n\n if (data.type !== undefined) this.type(data.type);\n if ('trigger' in data && data.trigger !== undefined)\n this.trigger(data.trigger);\n if ('triggerBefore' in data && data.triggerBefore !== undefined)\n this.triggerBefore(data.triggerBefore);\n if ('triggerAfter' in data && data.triggerAfter !== undefined)\n this.triggerAfter(data.triggerAfter);\n if (data.repeat) this.repeat(data.repeat);\n if (data.attach !== undefined) this.attach(data.attach);\n if (data.description !== undefined) this.description(data.description);\n if (data.summary !== undefined) this.summary(data.summary);\n if (data.attendees !== undefined) this.attendees(data.attendees);\n if (data.x !== undefined) this.x(data.x);\n }\n\n /**\n * Get Attachment\n * @since 0.2.1\n */\n attach(): null | { mime: null | string; uri: string };\n /**\n * Set Alarm attachment. Used to set the alarm sound\n * if alarm type is audio. Defaults to \"Basso\".\n *\n * ```javascript\n * const cal = ical();\n * const event = cal.createEvent();\n *\n * event.createAlarm({\n * attach: 'https://example.com/notification.aud'\n * });\n *\n * // OR\n *\n * event.createAlarm({\n * attach: {\n * uri: 'https://example.com/notification.aud',\n * mime: 'audio/basic'\n * }\n * });\n * ```\n *\n * @since 0.2.1\n */\n attach(\n attachment: null | string | { mime?: null | string; uri: string },\n ): this;\n attach(\n attachment?: null | string | { mime?: null | string; uri: string },\n ): null | this | { mime: null | string; uri: string } {\n if (attachment === undefined) {\n return this.data.attach;\n }\n if (!attachment) {\n this.data.attach = null;\n return this;\n }\n\n let _attach = null;\n if (typeof attachment === 'string') {\n _attach = {\n mime: null,\n uri: attachment,\n };\n } else if (typeof attachment === 'object') {\n _attach = {\n mime: attachment.mime || null,\n uri: attachment.uri,\n };\n } else {\n throw new Error(\n '`attachment` needs to be a valid formed string or an object. See https://sebbo2002.github.io/' +\n 'ical-generator/develop/reference/classes/ICalAlarm.html#attach',\n );\n }\n\n if (!_attach.uri) {\n throw new Error('`attach.uri` is empty!');\n }\n\n this.data.attach = {\n mime: _attach.mime,\n uri: _attach.uri,\n };\n return this;\n }\n\n /**\n * Get all attendees\n * @since 7.0.0\n */\n attendees(): ICalAttendee[];\n /**\n * Add multiple attendees to your event\n *\n * @since 7.0.0\n */\n attendees(attendees: (ICalAttendee | ICalAttendeeData | string)[]): this;\n attendees(\n attendees?: (ICalAttendee | ICalAttendeeData | string)[],\n ): ICalAttendee[] | this {\n if (!attendees) {\n return this.data.attendees;\n }\n\n attendees.forEach((attendee) => this.createAttendee(attendee));\n return this;\n }\n\n /**\n * Creates a new {@link ICalAttendee} and returns it. Use options to prefill\n * the attendee's attributes. Calling this method without options will create\n * an empty attendee.\n *\n * @since 7.0.0\n */\n createAttendee(\n data: ICalAttendee | ICalAttendeeData | string,\n ): ICalAttendee {\n if (data instanceof ICalAttendee) {\n this.data.attendees.push(data);\n return data;\n }\n if (typeof data === 'string') {\n data = { email: data, ...checkNameAndMail('data', data) };\n }\n\n const attendee = new ICalAttendee(data, this);\n this.data.attendees.push(attendee);\n return attendee;\n }\n\n /**\n * Get the alarm description. Used to set the alarm message\n * if alarm type is `display`. If the alarm type is `email`, it's\n * used to set the email body. Defaults to the event's summary.\n *\n * @since 0.2.1\n */\n description(): null | string;\n /**\n * Set the alarm description. Used to set the alarm message\n * if alarm type is `display`. If the alarm type is `email`, it's\n * used to set the email body. Defaults to the event's summary.\n *\n * @since 0.