UNPKG

ical-generator

Version:

ical-generator is a small piece of code which generates ical calendar files

694 lines (646 loc) 19.5 kB
'use strict'; import type ICalAlarm from './alarm.ts'; import type ICalEvent from './event.ts'; import { addOrGetCustomAttributes, checkEnum, checkNameAndMail, escape, } from './tools.ts'; export enum ICalAttendeeRole { CHAIR = 'CHAIR', NON = 'NON-PARTICIPANT', OPT = 'OPT-PARTICIPANT', REQ = 'REQ-PARTICIPANT', } // ref: https://datatracker.ietf.org/doc/html/rfc6638#section-7.1 export enum ICalAttendeeScheduleAgent { CLIENT = 'CLIENT', NONE = 'NONE', SERVER = 'SERVER', } export enum ICalAttendeeStatus { ACCEPTED = 'ACCEPTED', DECLINED = 'DECLINED', DELEGATED = 'DELEGATED', NEEDSACTION = 'NEEDS-ACTION', TENTATIVE = 'TENTATIVE', } // ref: https://tools.ietf.org/html/rfc2445#section-4.2.3 export enum ICalAttendeeType { GROUP = 'GROUP', INDIVIDUAL = 'INDIVIDUAL', RESOURCE = 'RESOURCE', ROOM = 'ROOM', UNKNOWN = 'UNKNOWN', } export interface ICalAttendeeData { delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string; delegatedTo?: ICalAttendee | ICalAttendeeData | null | string; delegatesFrom?: ICalAttendee | ICalAttendeeData | null | string; delegatesTo?: ICalAttendee | ICalAttendeeData | null | string; email: string; mailto?: null | string; name?: null | string; role?: ICalAttendeeRole; rsvp?: boolean | null; scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null; sentBy?: null | string; status?: ICalAttendeeStatus | null; type?: ICalAttendeeType | null; x?: | [string, string][] | Record<string, string> | { key: string; value: string }[]; } export interface ICalAttendeeJSONData { delegatedFrom: null | string; delegatedTo: null | string; email: string; mailto: null | string; name: null | string; role: ICalAttendeeRole; rsvp: boolean | null; sentBy: null | string; status: ICalAttendeeStatus | null; type: ICalAttendeeType | null; x: { key: string; value: string }[]; } export type ICalXName = `X-${string}`; interface ICalInternalAttendeeData { delegatedFrom: ICalAttendee | null; delegatedTo: ICalAttendee | null; email: string; mailto: null | string; name: null | string; role: ICalAttendeeRole; rsvp: boolean | null; scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null; sentBy: null | string; status: ICalAttendeeStatus | null; type: ICalAttendeeType | null; x: [string, string][]; } /** * Usually you get an {@link ICalAttendee} object like this: * * ```javascript * import ical from 'ical-generator'; * const calendar = ical(); * const event = calendar.createEvent(); * const attendee = event.createAttendee({ email: 'mail@example.com' }); * ``` * * You can also use the {@link ICalAttendee} object directly: * * ```javascript * import ical, {ICalAttendee} from 'ical-generator'; * const attendee = new ICalAttendee({ email: 'mail@example.com' }); * event.attendees([attendee]); * ``` */ export default class ICalAttendee { private readonly data: ICalInternalAttendeeData; private readonly parent: ICalAlarm | ICalEvent; /** * Constructor of {@link ICalAttendee}. The event reference is * required to query the calendar's timezone when required. * * @param data Attendee Data * @param parent Reference to ICalEvent object */ constructor(data: ICalAttendeeData, parent: ICalAlarm | ICalEvent) { this.data = { delegatedFrom: null, delegatedTo: null, email: '', mailto: null, name: null, role: ICalAttendeeRole.REQ, rsvp: null, scheduleAgent: null, sentBy: null, status: null, type: null, x: [], }; this.parent = parent; if (!this.parent) { throw new Error('`event` option required!'); } if (!data.email) { throw new Error('No value for `email` in ICalAttendee given!'); } if (data.name !== undefined) this.name(data.name); if (data.email !== undefined) this.email(data.email); if (data.mailto !== undefined) this.mailto(data.mailto); if (data.sentBy !== undefined) this.sentBy(data.sentBy); if (data.status !== undefined) this.status(data.status); if (data.role !== undefined) this.role(data.role); if (data.rsvp !== undefined) this.rsvp(data.rsvp); if (data.type !== undefined) this.type(data.type); if (data.delegatedTo !== undefined) this.delegatedTo(data.delegatedTo); if (data.delegatedFrom !== undefined) this.delegatedFrom(data.delegatedFrom); if (data.delegatesTo) this.delegatesTo(data.delegatesTo); if (data.delegatesFrom) this.delegatesFrom(data.delegatesFrom); if (data.scheduleAgent !== undefined) this.scheduleAgent(data.scheduleAgent); if (data.x !== undefined) this.x(data.x); } /** * Get the attendee's delegated-from field * @since 0.2.0 */ delegatedFrom(): ICalAttendee | null; /** * Set the attendee's delegated-from field * * Creates a new Attendee if the passed object is not already a * {@link ICalAttendee} object. Will set the `delegatedTo` and * `delegatedFrom` attributes. * * @param delegatedFrom */ delegatedFrom( delegatedFrom: ICalAttendee | ICalAttendeeData | null | string, ): this; delegatedFrom( delegatedFrom?: ICalAttendee | ICalAttendeeData | null | string, ): ICalAttendee | null | this { if (delegatedFrom === undefined) { return this.data.delegatedFrom; } if (!delegatedFrom) { this.data.delegatedFrom = null; } else if (typeof delegatedFrom === 'string') { this.data.delegatedFrom = new ICalAttendee( { email: delegatedFrom, ...checkNameAndMail('delegatedFrom', delegatedFrom), }, this.parent, ); } else if (delegatedFrom instanceof ICalAttendee) { this.data.delegatedFrom = delegatedFrom; } else { this.data.delegatedFrom = new ICalAttendee( delegatedFrom, this.parent, ); } return this; } /** * Get the attendee's delegated-to value. * @since 0.2.0 */ delegatedTo(): ICalAttendee | null; /** * Set the attendee's delegated-to field. * * Creates a new Attendee if the passed object is not already a * {@link ICalAttendee} object. Will set the `delegatedTo` and * `delegatedFrom` attributes. * * Will also set the `status` to `DELEGATED`, if attribute is set. * * ```javascript * const cal = ical(); * const event = cal.createEvent(); * const attendee = cal.createAttendee(); * * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'}); ``` * * @since 0.2.0 */ delegatedTo( delegatedTo: ICalAttendee | ICalAttendeeData | null | string, ): this; delegatedTo( delegatedTo?: ICalAttendee | ICalAttendeeData | null | string, ): ICalAttendee | null | this { if (delegatedTo === undefined) { return this.data.delegatedTo; } if (!delegatedTo) { this.data.delegatedTo = null; if (this.data.status === ICalAttendeeStatus.DELEGATED) { this.data.status = null; } return this; } if (typeof delegatedTo === 'string') { this.data.delegatedTo = new ICalAttendee( { email: delegatedTo, ...checkNameAndMail('delegatedTo', delegatedTo), }, this.parent, ); } else if (delegatedTo instanceof ICalAttendee) { this.data.delegatedTo = delegatedTo; } else { this.data.delegatedTo = new ICalAttendee(delegatedTo, this.parent); } this.data.status = ICalAttendeeStatus.DELEGATED; return this; } /** * Create a new attendee this attendee delegates from and returns * this new attendee. Creates a new attendee if the passed object * is not already an {@link ICalAttendee}. * * ```javascript * const cal = ical(); * const event = cal.createEvent(); * const attendee = cal.createAttendee(); * * attendee.delegatesFrom({email: 'foo@bar.com', name: 'Foo'}); * ``` * * @since 0.2.0 */ delegatesFrom( options: ICalAttendee | ICalAttendeeData | string, ): ICalAttendee { const a = options instanceof ICalAttendee ? options : this.parent.createAttendee(options); this.delegatedFrom(a); a.delegatedTo(this); return a; } /** * Create a new attendee this attendee delegates to and returns * this new attendee. Creates a new attendee if the passed object * is not already an {@link ICalAttendee}. * * ```javascript * const cal = ical(); * const event = cal.createEvent(); * const attendee = cal.createAttendee(); * * attendee.delegatesTo({email: 'foo@bar.com', name: 'Foo'}); * ``` * * @since 0.2.0 */ delegatesTo( options: ICalAttendee | ICalAttendeeData | string, ): ICalAttendee { const a = options instanceof ICalAttendee ? options : this.parent.createAttendee(options); this.delegatedTo(a); a.delegatedFrom(this); return a; } /** * Get the attendee's email address * @since 0.2.0 */ email(): string; /** * Set the attendee's email address * @since 0.2.0 */ email(email: string): this; email(email?: string): string | this { if (!email) { return this.data.email; } this.data.email = email; return this; } /** * Get the attendee's email address * @since 1.3.0 */ mailto(): null | string; /** * Set the attendee's email address * @since 1.3.0 */ mailto(mailto: null | string): this; mailto(mailto?: null | string): null | string | this { if (mailto === undefined) { return this.data.mailto; } this.data.mailto = mailto || null; return this; } /** * Get the attendee's name * @since 0.2.0 */ name(): null | string; /** * Set the attendee's name * @since 0.2.0 */ name(name: null | string): this; name(name?: null | string): null | string | this { if (name === undefined) { return this.data.name; } this.data.name = name || null; return this; } /** * Get attendee's role * @since 0.2.0 */ role(): ICalAttendeeRole; /** * Set the attendee's role, defaults to `REQ` / `REQ-PARTICIPANT`. * Checkout {@link ICalAttendeeRole} for available roles. * * @since 0.2.0 */ role(role: ICalAttendeeRole): this; role(role?: ICalAttendeeRole): ICalAttendeeRole | this { if (role === undefined) { return this.data.role; } this.data.role = checkEnum(ICalAttendeeRole, role) as ICalAttendeeRole; return this; } /** * Get attendee's RSVP expectation * @since 0.2.1 */ rsvp(): boolean | null; /** * Set the attendee's RSVP expectation * @since 0.2.1 */ rsvp(rsvp: boolean | null): this; rsvp(rsvp?: boolean | null): boolean | null | this { if (rsvp === undefined) { return this.data.rsvp; } if (rsvp === null) { this.data.rsvp = null; return this; } this.data.rsvp = Boolean(rsvp); return this; } /** * Get attendee's schedule agent * @since 9.0.0 */ scheduleAgent(): ICalAttendeeScheduleAgent | ICalXName; /** * Set attendee's schedule agent * See {@link ICalAttendeeScheduleAgent} for available values. * You can also use custom X-* values. * * @since 9.0.0 */ scheduleAgent( scheduleAgent: ICalAttendeeScheduleAgent | ICalXName | null, ): this; scheduleAgent( scheduleAgent?: ICalAttendeeScheduleAgent | ICalXName | null, ): ICalAttendeeScheduleAgent | ICalXName | null | this { if (scheduleAgent === undefined) { return this.data.scheduleAgent; } if (!scheduleAgent) { this.data.scheduleAgent = null; return this; } if ( typeof scheduleAgent == 'string' && scheduleAgent.toUpperCase().startsWith('X-') ) { this.data.scheduleAgent = scheduleAgent as string as ICalXName; return this; } this.data.scheduleAgent = checkEnum( ICalAttendeeScheduleAgent, scheduleAgent, ) as ICalAttendeeScheduleAgent; return this; } /** * Get the acting user's email adress * @since 3.3.0 */ sentBy(): null | string; /** * Set the acting user's email adress * @since 3.3.0 */ sentBy(email: null | string): this; sentBy(email?: null | string): null | string | this { if (!email) { return this.data.sentBy; } this.data.sentBy = email; return this; } /** * Get attendee's status * @since 0.2.0 */ status(): ICalAttendeeStatus | null; /** * Set the attendee's status. See {@link ICalAttendeeStatus} * for available status options. * * @since 0.2.0 */ status(status: ICalAttendeeStatus | null): this; status( status?: ICalAttendeeStatus | null, ): ICalAttendeeStatus | null | this { if (status === undefined) { return this.data.status; } if (!status) { this.data.status = null; return this; } this.data.status = checkEnum( ICalAttendeeStatus, status, ) as ICalAttendeeStatus; return this; } /** * Return a shallow copy of the attendee's options for JSON stringification. * Can be used for persistence. * * @since 0.2.4 */ toJSON(): ICalAttendeeJSONData { return Object.assign({}, this.data, { delegatedFrom: this.data.delegatedFrom?.email() || null, delegatedTo: this.data.delegatedTo?.email() || null, x: this.x(), }); } /** * Return generated attendee as a string. * * ```javascript * console.log(attendee.toString()); // → ATTENDEE;ROLE=… * ``` */ toString(): string { let g = 'ATTENDEE'; if (!this.data.email) { throw new Error('No value for `email` in ICalAttendee given!'); } // ROLE g += ';ROLE=' + this.data.role; // TYPE if (this.data.type) { g += ';CUTYPE=' + this.data.type; } // PARTSTAT if (this.data.status) { g += ';PARTSTAT=' + this.data.status; } // RSVP if (this.data.rsvp !== null) { g += ';RSVP=' + this.data.rsvp.toString().toUpperCase(); } // SENT-BY if (this.data.sentBy !== null) { g += ';SENT-BY="mailto:' + this.data.sentBy + '"'; } // DELEGATED-TO if (this.data.delegatedTo) { g += ';DELEGATED-TO="' + this.data.delegatedTo.email() + '"'; } // DELEGATED-FROM if (this.data.delegatedFrom) { g += ';DELEGATED-FROM="' + this.data.delegatedFrom.email() + '"'; } // CN / Name if (this.data.name) { g += ';CN="' + escape(this.data.name, true) + '"'; } // EMAIL if (this.data.email && this.data.mailto) { g += ';EMAIL=' + escape(this.data.email, false); } // SCHEDULE-AGENT if (this.data.scheduleAgent) { g += ';SCHEDULE-AGENT=' + this.data.scheduleAgent; } // CUSTOM X ATTRIBUTES if (this.data.x.length) { g += ';' + this.data.x .map( ([key, value]) => key.toUpperCase() + '=' + escape(value, false), ) .join(';'); } g += ':MAILTO:' + escape(this.data.mailto || this.data.email, false) + '\r\n'; return g; } /** * Get attendee's type (a.k.a. CUTYPE) * @since 0.2.3 */ type(): ICalAttendeeType; /** * Set attendee's type (a.k.a. CUTYPE). * See {@link ICalAttendeeType} for available status options. * * @since 0.2.3 */ type(type: ICalAttendeeType | null): this; type(type?: ICalAttendeeType | null): ICalAttendeeType | null | this { if (type === undefined) { return this.data.type; } if (!type) { this.data.type = null; return this; } this.data.type = checkEnum(ICalAttendeeType, type) as ICalAttendeeType; return this; } /** * Set X-* attributes. Woun't filter double attributes, * which are also added by another method (e.g. status), * so these attributes may be inserted twice. * * ```javascript * attendee.x([ * { * key: "X-MY-CUSTOM-ATTR", * value: "1337!" * } * ]); * * attendee.x([ * ["X-MY-CUSTOM-ATTR", "1337!"] * ]); * * attendee.x({ * "X-MY-CUSTOM-ATTR": "1337!" * }); * ``` * * @since 1.9.0 */ x( keyOrArray: | [string, string][] | Record<string, string> | { key: string; value: string }[], ): this; /** * Set a X-* attribute. Woun't filter double attributes, * which are also added by another method (e.g. status), * so these attributes may be inserted twice. * * ```javascript * attendee.x("X-MY-CUSTOM-ATTR", "1337!"); * ``` * * @since 1.9.0 */ x(keyOrArray: string, value: string): this; /** * Get all custom X-* attributes. * @since 1.9.0 */ x(): { key: string; value: string }[]; x( keyOrArray?: | [string, string][] | Record<string, string> | string | { key: string; value: string }[], value?: string, ): this | void | { key: string; value: string }[] { if (keyOrArray === undefined) { return addOrGetCustomAttributes(this.data); } if (typeof keyOrArray === 'string' && typeof value === 'string') { addOrGetCustomAttributes(this.data, keyOrArray, value); } else if (typeof keyOrArray === 'object') { addOrGetCustomAttributes(this.data, keyOrArray); } else { throw new Error('Either key or value is not a string!'); } return this; } }