@forcecalendar/core 2.1.5 → 2.1.7
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
CHANGED
|
@@ -59,6 +59,15 @@ export class Event {
|
|
|
59
59
|
|
|
60
60
|
normalized.attachments = Array.isArray(normalized.attachments) ? normalized.attachments : [];
|
|
61
61
|
|
|
62
|
+
// Backward compatibility: support legacy "recurrence" alias.
|
|
63
|
+
// Canonical fields are "recurring" + "recurrenceRule".
|
|
64
|
+
if (normalized.recurrence && !normalized.recurrenceRule) {
|
|
65
|
+
normalized.recurrenceRule = normalized.recurrence;
|
|
66
|
+
}
|
|
67
|
+
if (normalized.recurrenceRule) {
|
|
68
|
+
normalized.recurring = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
// Normalize status and visibility
|
|
63
72
|
const validStatuses = ['confirmed', 'tentative', 'cancelled'];
|
|
64
73
|
if (!validStatuses.includes(normalized.status)) {
|
|
@@ -172,6 +181,7 @@ export class Event {
|
|
|
172
181
|
textColor = null,
|
|
173
182
|
recurring = false,
|
|
174
183
|
recurrenceRule = null,
|
|
184
|
+
recurrence = null, // Backward-compatible alias for recurrenceRule
|
|
175
185
|
timeZone = null,
|
|
176
186
|
endTimeZone = null,
|
|
177
187
|
status = 'confirmed',
|
|
@@ -201,6 +211,7 @@ export class Event {
|
|
|
201
211
|
textColor,
|
|
202
212
|
recurring,
|
|
203
213
|
recurrenceRule,
|
|
214
|
+
recurrence,
|
|
204
215
|
timeZone,
|
|
205
216
|
endTimeZone,
|
|
206
217
|
status,
|
|
@@ -390,6 +401,14 @@ export class Event {
|
|
|
390
401
|
return this.recurring && this.recurrenceRule !== null;
|
|
391
402
|
}
|
|
392
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Backward-compatible alias for recurrenceRule
|
|
406
|
+
* @returns {import('../../types.js').RecurrenceRule|string|null}
|
|
407
|
+
*/
|
|
408
|
+
get recurrence() {
|
|
409
|
+
return this.recurrenceRule;
|
|
410
|
+
}
|
|
411
|
+
|
|
393
412
|
/**
|
|
394
413
|
* Check if event occurs on a specific date
|
|
395
414
|
* @param {Date|string} date - The date to check
|
|
@@ -471,6 +490,7 @@ export class Event {
|
|
|
471
490
|
textColor: this.textColor,
|
|
472
491
|
recurring: this.recurring,
|
|
473
492
|
recurrenceRule: this.recurrenceRule,
|
|
493
|
+
recurrence: this.recurrenceRule,
|
|
474
494
|
timeZone: this.timeZone,
|
|
475
495
|
status: this.status,
|
|
476
496
|
visibility: this.visibility,
|
package/core/ics/ICSHandler.js
CHANGED
|
@@ -133,7 +133,7 @@ export class ICSHandler {
|
|
|
133
133
|
if (expandRecurring) {
|
|
134
134
|
events = this.expandRecurringEvents(events, dateRange);
|
|
135
135
|
} else if (!includeRecurring) {
|
|
136
|
-
events = events.filter(event => !event.recurrence);
|
|
136
|
+
events = events.filter(event => !(event.recurring || event.recurrenceRule || event.recurrence));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// Generate ICS
|
|
@@ -300,7 +300,7 @@ export class ICSHandler {
|
|
|
300
300
|
const rangeEnd = dateRange?.end || new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
|
|
301
301
|
|
|
302
302
|
for (const event of events) {
|
|
303
|
-
if (!event.recurrence) {
|
|
303
|
+
if (!(event.recurring || event.recurrenceRule || event.recurrence)) {
|
|
304
304
|
expanded.push(event);
|
|
305
305
|
continue;
|
|
306
306
|
}
|
|
@@ -321,6 +321,8 @@ export class ICSHandler {
|
|
|
321
321
|
id: `${event.id}-${instance.start.getTime()}`,
|
|
322
322
|
start: instance.start,
|
|
323
323
|
end: instance.end,
|
|
324
|
+
recurring: false,
|
|
325
|
+
recurrenceRule: null,
|
|
324
326
|
recurrence: null, // Remove recurrence from instances
|
|
325
327
|
parentId: event.id // Reference to original
|
|
326
328
|
});
|
package/core/ics/ICSParser.js
CHANGED
|
@@ -22,7 +22,7 @@ export class ICSParser {
|
|
|
22
22
|
TRANSP: 'showAs',
|
|
23
23
|
ORGANIZER: 'organizer',
|
|
24
24
|
ATTENDEE: 'attendees',
|
|
25
|
-
RRULE: '
|
|
25
|
+
RRULE: 'recurrenceRule',
|
|
26
26
|
EXDATE: 'excludeDates'
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -184,17 +184,18 @@ export class ICSParser {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// Recurrence
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const recurrenceRule = event.recurrenceRule || event.recurrence;
|
|
188
|
+
if (recurrenceRule) {
|
|
189
|
+
if (typeof recurrenceRule === 'string') {
|
|
189
190
|
// Already in RRULE format
|
|
190
|
-
if (
|
|
191
|
-
lines.push(
|
|
191
|
+
if (recurrenceRule.startsWith('RRULE:')) {
|
|
192
|
+
lines.push(recurrenceRule);
|
|
192
193
|
} else {
|
|
193
|
-
lines.push(`RRULE:${
|
|
194
|
+
lines.push(`RRULE:${recurrenceRule}`);
|
|
194
195
|
}
|
|
195
|
-
} else if (typeof
|
|
196
|
+
} else if (typeof recurrenceRule === 'object') {
|
|
196
197
|
// Convert object to RRULE
|
|
197
|
-
lines.push(`RRULE:${this.objectToRRule(
|
|
198
|
+
lines.push(`RRULE:${this.objectToRRule(recurrenceRule)}`);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
|
|
@@ -277,7 +278,8 @@ export class ICSParser {
|
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
case 'RRULE':
|
|
280
|
-
event.
|
|
281
|
+
event.recurrenceRule = value;
|
|
282
|
+
event.recurring = true;
|
|
281
283
|
break;
|
|
282
284
|
}
|
|
283
285
|
}
|
|
@@ -423,6 +425,8 @@ export class ICSParser {
|
|
|
423
425
|
allDay: false,
|
|
424
426
|
location: '',
|
|
425
427
|
category: '',
|
|
428
|
+
recurring: false,
|
|
429
|
+
recurrenceRule: null,
|
|
426
430
|
status: 'confirmed',
|
|
427
431
|
showAs: 'busy',
|
|
428
432
|
attendees: [],
|
|
@@ -64,7 +64,7 @@ export class EnhancedCalendar extends Calendar {
|
|
|
64
64
|
const recurringEvents = [];
|
|
65
65
|
|
|
66
66
|
// Separate regular and recurring events
|
|
67
|
-
const allEvents = this.eventStore.
|
|
67
|
+
const allEvents = this.eventStore.getEventsInRange(startDate, endDate, false);
|
|
68
68
|
|
|
69
69
|
for (const event of allEvents) {
|
|
70
70
|
if (event.recurring) {
|
|
@@ -107,14 +107,11 @@ export class EnhancedCalendar extends Calendar {
|
|
|
107
107
|
this.recurrenceEngine.addModifiedInstance(eventId, occurrenceDate, modifications);
|
|
108
108
|
|
|
109
109
|
// Emit change event
|
|
110
|
-
this.
|
|
110
|
+
this._emit('occurrenceModified', {
|
|
111
111
|
eventId,
|
|
112
112
|
occurrenceDate,
|
|
113
113
|
modifications
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
// Trigger re-render if in view
|
|
117
|
-
this.refreshView();
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
/**
|
|
@@ -125,14 +122,11 @@ export class EnhancedCalendar extends Calendar {
|
|
|
125
122
|
this.recurrenceEngine.addException(eventId, occurrenceDate, reason);
|
|
126
123
|
|
|
127
124
|
// Emit change event
|
|
128
|
-
this.
|
|
125
|
+
this._emit('occurrenceCancelled', {
|
|
129
126
|
eventId,
|
|
130
127
|
occurrenceDate,
|
|
131
128
|
reason
|
|
132
129
|
});
|
|
133
|
-
|
|
134
|
-
// Trigger re-render
|
|
135
|
-
this.refreshView();
|
|
136
130
|
}
|
|
137
131
|
|
|
138
132
|
/**
|
|
@@ -153,13 +147,11 @@ export class EnhancedCalendar extends Calendar {
|
|
|
153
147
|
}
|
|
154
148
|
|
|
155
149
|
// Emit bulk change event
|
|
156
|
-
this.
|
|
150
|
+
this._emit('occurrencesBulkModified', {
|
|
157
151
|
eventId,
|
|
158
152
|
count: occurrences.length,
|
|
159
153
|
modifications
|
|
160
154
|
});
|
|
161
|
-
|
|
162
|
-
this.refreshView();
|
|
163
155
|
}
|
|
164
156
|
|
|
165
157
|
/**
|
|
@@ -211,28 +203,33 @@ export class EnhancedCalendar extends Calendar {
|
|
|
211
203
|
* Setup real-time indexing for search
|
|
212
204
|
*/
|
|
213
205
|
setupRealtimeIndexing() {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
this.on('event:updated', event => {
|
|
221
|
-
this.searchManager.indexEvents();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Re-index when events are removed
|
|
225
|
-
this.on('event:removed', eventId => {
|
|
226
|
-
this.searchManager.indexEvents();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Batch re-indexing for bulk operations
|
|
230
|
-
let reindexTimeout;
|
|
231
|
-
this.on('events:bulk-operation', () => {
|
|
232
|
-
clearTimeout(reindexTimeout);
|
|
206
|
+
// Batch re-indexing to avoid rebuilding the index repeatedly for rapid event changes.
|
|
207
|
+
let reindexTimeout = null;
|
|
208
|
+
const scheduleReindex = () => {
|
|
209
|
+
if (reindexTimeout) {
|
|
210
|
+
clearTimeout(reindexTimeout);
|
|
211
|
+
}
|
|
233
212
|
reindexTimeout = setTimeout(() => {
|
|
234
213
|
this.searchManager.indexEvents();
|
|
235
214
|
}, 100);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Store cleanup handle so timers are cleared on destroy.
|
|
218
|
+
this._clearReindexTimeout = () => {
|
|
219
|
+
if (reindexTimeout) {
|
|
220
|
+
clearTimeout(reindexTimeout);
|
|
221
|
+
reindexTimeout = null;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
this.on('eventAdd', scheduleReindex);
|
|
226
|
+
this.on('eventUpdate', scheduleReindex);
|
|
227
|
+
this.on('eventRemove', scheduleReindex);
|
|
228
|
+
this.on('eventsSet', scheduleReindex);
|
|
229
|
+
this.on('eventStoreChange', change => {
|
|
230
|
+
if (change?.type === 'batch') {
|
|
231
|
+
scheduleReindex();
|
|
232
|
+
}
|
|
236
233
|
});
|
|
237
234
|
}
|
|
238
235
|
|
|
@@ -365,6 +362,11 @@ export class EnhancedCalendar extends Calendar {
|
|
|
365
362
|
* Clean up resources
|
|
366
363
|
*/
|
|
367
364
|
destroy() {
|
|
365
|
+
if (typeof this._clearReindexTimeout === 'function') {
|
|
366
|
+
this._clearReindexTimeout();
|
|
367
|
+
this._clearReindexTimeout = null;
|
|
368
|
+
}
|
|
369
|
+
|
|
368
370
|
// Clean up worker
|
|
369
371
|
if (this.searchManager) {
|
|
370
372
|
this.searchManager.destroy();
|
|
@@ -152,7 +152,7 @@ export class EventSearch {
|
|
|
152
152
|
// Recurring filter
|
|
153
153
|
if (recurring !== null) {
|
|
154
154
|
events = events.filter(event => {
|
|
155
|
-
const hasRecurrence = !!event.recurrence;
|
|
155
|
+
const hasRecurrence = !!(event.recurring || event.recurrenceRule || event.recurrence);
|
|
156
156
|
return recurring ? hasRecurrence : !hasRecurrence;
|
|
157
157
|
});
|
|
158
158
|
}
|