@hebcal/icalendar 4.18.2 → 4.18.4
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/dist/index.js +34 -107
- package/dist/index.mjs +34 -105
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
/*! @hebcal/icalendar v4.18.
|
|
1
|
+
/*! @hebcal/icalendar v4.18.4 */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
-
|
|
6
4
|
var core = require('@hebcal/core');
|
|
7
5
|
var murmurhash3 = require('murmurhash3');
|
|
8
6
|
var restApi = require('@hebcal/rest-api');
|
|
9
7
|
var fs = require('fs');
|
|
10
8
|
|
|
11
|
-
const version="4.18.
|
|
9
|
+
const version="4.18.4";
|
|
12
10
|
|
|
13
11
|
const VTIMEZONE = {};
|
|
14
12
|
const CATEGORY = {
|
|
15
13
|
candles: 'Holiday',
|
|
16
14
|
dafyomi: 'Daf Yomi',
|
|
17
15
|
mishnayomi: 'Mishna Yomi',
|
|
16
|
+
yerushalmi: 'Yerushalmi Yomi',
|
|
18
17
|
havdalah: 'Holiday',
|
|
19
18
|
hebdate: null,
|
|
20
19
|
holiday: 'Holiday',
|
|
@@ -26,44 +25,41 @@ const CATEGORY = {
|
|
|
26
25
|
user: 'Personal',
|
|
27
26
|
zmanim: null
|
|
28
27
|
};
|
|
28
|
+
|
|
29
29
|
/**
|
|
30
30
|
* @private
|
|
31
31
|
* @param {string[]} arr
|
|
32
32
|
* @param {string} key
|
|
33
33
|
* @param {string} val
|
|
34
34
|
*/
|
|
35
|
-
|
|
36
35
|
function addOptional(arr, key, val) {
|
|
37
36
|
if (val) {
|
|
38
37
|
const str = IcalEvent.escape(val);
|
|
39
38
|
arr.push(key + ':' + str);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
41
|
+
|
|
42
42
|
/**
|
|
43
43
|
* @private
|
|
44
44
|
* @param {string} url
|
|
45
45
|
* @param {HebrewCalendar.Options} options
|
|
46
46
|
* @return {string}
|
|
47
47
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
48
|
function appendTrackingToUrl(url, options) {
|
|
51
49
|
if (!url) {
|
|
52
50
|
return null;
|
|
53
51
|
}
|
|
54
|
-
|
|
55
52
|
const utmSource = options.utmSource || 'js';
|
|
56
53
|
const utmMedium = options.utmMedium || 'icalendar';
|
|
57
54
|
const utmCampaign = options.utmCampaign;
|
|
58
55
|
return restApi.appendIsraelAndTracking(url, options.il, utmSource, utmMedium, utmCampaign);
|
|
59
56
|
}
|
|
60
|
-
|
|
61
57
|
const encoder = new TextEncoder();
|
|
62
58
|
const char74re = /(.{1,74})/g;
|
|
59
|
+
|
|
63
60
|
/**
|
|
64
61
|
* Represents an RFC 2445 iCalendar VEVENT
|
|
65
62
|
*/
|
|
66
|
-
|
|
67
63
|
class IcalEvent {
|
|
68
64
|
/**
|
|
69
65
|
* Builds an IcalEvent object from a Hebcal Event
|
|
@@ -78,7 +74,6 @@ class IcalEvent {
|
|
|
78
74
|
const locale = options.locale;
|
|
79
75
|
let subj = restApi.shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
|
|
80
76
|
const mask = ev.getFlags();
|
|
81
|
-
|
|
82
77
|
if (ev.locationName) {
|
|
83
78
|
this.locationName = ev.locationName;
|
|
84
79
|
} else if (mask & core.flags.DAF_YOMI) {
|
|
@@ -89,39 +84,33 @@ class IcalEvent {
|
|
|
89
84
|
const comma = options.location.name.indexOf(',');
|
|
90
85
|
this.locationName = comma == -1 ? options.location.name : options.location.name.substring(0, comma);
|
|
91
86
|
}
|
|
92
|
-
|
|
93
87
|
const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
|
|
94
88
|
this.startDate = date;
|
|
95
89
|
this.dtargs = '';
|
|
96
90
|
this.transp = 'TRANSPARENT';
|
|
97
91
|
this.busyStatus = 'FREE';
|
|
98
|
-
|
|
99
92
|
if (timed) {
|
|
100
93
|
let [hour, minute] = ev.eventTimeStr.split(':');
|
|
101
94
|
hour = +hour;
|
|
102
95
|
minute = +minute;
|
|
103
96
|
this.startDate += 'T' + restApi.pad2(hour) + restApi.pad2(minute) + '00';
|
|
104
97
|
this.endDate = this.startDate;
|
|
105
|
-
|
|
106
98
|
if (options.location && options.location.tzid) {
|
|
107
99
|
this.dtargs = `;TZID=${options.location.tzid}`;
|
|
108
100
|
}
|
|
109
101
|
} else {
|
|
110
|
-
this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
|
|
102
|
+
this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
|
|
103
|
+
// for all-day untimed, use DTEND;VALUE=DATE intsead of DURATION:P1D.
|
|
111
104
|
// It's more compatible with everthing except ancient versions of
|
|
112
105
|
// Lotus Notes circa 2004
|
|
113
|
-
|
|
114
106
|
this.dtargs = ';VALUE=DATE';
|
|
115
|
-
|
|
116
107
|
if (mask & core.flags.CHAG) {
|
|
117
108
|
this.transp = 'OPAQUE';
|
|
118
109
|
this.busyStatus = 'OOF';
|
|
119
110
|
}
|
|
120
111
|
}
|
|
121
|
-
|
|
122
112
|
if (options.emoji) {
|
|
123
113
|
const prefix = ev.getEmoji();
|
|
124
|
-
|
|
125
114
|
if (prefix) {
|
|
126
115
|
if (mask & core.flags.OMER_COUNT) {
|
|
127
116
|
subj = subj + ' ' + prefix;
|
|
@@ -129,32 +118,27 @@ class IcalEvent {
|
|
|
129
118
|
subj = prefix + ' ' + subj;
|
|
130
119
|
}
|
|
131
120
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
121
|
+
}
|
|
134
122
|
|
|
123
|
+
// make subject safe for iCalendar
|
|
135
124
|
subj = IcalEvent.escape(subj);
|
|
136
|
-
|
|
137
125
|
if (options.appendHebrewToSubject) {
|
|
138
126
|
const hebrew = ev.renderBrief('he');
|
|
139
|
-
|
|
140
127
|
if (hebrew) {
|
|
141
128
|
subj += ` / ${hebrew}`;
|
|
142
129
|
}
|
|
143
130
|
}
|
|
144
|
-
|
|
145
131
|
this.subj = subj;
|
|
146
132
|
this.category = ev.category || CATEGORY[restApi.getEventCategories(ev)[0]];
|
|
147
133
|
}
|
|
134
|
+
|
|
148
135
|
/**
|
|
149
136
|
* @return {string}
|
|
150
137
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
138
|
getAlarm() {
|
|
154
139
|
const ev = this.ev;
|
|
155
140
|
const mask = ev.getFlags();
|
|
156
141
|
const evAlarm = ev.alarm;
|
|
157
|
-
|
|
158
142
|
if (typeof evAlarm === 'string') {
|
|
159
143
|
return 'TRIGGER:' + evAlarm;
|
|
160
144
|
} else if (core.greg.isDate(evAlarm)) {
|
|
@@ -167,19 +151,16 @@ class IcalEvent {
|
|
|
167
151
|
} else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
|
|
168
152
|
return 'TRIGGER:-P0DT0H10M0S';
|
|
169
153
|
}
|
|
170
|
-
|
|
171
154
|
return null;
|
|
172
155
|
}
|
|
156
|
+
|
|
173
157
|
/**
|
|
174
158
|
* @return {string}
|
|
175
159
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
160
|
getUid() {
|
|
179
161
|
const options = this.options;
|
|
180
162
|
const digest = murmurhash3.murmur32HexSync(this.ev.getDesc());
|
|
181
163
|
let uid = `hebcal-${this.startDate}-${digest}`;
|
|
182
|
-
|
|
183
164
|
if (this.timed && options.location) {
|
|
184
165
|
if (options.location.geoid) {
|
|
185
166
|
uid += `-${options.location.geoid}`;
|
|
@@ -187,104 +168,85 @@ class IcalEvent {
|
|
|
187
168
|
uid += '-' + restApi.makeAnchor(options.location.name);
|
|
188
169
|
}
|
|
189
170
|
}
|
|
190
|
-
|
|
191
171
|
return uid;
|
|
192
172
|
}
|
|
173
|
+
|
|
193
174
|
/**
|
|
194
175
|
* @return {string[]}
|
|
195
176
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
177
|
getLongLines() {
|
|
199
178
|
if (this.lines) return this.lines;
|
|
200
179
|
const categoryLine = this.category ? `CATEGORIES:${this.category}` : [];
|
|
201
180
|
const uid = this.ev.uid || this.getUid();
|
|
202
181
|
const arr = this.lines = ['BEGIN:VEVENT', `DTSTAMP:${this.dtstamp}`].concat(categoryLine).concat([`SUMMARY:${this.subj}`, `DTSTART${this.dtargs}:${this.startDate}`, `DTEND${this.dtargs}:${this.endDate}`, `UID:${uid}`, `TRANSP:${this.transp}`, `X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`]);
|
|
203
|
-
|
|
204
182
|
if (!this.timed) {
|
|
205
183
|
arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
|
|
206
184
|
}
|
|
207
|
-
|
|
208
185
|
const ev = this.ev;
|
|
209
186
|
const mask = ev.getFlags();
|
|
210
187
|
const isUserEvent = Boolean(mask & core.flags.USER_EVENT);
|
|
211
|
-
|
|
212
188
|
if (!isUserEvent) {
|
|
213
189
|
arr.push('CLASS:PUBLIC');
|
|
214
190
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
191
|
+
const options = this.options;
|
|
192
|
+
// create memo (holiday descr, Torah, etc)
|
|
218
193
|
const memo = createMemo(ev, options);
|
|
219
194
|
addOptional(arr, 'DESCRIPTION', memo);
|
|
220
195
|
addOptional(arr, 'LOCATION', this.locationName);
|
|
221
|
-
|
|
222
196
|
if (this.timed && options.location) {
|
|
223
197
|
arr.push('GEO:' + options.location.latitude + ';' + options.location.longitude);
|
|
224
198
|
}
|
|
225
|
-
|
|
226
199
|
const trigger = this.getAlarm();
|
|
227
|
-
|
|
228
200
|
if (trigger) {
|
|
229
201
|
arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
|
|
230
202
|
}
|
|
231
|
-
|
|
232
203
|
arr.push('END:VEVENT');
|
|
233
204
|
return arr;
|
|
234
205
|
}
|
|
206
|
+
|
|
235
207
|
/**
|
|
236
208
|
* @return {string}
|
|
237
209
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
240
210
|
toString() {
|
|
241
211
|
return this.getLines().join('\r\n');
|
|
242
212
|
}
|
|
213
|
+
|
|
243
214
|
/**
|
|
244
215
|
* fold lines to 75 characters
|
|
245
216
|
* @return {string[]}
|
|
246
217
|
*/
|
|
247
|
-
|
|
248
|
-
|
|
249
218
|
getLines() {
|
|
250
219
|
return this.getLongLines().map(IcalEvent.fold);
|
|
251
220
|
}
|
|
221
|
+
|
|
252
222
|
/**
|
|
253
223
|
* fold line to 75 characters
|
|
254
224
|
* @param {string} line
|
|
255
225
|
* @return {string}
|
|
256
226
|
*/
|
|
257
|
-
|
|
258
|
-
|
|
259
227
|
static fold(line) {
|
|
260
228
|
let isASCII = true;
|
|
261
|
-
|
|
262
229
|
for (let i = 0; i < line.length; i++) {
|
|
263
230
|
if (line.charCodeAt(i) > 255) {
|
|
264
231
|
isASCII = false;
|
|
265
232
|
break;
|
|
266
233
|
}
|
|
267
234
|
}
|
|
268
|
-
|
|
269
235
|
if (isASCII) {
|
|
270
236
|
return line.length <= 74 ? line : line.match(char74re).join('\r\n ');
|
|
271
237
|
}
|
|
272
|
-
|
|
273
238
|
if (encoder.encode(line).length <= 74) {
|
|
274
239
|
return line;
|
|
275
|
-
}
|
|
240
|
+
}
|
|
241
|
+
// iterate unicode character by character, making sure
|
|
276
242
|
// that adding a new character would keep the line <= 75 octets
|
|
277
|
-
|
|
278
|
-
|
|
279
243
|
let result = '';
|
|
280
244
|
let current = '';
|
|
281
245
|
let len = 0;
|
|
282
|
-
|
|
283
246
|
for (let i = 0; i < line.length; i++) {
|
|
284
247
|
const char = line[i];
|
|
285
248
|
const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
|
|
286
249
|
const newlen = len + octets;
|
|
287
|
-
|
|
288
250
|
if (newlen < 75) {
|
|
289
251
|
current += char;
|
|
290
252
|
len = newlen;
|
|
@@ -296,176 +258,150 @@ class IcalEvent {
|
|
|
296
258
|
i = -1;
|
|
297
259
|
}
|
|
298
260
|
}
|
|
299
|
-
|
|
300
261
|
return result + current;
|
|
301
262
|
}
|
|
263
|
+
|
|
302
264
|
/**
|
|
303
265
|
* @param {string} str
|
|
304
266
|
* @return {string}
|
|
305
267
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
308
268
|
static escape(str) {
|
|
309
269
|
if (str.indexOf(',') !== -1) {
|
|
310
270
|
str = str.replace(/,/g, '\\,');
|
|
311
271
|
}
|
|
312
|
-
|
|
313
272
|
if (str.indexOf(';') !== -1) {
|
|
314
273
|
str = str.replace(/;/g, '\\;');
|
|
315
274
|
}
|
|
316
|
-
|
|
317
275
|
return str;
|
|
318
276
|
}
|
|
277
|
+
|
|
319
278
|
/**
|
|
320
279
|
* @param {Date} dt
|
|
321
280
|
* @return {string}
|
|
322
281
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
325
282
|
static formatYYYYMMDD(dt) {
|
|
326
283
|
return restApi.pad4(dt.getFullYear()) + restApi.pad2(dt.getMonth() + 1) + restApi.pad2(dt.getDate());
|
|
327
284
|
}
|
|
285
|
+
|
|
328
286
|
/**
|
|
329
287
|
* Returns UTC string for iCalendar
|
|
330
288
|
* @param {Date} dt
|
|
331
289
|
* @return {string}
|
|
332
290
|
*/
|
|
333
|
-
|
|
334
|
-
|
|
335
291
|
static makeDtstamp(dt) {
|
|
336
292
|
const s = dt.toISOString();
|
|
337
293
|
return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) + s.slice(14, 16) + s.slice(17, 19) + 'Z';
|
|
338
294
|
}
|
|
339
|
-
/** @return {string} */
|
|
340
|
-
|
|
341
295
|
|
|
296
|
+
/** @return {string} */
|
|
342
297
|
static version() {
|
|
343
298
|
return version;
|
|
344
299
|
}
|
|
345
|
-
|
|
346
300
|
}
|
|
301
|
+
|
|
347
302
|
/**
|
|
348
303
|
* Transforms a single Event into a VEVENT string
|
|
349
304
|
* @param {Event} ev
|
|
350
305
|
* @param {HebrewCalendar.Options} options
|
|
351
306
|
* @return {string} multi-line result, delimited by \r\n
|
|
352
307
|
*/
|
|
353
|
-
|
|
354
308
|
function eventToIcal(ev, options) {
|
|
355
309
|
const ical = new IcalEvent(ev, options);
|
|
356
310
|
return ical.toString();
|
|
357
311
|
}
|
|
358
312
|
const torahMemoCache = new Map();
|
|
359
|
-
const HOLIDAY_IGNORE_MASK = core.flags.DAF_YOMI | core.flags.OMER_COUNT | core.flags.SHABBAT_MEVARCHIM | core.flags.MOLAD | core.flags.USER_EVENT | core.flags.MISHNA_YOMI | core.flags.HEBREW_DATE;
|
|
313
|
+
const HOLIDAY_IGNORE_MASK = core.flags.DAF_YOMI | core.flags.OMER_COUNT | core.flags.SHABBAT_MEVARCHIM | core.flags.MOLAD | core.flags.USER_EVENT | core.flags.MISHNA_YOMI | core.flags.YERUSHALMI_YOMI | core.flags.HEBREW_DATE;
|
|
314
|
+
|
|
360
315
|
/**
|
|
361
316
|
* @private
|
|
362
317
|
* @param {Event} ev
|
|
363
318
|
* @param {boolean} il
|
|
364
319
|
* @return {string}
|
|
365
320
|
*/
|
|
366
|
-
|
|
367
321
|
function makeTorahMemo(ev, il) {
|
|
368
322
|
if (ev.getFlags() & HOLIDAY_IGNORE_MASK || ev.eventTime) {
|
|
369
323
|
return '';
|
|
370
324
|
}
|
|
371
|
-
|
|
372
325
|
const hd = ev.getDate();
|
|
373
326
|
const yy = hd.getFullYear();
|
|
374
327
|
const mm = hd.getMonth();
|
|
375
328
|
const dd = hd.getDate();
|
|
376
329
|
const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
|
|
377
330
|
let memo = torahMemoCache.get(key);
|
|
378
|
-
|
|
379
331
|
if (typeof memo === 'string') {
|
|
380
332
|
return memo;
|
|
381
333
|
}
|
|
382
|
-
|
|
383
334
|
memo = restApi.makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
|
|
384
335
|
torahMemoCache.set(key, memo);
|
|
385
336
|
return memo;
|
|
386
337
|
}
|
|
338
|
+
|
|
387
339
|
/**
|
|
388
340
|
* @private
|
|
389
341
|
* @param {Event} e
|
|
390
342
|
* @param {HebrewCalendar.Options} options
|
|
391
343
|
* @return {string}
|
|
392
344
|
*/
|
|
393
|
-
|
|
394
|
-
|
|
395
345
|
function createMemo(e, options) {
|
|
396
346
|
const desc = e.getDesc();
|
|
397
347
|
const candles = desc === 'Havdalah' || desc === 'Candle lighting';
|
|
398
|
-
|
|
399
348
|
if (typeof e.memo === 'string' && e.memo.length && e.memo.indexOf('\n') !== -1) {
|
|
400
349
|
e.memo = e.memo.replace(/\n/g, '\\n');
|
|
401
350
|
}
|
|
402
|
-
|
|
403
351
|
if (candles) {
|
|
404
352
|
return e.memo || '';
|
|
405
353
|
}
|
|
406
|
-
|
|
407
354
|
const mask = e.getFlags();
|
|
408
|
-
|
|
409
355
|
if (mask & core.flags.OMER_COUNT) {
|
|
410
356
|
const sefira = [e.sefira('en'), e.sefira('he'), e.sefira('translit')].join('\\n');
|
|
411
357
|
return e.getTodayIs('en') + '\\n\\n' + e.getTodayIs('he') + '\\n\\n' + sefira;
|
|
412
358
|
}
|
|
413
|
-
|
|
414
359
|
const url = appendTrackingToUrl(e.url(), options);
|
|
415
360
|
const torahMemo = makeTorahMemo(e, options.il);
|
|
416
|
-
|
|
417
361
|
if (mask & core.flags.PARSHA_HASHAVUA) {
|
|
418
362
|
return torahMemo + '\\n\\n' + url;
|
|
419
363
|
} else {
|
|
420
364
|
let memo = e.memo || restApi.getHolidayDescription(e);
|
|
421
|
-
|
|
422
365
|
if (!memo && typeof e.linkedEvent !== 'undefined') {
|
|
423
366
|
memo = e.linkedEvent.render(options.locale);
|
|
424
367
|
}
|
|
425
|
-
|
|
426
368
|
if (torahMemo) {
|
|
427
369
|
memo += '\\n\\n' + torahMemo;
|
|
428
370
|
}
|
|
429
|
-
|
|
430
371
|
if (url) {
|
|
431
372
|
if (memo.length) {
|
|
432
373
|
memo += '\\n\\n';
|
|
433
374
|
}
|
|
434
|
-
|
|
435
375
|
memo += url;
|
|
436
376
|
}
|
|
437
|
-
|
|
438
377
|
return memo;
|
|
439
378
|
}
|
|
440
379
|
}
|
|
380
|
+
|
|
441
381
|
/**
|
|
442
382
|
* Generates an RFC 2445 iCalendar string from an array of events
|
|
443
383
|
* @param {Event[]} events
|
|
444
384
|
* @param {HebrewCalendar.Options} options
|
|
445
385
|
* @return {string}
|
|
446
386
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
387
|
async function eventsToIcalendar(events, options) {
|
|
450
388
|
if (!events.length) throw new RangeError('Events can not be empty');
|
|
451
389
|
if (!options) throw new TypeError('Invalid options object');
|
|
452
390
|
const opts = Object.assign({}, options);
|
|
453
391
|
opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
454
|
-
|
|
455
392
|
if (!opts.title) {
|
|
456
393
|
opts.title = restApi.getCalendarTitle(events, opts);
|
|
457
394
|
}
|
|
458
|
-
|
|
459
395
|
const icals = events.map(ev => new IcalEvent(ev, opts));
|
|
460
396
|
return icalEventsToString(icals, opts);
|
|
461
397
|
}
|
|
398
|
+
|
|
462
399
|
/**
|
|
463
400
|
* Generates an RFC 2445 iCalendar string from an array of IcalEvents
|
|
464
401
|
* @param {IcalEvent[]} icals
|
|
465
402
|
* @param {HebrewCalendar.Options} options
|
|
466
403
|
* @return {string}
|
|
467
404
|
*/
|
|
468
|
-
|
|
469
405
|
async function icalEventsToString(icals, options) {
|
|
470
406
|
const stream = [];
|
|
471
407
|
const uclang = core.Locale.getLocaleName().toUpperCase();
|
|
@@ -476,55 +412,46 @@ async function icalEventsToString(icals, options) {
|
|
|
476
412
|
const publishedTTL = opts.publishedTTL || 'PT7D';
|
|
477
413
|
const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
|
|
478
414
|
const preamble = ['BEGIN:VCALENDAR', 'VERSION:2.0', `PRODID:${prodid}`, 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-LOTUS-CHARSET:UTF-8', `X-PUBLISHED-TTL:${publishedTTL}`, `X-WR-CALNAME:${title}`, `X-WR-CALDESC:${caldesc}`];
|
|
479
|
-
|
|
480
415
|
for (const line of preamble.map(IcalEvent.fold)) {
|
|
481
416
|
stream.push(line);
|
|
482
417
|
stream.push('\r\n');
|
|
483
418
|
}
|
|
484
|
-
|
|
485
419
|
if (opts.relcalid) {
|
|
486
420
|
stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
|
|
487
421
|
stream.push('\r\n');
|
|
488
422
|
}
|
|
489
|
-
|
|
490
423
|
if (opts.calendarColor) {
|
|
491
424
|
stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
|
|
492
425
|
}
|
|
493
|
-
|
|
494
426
|
const location = opts.location;
|
|
495
|
-
|
|
496
427
|
if (location && location.tzid) {
|
|
497
428
|
const tzid = location.tzid;
|
|
498
429
|
stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
|
|
499
|
-
|
|
500
430
|
if (VTIMEZONE[tzid]) {
|
|
501
431
|
stream.push(VTIMEZONE[tzid]);
|
|
502
432
|
stream.push('\r\n');
|
|
503
433
|
} else {
|
|
504
434
|
const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
|
|
505
|
-
|
|
506
435
|
try {
|
|
507
436
|
const vtimezoneIcs = await fs.promises.readFile(vtimezoneFilename, 'utf-8');
|
|
508
|
-
const lines = vtimezoneIcs.split('\r\n');
|
|
509
|
-
|
|
437
|
+
const lines = vtimezoneIcs.split('\r\n');
|
|
438
|
+
// ignore first 3 and last 1 lines
|
|
510
439
|
const str = lines.slice(3, lines.length - 2).join('\r\n');
|
|
511
440
|
stream.push(str);
|
|
512
441
|
stream.push('\r\n');
|
|
513
442
|
VTIMEZONE[tzid] = str; // cache for later
|
|
514
|
-
} catch (error) {
|
|
443
|
+
} catch (error) {
|
|
444
|
+
// ignore failure when no timezone definition to read
|
|
515
445
|
}
|
|
516
446
|
}
|
|
517
447
|
}
|
|
518
|
-
|
|
519
448
|
for (const ical of icals) {
|
|
520
449
|
const lines = ical.getLines();
|
|
521
|
-
|
|
522
450
|
for (const line of lines) {
|
|
523
451
|
stream.push(line);
|
|
524
452
|
stream.push('\r\n');
|
|
525
453
|
}
|
|
526
454
|
}
|
|
527
|
-
|
|
528
455
|
stream.push('END:VCALENDAR\r\n');
|
|
529
456
|
return stream.join('');
|
|
530
457
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
/*! @hebcal/icalendar v4.18.
|
|
1
|
+
/*! @hebcal/icalendar v4.18.4 */
|
|
2
2
|
import { flags, Locale, greg } from '@hebcal/core';
|
|
3
3
|
import { murmur32HexSync } from 'murmurhash3';
|
|
4
4
|
import { shouldRenderBrief, pad2, getEventCategories, makeAnchor, pad4, getHolidayDescription, getCalendarTitle, appendIsraelAndTracking, makeTorahMemoText } from '@hebcal/rest-api';
|
|
5
5
|
import { promises } from 'fs';
|
|
6
6
|
|
|
7
|
-
const version="4.18.
|
|
7
|
+
const version="4.18.4";
|
|
8
8
|
|
|
9
9
|
const VTIMEZONE = {};
|
|
10
10
|
const CATEGORY = {
|
|
11
11
|
candles: 'Holiday',
|
|
12
12
|
dafyomi: 'Daf Yomi',
|
|
13
13
|
mishnayomi: 'Mishna Yomi',
|
|
14
|
+
yerushalmi: 'Yerushalmi Yomi',
|
|
14
15
|
havdalah: 'Holiday',
|
|
15
16
|
hebdate: null,
|
|
16
17
|
holiday: 'Holiday',
|
|
@@ -22,44 +23,41 @@ const CATEGORY = {
|
|
|
22
23
|
user: 'Personal',
|
|
23
24
|
zmanim: null
|
|
24
25
|
};
|
|
26
|
+
|
|
25
27
|
/**
|
|
26
28
|
* @private
|
|
27
29
|
* @param {string[]} arr
|
|
28
30
|
* @param {string} key
|
|
29
31
|
* @param {string} val
|
|
30
32
|
*/
|
|
31
|
-
|
|
32
33
|
function addOptional(arr, key, val) {
|
|
33
34
|
if (val) {
|
|
34
35
|
const str = IcalEvent.escape(val);
|
|
35
36
|
arr.push(key + ':' + str);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
|
|
38
40
|
/**
|
|
39
41
|
* @private
|
|
40
42
|
* @param {string} url
|
|
41
43
|
* @param {HebrewCalendar.Options} options
|
|
42
44
|
* @return {string}
|
|
43
45
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
46
|
function appendTrackingToUrl(url, options) {
|
|
47
47
|
if (!url) {
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
|
-
|
|
51
50
|
const utmSource = options.utmSource || 'js';
|
|
52
51
|
const utmMedium = options.utmMedium || 'icalendar';
|
|
53
52
|
const utmCampaign = options.utmCampaign;
|
|
54
53
|
return appendIsraelAndTracking(url, options.il, utmSource, utmMedium, utmCampaign);
|
|
55
54
|
}
|
|
56
|
-
|
|
57
55
|
const encoder = new TextEncoder();
|
|
58
56
|
const char74re = /(.{1,74})/g;
|
|
57
|
+
|
|
59
58
|
/**
|
|
60
59
|
* Represents an RFC 2445 iCalendar VEVENT
|
|
61
60
|
*/
|
|
62
|
-
|
|
63
61
|
class IcalEvent {
|
|
64
62
|
/**
|
|
65
63
|
* Builds an IcalEvent object from a Hebcal Event
|
|
@@ -74,7 +72,6 @@ class IcalEvent {
|
|
|
74
72
|
const locale = options.locale;
|
|
75
73
|
let subj = shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
|
|
76
74
|
const mask = ev.getFlags();
|
|
77
|
-
|
|
78
75
|
if (ev.locationName) {
|
|
79
76
|
this.locationName = ev.locationName;
|
|
80
77
|
} else if (mask & flags.DAF_YOMI) {
|
|
@@ -85,39 +82,33 @@ class IcalEvent {
|
|
|
85
82
|
const comma = options.location.name.indexOf(',');
|
|
86
83
|
this.locationName = comma == -1 ? options.location.name : options.location.name.substring(0, comma);
|
|
87
84
|
}
|
|
88
|
-
|
|
89
85
|
const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
|
|
90
86
|
this.startDate = date;
|
|
91
87
|
this.dtargs = '';
|
|
92
88
|
this.transp = 'TRANSPARENT';
|
|
93
89
|
this.busyStatus = 'FREE';
|
|
94
|
-
|
|
95
90
|
if (timed) {
|
|
96
91
|
let [hour, minute] = ev.eventTimeStr.split(':');
|
|
97
92
|
hour = +hour;
|
|
98
93
|
minute = +minute;
|
|
99
94
|
this.startDate += 'T' + pad2(hour) + pad2(minute) + '00';
|
|
100
95
|
this.endDate = this.startDate;
|
|
101
|
-
|
|
102
96
|
if (options.location && options.location.tzid) {
|
|
103
97
|
this.dtargs = `;TZID=${options.location.tzid}`;
|
|
104
98
|
}
|
|
105
99
|
} else {
|
|
106
|
-
this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
|
|
100
|
+
this.endDate = IcalEvent.formatYYYYMMDD(ev.getDate().next().greg());
|
|
101
|
+
// for all-day untimed, use DTEND;VALUE=DATE intsead of DURATION:P1D.
|
|
107
102
|
// It's more compatible with everthing except ancient versions of
|
|
108
103
|
// Lotus Notes circa 2004
|
|
109
|
-
|
|
110
104
|
this.dtargs = ';VALUE=DATE';
|
|
111
|
-
|
|
112
105
|
if (mask & flags.CHAG) {
|
|
113
106
|
this.transp = 'OPAQUE';
|
|
114
107
|
this.busyStatus = 'OOF';
|
|
115
108
|
}
|
|
116
109
|
}
|
|
117
|
-
|
|
118
110
|
if (options.emoji) {
|
|
119
111
|
const prefix = ev.getEmoji();
|
|
120
|
-
|
|
121
112
|
if (prefix) {
|
|
122
113
|
if (mask & flags.OMER_COUNT) {
|
|
123
114
|
subj = subj + ' ' + prefix;
|
|
@@ -125,32 +116,27 @@ class IcalEvent {
|
|
|
125
116
|
subj = prefix + ' ' + subj;
|
|
126
117
|
}
|
|
127
118
|
}
|
|
128
|
-
}
|
|
129
|
-
|
|
119
|
+
}
|
|
130
120
|
|
|
121
|
+
// make subject safe for iCalendar
|
|
131
122
|
subj = IcalEvent.escape(subj);
|
|
132
|
-
|
|
133
123
|
if (options.appendHebrewToSubject) {
|
|
134
124
|
const hebrew = ev.renderBrief('he');
|
|
135
|
-
|
|
136
125
|
if (hebrew) {
|
|
137
126
|
subj += ` / ${hebrew}`;
|
|
138
127
|
}
|
|
139
128
|
}
|
|
140
|
-
|
|
141
129
|
this.subj = subj;
|
|
142
130
|
this.category = ev.category || CATEGORY[getEventCategories(ev)[0]];
|
|
143
131
|
}
|
|
132
|
+
|
|
144
133
|
/**
|
|
145
134
|
* @return {string}
|
|
146
135
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
136
|
getAlarm() {
|
|
150
137
|
const ev = this.ev;
|
|
151
138
|
const mask = ev.getFlags();
|
|
152
139
|
const evAlarm = ev.alarm;
|
|
153
|
-
|
|
154
140
|
if (typeof evAlarm === 'string') {
|
|
155
141
|
return 'TRIGGER:' + evAlarm;
|
|
156
142
|
} else if (greg.isDate(evAlarm)) {
|
|
@@ -163,19 +149,16 @@ class IcalEvent {
|
|
|
163
149
|
} else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
|
|
164
150
|
return 'TRIGGER:-P0DT0H10M0S';
|
|
165
151
|
}
|
|
166
|
-
|
|
167
152
|
return null;
|
|
168
153
|
}
|
|
154
|
+
|
|
169
155
|
/**
|
|
170
156
|
* @return {string}
|
|
171
157
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
158
|
getUid() {
|
|
175
159
|
const options = this.options;
|
|
176
160
|
const digest = murmur32HexSync(this.ev.getDesc());
|
|
177
161
|
let uid = `hebcal-${this.startDate}-${digest}`;
|
|
178
|
-
|
|
179
162
|
if (this.timed && options.location) {
|
|
180
163
|
if (options.location.geoid) {
|
|
181
164
|
uid += `-${options.location.geoid}`;
|
|
@@ -183,104 +166,85 @@ class IcalEvent {
|
|
|
183
166
|
uid += '-' + makeAnchor(options.location.name);
|
|
184
167
|
}
|
|
185
168
|
}
|
|
186
|
-
|
|
187
169
|
return uid;
|
|
188
170
|
}
|
|
171
|
+
|
|
189
172
|
/**
|
|
190
173
|
* @return {string[]}
|
|
191
174
|
*/
|
|
192
|
-
|
|
193
|
-
|
|
194
175
|
getLongLines() {
|
|
195
176
|
if (this.lines) return this.lines;
|
|
196
177
|
const categoryLine = this.category ? `CATEGORIES:${this.category}` : [];
|
|
197
178
|
const uid = this.ev.uid || this.getUid();
|
|
198
179
|
const arr = this.lines = ['BEGIN:VEVENT', `DTSTAMP:${this.dtstamp}`].concat(categoryLine).concat([`SUMMARY:${this.subj}`, `DTSTART${this.dtargs}:${this.startDate}`, `DTEND${this.dtargs}:${this.endDate}`, `UID:${uid}`, `TRANSP:${this.transp}`, `X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`]);
|
|
199
|
-
|
|
200
180
|
if (!this.timed) {
|
|
201
181
|
arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
|
|
202
182
|
}
|
|
203
|
-
|
|
204
183
|
const ev = this.ev;
|
|
205
184
|
const mask = ev.getFlags();
|
|
206
185
|
const isUserEvent = Boolean(mask & flags.USER_EVENT);
|
|
207
|
-
|
|
208
186
|
if (!isUserEvent) {
|
|
209
187
|
arr.push('CLASS:PUBLIC');
|
|
210
188
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
189
|
+
const options = this.options;
|
|
190
|
+
// create memo (holiday descr, Torah, etc)
|
|
214
191
|
const memo = createMemo(ev, options);
|
|
215
192
|
addOptional(arr, 'DESCRIPTION', memo);
|
|
216
193
|
addOptional(arr, 'LOCATION', this.locationName);
|
|
217
|
-
|
|
218
194
|
if (this.timed && options.location) {
|
|
219
195
|
arr.push('GEO:' + options.location.latitude + ';' + options.location.longitude);
|
|
220
196
|
}
|
|
221
|
-
|
|
222
197
|
const trigger = this.getAlarm();
|
|
223
|
-
|
|
224
198
|
if (trigger) {
|
|
225
199
|
arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
|
|
226
200
|
}
|
|
227
|
-
|
|
228
201
|
arr.push('END:VEVENT');
|
|
229
202
|
return arr;
|
|
230
203
|
}
|
|
204
|
+
|
|
231
205
|
/**
|
|
232
206
|
* @return {string}
|
|
233
207
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
208
|
toString() {
|
|
237
209
|
return this.getLines().join('\r\n');
|
|
238
210
|
}
|
|
211
|
+
|
|
239
212
|
/**
|
|
240
213
|
* fold lines to 75 characters
|
|
241
214
|
* @return {string[]}
|
|
242
215
|
*/
|
|
243
|
-
|
|
244
|
-
|
|
245
216
|
getLines() {
|
|
246
217
|
return this.getLongLines().map(IcalEvent.fold);
|
|
247
218
|
}
|
|
219
|
+
|
|
248
220
|
/**
|
|
249
221
|
* fold line to 75 characters
|
|
250
222
|
* @param {string} line
|
|
251
223
|
* @return {string}
|
|
252
224
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
255
225
|
static fold(line) {
|
|
256
226
|
let isASCII = true;
|
|
257
|
-
|
|
258
227
|
for (let i = 0; i < line.length; i++) {
|
|
259
228
|
if (line.charCodeAt(i) > 255) {
|
|
260
229
|
isASCII = false;
|
|
261
230
|
break;
|
|
262
231
|
}
|
|
263
232
|
}
|
|
264
|
-
|
|
265
233
|
if (isASCII) {
|
|
266
234
|
return line.length <= 74 ? line : line.match(char74re).join('\r\n ');
|
|
267
235
|
}
|
|
268
|
-
|
|
269
236
|
if (encoder.encode(line).length <= 74) {
|
|
270
237
|
return line;
|
|
271
|
-
}
|
|
238
|
+
}
|
|
239
|
+
// iterate unicode character by character, making sure
|
|
272
240
|
// that adding a new character would keep the line <= 75 octets
|
|
273
|
-
|
|
274
|
-
|
|
275
241
|
let result = '';
|
|
276
242
|
let current = '';
|
|
277
243
|
let len = 0;
|
|
278
|
-
|
|
279
244
|
for (let i = 0; i < line.length; i++) {
|
|
280
245
|
const char = line[i];
|
|
281
246
|
const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
|
|
282
247
|
const newlen = len + octets;
|
|
283
|
-
|
|
284
248
|
if (newlen < 75) {
|
|
285
249
|
current += char;
|
|
286
250
|
len = newlen;
|
|
@@ -292,176 +256,150 @@ class IcalEvent {
|
|
|
292
256
|
i = -1;
|
|
293
257
|
}
|
|
294
258
|
}
|
|
295
|
-
|
|
296
259
|
return result + current;
|
|
297
260
|
}
|
|
261
|
+
|
|
298
262
|
/**
|
|
299
263
|
* @param {string} str
|
|
300
264
|
* @return {string}
|
|
301
265
|
*/
|
|
302
|
-
|
|
303
|
-
|
|
304
266
|
static escape(str) {
|
|
305
267
|
if (str.indexOf(',') !== -1) {
|
|
306
268
|
str = str.replace(/,/g, '\\,');
|
|
307
269
|
}
|
|
308
|
-
|
|
309
270
|
if (str.indexOf(';') !== -1) {
|
|
310
271
|
str = str.replace(/;/g, '\\;');
|
|
311
272
|
}
|
|
312
|
-
|
|
313
273
|
return str;
|
|
314
274
|
}
|
|
275
|
+
|
|
315
276
|
/**
|
|
316
277
|
* @param {Date} dt
|
|
317
278
|
* @return {string}
|
|
318
279
|
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
280
|
static formatYYYYMMDD(dt) {
|
|
322
281
|
return pad4(dt.getFullYear()) + pad2(dt.getMonth() + 1) + pad2(dt.getDate());
|
|
323
282
|
}
|
|
283
|
+
|
|
324
284
|
/**
|
|
325
285
|
* Returns UTC string for iCalendar
|
|
326
286
|
* @param {Date} dt
|
|
327
287
|
* @return {string}
|
|
328
288
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
289
|
static makeDtstamp(dt) {
|
|
332
290
|
const s = dt.toISOString();
|
|
333
291
|
return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) + s.slice(14, 16) + s.slice(17, 19) + 'Z';
|
|
334
292
|
}
|
|
335
|
-
/** @return {string} */
|
|
336
|
-
|
|
337
293
|
|
|
294
|
+
/** @return {string} */
|
|
338
295
|
static version() {
|
|
339
296
|
return version;
|
|
340
297
|
}
|
|
341
|
-
|
|
342
298
|
}
|
|
299
|
+
|
|
343
300
|
/**
|
|
344
301
|
* Transforms a single Event into a VEVENT string
|
|
345
302
|
* @param {Event} ev
|
|
346
303
|
* @param {HebrewCalendar.Options} options
|
|
347
304
|
* @return {string} multi-line result, delimited by \r\n
|
|
348
305
|
*/
|
|
349
|
-
|
|
350
306
|
function eventToIcal(ev, options) {
|
|
351
307
|
const ical = new IcalEvent(ev, options);
|
|
352
308
|
return ical.toString();
|
|
353
309
|
}
|
|
354
310
|
const torahMemoCache = new Map();
|
|
355
|
-
const HOLIDAY_IGNORE_MASK = flags.DAF_YOMI | flags.OMER_COUNT | flags.SHABBAT_MEVARCHIM | flags.MOLAD | flags.USER_EVENT | flags.MISHNA_YOMI | flags.HEBREW_DATE;
|
|
311
|
+
const HOLIDAY_IGNORE_MASK = flags.DAF_YOMI | flags.OMER_COUNT | flags.SHABBAT_MEVARCHIM | flags.MOLAD | flags.USER_EVENT | flags.MISHNA_YOMI | flags.YERUSHALMI_YOMI | flags.HEBREW_DATE;
|
|
312
|
+
|
|
356
313
|
/**
|
|
357
314
|
* @private
|
|
358
315
|
* @param {Event} ev
|
|
359
316
|
* @param {boolean} il
|
|
360
317
|
* @return {string}
|
|
361
318
|
*/
|
|
362
|
-
|
|
363
319
|
function makeTorahMemo(ev, il) {
|
|
364
320
|
if (ev.getFlags() & HOLIDAY_IGNORE_MASK || ev.eventTime) {
|
|
365
321
|
return '';
|
|
366
322
|
}
|
|
367
|
-
|
|
368
323
|
const hd = ev.getDate();
|
|
369
324
|
const yy = hd.getFullYear();
|
|
370
325
|
const mm = hd.getMonth();
|
|
371
326
|
const dd = hd.getDate();
|
|
372
327
|
const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
|
|
373
328
|
let memo = torahMemoCache.get(key);
|
|
374
|
-
|
|
375
329
|
if (typeof memo === 'string') {
|
|
376
330
|
return memo;
|
|
377
331
|
}
|
|
378
|
-
|
|
379
332
|
memo = makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
|
|
380
333
|
torahMemoCache.set(key, memo);
|
|
381
334
|
return memo;
|
|
382
335
|
}
|
|
336
|
+
|
|
383
337
|
/**
|
|
384
338
|
* @private
|
|
385
339
|
* @param {Event} e
|
|
386
340
|
* @param {HebrewCalendar.Options} options
|
|
387
341
|
* @return {string}
|
|
388
342
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
343
|
function createMemo(e, options) {
|
|
392
344
|
const desc = e.getDesc();
|
|
393
345
|
const candles = desc === 'Havdalah' || desc === 'Candle lighting';
|
|
394
|
-
|
|
395
346
|
if (typeof e.memo === 'string' && e.memo.length && e.memo.indexOf('\n') !== -1) {
|
|
396
347
|
e.memo = e.memo.replace(/\n/g, '\\n');
|
|
397
348
|
}
|
|
398
|
-
|
|
399
349
|
if (candles) {
|
|
400
350
|
return e.memo || '';
|
|
401
351
|
}
|
|
402
|
-
|
|
403
352
|
const mask = e.getFlags();
|
|
404
|
-
|
|
405
353
|
if (mask & flags.OMER_COUNT) {
|
|
406
354
|
const sefira = [e.sefira('en'), e.sefira('he'), e.sefira('translit')].join('\\n');
|
|
407
355
|
return e.getTodayIs('en') + '\\n\\n' + e.getTodayIs('he') + '\\n\\n' + sefira;
|
|
408
356
|
}
|
|
409
|
-
|
|
410
357
|
const url = appendTrackingToUrl(e.url(), options);
|
|
411
358
|
const torahMemo = makeTorahMemo(e, options.il);
|
|
412
|
-
|
|
413
359
|
if (mask & flags.PARSHA_HASHAVUA) {
|
|
414
360
|
return torahMemo + '\\n\\n' + url;
|
|
415
361
|
} else {
|
|
416
362
|
let memo = e.memo || getHolidayDescription(e);
|
|
417
|
-
|
|
418
363
|
if (!memo && typeof e.linkedEvent !== 'undefined') {
|
|
419
364
|
memo = e.linkedEvent.render(options.locale);
|
|
420
365
|
}
|
|
421
|
-
|
|
422
366
|
if (torahMemo) {
|
|
423
367
|
memo += '\\n\\n' + torahMemo;
|
|
424
368
|
}
|
|
425
|
-
|
|
426
369
|
if (url) {
|
|
427
370
|
if (memo.length) {
|
|
428
371
|
memo += '\\n\\n';
|
|
429
372
|
}
|
|
430
|
-
|
|
431
373
|
memo += url;
|
|
432
374
|
}
|
|
433
|
-
|
|
434
375
|
return memo;
|
|
435
376
|
}
|
|
436
377
|
}
|
|
378
|
+
|
|
437
379
|
/**
|
|
438
380
|
* Generates an RFC 2445 iCalendar string from an array of events
|
|
439
381
|
* @param {Event[]} events
|
|
440
382
|
* @param {HebrewCalendar.Options} options
|
|
441
383
|
* @return {string}
|
|
442
384
|
*/
|
|
443
|
-
|
|
444
|
-
|
|
445
385
|
async function eventsToIcalendar(events, options) {
|
|
446
386
|
if (!events.length) throw new RangeError('Events can not be empty');
|
|
447
387
|
if (!options) throw new TypeError('Invalid options object');
|
|
448
388
|
const opts = Object.assign({}, options);
|
|
449
389
|
opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
|
|
450
|
-
|
|
451
390
|
if (!opts.title) {
|
|
452
391
|
opts.title = getCalendarTitle(events, opts);
|
|
453
392
|
}
|
|
454
|
-
|
|
455
393
|
const icals = events.map(ev => new IcalEvent(ev, opts));
|
|
456
394
|
return icalEventsToString(icals, opts);
|
|
457
395
|
}
|
|
396
|
+
|
|
458
397
|
/**
|
|
459
398
|
* Generates an RFC 2445 iCalendar string from an array of IcalEvents
|
|
460
399
|
* @param {IcalEvent[]} icals
|
|
461
400
|
* @param {HebrewCalendar.Options} options
|
|
462
401
|
* @return {string}
|
|
463
402
|
*/
|
|
464
|
-
|
|
465
403
|
async function icalEventsToString(icals, options) {
|
|
466
404
|
const stream = [];
|
|
467
405
|
const uclang = Locale.getLocaleName().toUpperCase();
|
|
@@ -472,55 +410,46 @@ async function icalEventsToString(icals, options) {
|
|
|
472
410
|
const publishedTTL = opts.publishedTTL || 'PT7D';
|
|
473
411
|
const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
|
|
474
412
|
const preamble = ['BEGIN:VCALENDAR', 'VERSION:2.0', `PRODID:${prodid}`, 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-LOTUS-CHARSET:UTF-8', `X-PUBLISHED-TTL:${publishedTTL}`, `X-WR-CALNAME:${title}`, `X-WR-CALDESC:${caldesc}`];
|
|
475
|
-
|
|
476
413
|
for (const line of preamble.map(IcalEvent.fold)) {
|
|
477
414
|
stream.push(line);
|
|
478
415
|
stream.push('\r\n');
|
|
479
416
|
}
|
|
480
|
-
|
|
481
417
|
if (opts.relcalid) {
|
|
482
418
|
stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
|
|
483
419
|
stream.push('\r\n');
|
|
484
420
|
}
|
|
485
|
-
|
|
486
421
|
if (opts.calendarColor) {
|
|
487
422
|
stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
|
|
488
423
|
}
|
|
489
|
-
|
|
490
424
|
const location = opts.location;
|
|
491
|
-
|
|
492
425
|
if (location && location.tzid) {
|
|
493
426
|
const tzid = location.tzid;
|
|
494
427
|
stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
|
|
495
|
-
|
|
496
428
|
if (VTIMEZONE[tzid]) {
|
|
497
429
|
stream.push(VTIMEZONE[tzid]);
|
|
498
430
|
stream.push('\r\n');
|
|
499
431
|
} else {
|
|
500
432
|
const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
|
|
501
|
-
|
|
502
433
|
try {
|
|
503
434
|
const vtimezoneIcs = await promises.readFile(vtimezoneFilename, 'utf-8');
|
|
504
|
-
const lines = vtimezoneIcs.split('\r\n');
|
|
505
|
-
|
|
435
|
+
const lines = vtimezoneIcs.split('\r\n');
|
|
436
|
+
// ignore first 3 and last 1 lines
|
|
506
437
|
const str = lines.slice(3, lines.length - 2).join('\r\n');
|
|
507
438
|
stream.push(str);
|
|
508
439
|
stream.push('\r\n');
|
|
509
440
|
VTIMEZONE[tzid] = str; // cache for later
|
|
510
|
-
} catch (error) {
|
|
441
|
+
} catch (error) {
|
|
442
|
+
// ignore failure when no timezone definition to read
|
|
511
443
|
}
|
|
512
444
|
}
|
|
513
445
|
}
|
|
514
|
-
|
|
515
446
|
for (const ical of icals) {
|
|
516
447
|
const lines = ical.getLines();
|
|
517
|
-
|
|
518
448
|
for (const line of lines) {
|
|
519
449
|
stream.push(line);
|
|
520
450
|
stream.push('\r\n');
|
|
521
451
|
}
|
|
522
452
|
}
|
|
523
|
-
|
|
524
453
|
stream.push('END:VCALENDAR\r\n');
|
|
525
454
|
return stream.join('');
|
|
526
455
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hebcal/icalendar",
|
|
3
|
-
"version": "4.18.
|
|
3
|
+
"version": "4.18.4",
|
|
4
4
|
"author": "Michael J. Radwin (https://github.com/mjradwin)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ical",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"url": "https://github.com/hebcal/hebcal-icalendar/issues"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@hebcal/core": "^3.
|
|
28
|
-
"@hebcal/rest-api": "^4.
|
|
27
|
+
"@hebcal/core": "^3.46.1",
|
|
28
|
+
"@hebcal/rest-api": "^4.3.7",
|
|
29
29
|
"murmurhash3": "^0.5.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"verbose": true
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@babel/core": "^7.
|
|
52
|
-
"@babel/preset-env": "^7.
|
|
51
|
+
"@babel/core": "^7.20.2",
|
|
52
|
+
"@babel/preset-env": "^7.20.2",
|
|
53
53
|
"@babel/register": "^7.18.9",
|
|
54
|
-
"@rollup/plugin-babel": "^
|
|
55
|
-
"@rollup/plugin-commonjs": "^
|
|
56
|
-
"@rollup/plugin-json": "^
|
|
57
|
-
"ava": "^
|
|
58
|
-
"eslint": "^8.
|
|
54
|
+
"@rollup/plugin-babel": "^6.0.2",
|
|
55
|
+
"@rollup/plugin-commonjs": "^23.0.2",
|
|
56
|
+
"@rollup/plugin-json": "^5.0.1",
|
|
57
|
+
"ava": "^5.1.0",
|
|
58
|
+
"eslint": "^8.28.0",
|
|
59
59
|
"eslint-config-google": "^0.14.0",
|
|
60
|
-
"jsdoc": "^
|
|
60
|
+
"jsdoc": "^4.0.0",
|
|
61
61
|
"jsdoc-to-markdown": "^7.1.1",
|
|
62
|
-
"rollup": "^
|
|
62
|
+
"rollup": "^3.4.0"
|
|
63
63
|
}
|
|
64
64
|
}
|