-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.ts
122 lines (103 loc) · 3.4 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { crypto } from 'https://deno.land/std@0.154.0/crypto/mod.ts';
import { parseDate, DateData, Zone } from './date.ts';
import { ContentLine, stringifyLines } from './stringify.ts';
import { parseRRule, RecurrenceRule } from './rrule.ts';
const calendarBegin: ContentLine = ['BEGIN', 'VCALENDAR'];
const calendarProps: ContentLine[] = [
['VERSION', '2.0'],
['PRODID', 'peron/simple_ics'],
['METHOD', 'PUBLISH'],
];
const calendarEnd: ContentLine = ['END', 'VCALENDAR'];
const eventBegin: ContentLine = ['BEGIN', 'VEVENT'];
const eventEnd: ContentLine = ['END', 'VEVENT'];
const parseGeo = (geo?: {lat: number, lon: number}) => {
return geo ? `${geo.lat};${geo.lon}` : undefined;
}
const parseOrganizer = (organizer?: {name: string, email: string}) => {
return organizer? `CN=${organizer.name}:mailto:${organizer.email}` : undefined
}
export class Event {
zone: Zone = 'local'
constructor (protected config: EventConfig) {
if (config.zone) this.zone = config.zone
if (config.duration !== undefined) {
// Duration is provided
if (!(config.beginDate instanceof Date)) {
// Convert beginDate to Date object
config.beginDate = Date.constructor.apply(null, config.beginDate);
}
// Calculate endDate
const endStamp =
(config.beginDate.valueOf() as number) + config.duration * 1e3;
config.endDate = new Date(endStamp);
} else if (config.endDate === undefined) {
// Neither duration nor endDate is provided
throw new TypeError(
'Invalid Event Config, either duration or endDate should be provided'
);
}
}
toLines(): ContentLine[] {
const uid = crypto.randomUUID();
const { title, desc, rrule, alarm, location, url, organizer, geo, htmlContent } = this.config;
const result = [
eventBegin,
['UID', uid],
['DTSTAMP', parseDate(new Date(), "utc")],
['DTSTART', parseDate(this.config.beginDate, this.zone)],
['DTEND', parseDate(this.config.endDate!, this.zone)],
['SUMMARY', title],
['DESCRIPTION', desc],
['LOCATION', location],
['URL', url],
['GEO', parseGeo(geo)],
['RRULE', parseRRule(rrule)],
['ORGANIZER', parseOrganizer(organizer)],
...parseAlarm(alarm),
eventEnd,
].filter(line => line[1] !== undefined) as ContentLine[];
return result;
}
}
export class Calendar {
constructor(protected events: Event[]) {}
toLines(): ContentLine[] {
const eventLines = this.events.map(evt => evt.toLines()).flat();
return [calendarBegin, ...calendarProps, ...eventLines, calendarEnd];
}
toString() {
return stringifyLines(this.toLines());
}
}
export interface EventConfig {
title: string;
beginDate: DateData;
endDate?: DateData;
duration?: number;
desc?: string;
rrule?: RecurrenceRule;
alarm?: AlarmConfig;
location?: string;
url?: string;
organizer?: { name: string; email: string; dir?: string; };
geo?: { lat: number; lon: number; };
htmlContent?: string;
zone?: Zone // default to local
}
export interface AlarmConfig {
advance: number; // In minutes
desc: string;
}
export function parseAlarm(
config?: AlarmConfig
): ContentLine[] | [[string, undefined]] {
if (config === undefined) return [['VALARM', undefined]];
return [
['BEGIN', 'VALARM'],
['TRIGGER', `-PT${config.advance}M`],
['ACTION', 'DISPLAY'],
['DESCRIPTION', config.desc],
['END', 'VALARM'],
];
}