@hebcal/icalendar 5.1.0 → 6.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/README.md +12 -55
- package/dist/icalendar.d.ts +2 -24
- package/dist/index.mjs +67 -54
- package/dist/pkgVersion.d.ts +1 -1
- package/package.json +24 -21
- package/dist/index.cjs +0 -492
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# hebcal
|
|
1
|
+
# @hebcal/icalendar
|
|
2
2
|
Jewish holidays and Hebrew calendar as iCalendar RFC 2445
|
|
3
3
|
|
|
4
4
|
## Installation
|
|
@@ -10,68 +10,25 @@ $ npm install @hebcal/icalendar
|
|
|
10
10
|
```javascript
|
|
11
11
|
import {HebrewCalendar, Location} from '@hebcal/core';
|
|
12
12
|
import {eventsToIcalendar} from '@hebcal/icalendar';
|
|
13
|
+
import fs from 'fs';
|
|
13
14
|
|
|
14
15
|
const options = {
|
|
15
16
|
year: 2020,
|
|
16
17
|
month: 2,
|
|
17
18
|
sedrot: true,
|
|
18
19
|
candlelighting: true,
|
|
19
|
-
location: Location.lookup('
|
|
20
|
+
location: Location.lookup('Tel Aviv'),
|
|
20
21
|
};
|
|
21
22
|
const events = HebrewCalendar.calendar(options);
|
|
22
|
-
console.log(await eventsToIcalendar(ev, options));
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Functions
|
|
26
|
-
|
|
27
|
-
<dl>
|
|
28
|
-
<dt><a href="#eventToIcal">eventToIcal(e, options)</a> ⇒ <code>string</code></dt>
|
|
29
|
-
<dd><p>Transforms a single Event into a VEVENT string</p>
|
|
30
|
-
</dd>
|
|
31
|
-
<dt><a href="#eventsToIcalendarStream">eventsToIcalendarStream(readable, events, options)</a> ⇒ <code>stream.Readable</code></dt>
|
|
32
|
-
<dd><p>Generates an RFC 2445 iCalendar stream from an array of events</p>
|
|
33
|
-
</dd>
|
|
34
|
-
<dt><a href="#eventsToIcalendar">eventsToIcalendar(events, options)</a> ⇒ <code>string</code></dt>
|
|
35
|
-
<dd><p>Renders an array of events as a full RFC 2445 iCalendar string</p>
|
|
36
|
-
</dd>
|
|
37
|
-
</dl>
|
|
38
|
-
|
|
39
|
-
<a name="eventToIcal"></a>
|
|
40
|
-
|
|
41
|
-
## eventToIcal(e, options) ⇒ <code>string</code>
|
|
42
|
-
Transforms a single Event into a VEVENT string
|
|
43
|
-
|
|
44
|
-
**Kind**: global function
|
|
45
|
-
**Returns**: <code>string</code> - multi-line result, delimited by \r\n
|
|
46
|
-
|
|
47
|
-
| Param | Type |
|
|
48
|
-
| --- | --- |
|
|
49
|
-
| e | <code>Event</code> |
|
|
50
|
-
| options | <code>HebcalOptions</code> |
|
|
51
23
|
|
|
52
|
-
|
|
24
|
+
const str = await eventsToIcalendar(events, {
|
|
25
|
+
locale: 'he',
|
|
26
|
+
...options,
|
|
27
|
+
})
|
|
53
28
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| Param | Type |
|
|
60
|
-
| --- | --- |
|
|
61
|
-
| readable | <code>stream.Readable</code> |
|
|
62
|
-
| events | <code>Array.<Event></code> |
|
|
63
|
-
| options | <code>HebcalOptions</code> |
|
|
64
|
-
|
|
65
|
-
<a name="eventsToIcalendar"></a>
|
|
66
|
-
|
|
67
|
-
## eventsToIcalendar(events, options) ⇒ <code>string</code>
|
|
68
|
-
Renders an array of events as a full RFC 2445 iCalendar string
|
|
69
|
-
|
|
70
|
-
**Kind**: global function
|
|
71
|
-
**Returns**: <code>string</code> - multi-line result, delimited by \r\n
|
|
72
|
-
|
|
73
|
-
| Param | Type |
|
|
74
|
-
| --- | --- |
|
|
75
|
-
| events | <code>Array.<Event></code> |
|
|
76
|
-
| options | <code>HebcalOptions</code> |
|
|
29
|
+
const icalStream = fs.createWriteStream('feed.ics');
|
|
30
|
+
icalStream.write(str);
|
|
31
|
+
icalStream.close();
|
|
32
|
+
```
|
|
77
33
|
|
|
34
|
+
## [API Documentation](https://hebcal.github.io/api/icalendar/index.html)
|
package/dist/icalendar.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type ICalEventOptions = {
|
|
|
15
15
|
relcalid?: string;
|
|
16
16
|
yahrzeit?: boolean;
|
|
17
17
|
subscribe?: string | boolean;
|
|
18
|
+
url?: boolean;
|
|
18
19
|
};
|
|
19
20
|
export type ICalOptions = CalOptions & ICalEventOptions;
|
|
20
21
|
/**
|
|
@@ -41,51 +42,28 @@ export declare class IcalEvent {
|
|
|
41
42
|
*/
|
|
42
43
|
constructor(ev: Event, options?: ICalOptions);
|
|
43
44
|
getAlarm(): string | null;
|
|
44
|
-
/**
|
|
45
|
-
* @return {string}
|
|
46
|
-
*/
|
|
47
45
|
getUid(): string;
|
|
48
|
-
/**
|
|
49
|
-
* @return {string[]}
|
|
50
|
-
*/
|
|
51
46
|
getLongLines(): string[];
|
|
52
|
-
/**
|
|
53
|
-
* @return {string}
|
|
54
|
-
*/
|
|
55
47
|
toString(): string;
|
|
56
48
|
/**
|
|
57
49
|
* fold lines to 75 characters
|
|
58
|
-
* @return {string[]}
|
|
59
50
|
*/
|
|
60
51
|
getLines(): string[];
|
|
61
52
|
/**
|
|
62
53
|
* fold line to 75 characters
|
|
63
|
-
* @param {string} line
|
|
64
|
-
* @return {string}
|
|
65
54
|
*/
|
|
66
55
|
static fold(line: string): string;
|
|
67
|
-
/**
|
|
68
|
-
* @param {string} str
|
|
69
|
-
* @return {string}
|
|
70
|
-
*/
|
|
71
56
|
static escape(str: string): string;
|
|
72
|
-
/**
|
|
73
|
-
* @param {Date} dt
|
|
74
|
-
* @return {string}
|
|
75
|
-
*/
|
|
76
57
|
static formatYYYYMMDD(dt: Date): string;
|
|
77
58
|
/**
|
|
78
59
|
* Returns UTC string for iCalendar
|
|
79
|
-
* @param {Date} dt
|
|
80
|
-
* @return {string}
|
|
81
60
|
*/
|
|
82
61
|
static makeDtstamp(dt: Date): string;
|
|
83
|
-
/** @return {string} */
|
|
84
62
|
static version(): string;
|
|
85
63
|
}
|
|
86
64
|
/**
|
|
87
65
|
* Transforms a single Event into a VEVENT string
|
|
88
|
-
* @
|
|
66
|
+
* @returns multi-line result, delimited by \r\n
|
|
89
67
|
*/
|
|
90
68
|
export declare function eventToIcal(ev: Event, options: ICalOptions): string;
|
|
91
69
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
/*! @hebcal/icalendar
|
|
2
|
-
import {
|
|
1
|
+
/*! @hebcal/icalendar v6.0.1 */
|
|
2
|
+
import { Locale, pad2, greg, pad4 } from '@hebcal/hdate';
|
|
3
|
+
import { flags } from '@hebcal/core';
|
|
3
4
|
import { murmur32HexSync } from 'murmurhash3';
|
|
4
|
-
import { shouldRenderBrief,
|
|
5
|
+
import { shouldRenderBrief, getEventCategories, makeAnchor, getHolidayDescription, getCalendarTitle, appendIsraelAndTracking, makeTorahMemoText } from '@hebcal/rest-api';
|
|
5
6
|
import { promises } from 'fs';
|
|
6
7
|
|
|
7
8
|
// DO NOT EDIT THIS AUTO-GENERATED FILE!
|
|
8
|
-
const version = '
|
|
9
|
+
const version = '6.0.1';
|
|
9
10
|
|
|
10
11
|
const vtimezoneCache = new Map();
|
|
11
12
|
const CATEGORY = {
|
|
@@ -48,8 +49,11 @@ function appendTrackingToUrl(url, options) {
|
|
|
48
49
|
}
|
|
49
50
|
const encoder = new TextEncoder();
|
|
50
51
|
const char74re = /(.{1,74})/g;
|
|
51
|
-
const DAILY_LEARNING = flags.DAILY_LEARNING |
|
|
52
|
-
flags.
|
|
52
|
+
const DAILY_LEARNING = flags.DAILY_LEARNING |
|
|
53
|
+
flags.DAF_YOMI |
|
|
54
|
+
flags.MISHNA_YOMI |
|
|
55
|
+
flags.YERUSHALMI_YOMI |
|
|
56
|
+
flags.NACH_YOMI;
|
|
53
57
|
/**
|
|
54
58
|
* Represents an RFC 2445 iCalendar VEVENT
|
|
55
59
|
*/
|
|
@@ -69,10 +73,12 @@ class IcalEvent {
|
|
|
69
73
|
else if (typeof opts.sequence === 'number') {
|
|
70
74
|
this.sequence = opts.sequence;
|
|
71
75
|
}
|
|
72
|
-
const timed = this.timed = Boolean(ev.eventTime);
|
|
76
|
+
const timed = (this.timed = Boolean(ev.eventTime));
|
|
73
77
|
const locale = opts.locale;
|
|
74
78
|
const location = opts.location;
|
|
75
|
-
let subj = shouldRenderBrief(ev)
|
|
79
|
+
let subj = shouldRenderBrief(ev)
|
|
80
|
+
? ev.renderBrief(locale)
|
|
81
|
+
: ev.render(locale);
|
|
76
82
|
const mask = ev.getFlags();
|
|
77
83
|
if (ev.locationName) {
|
|
78
84
|
this.locationName = ev.locationName;
|
|
@@ -80,7 +86,7 @@ class IcalEvent {
|
|
|
80
86
|
else if (timed && location) {
|
|
81
87
|
this.locationName = location.getShortName();
|
|
82
88
|
}
|
|
83
|
-
else if (
|
|
89
|
+
else if (mask & DAILY_LEARNING && ev.category) {
|
|
84
90
|
this.locationName = Locale.gettext(ev.category, locale);
|
|
85
91
|
}
|
|
86
92
|
const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
|
|
@@ -129,7 +135,8 @@ class IcalEvent {
|
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
this.subj = subj;
|
|
132
|
-
this.category =
|
|
138
|
+
this.category =
|
|
139
|
+
ev.category || CATEGORY[(_a = getEventCategories(ev)) === null || _a === void 0 ? void 0 : _a[0]];
|
|
133
140
|
}
|
|
134
141
|
getAlarm() {
|
|
135
142
|
const ev = this.ev;
|
|
@@ -157,9 +164,6 @@ class IcalEvent {
|
|
|
157
164
|
}
|
|
158
165
|
return null;
|
|
159
166
|
}
|
|
160
|
-
/**
|
|
161
|
-
* @return {string}
|
|
162
|
-
*/
|
|
163
167
|
getUid() {
|
|
164
168
|
if (this.ev.uid) {
|
|
165
169
|
return this.ev.uid;
|
|
@@ -178,9 +182,6 @@ class IcalEvent {
|
|
|
178
182
|
}
|
|
179
183
|
return uid;
|
|
180
184
|
}
|
|
181
|
-
/**
|
|
182
|
-
* @return {string[]}
|
|
183
|
-
*/
|
|
184
185
|
getLongLines() {
|
|
185
186
|
if (this.lines)
|
|
186
187
|
return this.lines;
|
|
@@ -189,17 +190,16 @@ class IcalEvent {
|
|
|
189
190
|
if (this.sequence) {
|
|
190
191
|
categoryLine.unshift(`SEQUENCE:${this.sequence}`);
|
|
191
192
|
}
|
|
192
|
-
const arr = this.lines = [
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
].concat(categoryLine).concat([
|
|
193
|
+
const arr = (this.lines = ['BEGIN:VEVENT', `DTSTAMP:${this.dtstamp}`]
|
|
194
|
+
.concat(categoryLine)
|
|
195
|
+
.concat([
|
|
196
196
|
`SUMMARY:${this.subj}`,
|
|
197
197
|
`DTSTART${this.dtargs}:${this.startDate}`,
|
|
198
198
|
`DTEND${this.dtargs}:${this.endDate}`,
|
|
199
199
|
`UID:${uid}`,
|
|
200
200
|
`TRANSP:${this.transp}`,
|
|
201
201
|
`X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`,
|
|
202
|
-
]);
|
|
202
|
+
]));
|
|
203
203
|
if (!this.timed) {
|
|
204
204
|
arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
|
|
205
205
|
}
|
|
@@ -218,6 +218,14 @@ class IcalEvent {
|
|
|
218
218
|
if (this.timed && loc) {
|
|
219
219
|
arr.push('GEO:' + loc.getLatitude() + ';' + loc.getLongitude());
|
|
220
220
|
}
|
|
221
|
+
// In addition to the URL being part of the DESCRIPTION field,
|
|
222
|
+
// should we also generate an RFC 5545 URL property?
|
|
223
|
+
if (options.url) {
|
|
224
|
+
const url = ev.url();
|
|
225
|
+
if (url) {
|
|
226
|
+
arr.push('URL:' + appendTrackingToUrl(url, options));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
221
229
|
const trigger = this.getAlarm();
|
|
222
230
|
if (trigger) {
|
|
223
231
|
arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
|
|
@@ -225,23 +233,17 @@ class IcalEvent {
|
|
|
225
233
|
arr.push('END:VEVENT');
|
|
226
234
|
return arr;
|
|
227
235
|
}
|
|
228
|
-
/**
|
|
229
|
-
* @return {string}
|
|
230
|
-
*/
|
|
231
236
|
toString() {
|
|
232
237
|
return this.getLines().join('\r\n');
|
|
233
238
|
}
|
|
234
239
|
/**
|
|
235
240
|
* fold lines to 75 characters
|
|
236
|
-
* @return {string[]}
|
|
237
241
|
*/
|
|
238
242
|
getLines() {
|
|
239
243
|
return this.getLongLines().map(IcalEvent.fold);
|
|
240
244
|
}
|
|
241
245
|
/**
|
|
242
246
|
* fold line to 75 characters
|
|
243
|
-
* @param {string} line
|
|
244
|
-
* @return {string}
|
|
245
247
|
*/
|
|
246
248
|
static fold(line) {
|
|
247
249
|
let isASCII = true;
|
|
@@ -284,10 +286,6 @@ class IcalEvent {
|
|
|
284
286
|
}
|
|
285
287
|
return result + current;
|
|
286
288
|
}
|
|
287
|
-
/**
|
|
288
|
-
* @param {string} str
|
|
289
|
-
* @return {string}
|
|
290
|
-
*/
|
|
291
289
|
static escape(str) {
|
|
292
290
|
if (str.indexOf(',') !== -1) {
|
|
293
291
|
str = str.replace(/,/g, '\\,');
|
|
@@ -297,46 +295,45 @@ class IcalEvent {
|
|
|
297
295
|
}
|
|
298
296
|
return str;
|
|
299
297
|
}
|
|
300
|
-
/**
|
|
301
|
-
* @param {Date} dt
|
|
302
|
-
* @return {string}
|
|
303
|
-
*/
|
|
304
298
|
static formatYYYYMMDD(dt) {
|
|
305
|
-
return pad4(dt.getFullYear()) +
|
|
306
|
-
pad2(dt.getMonth() + 1) + pad2(dt.getDate());
|
|
299
|
+
return (pad4(dt.getFullYear()) + pad2(dt.getMonth() + 1) + pad2(dt.getDate()));
|
|
307
300
|
}
|
|
308
301
|
/**
|
|
309
302
|
* Returns UTC string for iCalendar
|
|
310
|
-
* @param {Date} dt
|
|
311
|
-
* @return {string}
|
|
312
303
|
*/
|
|
313
304
|
static makeDtstamp(dt) {
|
|
314
305
|
const s = dt.toISOString();
|
|
315
|
-
return s.slice(0, 4) +
|
|
316
|
-
s.slice(
|
|
306
|
+
return (s.slice(0, 4) +
|
|
307
|
+
s.slice(5, 7) +
|
|
308
|
+
s.slice(8, 13) +
|
|
309
|
+
s.slice(14, 16) +
|
|
310
|
+
s.slice(17, 19) +
|
|
311
|
+
'Z');
|
|
317
312
|
}
|
|
318
|
-
/** @return {string} */
|
|
319
313
|
static version() {
|
|
320
314
|
return version;
|
|
321
315
|
}
|
|
322
316
|
}
|
|
323
317
|
/**
|
|
324
318
|
* Transforms a single Event into a VEVENT string
|
|
325
|
-
* @
|
|
319
|
+
* @returns multi-line result, delimited by \r\n
|
|
326
320
|
*/
|
|
327
321
|
function eventToIcal(ev, options) {
|
|
328
322
|
const ical = new IcalEvent(ev, options);
|
|
329
323
|
return ical.toString();
|
|
330
324
|
}
|
|
331
325
|
const torahMemoCache = new Map();
|
|
332
|
-
const HOLIDAY_IGNORE_MASK = DAILY_LEARNING |
|
|
333
|
-
flags.
|
|
326
|
+
const HOLIDAY_IGNORE_MASK = DAILY_LEARNING |
|
|
327
|
+
flags.OMER_COUNT |
|
|
328
|
+
flags.SHABBAT_MEVARCHIM |
|
|
329
|
+
flags.MOLAD |
|
|
330
|
+
flags.USER_EVENT |
|
|
334
331
|
flags.HEBREW_DATE;
|
|
335
332
|
/**
|
|
336
333
|
* @private
|
|
337
334
|
*/
|
|
338
335
|
function makeTorahMemo(ev, il) {
|
|
339
|
-
if (
|
|
336
|
+
if (ev.getFlags() & HOLIDAY_IGNORE_MASK || ev.eventTime) {
|
|
340
337
|
return '';
|
|
341
338
|
}
|
|
342
339
|
const hd = ev.getDate();
|
|
@@ -367,8 +364,16 @@ function createMemo(ev, options) {
|
|
|
367
364
|
const mask = ev.getFlags();
|
|
368
365
|
if (mask & flags.OMER_COUNT) {
|
|
369
366
|
const omerEv = ev;
|
|
370
|
-
const sefira = [
|
|
371
|
-
|
|
367
|
+
const sefira = [
|
|
368
|
+
omerEv.sefira('en'),
|
|
369
|
+
omerEv.sefira('he'),
|
|
370
|
+
omerEv.sefira('translit'),
|
|
371
|
+
].join('\\n');
|
|
372
|
+
return (omerEv.getTodayIs('en') +
|
|
373
|
+
'\\n\\n' +
|
|
374
|
+
omerEv.getTodayIs('he') +
|
|
375
|
+
'\\n\\n' +
|
|
376
|
+
sefira);
|
|
372
377
|
}
|
|
373
378
|
const url = appendTrackingToUrl(ev.url(), options);
|
|
374
379
|
const torahMemo = makeTorahMemo(ev, options.il);
|
|
@@ -398,6 +403,8 @@ function createMemo(ev, options) {
|
|
|
398
403
|
* Generates an RFC 2445 iCalendar string from an array of events
|
|
399
404
|
*/
|
|
400
405
|
async function eventsToIcalendar(events, options) {
|
|
406
|
+
if (!events.length)
|
|
407
|
+
throw new RangeError('Events can not be empty');
|
|
401
408
|
if (!options)
|
|
402
409
|
throw new TypeError('Invalid options object');
|
|
403
410
|
const opts = Object.assign({}, options);
|
|
@@ -405,24 +412,30 @@ async function eventsToIcalendar(events, options) {
|
|
|
405
412
|
if (!opts.title) {
|
|
406
413
|
opts.title = getCalendarTitle(events, opts);
|
|
407
414
|
}
|
|
408
|
-
const icals = events.map(
|
|
415
|
+
const icals = events.map(ev => new IcalEvent(ev, opts));
|
|
409
416
|
return icalEventsToString(icals, opts);
|
|
410
417
|
}
|
|
411
418
|
/**
|
|
412
419
|
* Generates an RFC 2445 iCalendar string from an array of IcalEvents
|
|
413
420
|
*/
|
|
414
421
|
async function icalEventsToString(icals, options) {
|
|
422
|
+
if (!icals.length)
|
|
423
|
+
throw new RangeError('Events can not be empty');
|
|
424
|
+
if (!options)
|
|
425
|
+
throw new TypeError('Invalid options object');
|
|
415
426
|
const stream = [];
|
|
416
427
|
const locale = options.locale || Locale.getLocaleName();
|
|
417
428
|
const uclang = locale.toUpperCase();
|
|
418
429
|
const opts = Object.assign({}, options);
|
|
419
430
|
opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
420
431
|
const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
|
|
421
|
-
const caldesc = opts.caldesc
|
|
422
|
-
opts.
|
|
423
|
-
|
|
424
|
-
'
|
|
425
|
-
|
|
432
|
+
const caldesc = opts.caldesc
|
|
433
|
+
? IcalEvent.escape(opts.caldesc)
|
|
434
|
+
: opts.yahrzeit
|
|
435
|
+
? 'Yahrzeits + Anniversaries from www.hebcal.com'
|
|
436
|
+
: 'Jewish Holidays from www.hebcal.com';
|
|
437
|
+
const prodid = opts.prodid ||
|
|
438
|
+
`-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
|
|
426
439
|
const preamble = [
|
|
427
440
|
'BEGIN:VCALENDAR',
|
|
428
441
|
'VERSION:2.0',
|
package/dist/pkgVersion.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "6.0.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hebcal/icalendar",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"author": "Michael J. Radwin (https://github.com/mjradwin)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ical",
|
|
@@ -9,15 +9,16 @@
|
|
|
9
9
|
"hebcal"
|
|
10
10
|
],
|
|
11
11
|
"description": "Jewish holidays and Hebrew calendar as iCalendar RFC 2445",
|
|
12
|
-
"main": "dist/index.cjs",
|
|
13
12
|
"module": "dist/index.mjs",
|
|
14
13
|
"type": "module",
|
|
15
14
|
"exports": {
|
|
16
15
|
"import": "./dist/index.mjs",
|
|
17
|
-
"require": "./dist/index.cjs",
|
|
18
16
|
"types": "./dist/icalendar.d.ts"
|
|
19
17
|
},
|
|
20
18
|
"typings": "dist/icalendar.d.ts",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">= 18.0.0"
|
|
21
|
+
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist"
|
|
23
24
|
],
|
|
@@ -29,10 +30,11 @@
|
|
|
29
30
|
"url": "https://github.com/hebcal/hebcal-icalendar/issues"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@hebcal/core": "^5.
|
|
33
|
-
"@hebcal/
|
|
33
|
+
"@hebcal/core": "^5.8.0",
|
|
34
|
+
"@hebcal/hdate": "^0.12.0",
|
|
35
|
+
"@hebcal/rest-api": "^5.2.0",
|
|
34
36
|
"murmurhash3": "^0.5.0",
|
|
35
|
-
"tslib": "^2.
|
|
37
|
+
"tslib": "^2.8.1"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
40
|
"build:rollup": "rollup -c",
|
|
@@ -40,27 +42,28 @@
|
|
|
40
42
|
"build": "npm run build:version && npm run build:rollup",
|
|
41
43
|
"prepare": "npm run build",
|
|
42
44
|
"pretest": "npm run build",
|
|
43
|
-
"
|
|
45
|
+
"docs": "typedoc",
|
|
44
46
|
"coverage": "jest --coverage",
|
|
45
|
-
"test": "jest"
|
|
47
|
+
"test": "jest",
|
|
48
|
+
"lint": "gts lint",
|
|
49
|
+
"clean": "gts clean",
|
|
50
|
+
"compile": "tsc",
|
|
51
|
+
"fix": "gts fix"
|
|
46
52
|
},
|
|
47
53
|
"license": "BSD-2-Clause",
|
|
48
54
|
"devDependencies": {
|
|
49
|
-
"@babel/preset-env": "^7.
|
|
50
|
-
"@babel/preset-typescript": "^7.
|
|
51
|
-
"@hebcal/learning": "^5.
|
|
52
|
-
"@rollup/plugin-commonjs": "^
|
|
55
|
+
"@babel/preset-env": "^7.26.0",
|
|
56
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
57
|
+
"@hebcal/learning": "^5.1.2",
|
|
58
|
+
"@rollup/plugin-commonjs": "^28.0.1",
|
|
53
59
|
"@rollup/plugin-json": "^6.1.0",
|
|
54
|
-
"@rollup/plugin-typescript": "^
|
|
55
|
-
"@types/jest": "^29.5.
|
|
56
|
-
"
|
|
57
|
-
"eslint-config-google": "^0.14.0",
|
|
60
|
+
"@rollup/plugin-typescript": "^12.1.1",
|
|
61
|
+
"@types/jest": "^29.5.14",
|
|
62
|
+
"@types/node": "22.9.0",
|
|
58
63
|
"gts": "^5.3.1",
|
|
59
64
|
"jest": "^29.7.0",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"ts-jest": "^29.1.5",
|
|
64
|
-
"typescript": "^5.5.2"
|
|
65
|
+
"rollup": "^4.25.0",
|
|
66
|
+
"typedoc": "^0.26.11",
|
|
67
|
+
"typescript": "^5.6.3"
|
|
65
68
|
}
|
|
66
69
|
}
|
package/dist/index.cjs
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
/*! @hebcal/icalendar v5.1.0 */
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
var core = require('@hebcal/core');
|
|
5
|
-
var murmurhash3 = require('murmurhash3');
|
|
6
|
-
var restApi = require('@hebcal/rest-api');
|
|
7
|
-
var fs = require('fs');
|
|
8
|
-
|
|
9
|
-
// DO NOT EDIT THIS AUTO-GENERATED FILE!
|
|
10
|
-
const version = '5.1.0';
|
|
11
|
-
|
|
12
|
-
const vtimezoneCache = new Map();
|
|
13
|
-
const CATEGORY = {
|
|
14
|
-
candles: 'Holiday',
|
|
15
|
-
dafyomi: 'Daf Yomi',
|
|
16
|
-
mishnayomi: 'Mishna Yomi',
|
|
17
|
-
nachyomi: 'Nach Yomi',
|
|
18
|
-
yerushalmi: 'Yerushalmi Yomi',
|
|
19
|
-
havdalah: 'Holiday',
|
|
20
|
-
hebdate: null,
|
|
21
|
-
holiday: 'Holiday',
|
|
22
|
-
mevarchim: null,
|
|
23
|
-
molad: null,
|
|
24
|
-
omer: null,
|
|
25
|
-
parashat: 'Parsha',
|
|
26
|
-
roshchodesh: 'Holiday',
|
|
27
|
-
user: 'Personal',
|
|
28
|
-
zmanim: null,
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* @private
|
|
32
|
-
*/
|
|
33
|
-
function addOptional(arr, key, val) {
|
|
34
|
-
if (val) {
|
|
35
|
-
const str = IcalEvent.escape(val);
|
|
36
|
-
arr.push(key + ':' + str);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* @private
|
|
41
|
-
*/
|
|
42
|
-
function appendTrackingToUrl(url, options) {
|
|
43
|
-
if (!url) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
const utmSource = options.utmSource || 'js';
|
|
47
|
-
const utmMedium = options.utmMedium || 'icalendar';
|
|
48
|
-
const utmCampaign = options.utmCampaign;
|
|
49
|
-
return restApi.appendIsraelAndTracking(url, options.il, utmSource, utmMedium, utmCampaign);
|
|
50
|
-
}
|
|
51
|
-
const encoder = new TextEncoder();
|
|
52
|
-
const char74re = /(.{1,74})/g;
|
|
53
|
-
const DAILY_LEARNING = core.flags.DAILY_LEARNING | core.flags.DAF_YOMI |
|
|
54
|
-
core.flags.MISHNA_YOMI | core.flags.YERUSHALMI_YOMI | core.flags.NACH_YOMI;
|
|
55
|
-
/**
|
|
56
|
-
* Represents an RFC 2445 iCalendar VEVENT
|
|
57
|
-
*/
|
|
58
|
-
class IcalEvent {
|
|
59
|
-
/**
|
|
60
|
-
* Builds an IcalEvent object from a Hebcal Event
|
|
61
|
-
*/
|
|
62
|
-
constructor(ev, options = {}) {
|
|
63
|
-
var _a;
|
|
64
|
-
this.ev = ev;
|
|
65
|
-
const opts = Object.assign({}, options);
|
|
66
|
-
this.options = opts;
|
|
67
|
-
this.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
68
|
-
if (typeof ev.sequence === 'number') {
|
|
69
|
-
this.sequence = ev.sequence;
|
|
70
|
-
}
|
|
71
|
-
else if (typeof opts.sequence === 'number') {
|
|
72
|
-
this.sequence = opts.sequence;
|
|
73
|
-
}
|
|
74
|
-
const timed = this.timed = Boolean(ev.eventTime);
|
|
75
|
-
const locale = opts.locale;
|
|
76
|
-
const location = opts.location;
|
|
77
|
-
let subj = restApi.shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
|
|
78
|
-
const mask = ev.getFlags();
|
|
79
|
-
if (ev.locationName) {
|
|
80
|
-
this.locationName = ev.locationName;
|
|
81
|
-
}
|
|
82
|
-
else if (timed && location) {
|
|
83
|
-
this.locationName = location.getShortName();
|
|
84
|
-
}
|
|
85
|
-
else if ((mask & DAILY_LEARNING) && ev.category) {
|
|
86
|
-
this.locationName = core.Locale.gettext(ev.category, locale);
|
|
87
|
-
}
|
|
88
|
-
const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
|
|
89
|
-
this.startDate = this.isoDateOnly = date;
|
|
90
|
-
this.dtargs = '';
|
|
91
|
-
this.transp = 'TRANSPARENT';
|
|
92
|
-
this.busyStatus = 'FREE';
|
|
93
|
-
if (timed) {
|
|
94
|
-
let [hour, minute] = ev.eventTimeStr.split(':');
|
|
95
|
-
hour = +hour;
|
|
96
|
-
minute = +minute;
|
|
97
|
-
this.startDate += 'T' + restApi.pad2(hour) + restApi.pad2(minute) + '00';
|
|
98
|
-
this.endDate = this.startDate;
|
|
99
|
-
if (location === null || location === void 0 ? void 0 : location.getTzid()) {
|
|
100
|
-
this.dtargs = `;TZID=${location.getTzid()}`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
|
|
105
|
-
// for all-day untimed, use DTEND;VALUE=DATE intsead of DURATION:P1D.
|
|
106
|
-
// It's more compatible with everthing except ancient versions of
|
|
107
|
-
// Lotus Notes circa 2004
|
|
108
|
-
this.dtargs = ';VALUE=DATE';
|
|
109
|
-
if (mask & core.flags.CHAG) {
|
|
110
|
-
this.transp = 'OPAQUE';
|
|
111
|
-
this.busyStatus = 'OOF';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (opts.emoji) {
|
|
115
|
-
const prefix = ev.getEmoji();
|
|
116
|
-
if (prefix) {
|
|
117
|
-
if (mask & core.flags.OMER_COUNT) {
|
|
118
|
-
subj = subj + ' ' + prefix;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
subj = prefix + ' ' + subj;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// make subject safe for iCalendar
|
|
126
|
-
subj = IcalEvent.escape(subj);
|
|
127
|
-
if (opts.appendHebrewToSubject) {
|
|
128
|
-
const hebrew = ev.renderBrief('he');
|
|
129
|
-
if (hebrew) {
|
|
130
|
-
subj += ` / ${hebrew}`;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
this.subj = subj;
|
|
134
|
-
this.category = ev.category || CATEGORY[(_a = restApi.getEventCategories(ev)) === null || _a === void 0 ? void 0 : _a[0]];
|
|
135
|
-
}
|
|
136
|
-
getAlarm() {
|
|
137
|
-
const ev = this.ev;
|
|
138
|
-
const mask = ev.getFlags();
|
|
139
|
-
const evAlarm = ev.alarm;
|
|
140
|
-
if (typeof evAlarm === 'string') {
|
|
141
|
-
return 'TRIGGER:' + evAlarm;
|
|
142
|
-
}
|
|
143
|
-
else if (typeof evAlarm === 'boolean' && !evAlarm) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
else if (core.greg.isDate(evAlarm)) {
|
|
147
|
-
const alarmDt = evAlarm;
|
|
148
|
-
alarmDt.setSeconds(0);
|
|
149
|
-
return 'TRIGGER;VALUE=DATE-TIME:' + IcalEvent.makeDtstamp(alarmDt);
|
|
150
|
-
}
|
|
151
|
-
else if (mask & core.flags.OMER_COUNT) {
|
|
152
|
-
return 'TRIGGER:-P0DT3H30M0S'; // 8:30pm Omer alarm evening before
|
|
153
|
-
}
|
|
154
|
-
else if (mask & core.flags.USER_EVENT) {
|
|
155
|
-
return 'TRIGGER:-P0DT12H0M0S'; // noon the day before
|
|
156
|
-
}
|
|
157
|
-
else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
|
|
158
|
-
return 'TRIGGER:-P0DT0H10M0S';
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* @return {string}
|
|
164
|
-
*/
|
|
165
|
-
getUid() {
|
|
166
|
-
if (this.ev.uid) {
|
|
167
|
-
return this.ev.uid;
|
|
168
|
-
}
|
|
169
|
-
const options = this.options;
|
|
170
|
-
const digest = murmurhash3.murmur32HexSync(this.ev.getDesc());
|
|
171
|
-
let uid = `hebcal-${this.isoDateOnly}-${digest}`;
|
|
172
|
-
const loc = options.location;
|
|
173
|
-
if (this.timed && loc) {
|
|
174
|
-
if (loc.getGeoId()) {
|
|
175
|
-
uid += `-${loc.getGeoId()}`;
|
|
176
|
-
}
|
|
177
|
-
else if (loc.getName()) {
|
|
178
|
-
uid += '-' + restApi.makeAnchor(loc.getName());
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return uid;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* @return {string[]}
|
|
185
|
-
*/
|
|
186
|
-
getLongLines() {
|
|
187
|
-
if (this.lines)
|
|
188
|
-
return this.lines;
|
|
189
|
-
const categoryLine = this.category ? [`CATEGORIES:${this.category}`] : [];
|
|
190
|
-
const uid = this.getUid();
|
|
191
|
-
if (this.sequence) {
|
|
192
|
-
categoryLine.unshift(`SEQUENCE:${this.sequence}`);
|
|
193
|
-
}
|
|
194
|
-
const arr = this.lines = [
|
|
195
|
-
'BEGIN:VEVENT',
|
|
196
|
-
`DTSTAMP:${this.dtstamp}`,
|
|
197
|
-
].concat(categoryLine).concat([
|
|
198
|
-
`SUMMARY:${this.subj}`,
|
|
199
|
-
`DTSTART${this.dtargs}:${this.startDate}`,
|
|
200
|
-
`DTEND${this.dtargs}:${this.endDate}`,
|
|
201
|
-
`UID:${uid}`,
|
|
202
|
-
`TRANSP:${this.transp}`,
|
|
203
|
-
`X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`,
|
|
204
|
-
]);
|
|
205
|
-
if (!this.timed) {
|
|
206
|
-
arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
|
|
207
|
-
}
|
|
208
|
-
const ev = this.ev;
|
|
209
|
-
const mask = ev.getFlags();
|
|
210
|
-
const isUserEvent = Boolean(mask & core.flags.USER_EVENT);
|
|
211
|
-
if (!isUserEvent) {
|
|
212
|
-
arr.push('CLASS:PUBLIC');
|
|
213
|
-
}
|
|
214
|
-
const options = this.options;
|
|
215
|
-
// create memo (holiday descr, Torah, etc)
|
|
216
|
-
const memo = createMemo(ev, options);
|
|
217
|
-
addOptional(arr, 'DESCRIPTION', memo);
|
|
218
|
-
addOptional(arr, 'LOCATION', this.locationName);
|
|
219
|
-
const loc = options.location;
|
|
220
|
-
if (this.timed && loc) {
|
|
221
|
-
arr.push('GEO:' + loc.getLatitude() + ';' + loc.getLongitude());
|
|
222
|
-
}
|
|
223
|
-
const trigger = this.getAlarm();
|
|
224
|
-
if (trigger) {
|
|
225
|
-
arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
|
|
226
|
-
}
|
|
227
|
-
arr.push('END:VEVENT');
|
|
228
|
-
return arr;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* @return {string}
|
|
232
|
-
*/
|
|
233
|
-
toString() {
|
|
234
|
-
return this.getLines().join('\r\n');
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* fold lines to 75 characters
|
|
238
|
-
* @return {string[]}
|
|
239
|
-
*/
|
|
240
|
-
getLines() {
|
|
241
|
-
return this.getLongLines().map(IcalEvent.fold);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* fold line to 75 characters
|
|
245
|
-
* @param {string} line
|
|
246
|
-
* @return {string}
|
|
247
|
-
*/
|
|
248
|
-
static fold(line) {
|
|
249
|
-
let isASCII = true;
|
|
250
|
-
for (let i = 0; i < line.length; i++) {
|
|
251
|
-
if (line.charCodeAt(i) > 255) {
|
|
252
|
-
isASCII = false;
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (isASCII) {
|
|
257
|
-
if (line.length <= 74) {
|
|
258
|
-
return line;
|
|
259
|
-
}
|
|
260
|
-
const matches = line.match(char74re);
|
|
261
|
-
return matches.join('\r\n ');
|
|
262
|
-
}
|
|
263
|
-
if (encoder.encode(line).length <= 74) {
|
|
264
|
-
return line;
|
|
265
|
-
}
|
|
266
|
-
// iterate unicode character by character, making sure
|
|
267
|
-
// that adding a new character would keep the line <= 75 octets
|
|
268
|
-
let result = '';
|
|
269
|
-
let current = '';
|
|
270
|
-
let len = 0;
|
|
271
|
-
for (let i = 0; i < line.length; i++) {
|
|
272
|
-
const char = line[i];
|
|
273
|
-
const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
|
|
274
|
-
const newlen = len + octets;
|
|
275
|
-
if (newlen < 75) {
|
|
276
|
-
current += char;
|
|
277
|
-
len = newlen;
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
result += current + '\r\n ';
|
|
281
|
-
line = line.substring(i);
|
|
282
|
-
current = '';
|
|
283
|
-
len = 0;
|
|
284
|
-
i = -1;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return result + current;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* @param {string} str
|
|
291
|
-
* @return {string}
|
|
292
|
-
*/
|
|
293
|
-
static escape(str) {
|
|
294
|
-
if (str.indexOf(',') !== -1) {
|
|
295
|
-
str = str.replace(/,/g, '\\,');
|
|
296
|
-
}
|
|
297
|
-
if (str.indexOf(';') !== -1) {
|
|
298
|
-
str = str.replace(/;/g, '\\;');
|
|
299
|
-
}
|
|
300
|
-
return str;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* @param {Date} dt
|
|
304
|
-
* @return {string}
|
|
305
|
-
*/
|
|
306
|
-
static formatYYYYMMDD(dt) {
|
|
307
|
-
return restApi.pad4(dt.getFullYear()) +
|
|
308
|
-
restApi.pad2(dt.getMonth() + 1) + restApi.pad2(dt.getDate());
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Returns UTC string for iCalendar
|
|
312
|
-
* @param {Date} dt
|
|
313
|
-
* @return {string}
|
|
314
|
-
*/
|
|
315
|
-
static makeDtstamp(dt) {
|
|
316
|
-
const s = dt.toISOString();
|
|
317
|
-
return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
|
|
318
|
-
s.slice(14, 16) + s.slice(17, 19) + 'Z';
|
|
319
|
-
}
|
|
320
|
-
/** @return {string} */
|
|
321
|
-
static version() {
|
|
322
|
-
return version;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Transforms a single Event into a VEVENT string
|
|
327
|
-
* @return {string} multi-line result, delimited by \r\n
|
|
328
|
-
*/
|
|
329
|
-
function eventToIcal(ev, options) {
|
|
330
|
-
const ical = new IcalEvent(ev, options);
|
|
331
|
-
return ical.toString();
|
|
332
|
-
}
|
|
333
|
-
const torahMemoCache = new Map();
|
|
334
|
-
const HOLIDAY_IGNORE_MASK = DAILY_LEARNING | core.flags.OMER_COUNT |
|
|
335
|
-
core.flags.SHABBAT_MEVARCHIM | core.flags.MOLAD | core.flags.USER_EVENT |
|
|
336
|
-
core.flags.HEBREW_DATE;
|
|
337
|
-
/**
|
|
338
|
-
* @private
|
|
339
|
-
*/
|
|
340
|
-
function makeTorahMemo(ev, il) {
|
|
341
|
-
if ((ev.getFlags() & HOLIDAY_IGNORE_MASK) || ev.eventTime) {
|
|
342
|
-
return '';
|
|
343
|
-
}
|
|
344
|
-
const hd = ev.getDate();
|
|
345
|
-
const yy = hd.getFullYear();
|
|
346
|
-
const mm = hd.getMonth();
|
|
347
|
-
const dd = hd.getDate();
|
|
348
|
-
const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
|
|
349
|
-
let memo = torahMemoCache.get(key);
|
|
350
|
-
if (typeof memo === 'string') {
|
|
351
|
-
return memo;
|
|
352
|
-
}
|
|
353
|
-
memo = restApi.makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
|
|
354
|
-
torahMemoCache.set(key, memo);
|
|
355
|
-
return memo;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* @private
|
|
359
|
-
*/
|
|
360
|
-
function createMemo(ev, options) {
|
|
361
|
-
let memo = ev.memo;
|
|
362
|
-
if (typeof memo === 'string' && memo.length && memo.indexOf('\n') !== -1) {
|
|
363
|
-
memo = memo.replace(/\n/g, '\\n');
|
|
364
|
-
}
|
|
365
|
-
const desc = ev.getDesc();
|
|
366
|
-
if (desc === 'Havdalah' || desc === 'Candle lighting') {
|
|
367
|
-
return memo || '';
|
|
368
|
-
}
|
|
369
|
-
const mask = ev.getFlags();
|
|
370
|
-
if (mask & core.flags.OMER_COUNT) {
|
|
371
|
-
const omerEv = ev;
|
|
372
|
-
const sefira = [omerEv.sefira('en'), omerEv.sefira('he'), omerEv.sefira('translit')].join('\\n');
|
|
373
|
-
return omerEv.getTodayIs('en') + '\\n\\n' + omerEv.getTodayIs('he') + '\\n\\n' + sefira;
|
|
374
|
-
}
|
|
375
|
-
const url = appendTrackingToUrl(ev.url(), options);
|
|
376
|
-
const torahMemo = makeTorahMemo(ev, options.il);
|
|
377
|
-
if (!memo) {
|
|
378
|
-
if (typeof ev.linkedEvent !== 'undefined') {
|
|
379
|
-
memo = ev.linkedEvent.render(options.locale);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
memo = restApi.getHolidayDescription(ev);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (torahMemo) {
|
|
386
|
-
if (memo.length) {
|
|
387
|
-
memo += '\\n\\n';
|
|
388
|
-
}
|
|
389
|
-
memo += torahMemo;
|
|
390
|
-
}
|
|
391
|
-
if (url) {
|
|
392
|
-
if (memo.length) {
|
|
393
|
-
memo += '\\n\\n';
|
|
394
|
-
}
|
|
395
|
-
memo += url;
|
|
396
|
-
}
|
|
397
|
-
return memo;
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Generates an RFC 2445 iCalendar string from an array of events
|
|
401
|
-
*/
|
|
402
|
-
async function eventsToIcalendar(events, options) {
|
|
403
|
-
if (!options)
|
|
404
|
-
throw new TypeError('Invalid options object');
|
|
405
|
-
const opts = Object.assign({}, options);
|
|
406
|
-
opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
407
|
-
if (!opts.title) {
|
|
408
|
-
opts.title = restApi.getCalendarTitle(events, opts);
|
|
409
|
-
}
|
|
410
|
-
const icals = events.map((ev) => new IcalEvent(ev, opts));
|
|
411
|
-
return icalEventsToString(icals, opts);
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Generates an RFC 2445 iCalendar string from an array of IcalEvents
|
|
415
|
-
*/
|
|
416
|
-
async function icalEventsToString(icals, options) {
|
|
417
|
-
const stream = [];
|
|
418
|
-
const locale = options.locale || core.Locale.getLocaleName();
|
|
419
|
-
const uclang = locale.toUpperCase();
|
|
420
|
-
const opts = Object.assign({}, options);
|
|
421
|
-
opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
422
|
-
const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
|
|
423
|
-
const caldesc = opts.caldesc ? IcalEvent.escape(opts.caldesc) :
|
|
424
|
-
opts.yahrzeit ?
|
|
425
|
-
'Yahrzeits + Anniversaries from www.hebcal.com' :
|
|
426
|
-
'Jewish Holidays from www.hebcal.com';
|
|
427
|
-
const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
|
|
428
|
-
const preamble = [
|
|
429
|
-
'BEGIN:VCALENDAR',
|
|
430
|
-
'VERSION:2.0',
|
|
431
|
-
`PRODID:${prodid}`,
|
|
432
|
-
'CALSCALE:GREGORIAN',
|
|
433
|
-
'METHOD:PUBLISH',
|
|
434
|
-
'X-LOTUS-CHARSET:UTF-8',
|
|
435
|
-
];
|
|
436
|
-
if (opts.publishedTTL !== false) {
|
|
437
|
-
const publishedTTL = opts.publishedTTL || 'PT7D';
|
|
438
|
-
preamble.push(`X-PUBLISHED-TTL:${publishedTTL}`);
|
|
439
|
-
}
|
|
440
|
-
preamble.push(`X-WR-CALNAME:${title}`);
|
|
441
|
-
preamble.push(`X-WR-CALDESC:${caldesc}`);
|
|
442
|
-
for (const line of preamble.map(IcalEvent.fold)) {
|
|
443
|
-
stream.push(line);
|
|
444
|
-
stream.push('\r\n');
|
|
445
|
-
}
|
|
446
|
-
if (opts.relcalid) {
|
|
447
|
-
stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
|
|
448
|
-
stream.push('\r\n');
|
|
449
|
-
}
|
|
450
|
-
if (opts.calendarColor) {
|
|
451
|
-
stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
|
|
452
|
-
}
|
|
453
|
-
const location = opts.location;
|
|
454
|
-
const tzid = location === null || location === void 0 ? void 0 : location.getTzid();
|
|
455
|
-
if (tzid) {
|
|
456
|
-
stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
|
|
457
|
-
const vtz = vtimezoneCache.get(tzid);
|
|
458
|
-
if (typeof vtz === 'string') {
|
|
459
|
-
stream.push(vtz);
|
|
460
|
-
stream.push('\r\n');
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
|
|
464
|
-
try {
|
|
465
|
-
const vtimezoneIcs = await fs.promises.readFile(vtimezoneFilename, 'utf-8');
|
|
466
|
-
const lines = vtimezoneIcs.split('\r\n');
|
|
467
|
-
// ignore first 3 and last 1 lines
|
|
468
|
-
const str = lines.slice(3, lines.length - 2).join('\r\n');
|
|
469
|
-
stream.push(str);
|
|
470
|
-
stream.push('\r\n');
|
|
471
|
-
vtimezoneCache.set(tzid, str);
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
// ignore failure when no timezone definition to read
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
for (const ical of icals) {
|
|
479
|
-
const lines = ical.getLines();
|
|
480
|
-
for (const line of lines) {
|
|
481
|
-
stream.push(line);
|
|
482
|
-
stream.push('\r\n');
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
stream.push('END:VCALENDAR\r\n');
|
|
486
|
-
return stream.join('');
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
exports.IcalEvent = IcalEvent;
|
|
490
|
-
exports.eventToIcal = eventToIcal;
|
|
491
|
-
exports.eventsToIcalendar = eventsToIcalendar;
|
|
492
|
-
exports.icalEventsToString = icalEventsToString;
|