@pipobscure/ical 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +661 -0
- package/dist/alarm.d.ts +35 -0
- package/dist/alarm.d.ts.map +1 -0
- package/dist/alarm.js +87 -0
- package/dist/alarm.js.map +1 -0
- package/dist/calendar.d.ts +52 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +121 -0
- package/dist/calendar.js.map +1 -0
- package/dist/component.d.ts +48 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +170 -0
- package/dist/component.js.map +1 -0
- package/dist/event.d.ts +74 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +263 -0
- package/dist/event.js.map +1 -0
- package/dist/freebusy.d.ts +45 -0
- package/dist/freebusy.d.ts.map +1 -0
- package/dist/freebusy.js +111 -0
- package/dist/freebusy.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +48 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +148 -0
- package/dist/journal.js.map +1 -0
- package/dist/parse.d.ts +27 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +140 -0
- package/dist/parse.js.map +1 -0
- package/dist/property-registry.d.ts +31 -0
- package/dist/property-registry.d.ts.map +1 -0
- package/dist/property-registry.js +83 -0
- package/dist/property-registry.js.map +1 -0
- package/dist/property.d.ts +36 -0
- package/dist/property.d.ts.map +1 -0
- package/dist/property.js +135 -0
- package/dist/property.js.map +1 -0
- package/dist/timezone.d.ts +55 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +141 -0
- package/dist/timezone.js.map +1 -0
- package/dist/todo.d.ts +67 -0
- package/dist/todo.d.ts.map +1 -0
- package/dist/todo.js +220 -0
- package/dist/todo.js.map +1 -0
- package/dist/tokenize.d.ts +22 -0
- package/dist/tokenize.d.ts.map +1 -0
- package/dist/tokenize.js +95 -0
- package/dist/tokenize.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/value-types.d.ts +81 -0
- package/dist/value-types.d.ts.map +1 -0
- package/dist/value-types.js +445 -0
- package/dist/value-types.js.map +1 -0
- package/package.json +45 -0
- package/src/alarm.ts +105 -0
- package/src/calendar.ts +153 -0
- package/src/component.ts +193 -0
- package/src/event.ts +307 -0
- package/src/freebusy.ts +133 -0
- package/src/index.ts +85 -0
- package/src/journal.ts +174 -0
- package/src/parse.ts +166 -0
- package/src/property-registry.ts +124 -0
- package/src/property.ts +163 -0
- package/src/timezone.ts +169 -0
- package/src/todo.ts +253 -0
- package/src/tokenize.ts +99 -0
- package/src/types.ts +135 -0
- package/src/value-types.ts +498 -0
package/src/event.ts
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VEVENT component (RFC 5545 §3.6.1)
|
|
3
|
+
*
|
|
4
|
+
* UID and DTSTAMP are required.
|
|
5
|
+
* DTSTART is required when METHOD is not present in the calendar.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Component } from './component.js';
|
|
9
|
+
import { Property, parseProperty } from './property.js';
|
|
10
|
+
import { Alarm } from './alarm.js';
|
|
11
|
+
import type {
|
|
12
|
+
ICalDate,
|
|
13
|
+
ICalDateTime,
|
|
14
|
+
ICalDuration,
|
|
15
|
+
ICalRecur,
|
|
16
|
+
ICalGeo,
|
|
17
|
+
ICalValue,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
|
|
20
|
+
export class Event extends Component {
|
|
21
|
+
constructor() {
|
|
22
|
+
super('VEVENT');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Required / very common properties ───────────────────────────────
|
|
26
|
+
|
|
27
|
+
get uid(): string | null {
|
|
28
|
+
return this.getProperty('UID')?.text ?? null;
|
|
29
|
+
}
|
|
30
|
+
set uid(v: string) {
|
|
31
|
+
this.setProperty('UID', v);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get dtstamp(): ICalDateTime | null {
|
|
35
|
+
const v = this.getValue('DTSTAMP');
|
|
36
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
37
|
+
}
|
|
38
|
+
set dtstamp(v: ICalDateTime | Date) {
|
|
39
|
+
if (v instanceof Date) {
|
|
40
|
+
this.setProperty('DTSTAMP', {
|
|
41
|
+
type: 'date-time',
|
|
42
|
+
year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(),
|
|
43
|
+
hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(),
|
|
44
|
+
utc: true,
|
|
45
|
+
} satisfies ICalDateTime);
|
|
46
|
+
} else {
|
|
47
|
+
this.setProperty('DTSTAMP', v);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get dtstart(): ICalDateTime | ICalDate | null {
|
|
52
|
+
const v = this.getValue('DTSTART');
|
|
53
|
+
if (!v || typeof v !== 'object') return null;
|
|
54
|
+
return v.type === 'date-time' || v.type === 'date' ? (v as ICalDateTime | ICalDate) : null;
|
|
55
|
+
}
|
|
56
|
+
set dtstart(v: ICalDateTime | ICalDate | Date) {
|
|
57
|
+
if (v instanceof Date) {
|
|
58
|
+
this.setProperty('DTSTART', {
|
|
59
|
+
type: 'date-time',
|
|
60
|
+
year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(),
|
|
61
|
+
hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(),
|
|
62
|
+
utc: true,
|
|
63
|
+
} satisfies ICalDateTime);
|
|
64
|
+
} else if (v.type === 'date') {
|
|
65
|
+
this.setProperty('DTSTART', v, { VALUE: 'DATE' });
|
|
66
|
+
} else {
|
|
67
|
+
this.setProperty('DTSTART', v);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get dtend(): ICalDateTime | ICalDate | null {
|
|
72
|
+
const v = this.getValue('DTEND');
|
|
73
|
+
if (!v || typeof v !== 'object') return null;
|
|
74
|
+
return v.type === 'date-time' || v.type === 'date' ? (v as ICalDateTime | ICalDate) : null;
|
|
75
|
+
}
|
|
76
|
+
set dtend(v: ICalDateTime | ICalDate | Date) {
|
|
77
|
+
if (v instanceof Date) {
|
|
78
|
+
this.setProperty('DTEND', {
|
|
79
|
+
type: 'date-time',
|
|
80
|
+
year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(),
|
|
81
|
+
hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(),
|
|
82
|
+
utc: true,
|
|
83
|
+
} satisfies ICalDateTime);
|
|
84
|
+
} else if (v.type === 'date') {
|
|
85
|
+
this.setProperty('DTEND', v, { VALUE: 'DATE' });
|
|
86
|
+
} else {
|
|
87
|
+
this.setProperty('DTEND', v);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get duration(): ICalDuration | null {
|
|
92
|
+
const v = this.getValue('DURATION');
|
|
93
|
+
return v && typeof v === 'object' && v.type === 'duration' ? (v as ICalDuration) : null;
|
|
94
|
+
}
|
|
95
|
+
set duration(v: ICalDuration) {
|
|
96
|
+
this.setProperty('DURATION', v);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get summary(): string | null {
|
|
100
|
+
return this.getProperty('SUMMARY')?.text ?? null;
|
|
101
|
+
}
|
|
102
|
+
set summary(v: string) {
|
|
103
|
+
this.setProperty('SUMMARY', v);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get description(): string | null {
|
|
107
|
+
return this.getProperty('DESCRIPTION')?.text ?? null;
|
|
108
|
+
}
|
|
109
|
+
set description(v: string) {
|
|
110
|
+
this.setProperty('DESCRIPTION', v);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get location(): string | null {
|
|
114
|
+
return this.getProperty('LOCATION')?.text ?? null;
|
|
115
|
+
}
|
|
116
|
+
set location(v: string) {
|
|
117
|
+
this.setProperty('LOCATION', v);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get url(): string | null {
|
|
121
|
+
return this.getProperty('URL')?.text ?? null;
|
|
122
|
+
}
|
|
123
|
+
set url(v: string) {
|
|
124
|
+
this.setProperty('URL', v);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get status(): string | null {
|
|
128
|
+
return this.getProperty('STATUS')?.text ?? null;
|
|
129
|
+
}
|
|
130
|
+
set status(v: 'TENTATIVE' | 'CONFIRMED' | 'CANCELLED' | string) {
|
|
131
|
+
this.setProperty('STATUS', v);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get transp(): 'OPAQUE' | 'TRANSPARENT' | string | null {
|
|
135
|
+
return this.getProperty('TRANSP')?.text ?? null;
|
|
136
|
+
}
|
|
137
|
+
set transp(v: 'OPAQUE' | 'TRANSPARENT' | string) {
|
|
138
|
+
this.setProperty('TRANSP', v);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get klass(): string | null {
|
|
142
|
+
return this.getProperty('CLASS')?.text ?? null;
|
|
143
|
+
}
|
|
144
|
+
set klass(v: 'PUBLIC' | 'PRIVATE' | 'CONFIDENTIAL' | string) {
|
|
145
|
+
this.setProperty('CLASS', v);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get priority(): number | null {
|
|
149
|
+
return this.getProperty('PRIORITY')?.number ?? null;
|
|
150
|
+
}
|
|
151
|
+
set priority(v: number) {
|
|
152
|
+
this.setProperty('PRIORITY', Math.max(0, Math.min(9, Math.trunc(v))));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get sequence(): number | null {
|
|
156
|
+
return this.getProperty('SEQUENCE')?.number ?? null;
|
|
157
|
+
}
|
|
158
|
+
set sequence(v: number) {
|
|
159
|
+
this.setProperty('SEQUENCE', Math.max(0, Math.trunc(v)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get created(): ICalDateTime | null {
|
|
163
|
+
const v = this.getValue('CREATED');
|
|
164
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
165
|
+
}
|
|
166
|
+
set created(v: ICalDateTime | Date) {
|
|
167
|
+
this.setProperty('CREATED', v instanceof Date
|
|
168
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
169
|
+
: v);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get lastModified(): ICalDateTime | null {
|
|
173
|
+
const v = this.getValue('LAST-MODIFIED');
|
|
174
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
175
|
+
}
|
|
176
|
+
set lastModified(v: ICalDateTime | Date) {
|
|
177
|
+
this.setProperty('LAST-MODIFIED', v instanceof Date
|
|
178
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
179
|
+
: v);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get recurrenceId(): ICalDateTime | ICalDate | null {
|
|
183
|
+
const v = this.getValue('RECURRENCE-ID');
|
|
184
|
+
if (!v || typeof v !== 'object') return null;
|
|
185
|
+
return v.type === 'date-time' || v.type === 'date' ? (v as ICalDateTime | ICalDate) : null;
|
|
186
|
+
}
|
|
187
|
+
set recurrenceId(v: ICalDateTime | ICalDate) {
|
|
188
|
+
const params: Record<string, string> = v.type === 'date' ? { VALUE: 'DATE' } : {};
|
|
189
|
+
this.setProperty('RECURRENCE-ID', v, params);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
get geo(): ICalGeo | null {
|
|
193
|
+
const v = this.getValue('GEO');
|
|
194
|
+
return v && typeof v === 'object' && v.type === 'geo' ? (v as ICalGeo) : null;
|
|
195
|
+
}
|
|
196
|
+
set geo(v: ICalGeo) {
|
|
197
|
+
this.setProperty('GEO', v);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
get organizer(): string | null {
|
|
201
|
+
return this.getProperty('ORGANIZER')?.text ?? null;
|
|
202
|
+
}
|
|
203
|
+
set organizer(v: string) {
|
|
204
|
+
this.setProperty('ORGANIZER', v);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Multi-occurrence properties ──────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
get attendees(): Property[] {
|
|
210
|
+
return this.getProperties('ATTENDEE');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
addAttendee(calAddress: string, params: Record<string, string> = {}): this {
|
|
214
|
+
this.appendProperty('ATTENDEE', calAddress, params);
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get categories(): ICalValue[] {
|
|
219
|
+
return this.getValues('CATEGORIES');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
set categories(v: string[]) {
|
|
223
|
+
this.setProperty('CATEGORIES', v);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get comments(): Property[] {
|
|
227
|
+
return this.getProperties('COMMENT');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
addComment(v: string): this {
|
|
231
|
+
this.appendProperty('COMMENT', v);
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get contacts(): Property[] {
|
|
236
|
+
return this.getProperties('CONTACT');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
addContact(v: string): this {
|
|
240
|
+
this.appendProperty('CONTACT', v);
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
get exdates(): ICalValue[] {
|
|
245
|
+
return this.getProperties('EXDATE').flatMap((p) => p.list);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
addExdate(v: ICalDateTime | ICalDate): this {
|
|
249
|
+
const params: Record<string, string> = v.type === 'date' ? { VALUE: 'DATE' } : {};
|
|
250
|
+
this.appendProperty('EXDATE', v, params);
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
get rdates(): ICalValue[] {
|
|
255
|
+
return this.getProperties('RDATE').flatMap((p) => p.list);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
addRdate(v: ICalDateTime | ICalDate): this {
|
|
259
|
+
const params: Record<string, string> = v.type === 'date' ? { VALUE: 'DATE' } : {};
|
|
260
|
+
this.appendProperty('RDATE', v, params);
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
get rrules(): ICalRecur[] {
|
|
265
|
+
return this.getProperties('RRULE')
|
|
266
|
+
.map((p) => p.scalar)
|
|
267
|
+
.filter((v): v is ICalRecur => typeof v === 'object' && v !== null && v.type === 'recur');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
addRrule(v: ICalRecur): this {
|
|
271
|
+
this.appendProperty('RRULE', v);
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
get alarms(): Alarm[] {
|
|
276
|
+
return this.getComponents('VALARM') as Alarm[];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
addAlarm(alarm: Alarm): this {
|
|
280
|
+
this.addComponent(alarm);
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Strict validation ────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
override toString(): string {
|
|
287
|
+
if (!this.uid) throw new Error('VEVENT: UID is required');
|
|
288
|
+
if (!this.getProperty('DTSTAMP')) throw new Error('VEVENT: DTSTAMP is required');
|
|
289
|
+
return super.toString();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Factory ──────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
static fromRaw(
|
|
295
|
+
props: ReadonlyArray<{ name: string; params: Record<string, string>; value: string }>,
|
|
296
|
+
subcomponents: Component[] = [],
|
|
297
|
+
): Event {
|
|
298
|
+
const event = new Event();
|
|
299
|
+
for (const { name, params, value } of props) {
|
|
300
|
+
event.addProperty(parseProperty(name, value, params));
|
|
301
|
+
}
|
|
302
|
+
for (const sub of subcomponents) {
|
|
303
|
+
event.addComponent(sub);
|
|
304
|
+
}
|
|
305
|
+
return event;
|
|
306
|
+
}
|
|
307
|
+
}
|
package/src/freebusy.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VFREEBUSY component (RFC 5545 §3.6.4)
|
|
3
|
+
*
|
|
4
|
+
* UID and DTSTAMP are required.
|
|
5
|
+
* FREEBUSY properties list free/busy time periods.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Component } from './component.js';
|
|
9
|
+
import { Property, parseProperty } from './property.js';
|
|
10
|
+
import type { ICalDateTime, ICalPeriod } from './types.js';
|
|
11
|
+
|
|
12
|
+
export class FreeBusy extends Component {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('VFREEBUSY');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── Required ─────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
get uid(): string | null {
|
|
20
|
+
return this.getProperty('UID')?.text ?? null;
|
|
21
|
+
}
|
|
22
|
+
set uid(v: string) {
|
|
23
|
+
this.setProperty('UID', v);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get dtstamp(): ICalDateTime | null {
|
|
27
|
+
const v = this.getValue('DTSTAMP');
|
|
28
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
29
|
+
}
|
|
30
|
+
set dtstamp(v: ICalDateTime | Date) {
|
|
31
|
+
this.setProperty('DTSTAMP', v instanceof Date
|
|
32
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
33
|
+
: v);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Date/time span ───────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
get dtstart(): ICalDateTime | null {
|
|
39
|
+
const v = this.getValue('DTSTART');
|
|
40
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
41
|
+
}
|
|
42
|
+
set dtstart(v: ICalDateTime | Date) {
|
|
43
|
+
this.setProperty('DTSTART', v instanceof Date
|
|
44
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
45
|
+
: v);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get dtend(): ICalDateTime | null {
|
|
49
|
+
const v = this.getValue('DTEND');
|
|
50
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
51
|
+
}
|
|
52
|
+
set dtend(v: ICalDateTime | Date) {
|
|
53
|
+
this.setProperty('DTEND', v instanceof Date
|
|
54
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
55
|
+
: v);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Participants ──────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
get organizer(): string | null {
|
|
61
|
+
return this.getProperty('ORGANIZER')?.text ?? null;
|
|
62
|
+
}
|
|
63
|
+
set organizer(v: string) {
|
|
64
|
+
this.setProperty('ORGANIZER', v);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get attendees(): Property[] {
|
|
68
|
+
return this.getProperties('ATTENDEE');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
addAttendee(calAddress: string, params: Record<string, string> = {}): this {
|
|
72
|
+
this.appendProperty('ATTENDEE', calAddress, params);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get url(): string | null {
|
|
77
|
+
return this.getProperty('URL')?.text ?? null;
|
|
78
|
+
}
|
|
79
|
+
set url(v: string) {
|
|
80
|
+
this.setProperty('URL', v);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get comment(): string | null {
|
|
84
|
+
return this.getProperty('COMMENT')?.text ?? null;
|
|
85
|
+
}
|
|
86
|
+
set comment(v: string) {
|
|
87
|
+
this.setProperty('COMMENT', v);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── FREEBUSY periods ─────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/** All FREEBUSY properties (may have multiple, each with FBTYPE parameter). */
|
|
93
|
+
get freebusyProperties(): Property[] {
|
|
94
|
+
return this.getProperties('FREEBUSY');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Flat list of all PERIOD values across all FREEBUSY properties. */
|
|
98
|
+
get periods(): ICalPeriod[] {
|
|
99
|
+
return this.getProperties('FREEBUSY')
|
|
100
|
+
.flatMap((p) => p.list)
|
|
101
|
+
.filter((v): v is ICalPeriod => typeof v === 'object' && v !== null && v.type === 'period');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Add a FREEBUSY property containing one or more periods.
|
|
106
|
+
* @param periods - Array of periods for this FREEBUSY property.
|
|
107
|
+
* @param fbtype - FBTYPE parameter value (FREE, BUSY, BUSY-UNAVAILABLE, BUSY-TENTATIVE).
|
|
108
|
+
*/
|
|
109
|
+
addFreebusy(periods: ICalPeriod[], fbtype = 'BUSY'): this {
|
|
110
|
+
this.appendProperty('FREEBUSY', periods, { FBTYPE: fbtype });
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Validation ────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
override toString(): string {
|
|
117
|
+
if (!this.uid) throw new Error('VFREEBUSY: UID is required');
|
|
118
|
+
if (!this.getProperty('DTSTAMP')) throw new Error('VFREEBUSY: DTSTAMP is required');
|
|
119
|
+
return super.toString();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Factory ──────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
static fromRaw(
|
|
125
|
+
props: ReadonlyArray<{ name: string; params: Record<string, string>; value: string }>,
|
|
126
|
+
): FreeBusy {
|
|
127
|
+
const fb = new FreeBusy();
|
|
128
|
+
for (const { name, params, value } of props) {
|
|
129
|
+
fb.addProperty(parseProperty(name, value, params));
|
|
130
|
+
}
|
|
131
|
+
return fb;
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pipobscure/ical — Full iCalendar (RFC 5545) implementation.
|
|
3
|
+
*
|
|
4
|
+
* Tolerant parsing, strict generation.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { parse, Calendar, Event } from '@pipobscure/ical';
|
|
9
|
+
*
|
|
10
|
+
* // Parse
|
|
11
|
+
* const cal = parse(icsString);
|
|
12
|
+
* for (const event of cal.events) {
|
|
13
|
+
* console.log(event.summary, event.dtstart);
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* // Build & serialize
|
|
17
|
+
* const cal = Calendar.create('-//My App//EN');
|
|
18
|
+
* const event = new Event();
|
|
19
|
+
* event.uid = crypto.randomUUID();
|
|
20
|
+
* event.dtstamp = new Date();
|
|
21
|
+
* event.dtstart = new Date('2025-01-01T10:00:00Z');
|
|
22
|
+
* event.dtend = new Date('2025-01-01T11:00:00Z');
|
|
23
|
+
* event.summary = 'New Year kickoff';
|
|
24
|
+
* cal.addEvent(event);
|
|
25
|
+
* console.log(cal.toString());
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ── Parsing ───────────────────────────────────────────────────────────────
|
|
30
|
+
export { parse, parseAll } from './parse.js';
|
|
31
|
+
|
|
32
|
+
// ── Components ────────────────────────────────────────────────────────────
|
|
33
|
+
export { Component } from './component.js';
|
|
34
|
+
export { Calendar } from './calendar.js';
|
|
35
|
+
export { Event } from './event.js';
|
|
36
|
+
export { Todo } from './todo.js';
|
|
37
|
+
export { Journal } from './journal.js';
|
|
38
|
+
export { FreeBusy } from './freebusy.js';
|
|
39
|
+
export { Timezone, Standard, Daylight, TimezoneRule } from './timezone.js';
|
|
40
|
+
export { Alarm } from './alarm.js';
|
|
41
|
+
|
|
42
|
+
// ── Property & value primitives ───────────────────────────────────────────
|
|
43
|
+
export { Property, parseProperty } from './property.js';
|
|
44
|
+
|
|
45
|
+
// ── Value type codecs (for advanced use) ─────────────────────────────────
|
|
46
|
+
export {
|
|
47
|
+
TEXT,
|
|
48
|
+
BOOLEAN,
|
|
49
|
+
INTEGER,
|
|
50
|
+
FLOAT,
|
|
51
|
+
URI,
|
|
52
|
+
CAL_ADDRESS,
|
|
53
|
+
BINARY,
|
|
54
|
+
UTC_OFFSET,
|
|
55
|
+
DATE,
|
|
56
|
+
DATE_TIME,
|
|
57
|
+
TIME,
|
|
58
|
+
DURATION,
|
|
59
|
+
PERIOD,
|
|
60
|
+
RECUR,
|
|
61
|
+
GEO,
|
|
62
|
+
CODECS,
|
|
63
|
+
} from './value-types.js';
|
|
64
|
+
|
|
65
|
+
// ── Types ─────────────────────────────────────────────────────────────────
|
|
66
|
+
export type {
|
|
67
|
+
ICalDate,
|
|
68
|
+
ICalDateTime,
|
|
69
|
+
ICalTime,
|
|
70
|
+
ICalDuration,
|
|
71
|
+
ICalPeriod,
|
|
72
|
+
ICalRecur,
|
|
73
|
+
ICalUtcOffset,
|
|
74
|
+
ICalGeo,
|
|
75
|
+
ICalBinary,
|
|
76
|
+
ICalStructured,
|
|
77
|
+
ICalValue,
|
|
78
|
+
RecurFreq,
|
|
79
|
+
Weekday,
|
|
80
|
+
ByDayRule,
|
|
81
|
+
ContentLine,
|
|
82
|
+
ParsedProperty,
|
|
83
|
+
} from './types.js';
|
|
84
|
+
|
|
85
|
+
export type { ValueTypeName, PropertyDef } from './property-registry.js';
|
package/src/journal.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VJOURNAL component (RFC 5545 §3.6.3)
|
|
3
|
+
*
|
|
4
|
+
* UID and DTSTAMP are required.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Component } from './component.js';
|
|
8
|
+
import { Property, parseProperty } from './property.js';
|
|
9
|
+
import type { ICalDate, ICalDateTime, ICalRecur, ICalValue } from './types.js';
|
|
10
|
+
|
|
11
|
+
export class Journal extends Component {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('VJOURNAL');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── Required ─────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
get uid(): string | null {
|
|
19
|
+
return this.getProperty('UID')?.text ?? null;
|
|
20
|
+
}
|
|
21
|
+
set uid(v: string) {
|
|
22
|
+
this.setProperty('UID', v);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get dtstamp(): ICalDateTime | null {
|
|
26
|
+
const v = this.getValue('DTSTAMP');
|
|
27
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
28
|
+
}
|
|
29
|
+
set dtstamp(v: ICalDateTime | Date) {
|
|
30
|
+
this.setProperty('DTSTAMP', v instanceof Date
|
|
31
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
32
|
+
: v);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Date/time ─────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
get dtstart(): ICalDateTime | ICalDate | null {
|
|
38
|
+
const v = this.getValue('DTSTART');
|
|
39
|
+
if (!v || typeof v !== 'object') return null;
|
|
40
|
+
return v.type === 'date-time' || v.type === 'date' ? (v as ICalDateTime | ICalDate) : null;
|
|
41
|
+
}
|
|
42
|
+
set dtstart(v: ICalDateTime | ICalDate | Date) {
|
|
43
|
+
if (v instanceof Date) {
|
|
44
|
+
this.setProperty('DTSTART', { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime);
|
|
45
|
+
} else if (v.type === 'date') {
|
|
46
|
+
this.setProperty('DTSTART', v, { VALUE: 'DATE' });
|
|
47
|
+
} else {
|
|
48
|
+
this.setProperty('DTSTART', v);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Descriptive ──────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
get summary(): string | null {
|
|
55
|
+
return this.getProperty('SUMMARY')?.text ?? null;
|
|
56
|
+
}
|
|
57
|
+
set summary(v: string) {
|
|
58
|
+
this.setProperty('SUMMARY', v);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get descriptions(): Property[] {
|
|
62
|
+
return this.getProperties('DESCRIPTION');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addDescription(v: string): this {
|
|
66
|
+
this.appendProperty('DESCRIPTION', v);
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get status(): 'DRAFT' | 'FINAL' | 'CANCELLED' | string | null {
|
|
71
|
+
return this.getProperty('STATUS')?.text ?? null;
|
|
72
|
+
}
|
|
73
|
+
set status(v: string) {
|
|
74
|
+
this.setProperty('STATUS', v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get klass(): string | null {
|
|
78
|
+
return this.getProperty('CLASS')?.text ?? null;
|
|
79
|
+
}
|
|
80
|
+
set klass(v: string) {
|
|
81
|
+
this.setProperty('CLASS', v);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get url(): string | null {
|
|
85
|
+
return this.getProperty('URL')?.text ?? null;
|
|
86
|
+
}
|
|
87
|
+
set url(v: string) {
|
|
88
|
+
this.setProperty('URL', v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get organizer(): string | null {
|
|
92
|
+
return this.getProperty('ORGANIZER')?.text ?? null;
|
|
93
|
+
}
|
|
94
|
+
set organizer(v: string) {
|
|
95
|
+
this.setProperty('ORGANIZER', v);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get sequence(): number | null {
|
|
99
|
+
return this.getProperty('SEQUENCE')?.number ?? null;
|
|
100
|
+
}
|
|
101
|
+
set sequence(v: number) {
|
|
102
|
+
this.setProperty('SEQUENCE', Math.max(0, Math.trunc(v)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get created(): ICalDateTime | null {
|
|
106
|
+
const v = this.getValue('CREATED');
|
|
107
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
108
|
+
}
|
|
109
|
+
set created(v: ICalDateTime | Date) {
|
|
110
|
+
this.setProperty('CREATED', v instanceof Date
|
|
111
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
112
|
+
: v);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get lastModified(): ICalDateTime | null {
|
|
116
|
+
const v = this.getValue('LAST-MODIFIED');
|
|
117
|
+
return v && typeof v === 'object' && v.type === 'date-time' ? (v as ICalDateTime) : null;
|
|
118
|
+
}
|
|
119
|
+
set lastModified(v: ICalDateTime | Date) {
|
|
120
|
+
this.setProperty('LAST-MODIFIED', v instanceof Date
|
|
121
|
+
? { type: 'date-time', year: v.getUTCFullYear(), month: v.getUTCMonth() + 1, day: v.getUTCDate(), hour: v.getUTCHours(), minute: v.getUTCMinutes(), second: v.getUTCSeconds(), utc: true } satisfies ICalDateTime
|
|
122
|
+
: v);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Multi-occurrence ──────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
get attendees(): Property[] {
|
|
128
|
+
return this.getProperties('ATTENDEE');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
addAttendee(calAddress: string, params: Record<string, string> = {}): this {
|
|
132
|
+
this.appendProperty('ATTENDEE', calAddress, params);
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get categories(): ICalValue[] {
|
|
137
|
+
return this.getValues('CATEGORIES');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
set categories(v: string[]) {
|
|
141
|
+
this.setProperty('CATEGORIES', v);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get rrules(): ICalRecur[] {
|
|
145
|
+
return this.getProperties('RRULE')
|
|
146
|
+
.map((p) => p.scalar)
|
|
147
|
+
.filter((v): v is ICalRecur => typeof v === 'object' && v !== null && v.type === 'recur');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
addRrule(v: ICalRecur): this {
|
|
151
|
+
this.appendProperty('RRULE', v);
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Validation ────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
override toString(): string {
|
|
158
|
+
if (!this.uid) throw new Error('VJOURNAL: UID is required');
|
|
159
|
+
if (!this.getProperty('DTSTAMP')) throw new Error('VJOURNAL: DTSTAMP is required');
|
|
160
|
+
return super.toString();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Factory ──────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
static fromRaw(
|
|
166
|
+
props: ReadonlyArray<{ name: string; params: Record<string, string>; value: string }>,
|
|
167
|
+
): Journal {
|
|
168
|
+
const journal = new Journal();
|
|
169
|
+
for (const { name, params, value } of props) {
|
|
170
|
+
journal.addProperty(parseProperty(name, value, params));
|
|
171
|
+
}
|
|
172
|
+
return journal;
|
|
173
|
+
}
|
|
174
|
+
}
|