@hebcal/icalendar 5.1.1 → 6.0.2

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 CHANGED
@@ -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('Hawaii'),
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
- <a name="eventsToIcalendarStream"></a>
24
+ const str = await eventsToIcalendar(events, {
25
+ locale: 'he',
26
+ ...options,
27
+ })
53
28
 
54
- ## eventsToIcalendarStream(readable, events, options) ⇒ <code>stream.Readable</code>
55
- Generates an RFC 2445 iCalendar stream from an array of events
56
-
57
- **Kind**: global function
58
-
59
- | Param | Type |
60
- | --- | --- |
61
- | readable | <code>stream.Readable</code> |
62
- | events | <code>Array.&lt;Event&gt;</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.&lt;Event&gt;</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)
@@ -42,51 +42,28 @@ export declare class IcalEvent {
42
42
  */
43
43
  constructor(ev: Event, options?: ICalOptions);
44
44
  getAlarm(): string | null;
45
- /**
46
- * @return {string}
47
- */
48
45
  getUid(): string;
49
- /**
50
- * @return {string[]}
51
- */
52
46
  getLongLines(): string[];
53
- /**
54
- * @return {string}
55
- */
56
47
  toString(): string;
57
48
  /**
58
49
  * fold lines to 75 characters
59
- * @return {string[]}
60
50
  */
61
51
  getLines(): string[];
62
52
  /**
63
53
  * fold line to 75 characters
64
- * @param {string} line
65
- * @return {string}
66
54
  */
67
55
  static fold(line: string): string;
68
- /**
69
- * @param {string} str
70
- * @return {string}
71
- */
72
56
  static escape(str: string): string;
73
- /**
74
- * @param {Date} dt
75
- * @return {string}
76
- */
77
57
  static formatYYYYMMDD(dt: Date): string;
78
58
  /**
79
59
  * Returns UTC string for iCalendar
80
- * @param {Date} dt
81
- * @return {string}
82
60
  */
83
61
  static makeDtstamp(dt: Date): string;
84
- /** @return {string} */
85
62
  static version(): string;
86
63
  }
87
64
  /**
88
65
  * Transforms a single Event into a VEVENT string
89
- * @return {string} multi-line result, delimited by \r\n
66
+ * @returns multi-line result, delimited by \r\n
90
67
  */
91
68
  export declare function eventToIcal(ev: Event, options: ICalOptions): string;
92
69
  /**
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- /*! @hebcal/icalendar v5.1.1 */
1
+ /*! @hebcal/icalendar v6.0.2 */
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
7
  // DO NOT EDIT THIS AUTO-GENERATED FILE!
8
- const version = '5.1.1';
8
+ const version = '6.0.2';
9
9
 
10
10
  const vtimezoneCache = new Map();
