@forcecalendar/core 2.0.0 → 2.1.0
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/core/events/Event.js +5 -5
- package/core/events/EventStore.js +3 -3
- package/core/events/RecurrenceEngine.js +5 -5
- package/core/events/RecurrenceEngineV2.js +1 -1
- package/core/ics/ICSParser.js +8 -6
- package/core/search/EventSearch.js +1 -1
- package/core/timezone/TimezoneManager.js +2 -2
- package/package.json +7 -2
package/core/events/Event.js
CHANGED
|
@@ -374,7 +374,7 @@ export class Event {
|
|
|
374
374
|
* @returns {boolean} True if event spans multiple days
|
|
375
375
|
*/
|
|
376
376
|
get isMultiDay() {
|
|
377
|
-
if (!
|
|
377
|
+
if (!Object.prototype.hasOwnProperty.call(this._cache, 'isMultiDay')) {
|
|
378
378
|
const startDay = this.start.toDateString();
|
|
379
379
|
const endDay = this.end.toDateString();
|
|
380
380
|
this._cache.isMultiDay = startDay !== endDay;
|
|
@@ -417,10 +417,10 @@ export class Event {
|
|
|
417
417
|
dayEnd.setHours(23, 59, 59, 999);
|
|
418
418
|
|
|
419
419
|
return this.start <= dayEnd && this.end >= dayStart;
|
|
420
|
-
}
|
|
420
|
+
}
|
|
421
421
|
// Single day event: check if it's on the same day
|
|
422
422
|
return startString === dateString;
|
|
423
|
-
|
|
423
|
+
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
/**
|
|
@@ -436,9 +436,9 @@ export class Event {
|
|
|
436
436
|
} else if (otherEvent && otherEvent.start && otherEvent.end) {
|
|
437
437
|
// Allow checking against time ranges
|
|
438
438
|
return !(this.end <= otherEvent.start || this.start >= otherEvent.end);
|
|
439
|
-
}
|
|
439
|
+
}
|
|
440
440
|
throw new Error('Parameter must be an Event instance or have start/end properties');
|
|
441
|
-
|
|
441
|
+
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
/**
|
|
@@ -226,12 +226,12 @@ export class EventStore {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// Filter by all-day events
|
|
229
|
-
if (
|
|
229
|
+
if (Object.prototype.hasOwnProperty.call(filters, 'allDay')) {
|
|
230
230
|
results = results.filter(event => event.allDay === filters.allDay);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
// Filter by recurring
|
|
234
|
-
if (
|
|
234
|
+
if (Object.prototype.hasOwnProperty.call(filters, 'recurring')) {
|
|
235
235
|
results = results.filter(event => event.recurring === filters.recurring);
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -250,7 +250,7 @@ export class EventStore {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
// Filter by having attendees
|
|
253
|
-
if (
|
|
253
|
+
if (Object.prototype.hasOwnProperty.call(filters, 'hasAttendees')) {
|
|
254
254
|
results = results.filter(event => filters.hasAttendees ? event.hasAttendees : !event.hasAttendees);
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -40,7 +40,7 @@ export class RecurrenceEngine {
|
|
|
40
40
|
let lastOffset = tzManager.getTimezoneOffset(currentDate, eventTimezone);
|
|
41
41
|
|
|
42
42
|
// Track last date to detect infinite loop (date not advancing)
|
|
43
|
-
|
|
43
|
+
const lastDateTimestamp = null;
|
|
44
44
|
let stuckCount = 0;
|
|
45
45
|
const maxStuckIterations = 3;
|
|
46
46
|
|
|
@@ -267,11 +267,11 @@ export class RecurrenceEngine {
|
|
|
267
267
|
return Math.abs(exceptionDate.getTime() - dateTime) < 1000; // Within 1 second
|
|
268
268
|
}
|
|
269
269
|
return exceptionDate.toDateString() === dateStr;
|
|
270
|
-
}
|
|
270
|
+
}
|
|
271
271
|
// Simple date exception
|
|
272
272
|
const exceptionDate = exDate instanceof Date ? exDate : new Date(exDate);
|
|
273
273
|
return exceptionDate.toDateString() === dateStr;
|
|
274
|
-
|
|
274
|
+
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
|
|
@@ -327,9 +327,9 @@ export class RecurrenceEngine {
|
|
|
327
327
|
|
|
328
328
|
if (dateStr.endsWith('Z')) {
|
|
329
329
|
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
330
|
-
}
|
|
330
|
+
}
|
|
331
331
|
return new Date(year, month, day, hour, minute, second);
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// Fallback to standard date parsing
|
|
@@ -609,7 +609,7 @@ export class RecurrenceEngineV2 {
|
|
|
609
609
|
*/
|
|
610
610
|
clearEventCache(eventId) {
|
|
611
611
|
for (const key of this.occurrenceCache.keys()) {
|
|
612
|
-
if (key.startsWith(eventId
|
|
612
|
+
if (key.startsWith(`${eventId }_`)) {
|
|
613
613
|
this.occurrenceCache.delete(key);
|
|
614
614
|
}
|
|
615
615
|
}
|
package/core/ics/ICSParser.js
CHANGED
|
@@ -39,7 +39,7 @@ export class ICSParser {
|
|
|
39
39
|
let inEvent = false;
|
|
40
40
|
let inAlarm = false;
|
|
41
41
|
|
|
42
|
-
for (
|
|
42
|
+
for (const line of lines) {
|
|
43
43
|
// Skip empty lines
|
|
44
44
|
if (!line.trim()) continue;
|
|
45
45
|
|
|
@@ -248,7 +248,7 @@ export class ICSParser {
|
|
|
248
248
|
event.category = value.split(',')[0]; // Take first category
|
|
249
249
|
break;
|
|
250
250
|
|
|
251
|
-
case 'STATUS':
|
|
251
|
+
case 'STATUS': {
|
|
252
252
|
const statusMap = {
|
|
253
253
|
'TENTATIVE': 'tentative',
|
|
254
254
|
'CONFIRMED': 'confirmed',
|
|
@@ -256,6 +256,7 @@ export class ICSParser {
|
|
|
256
256
|
};
|
|
257
257
|
event.status = statusMap[value] || 'confirmed';
|
|
258
258
|
break;
|
|
259
|
+
}
|
|
259
260
|
|
|
260
261
|
case 'TRANSP':
|
|
261
262
|
event.showAs = value === 'TRANSPARENT' ? 'free' : 'busy';
|
|
@@ -265,7 +266,7 @@ export class ICSParser {
|
|
|
265
266
|
event.organizer = value.replace('mailto:', '');
|
|
266
267
|
break;
|
|
267
268
|
|
|
268
|
-
case 'ATTENDEE':
|
|
269
|
+
case 'ATTENDEE': {
|
|
269
270
|
if (!event.attendees) event.attendees = [];
|
|
270
271
|
const email = value.replace('mailto:', '');
|
|
271
272
|
event.attendees.push({
|
|
@@ -273,6 +274,7 @@ export class ICSParser {
|
|
|
273
274
|
name: email.split('@')[0] // Use email prefix as name
|
|
274
275
|
});
|
|
275
276
|
break;
|
|
277
|
+
}
|
|
276
278
|
|
|
277
279
|
case 'RRULE':
|
|
278
280
|
event.recurrence = value;
|
|
@@ -308,10 +310,10 @@ export class ICSParser {
|
|
|
308
310
|
if (dateString.endsWith('Z')) {
|
|
309
311
|
// UTC time
|
|
310
312
|
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
311
|
-
}
|
|
313
|
+
}
|
|
312
314
|
// Local time
|
|
313
315
|
return new Date(year, month, day, hour, minute, second);
|
|
314
|
-
|
|
316
|
+
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
/**
|
|
@@ -365,7 +367,7 @@ export class ICSParser {
|
|
|
365
367
|
// Continuation lines (with space prefix)
|
|
366
368
|
while (remaining.length > 0) {
|
|
367
369
|
const chunk = remaining.substr(0, this.maxLineLength - 1);
|
|
368
|
-
folded.push(
|
|
370
|
+
folded.push(` ${ chunk}`);
|
|
369
371
|
remaining = remaining.substr(chunk.length);
|
|
370
372
|
}
|
|
371
373
|
|
|
@@ -288,7 +288,7 @@ export class EventSearch {
|
|
|
288
288
|
const events = this.eventStore.getAllEvents();
|
|
289
289
|
|
|
290
290
|
for (const event of events) {
|
|
291
|
-
const value = event[field] || (includeEmpty ?
|
|
291
|
+
const value = event[field] || (includeEmpty ? `(No ${ field })` : null);
|
|
292
292
|
if (value === null) continue;
|
|
293
293
|
|
|
294
294
|
if (!groups.has(value)) {
|
|
@@ -343,13 +343,13 @@ export class TimezoneManager {
|
|
|
343
343
|
if (!tzString) return 'UTC';
|
|
344
344
|
|
|
345
345
|
// Check if it's already an IANA identifier
|
|
346
|
-
if (this.database.timezones
|
|
346
|
+
if (Object.prototype.hasOwnProperty.call(this.database.timezones, tzString)) {
|
|
347
347
|
return tzString;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
// Check abbreviations
|
|
351
351
|
const upperTz = tzString.toUpperCase();
|
|
352
|
-
if (this.database.abbreviations && this.database.abbreviations
|
|
352
|
+
if (this.database.abbreviations && Object.prototype.hasOwnProperty.call(this.database.abbreviations, upperTz)) {
|
|
353
353
|
return this.database.abbreviations[upperTz];
|
|
354
354
|
}
|
|
355
355
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forcecalendar/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "A modern, lightweight, framework-agnostic calendar engine optimized for Salesforce",
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "node tests/run-all.js",
|
|
13
13
|
"test:ics": "node tests/integration/test-ics.js",
|
|
14
|
-
"test:search": "node tests/integration/test-search.js"
|
|
14
|
+
"test:search": "node tests/integration/test-search.js",
|
|
15
|
+
"lint": "eslint core/ --ext .js",
|
|
16
|
+
"lint:fix": "eslint core/ --ext .js --fix",
|
|
17
|
+
"format": "prettier --write \"core/**/*.js\"",
|
|
18
|
+
"format:check": "prettier --check \"core/**/*.js\"",
|
|
19
|
+
"quality": "npm run lint && npm run format:check"
|
|
15
20
|
},
|
|
16
21
|
"exports": {
|
|
17
22
|
".": "./core/index.js",
|