@hebcal/icalendar 5.0.6 → 5.1.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 +1 -1
- package/dist/icalendar.d.ts +99 -0
- package/dist/index.cjs +426 -450
- package/dist/index.mjs +426 -450
- package/dist/pkgVersion.d.ts +1 -0
- package/package.json +25 -17
- package/icalendar.d.ts +0 -36
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @hebcal/icalendar v5.
|
|
1
|
+
/*! @hebcal/icalendar v5.1.1 */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var core = require('@hebcal/core');
|
|
@@ -7,521 +7,497 @@ var restApi = require('@hebcal/rest-api');
|
|
|
7
7
|
var fs = require('fs');
|
|
8
8
|
|
|
9
9
|
// DO NOT EDIT THIS AUTO-GENERATED FILE!
|
|
10
|
-
const version = '5.
|
|
10
|
+
const version = '5.1.1';
|
|
11
11
|
|
|
12
12
|
const vtimezoneCache = new Map();
|
|
13
13
|
const CATEGORY = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
29
|
};
|
|
30
|
-
|
|
31
30
|
/**
|
|
32
31
|
* @private
|
|
33
|
-
* @param {string[]} arr
|
|
34
|
-
* @param {string} key
|
|
35
|
-
* @param {string} val
|
|
36
32
|
*/
|
|
37
33
|
function addOptional(arr, key, val) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
if (val) {
|
|
35
|
+
const str = IcalEvent.escape(val);
|
|
36
|
+
arr.push(key + ':' + str);
|
|
37
|
+
}
|
|
42
38
|
}
|
|
43
|
-
|
|
44
39
|
/**
|
|
45
40
|
* @private
|
|
46
|
-
* @param {string} url
|
|
47
|
-
* @param {CalOptions} options
|
|
48
|
-
* @return {string}
|
|
49
41
|
*/
|
|
50
42
|
function appendTrackingToUrl(url, options) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
options.il, utmSource, utmMedium, utmCampaign);
|
|
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);
|
|
59
50
|
}
|
|
60
|
-
|
|
61
51
|
const encoder = new TextEncoder();
|
|
62
52
|
const char74re = /(.{1,74})/g;
|
|
63
|
-
|
|
64
53
|
const DAILY_LEARNING = core.flags.DAILY_LEARNING | core.flags.DAF_YOMI |
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
core.flags.MISHNA_YOMI | core.flags.YERUSHALMI_YOMI | core.flags.NACH_YOMI;
|
|
67
55
|
/**
|
|
68
56
|
* Represents an RFC 2445 iCalendar VEVENT
|
|
69
57
|
*/
|
|
70
58
|
class IcalEvent {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @return {string}
|
|
150
|
-
*/
|
|
151
|
-
getAlarm() {
|
|
152
|
-
const ev = this.ev;
|
|
153
|
-
const mask = ev.getFlags();
|
|
154
|
-
const evAlarm = ev.alarm;
|
|
155
|
-
if (typeof evAlarm === 'string') {
|
|
156
|
-
return 'TRIGGER:' + evAlarm;
|
|
157
|
-
} else if (typeof evAlarm === 'boolean' && !evAlarm) {
|
|
158
|
-
return null;
|
|
159
|
-
} else if (core.greg.isDate(evAlarm)) {
|
|
160
|
-
evAlarm.setSeconds(0);
|
|
161
|
-
return 'TRIGGER;VALUE=DATE-TIME:' + IcalEvent.makeDtstamp(evAlarm);
|
|
162
|
-
} else if (mask & core.flags.OMER_COUNT) {
|
|
163
|
-
return 'TRIGGER:-P0DT3H30M0S'; // 8:30pm Omer alarm evening before
|
|
164
|
-
} else if (mask & core.flags.USER_EVENT) {
|
|
165
|
-
return 'TRIGGER:-P0DT12H0M0S'; // noon the day before
|
|
166
|
-
} else if (this.timed && ev.getDesc().startsWith('Candle lighting')) {
|
|
167
|
-
return 'TRIGGER:-P0DT0H10M0S';
|
|
168
|
-
}
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* @return {string}
|
|
174
|
-
*/
|
|
175
|
-
getUid() {
|
|
176
|
-
const options = this.options;
|
|
177
|
-
const digest = murmurhash3.murmur32HexSync(this.ev.getDesc());
|
|
178
|
-
let uid = `hebcal-${this.isoDateOnly}-${digest}`;
|
|
179
|
-
const loc = options.location;
|
|
180
|
-
if (this.timed && loc) {
|
|
181
|
-
if (loc.getGeoId()) {
|
|
182
|
-
uid += `-${loc.getGeoId()}`;
|
|
183
|
-
} else if (loc.getName()) {
|
|
184
|
-
uid += '-' + restApi.makeAnchor(loc.getName());
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return uid;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* @return {string[]}
|
|
192
|
-
*/
|
|
193
|
-
getLongLines() {
|
|
194
|
-
if (this.lines) return this.lines;
|
|
195
|
-
const categoryLine = this.category ? [`CATEGORIES:${this.category}`] : [];
|
|
196
|
-
const uid = this.ev.uid || this.getUid();
|
|
197
|
-
if (this.sequence) {
|
|
198
|
-
categoryLine.unshift(`SEQUENCE:${this.sequence}`);
|
|
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]];
|
|
199
135
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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;
|
|
214
161
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
221
182
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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;
|
|
231
237
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
'ACTION:DISPLAY',
|
|
238
|
-
'DESCRIPTION:Event reminder',
|
|
239
|
-
`${trigger}`,
|
|
240
|
-
'END:VALARM',
|
|
241
|
-
);
|
|
238
|
+
/**
|
|
239
|
+
* @return {string}
|
|
240
|
+
*/
|
|
241
|
+
toString() {
|
|
242
|
+
return this.getLines().join('\r\n');
|
|
242
243
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* @return {string}
|
|
251
|
-
*/
|
|
252
|
-
toString() {
|
|
253
|
-
return this.getLines().join('\r\n');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* fold lines to 75 characters
|
|
258
|
-
* @return {string[]}
|
|
259
|
-
*/
|
|
260
|
-
getLines() {
|
|
261
|
-
return this.getLongLines().map(IcalEvent.fold);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* fold line to 75 characters
|
|
266
|
-
* @param {string} line
|
|
267
|
-
* @return {string}
|
|
268
|
-
*/
|
|
269
|
-
static fold(line) {
|
|
270
|
-
let isASCII = true;
|
|
271
|
-
for (let i = 0; i < line.length; i++) {
|
|
272
|
-
if (line.charCodeAt(i) > 255) {
|
|
273
|
-
isASCII = false;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
244
|
+
/**
|
|
245
|
+
* fold lines to 75 characters
|
|
246
|
+
* @return {string[]}
|
|
247
|
+
*/
|
|
248
|
+
getLines() {
|
|
249
|
+
return this.getLongLines().map(IcalEvent.fold);
|
|
276
250
|
}
|
|
277
|
-
|
|
278
|
-
|
|
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;
|
|
279
296
|
}
|
|
280
|
-
|
|
281
|
-
|
|
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;
|
|
282
309
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const octets = char.charCodeAt(0) < 256 ? 1 : encoder.encode(char).length;
|
|
291
|
-
const newlen = len + octets;
|
|
292
|
-
if (newlen < 75) {
|
|
293
|
-
current += char;
|
|
294
|
-
len = newlen;
|
|
295
|
-
} else {
|
|
296
|
-
result += current + '\r\n ';
|
|
297
|
-
line = line.substring(i);
|
|
298
|
-
current = '';
|
|
299
|
-
len = 0;
|
|
300
|
-
i = -1;
|
|
301
|
-
}
|
|
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());
|
|
302
317
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
str = str.replace(/,/g, '\\,');
|
|
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';
|
|
313
327
|
}
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
/** @return {string} */
|
|
329
|
+
static version() {
|
|
330
|
+
return version;
|
|
316
331
|
}
|
|
317
|
-
return str;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* @param {Date} dt
|
|
322
|
-
* @return {string}
|
|
323
|
-
*/
|
|
324
|
-
static formatYYYYMMDD(dt) {
|
|
325
|
-
return restApi.pad4(dt.getFullYear()) +
|
|
326
|
-
restApi.pad2(dt.getMonth() + 1) + restApi.pad2(dt.getDate());
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Returns UTC string for iCalendar
|
|
331
|
-
* @param {Date} dt
|
|
332
|
-
* @return {string}
|
|
333
|
-
*/
|
|
334
|
-
static makeDtstamp(dt) {
|
|
335
|
-
const s = dt.toISOString();
|
|
336
|
-
return s.slice(0, 4) + s.slice(5, 7) + s.slice(8, 13) +
|
|
337
|
-
s.slice(14, 16) + s.slice(17, 19) + 'Z';
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/** @return {string} */
|
|
341
|
-
static version() {
|
|
342
|
-
return version;
|
|
343
|
-
}
|
|
344
332
|
}
|
|
345
|
-
|
|
346
333
|
/**
|
|
347
334
|
* Transforms a single Event into a VEVENT string
|
|
348
|
-
* @param {Event} ev
|
|
349
|
-
* @param {CalOptions} options
|
|
350
335
|
* @return {string} multi-line result, delimited by \r\n
|
|
351
336
|
*/
|
|
352
337
|
function eventToIcal(ev, options) {
|
|
353
|
-
|
|
354
|
-
|
|
338
|
+
const ical = new IcalEvent(ev, options);
|
|
339
|
+
return ical.toString();
|
|
355
340
|
}
|
|
356
|
-
|
|
357
341
|
const torahMemoCache = new Map();
|
|
358
|
-
|
|
359
342
|
const HOLIDAY_IGNORE_MASK = DAILY_LEARNING | core.flags.OMER_COUNT |
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
343
|
+
core.flags.SHABBAT_MEVARCHIM | core.flags.MOLAD | core.flags.USER_EVENT |
|
|
344
|
+
core.flags.HEBREW_DATE;
|
|
363
345
|
/**
|
|
364
346
|
* @private
|
|
365
|
-
* @param {Event} ev
|
|
366
|
-
* @param {boolean} il
|
|
367
|
-
* @return {string}
|
|
368
347
|
*/
|
|
369
348
|
function makeTorahMemo(ev, il) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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);
|
|
380
363
|
return memo;
|
|
381
|
-
}
|
|
382
|
-
memo = restApi.makeTorahMemoText(ev, il).replace(/\n/g, '\\n');
|
|
383
|
-
torahMemoCache.set(key, memo);
|
|
384
|
-
return memo;
|
|
385
364
|
}
|
|
386
|
-
|
|
387
365
|
/**
|
|
388
366
|
* @private
|
|
389
|
-
* @param {Event} e
|
|
390
|
-
* @param {CalOptions} options
|
|
391
|
-
* @return {string}
|
|
392
367
|
*/
|
|
393
|
-
function createMemo(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
+
}
|
|
414
392
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
393
|
+
if (torahMemo) {
|
|
394
|
+
if (memo.length) {
|
|
395
|
+
memo += '\\n\\n';
|
|
396
|
+
}
|
|
397
|
+
memo += torahMemo;
|
|
419
398
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
399
|
+
if (url) {
|
|
400
|
+
if (memo.length) {
|
|
401
|
+
memo += '\\n\\n';
|
|
402
|
+
}
|
|
403
|
+
memo += url;
|
|
425
404
|
}
|
|
426
|
-
memo
|
|
427
|
-
}
|
|
428
|
-
return memo;
|
|
405
|
+
return memo;
|
|
429
406
|
}
|
|
430
|
-
|
|
431
407
|
/**
|
|
432
408
|
* Generates an RFC 2445 iCalendar string from an array of events
|
|
433
|
-
* @param {Event[]} events
|
|
434
|
-
* @param {CalOptions} options
|
|
435
|
-
* @return {Promise<string>}
|
|
436
409
|
*/
|
|
437
410
|
async function eventsToIcalendar(events, options) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
opts.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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);
|
|
447
422
|
}
|
|
448
|
-
|
|
449
423
|
/**
|
|
450
424
|
* Generates an RFC 2445 iCalendar string from an array of IcalEvents
|
|
451
|
-
* @param {IcalEvent[]} icals
|
|
452
|
-
* @param {CalOptions} options
|
|
453
|
-
* @return {Promise<string>}
|
|
454
425
|
*/
|
|
455
426
|
async function icalEventsToString(icals, options) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
opts.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
stream.push('\r\n');
|
|
488
|
-
}
|
|
489
|
-
if (opts.calendarColor) {
|
|
490
|
-
stream.push(`X-APPLE-CALENDAR-COLOR:${opts.calendarColor}\r\n`);
|
|
491
|
-
}
|
|
492
|
-
const location = opts.location;
|
|
493
|
-
const tzid = location?.getTzid();
|
|
494
|
-
if (tzid) {
|
|
495
|
-
stream.push(`X-WR-TIMEZONE;VALUE=TEXT:${tzid}\r\n`);
|
|
496
|
-
const vtz = vtimezoneCache.get(tzid);
|
|
497
|
-
if (typeof vtz === 'string') {
|
|
498
|
-
stream.push(vtz);
|
|
499
|
-
stream.push('\r\n');
|
|
500
|
-
} else {
|
|
501
|
-
const vtimezoneFilename = `./zoneinfo/${tzid}.ics`;
|
|
502
|
-
try {
|
|
503
|
-
const vtimezoneIcs = await fs.promises.readFile(vtimezoneFilename, 'utf-8');
|
|
504
|
-
const lines = vtimezoneIcs.split('\r\n');
|
|
505
|
-
// ignore first 3 and last 1 lines
|
|
506
|
-
const str = lines.slice(3, lines.length - 2).join('\r\n');
|
|
507
|
-
stream.push(str);
|
|
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);
|
|
508
458
|
stream.push('\r\n');
|
|
509
|
-
vtimezoneCache.set(tzid, str);
|
|
510
|
-
} catch (error) {
|
|
511
|
-
// ignore failure when no timezone definition to read
|
|
512
|
-
}
|
|
513
459
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
+
}
|
|
521
498
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return stream.join('');
|
|
499
|
+
stream.push('END:VCALENDAR\r\n');
|
|
500
|
+
return stream.join('');
|
|
525
501
|
}
|
|
526
502
|
|
|
527
503
|
exports.IcalEvent = IcalEvent;
|