11
11
  const CATEGORY = {
@@ -48,8 +48,11 @@ function appendTrackingToUrl(url, options) {
48
48
  }
49
49
  const encoder = new TextEncoder();
50
50
  const char74re = /(.{1,74})/g;
51
- const DAILY_LEARNING = flags.DAILY_LEARNING | flags.DAF_YOMI |
52
- flags.MISHNA_YOMI | flags.YERUSHALMI_YOMI | flags.NACH_YOMI;
51
+ const DAILY_LEARNING = flags.DAILY_LEARNING |
52
+ flags.DAF_YOMI |
53
+ flags.MISHNA_YOMI |
54
+ flags.YERUSHALMI_YOMI |
55
+ flags.NACH_YOMI;
53
56
  /**
54
57
  * Represents an RFC 2445 iCalendar VEVENT
55
58
  */
@@ -69,10 +72,12 @@ class IcalEvent {
69
72
  else if (typeof opts.sequence === 'number') {
70
73
  this.sequence = opts.sequence;
71
74
  }
72
- const timed = this.timed = Boolean(ev.eventTime);
75
+ const timed = (this.timed = Boolean(ev.eventTime));
73
76
  const locale = opts.locale;
74
77
  const location = opts.location;
75
- let subj = shouldRenderBrief(ev) ? ev.renderBrief(locale) : ev.render(locale);
78
+ let subj = shouldRenderBrief(ev)
79
+ ? ev.renderBrief(locale)
80
+ : ev.render(locale);
76
81
  const mask = ev.getFlags();
77
82
  if (ev.locationName) {
78
83
  this.locationName = ev.locationName;
@@ -80,7 +85,7 @@ class IcalEvent {
80
85
  else if (timed && location) {
81
86
  this.locationName = location.getShortName();
82
87
  }
83
- else if ((mask & DAILY_LEARNING) && ev.category) {
88
+ else if (mask & DAILY_LEARNING && ev.category) {
84
89
  this.locationName = Locale.gettext(ev.category, locale);
85
90
  }
86
91
  const date = IcalEvent.formatYYYYMMDD(ev.getDate().greg());
@@ -129,7 +134,8 @@ class IcalEvent {
129
134
  }
130
135
  }
131
136
  this.subj = subj;
132
- this.category = ev.category || CATEGORY[(_a = getEventCategories(ev)) === null || _a === void 0 ? void 0 : _a[0]];
137
+ this.category =
138
+ ev.category || CATEGORY[(_a = getEventCategories(ev)) === null || _a === void 0 ? void 0 : _a[0]];
133
139
  }
134
140
  getAlarm() {
135
141
  const ev = this.ev;
@@ -157,9 +163,6 @@ class IcalEvent {
157
163
  }
158
164
  return null;
159
165
  }
160
- /**
161
- * @return {string}
162
- */
163
166
  getUid() {
164
167
  if (this.ev.uid) {
165
168
  return this.ev.uid;
@@ -178,9 +181,6 @@ class IcalEvent {
178
181
  }
179
182
  return uid;
180
183
  }
181
- /**
182
- * @return {string[]}
183
- */
184
184
  getLongLines() {
185
185
  if (this.lines)
186
186
  return this.lines;
@@ -189,17 +189,16 @@ class IcalEvent {
189
189
  if (this.sequence) {
190
190
  categoryLine.unshift(`SEQUENCE:${this.sequence}`);
191
191
  }
192
- const arr = this.lines = [
193
- 'BEGIN:VEVENT',
194
- `DTSTAMP:${this.dtstamp}`,
195
- ].concat(categoryLine).concat([
192
+ const arr = (this.lines = ['BEGIN:VEVENT', `DTSTAMP:${this.dtstamp}`]
193
+ .concat(categoryLine)
194
+ .concat([
196
195
  `SUMMARY:${this.subj}`,
197
196
  `DTSTART${this.dtargs}:${this.startDate}`,
198
197
  `DTEND${this.dtargs}:${this.endDate}`,
199
198
  `UID:${uid}`,
200
199
  `TRANSP:${this.transp}`,
201
200
  `X-MICROSOFT-CDO-BUSYSTATUS:${this.busyStatus}`,
202
- ]);
201
+ ]));
203
202
  if (!this.timed) {
204
203
  arr.push('X-MICROSOFT-CDO-ALLDAYEVENT:TRUE');
205
204
  }
@@ -233,23 +232,17 @@ class IcalEvent {
233
232
  arr.push('END:VEVENT');
234
233
  return arr;
235
234
  }
236
- /**
237
- * @return {string}
238
- */
239
235
  toString() {
240
236
  return this.getLines().join('\r\n');
241
237
  }
242
238
  /**
243
239
  * fold lines to 75 characters
244
- * @return {string[]}
245
240
  */
246
241
  getLines() {
247
242
  return this.getLongLines().map(IcalEvent.fold);
248
243
  }
249
244
  /**
250
245
  * fold line to 75 characters
251
- * @param {string} line
252
- * @return {string}
253
246
  */
254
247
  static fold(line) {
255
248
  let isASCII = true;
@@ -292,10 +285,6 @@ class IcalEvent {
292
285
  }
293
286
  return result + current;
294
287
  }
295
- /**
296
- * @param {string} str
297
- * @return {string}
298
- */
299
288
  static escape(str) {
300
289
  if (str.indexOf(',') !== -1) {
301
290
  str = str.replace(/,/g, '\\,');
@@ -305,46 +294,45 @@ class IcalEvent {
305
294
  }
306
295
  return str;
307
296
  }
308
- /**
309
- * @param {Date} dt
310
- * @return {string}
311
- */
312
297
  static formatYYYYMMDD(dt) {
313
- return pad4(dt.getFullYear()) +
314
- pad2(dt.getMonth() + 1) + pad2(dt.getDate());
298
+ return (pad4(dt.getFullYear()) + pad2(dt.getMonth() + 1) + pad2(dt.getDate()));
315
299
  }
316
300
  /**
317
301
  * Returns UTC string for iCalendar
318
- * @param {Date} dt
319
- * @return {string}
320
302
  */
321
303
  static makeDtstamp(dt) {
322
304
  const s = dt.toISOString();
323
- return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
324
- s.slice(14, 16) + s.slice(17, 19) + 'Z';
305
+ return (s.slice(0, 4) +
306
+ s.slice(5, 7) +
307
+ s.slice(8, 13) +
308
+ s.slice(14, 16) +
309
+ s.slice(17, 19) +
310
+ 'Z');
325
311
  }
326
- /** @return {string} */
327
312
  static version() {
328
313
  return version;
329
314
  }
330
315
  }
331
316
  /**
332
317
  * Transforms a single Event into a VEVENT string
333
- * @return {string} multi-line result, delimited by \r\n
318
+ * @returns multi-line result, delimited by \r\n
334
319
  */
335
320
  function eventToIcal(ev, options) {
336
321
  const ical = new IcalEvent(ev, options);
337
322
  return ical.toString();
338
323
  }
339
324
  const torahMemoCache = new Map();
340
- const HOLIDAY_IGNORE_MASK = DAILY_LEARNING | flags.OMER_COUNT |
341
- flags.SHABBAT_MEVARCHIM | flags.MOLAD | flags.USER_EVENT |
325
+ const HOLIDAY_IGNORE_MASK = DAILY_LEARNING |
326
+ flags.OMER_COUNT |
327
+ flags.SHABBAT_MEVARCHIM |
328
+ flags.MOLAD |
329
+ flags.USER_EVENT |
342
330
  flags.HEBREW_DATE;
343
331
  /**
344
332
  * @private
345
333
  */
346
334
  function makeTorahMemo(ev, il) {
347
- if ((ev.getFlags() & HOLIDAY_IGNORE_MASK) || ev.eventTime) {
335
+ if (ev.getFlags() & HOLIDAY_IGNORE_MASK || ev.eventTime) {
348
336
  return '';
349
337
  }
350
338
  const hd = ev.getDate();
@@ -375,8 +363,16 @@ function createMemo(ev, options) {
375
363
  const mask = ev.getFlags();
376
364
  if (mask & flags.OMER_COUNT) {
377
365
  const omerEv = ev;
378
- const sefira = [omerEv.sefira('en'), omerEv.sefira('he'), omerEv.sefira('translit')].join('\\n');
379
- return omerEv.getTodayIs('en') + '\\n\\n' + omerEv.getTodayIs('he') + '\\n\\n' + sefira;
366
+ const sefira = [
367
+ omerEv.sefira('en'),
368
+ omerEv.sefira('he'),
369
+ omerEv.sefira('translit'),
370
+ ].join('\\n');
371
+ return (omerEv.getTodayIs('en') +
372
+ '\\n\\n' +
373
+ omerEv.getTodayIs('he') +
374
+ '\\n\\n' +
375
+ sefira);
380
376
  }
381
377
  const url = appendTrackingToUrl(ev.url(), options);
382
378
  const torahMemo = makeTorahMemo(ev, options.il);
@@ -415,7 +411,7 @@ async function eventsToIcalendar(events, options) {
415
411
  if (!opts.title) {
416
412
  opts.title = getCalendarTitle(events, opts);
417
413
  }
418
- const icals = events.map((ev) => new IcalEvent(ev, opts));
414
+ const icals = events.map(ev => new IcalEvent(ev, opts));
419
415
  return icalEventsToString(icals, opts);
420
416
  }
421
417
  /**
@@ -432,11 +428,13 @@ async function icalEventsToString(icals, options) {
432
428
  const opts = Object.assign({}, options);
433
429
  opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
434
430
  const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
435
- const caldesc = opts.caldesc ? IcalEvent.escape(opts.caldesc) :
436
- opts.yahrzeit ?
437
- 'Yahrzeits + Anniversaries from www.hebcal.com' :
438
- 'Jewish Holidays from www.hebcal.com';
439
- const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
431
+ const caldesc = opts.caldesc
432
+ ? IcalEvent.escape(opts.caldesc)
433
+ : opts.yahrzeit
434
+ ? 'Yahrzeits + Anniversaries from www.hebcal.com'
435
+ : 'Jewish Holidays from www.hebcal.com';
436
+ const prodid = opts.prodid ||
437
+ `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
440
438
  const preamble = [
441
439
  'BEGIN:VCALENDAR',
442
440
  'VERSION:2.0',
@@ -1 +1 @@
1
- export declare const version = "5.1.1";
1
+ export declare const version = "6.0.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hebcal/icalendar",
3
- "version": "5.1.1",
3
+ "version": "6.0.2",
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,10 @@
29
30
  "url": "https://github.com/hebcal/hebcal-icalendar/issues"
30
31
  },
31
32
  "dependencies": {
32
- "@hebcal/core": "^5.4.6",
33
- "@hebcal/rest-api": "^5.0.5",
33
+ "@hebcal/core": "^5.8.0",
34
+ "@hebcal/rest-api": "^5.2.0",
34
35
  "murmurhash3": "^0.5.0",
35
- "tslib": "^2.6.3"
36
+ "tslib": "^2.8.1"
36
37
  },
37
38
  "scripts": {
38
39
  "build:rollup": "rollup -c",
@@ -40,27 +41,28 @@
40
41
  "build": "npm run build:version && npm run build:rollup",
41
42
  "prepare": "npm run build",
42
43
  "pretest": "npm run build",
43
- "readme": "npx jsdoc2md dist/index.ts",
44
+ "docs": "typedoc",
44
45
  "coverage": "jest --coverage",
45
- "test": "jest"
46
+ "test": "jest",
47
+ "lint": "gts lint",
48
+ "clean": "gts clean",
49
+ "compile": "tsc",
50
+ "fix": "gts fix"
46
51
  },
47
52
  "license": "BSD-2-Clause",
48
53
  "devDependencies": {
49
- "@babel/preset-env": "^7.24.7",
50
- "@babel/preset-typescript": "^7.24.7",
51
- "@hebcal/learning": "^5.0.8",
52
- "@rollup/plugin-commonjs": "^26.0.1",
54
+ "@babel/preset-env": "^7.26.0",
55
+ "@babel/preset-typescript": "^7.26.0",
56
+ "@hebcal/learning": "^5.1.2",
57
+ "@rollup/plugin-commonjs": "^28.0.1",
53
58
  "@rollup/plugin-json": "^6.1.0",
54
- "@rollup/plugin-typescript": "^11.1.6",
55
- "@types/jest": "^29.5.12",
56
- "eslint": "^8.57.0",
57
- "eslint-config-google": "^0.14.0",
59
+ "@rollup/plugin-typescript": "^12.1.1",
60
+ "@types/jest": "^29.5.14",
61
+ "@types/node": "22.9.0",
58
62
  "gts": "^5.3.1",
59
63
  "jest": "^29.7.0",
60
- "jsdoc": "^4.0.3",
61
- "jsdoc-to-markdown": "^8.0.1",
62
- "rollup": "^4.18.0",
63
- "ts-jest": "^29.1.5",
64
- "typescript": "^5.5.3"
64
+ "rollup": "^4.25.0",
65
+ "typedoc": "^0.26.11",
66
+ "typescript": "^5.6.3"
65
67
  }
66
68
  }
package/dist/index.cjs DELETED
@@ -1,506 +0,0 @@
1
- /*! @hebcal/icalendar v5.1.1 */
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.1';
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
- // In addition to the URL being part of the DESCRIPTION field,
224
- // should we also generate an RFC 5545 URL property?
225
- if (options.url) {
226
- const url = ev.url();
227
- if (url) {
228
- arr.push('URL:' + appendTrackingToUrl(url, options));
229
- }
230
- }
231
- const trigger = this.getAlarm();
232
- if (trigger) {
233
- arr.push('BEGIN:VALARM', 'ACTION:DISPLAY', 'DESCRIPTION:Event reminder', `${trigger}`, 'END:VALARM');
234
- }
235
- arr.push('END:VEVENT');
236
- return arr;
237
- }
238
- /**
239
- * @return {string}
240
- */
241
- toString() {
242
- return this.getLines().join('\r\n');
243
- }
244
- /**
245
- * fold lines to 75 characters
246
- * @return {string[]}
247
- */
248
- getLines() {
249
- return this.getLongLines().map(IcalEvent.fold);
250
- }
251
- /**
252
- * fold line to 75 characters
253
- * @param {string} line
254
- * @return {string}
255
- */
256
- static fold(line) {
257
- let isASCII = true;
258
- for (let i = 0; i < line.length; i++) {
259
- if (line.charCodeAt(i) > 255) {
260
- isASCII = false;
261
- break;
262
- }
263
- }
264
- if (isASCII) {
265
- if (line.length <= 74) {
266
- return line;
267
- }
268
- const matches = line.match(char74re);
269
- return matches.join('\r\n ');
270
- }
271
- if (encoder.encode(line).length <= 74) {
272
- return line;
273
- }
274
- // iterate unicode character by character, making sure
275
- // that adding a new character would keep the line <= 75 octets
276
- let result = '';
277
- let current = '';
278
- let len = 0;
279
- for (let i = 0; i < line.length; i++) {
280
- const char = line[i];
281
- const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
282
- const newlen = len + octets;
283
- if (newlen < 75) {
284
- current += char;
285
- len = newlen;
286
- }
287
- else {
288
- result += current + '\r\n ';
289
- line = line.substring(i);
290
- current = '';
291
- len = 0;
292
- i = -1;
293
- }
294
- }
295
- return result + current;
296
- }
297
- /**
298
- * @param {string} str
299
- * @return {string}
300
- */
301
- static escape(str) {
302
- if (str.indexOf(',') !== -1) {
303
- str = str.replace(/,/g, '\\,');
304
- }
305
- if (str.indexOf(';') !== -1) {
306
- str = str.replace(/;/g, '\\;');
307
- }
308
- return str;
309
- }
310
- /**
311
- * @param {Date} dt
312
- * @return {string}
313
- */
314
- static formatYYYYMMDD(dt) {
315
- return restApi.pad4(dt.getFullYear()) +
316
- restApi.pad2(dt.getMonth() + 1) + restApi.pad2(dt.getDate());
317
- }
318
- /**
319
- * Returns UTC string for iCalendar
320
- * @param {Date} dt
321
- * @return {string}
322
- */
323
- static makeDtstamp(dt) {
324
- const s = dt.toISOString();
325
- return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
326
- s.slice(14, 16) + s.slice(17, 19) + 'Z';
327
- }
328
- /** @return {string} */
329
- static version() {
330
- return version;
331
- }
332
- }
333
- /**
334
- * Transforms a single Event into a VEVENT string
335
- * @return {string} multi-line result, delimited by \r\n
336
- */
337
- function eventToIcal(ev, options) {
338
- const ical = new IcalEvent(ev, options);
339
- return ical.toString();
340
- }
341
- const torahMemoCache = new Map();
342
- const HOLIDAY_IGNORE_MASK = DAILY_LEARNING | core.flags.OMER_COUNT |
343
- core.flags.SHABBAT_MEVARCHIM | core.flags.MOLAD | core.flags.USER_EVENT |
344
- core.flags.HEBREW_DATE;
345
- /**
346
- * @private
347
- */
348
- function makeTorahMemo(ev, il) {
349
- if ((ev.getFlags() & HOLIDAY_IGNORE_MASK) || ev.eventTime) {
350
- return '';
351
- }
352
- const hd = ev.getDate();
353
- const yy = hd.getFullYear();
354
- const mm = hd.getMonth();
355
- const dd = hd.getDate();
356
- const key = [yy, mm, dd, il ? '1' : '0', ev.getDesc()].join('-');
357
- let memo = torahMemoCache.get(key);
358
- if (typeof memo === 'string') {
359
- return memo;
360
- }
361
- memo = restApi.makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
362
- torahMemoCache.set(key, memo);
363
- return memo;
364
- }
365
- /**
366
- * @private
367
- */
368
- function createMemo(ev, options) {
369
- let memo = ev.memo;
370
- if (typeof memo === 'string' && memo.length && memo.indexOf('\n') !== -1) {
371
- memo = memo.replace(/\n/g, '\\n');
372
- }
373
- const desc = ev.getDesc();
374
- if (desc === 'Havdalah' || desc === 'Candle lighting') {
375
- return memo || '';
376
- }
377
- const mask = ev.getFlags();
378
- if (mask & core.flags.OMER_COUNT) {
379
- const omerEv = ev;
380
- const sefira = [omerEv.sefira('en'), omerEv.sefira('he'), omerEv.sefira('translit')].join('\\n');
381
- return omerEv.getTodayIs('en') + '\\n\\n' + omerEv.getTodayIs('he') + '\\n\\n' + sefira;
382
- }
383
- const url = appendTrackingToUrl(ev.url(), options);
384
- const torahMemo = makeTorahMemo(ev, options.il);
385
- if (!memo) {
386
- if (typeof ev.linkedEvent !== 'undefined') {
387
- memo = ev.linkedEvent.render(options.locale);
388
- }
389
- else {
390
- memo = restApi.getHolidayDescription(ev);
391
- }
392
- }
393
- if (torahMemo) {
394
- if (memo.length) {
395
- memo += '\\n\\n';
396
- }
397
- memo += torahMemo;
398
- }
399
- if (url) {
400
- if (memo.length) {
401
- memo += '\\n\\n';
402
- }
403
- memo += url;
404
- }
405
- return memo;
406
- }
407
- /**
408
- * Generates an RFC 2445 iCalendar string from an array of events
409
- */
410
- async function eventsToIcalendar(events, options) {
411
- if (!events.length)
412
- throw new RangeError('Events can not be empty');
413
- if (!options)
414
- throw new TypeError('Invalid options object');
415
- const opts = Object.assign({}, options);
416
- opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
417
- if (!opts.title) {
418
- opts.title = restApi.getCalendarTitle(events, opts);
419
- }
420
- const icals = events.map((ev) => new IcalEvent(ev, opts));
421
- return icalEventsToString(icals, opts);
422
- }
423
- /**
424
- * Generates an RFC 2445 iCalendar string from an array of IcalEvents
425
- */
426
- async function icalEventsToString(icals, options) {
427
- if (!icals.length)
428
- throw new RangeError('Events can not be empty');
429
- if (!options)
430
- throw new TypeError('Invalid options object');
431
- const stream = [];
432
- const locale = options.locale || core.Locale.getLocaleName();
433
- const uclang = locale.toUpperCase();
434
- const opts = Object.assign({}, options);
435
- opts.dtstamp = opts.dtstamp || IcalEvent.makeDtstamp(new Date());
436
- const title = opts.title ? IcalEvent.escape(opts.title) : 'Untitled';
437
- const caldesc = opts.caldesc ? IcalEvent.escape(opts.caldesc) :
438
- opts.yahrzeit ?
439
- 'Yahrzeits + Anniversaries from www.hebcal.com' :
440
- 'Jewish Holidays from www.hebcal.com';
441
- const prodid = opts.prodid || `-//hebcal.com/NONSGML Hebcal Calendar v1${version}//${uclang}`;
442
- const preamble = [
443
- 'BEGIN:VCALENDAR',
444
- 'VERSION:2.0',
445
- `PRODID:${prodid}`,
446
- 'CALSCALE:GREGORIAN',
447
- 'METHOD:PUBLISH',
448
- 'X-LOTUS-CHARSET:UTF-8',
449
- ];
450
- if (opts.publishedTTL !== false) {
451
- const publishedTTL = opts.publishedTTL || 'PT7D';
452
- preamble.push(`X-PUBLISHED-TTL:${publishedTTL}`);
453
- }
454
- preamble.push(`X-WR-CALNAME:${title}`);
455
- preamble.push(`X-WR-CALDESC:${caldesc}`);
456
- for (const line of preamble.map(IcalEvent.fold)) {
457
- stream.push(line);
458
- stream.push('\r\n');
459
- }
460
- if (opts.relcalid) {
461
- stream.push(IcalEvent.fold(`X-WR-RELCALID:${opts.relcalid}`));
462
- stream.push('\r\n');
463
- }
464
- if (opts.calendarColor) {
465
- stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
466
- }
467
- const location = opts.location;
468
- const tzid = location === null || location === void 0 ? void 0 : location.getTzid();
469
- if (tzid) {
470
- stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
471
- const vtz = vtimezoneCache.get(tzid);
472
- if (typeof vtz === 'string') {
473
- stream.push(vtz);
474
- stream.push('\r\n');
475
- }
476
- else {
477
- const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
478
- try {
479
- const vtimezoneIcs = await fs.promises.readFile(vtimezoneFilename, 'utf-8');
480
- const lines = vtimezoneIcs.split('\r\n');
481
- // ignore first 3 and last 1 lines
482
- const str = lines.slice(3, lines.length - 2).join('\r\n');
483
- stream.push(str);
484
- stream.push('\r\n');
485
- vtimezoneCache.set(tzid, str);
486
- }
487
- catch (error) {
488
- // ignore failure when no timezone definition to read
489
- }
490
- }
491
- }
492
- for (const ical of icals) {
493
- const lines = ical.getLines();
494
- for (const line of lines) {
495
- stream.push(line);
496
- stream.push('\r\n');
497
- }
498
- }
499
- stream.push('END:VCALENDAR\r\n');
500
- return stream.join('');
501
- }
502
-
503
- exports.IcalEvent = IcalEvent;
504
- exports.eventToIcal = eventToIcal;
505
- exports.eventsToIcalendar = eventsToIcalendar;
506
- exports.icalEventsToString = icalEventsToString;