@forcecalendar/core 2.1.13 → 2.1.15
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 +9 -1
- package/core/events/EventStore.js +15 -6
- package/core/search/EventSearch.js +42 -0
- package/package.json +1 -1
package/core/events/Event.js
CHANGED
|
@@ -154,12 +154,20 @@ export class Event {
|
|
|
154
154
|
// Validate timezone if provided
|
|
155
155
|
if (data.timeZone) {
|
|
156
156
|
try {
|
|
157
|
-
// Test if timezone is valid by trying to use it
|
|
158
157
|
new Intl.DateTimeFormat('en-US', { timeZone: data.timeZone });
|
|
159
158
|
} catch (e) {
|
|
160
159
|
throw new Error(`Invalid timezone: ${data.timeZone}`);
|
|
161
160
|
}
|
|
162
161
|
}
|
|
162
|
+
|
|
163
|
+
// Validate end timezone if provided
|
|
164
|
+
if (data.endTimeZone) {
|
|
165
|
+
try {
|
|
166
|
+
new Intl.DateTimeFormat('en-US', { timeZone: data.endTimeZone });
|
|
167
|
+
} catch (e) {
|
|
168
|
+
throw new Error(`Invalid end timezone: ${data.endTimeZone}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
/**
|
|
@@ -503,15 +503,24 @@ export class EventStore {
|
|
|
503
503
|
* Get events for a date range
|
|
504
504
|
* @param {Date} start - Start date
|
|
505
505
|
* @param {Date} end - End date
|
|
506
|
-
* @param {boolean|
|
|
507
|
-
*
|
|
506
|
+
* @param {boolean|Object} [expandRecurringOrOptions=true] - Boolean to expand recurring events,
|
|
507
|
+
* or options object: { expandRecurring?: boolean, timezone?: string }
|
|
508
|
+
* @param {string} [timezone] - Timezone for the query
|
|
508
509
|
* @returns {Event[]}
|
|
509
510
|
*/
|
|
510
|
-
getEventsInRange(start, end,
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
511
|
+
getEventsInRange(start, end, expandRecurringOrOptions = true, timezone = null) {
|
|
512
|
+
let expandRecurring = true;
|
|
513
|
+
|
|
514
|
+
if (typeof expandRecurringOrOptions === 'object' && expandRecurringOrOptions !== null) {
|
|
515
|
+
// Options object form: getEventsInRange(start, end, { expandRecurring, timezone })
|
|
516
|
+
expandRecurring = expandRecurringOrOptions.expandRecurring !== false;
|
|
517
|
+
timezone = expandRecurringOrOptions.timezone || timezone;
|
|
518
|
+
} else if (typeof expandRecurringOrOptions === 'string') {
|
|
519
|
+
// Legacy overloaded form: string was treated as timezone (deprecated)
|
|
520
|
+
timezone = expandRecurringOrOptions;
|
|
514
521
|
expandRecurring = true;
|
|
522
|
+
} else {
|
|
523
|
+
expandRecurring = expandRecurringOrOptions;
|
|
515
524
|
}
|
|
516
525
|
|
|
517
526
|
timezone = timezone || this.defaultTimezone;
|
|
@@ -10,9 +10,37 @@ export class EventSearch {
|
|
|
10
10
|
// Search index for performance
|
|
11
11
|
this.searchIndex = new Map();
|
|
12
12
|
this.indexFields = ['title', 'description', 'location', 'category'];
|
|
13
|
+
this._indexDirty = false;
|
|
13
14
|
|
|
14
15
|
// Build initial index
|
|
15
16
|
this.rebuildIndex();
|
|
17
|
+
|
|
18
|
+
// Subscribe to EventStore changes to keep index in sync
|
|
19
|
+
if (this.eventStore && typeof this.eventStore.subscribe === 'function') {
|
|
20
|
+
this._unsubscribe = this.eventStore.subscribe(change => {
|
|
21
|
+
if (change.type === 'batch') {
|
|
22
|
+
this._indexDirty = true;
|
|
23
|
+
} else if (
|
|
24
|
+
change.type === 'add' ||
|
|
25
|
+
change.type === 'update' ||
|
|
26
|
+
change.type === 'remove' ||
|
|
27
|
+
change.type === 'clear'
|
|
28
|
+
) {
|
|
29
|
+
this._indexDirty = true;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Destroy the search engine and unsubscribe from store changes
|
|
37
|
+
*/
|
|
38
|
+
destroy() {
|
|
39
|
+
if (this._unsubscribe) {
|
|
40
|
+
this._unsubscribe();
|
|
41
|
+
this._unsubscribe = null;
|
|
42
|
+
}
|
|
43
|
+
this.searchIndex.clear();
|
|
16
44
|
}
|
|
17
45
|
|
|
18
46
|
/**
|
|
@@ -34,6 +62,9 @@ export class EventSearch {
|
|
|
34
62
|
return [];
|
|
35
63
|
}
|
|
36
64
|
|
|
65
|
+
// Rebuild index if stale
|
|
66
|
+
this._ensureIndex();
|
|
67
|
+
|
|
37
68
|
// Normalize query
|
|
38
69
|
const normalizedQuery = caseSensitive ? query : query.toLowerCase();
|
|
39
70
|
const queryTerms = this.tokenize(normalizedQuery);
|
|
@@ -443,11 +474,22 @@ export class EventSearch {
|
|
|
443
474
|
}
|
|
444
475
|
}
|
|
445
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Ensure the search index is up to date
|
|
479
|
+
* @private
|
|
480
|
+
*/
|
|
481
|
+
_ensureIndex() {
|
|
482
|
+
if (this._indexDirty) {
|
|
483
|
+
this.rebuildIndex();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
446
487
|
/**
|
|
447
488
|
* Rebuild search index
|
|
448
489
|
*/
|
|
449
490
|
rebuildIndex() {
|
|
450
491
|
this.searchIndex.clear();
|
|
492
|
+
this._indexDirty = false;
|
|
451
493
|
const events = this.eventStore.getAllEvents();
|
|
452
494
|
|
|
453
495
|
for (const event of events) {
|