@vicacha-devs/strapi-plugin-events 0.0.0-stage → 1.0.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.
Files changed (89) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +121 -2
  3. package/dist/admin/CalendarPage-CwCvPDRx.js +1215 -0
  4. package/dist/admin/CalendarPage-Dz7g19qp.mjs +1215 -0
  5. package/dist/admin/RRuleInput-BQl-siRj.js +11 -0
  6. package/dist/admin/RRuleInput-C8bR37X9.mjs +6 -0
  7. package/dist/admin/SettingsPage-CNG9AEO_.mjs +415 -0
  8. package/dist/admin/SettingsPage-DhjtKDmY.js +415 -0
  9. package/dist/admin/ar-mMKUXxch.js +216 -0
  10. package/dist/admin/ar-tWKEvJLB.mjs +216 -0
  11. package/dist/admin/de-CGk8UZ3W.js +216 -0
  12. package/dist/admin/de-OdbJEFs0.mjs +216 -0
  13. package/dist/admin/en-Dskn9ZPa.mjs +216 -0
  14. package/dist/admin/en-imdSlLhJ.js +216 -0
  15. package/dist/admin/es-DcMsMHrI.mjs +216 -0
  16. package/dist/admin/es-DkqqxUJQ.js +216 -0
  17. package/dist/admin/fr-BTEIPOFw.mjs +216 -0
  18. package/dist/admin/fr-D92B_ZTv.js +216 -0
  19. package/dist/admin/index-BTq_X4uT.js +1005 -0
  20. package/dist/admin/index-BatOiwBu.mjs +1006 -0
  21. package/dist/admin/index.js +6 -0
  22. package/dist/admin/index.mjs +6 -0
  23. package/dist/admin/it-CAxPpL_9.mjs +216 -0
  24. package/dist/admin/it-ClKaSCgv.js +216 -0
  25. package/dist/admin/nl-CS1aUfT7.mjs +216 -0
  26. package/dist/admin/nl-F6FIi7zw.js +216 -0
  27. package/dist/admin/pl-BfmBU9FW.mjs +216 -0
  28. package/dist/admin/pl-p8VLXi02.js +216 -0
  29. package/dist/admin/pt-BUdH0uSw.js +216 -0
  30. package/dist/admin/pt-DdulKcbk.mjs +216 -0
  31. package/dist/admin/src/components/EventSidePanel.d.ts +13 -0
  32. package/dist/admin/src/components/Initializer.d.ts +5 -0
  33. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  34. package/dist/admin/src/components/RRuleInput.d.ts +3 -0
  35. package/dist/admin/src/components/events/AttendeesManagerPanel.d.ts +15 -0
  36. package/dist/admin/src/components/events/EventsDirectoryPanel.d.ts +11 -0
  37. package/dist/admin/src/components/events/EventsKpiCards.d.ts +16 -0
  38. package/dist/admin/src/components/events/QuickCreateEventPanel.d.ts +21 -0
  39. package/dist/admin/src/components/events/calendarPageHelpers.d.ts +8 -0
  40. package/dist/admin/src/fields/rruleField.d.ts +41 -0
  41. package/dist/admin/src/index.d.ts +15 -0
  42. package/dist/admin/src/pages/CalendarPage.d.ts +3 -0
  43. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  44. package/dist/admin/src/permissions.d.ts +15 -0
  45. package/dist/admin/src/translations/cmLabels.d.ts +1 -0
  46. package/dist/admin/src/translations/cmPlaceholders.d.ts +1 -0
  47. package/dist/admin/tr-Bp50qvL8.mjs +216 -0
  48. package/dist/admin/tr-C7G9mPtg.js +216 -0
  49. package/dist/server/index.js +3709 -0
  50. package/dist/server/index.mjs +3705 -0
  51. package/dist/server/src/bootstrap.d.ts +5 -0
  52. package/dist/server/src/config/index.d.ts +7 -0
  53. package/dist/server/src/content-types/event/schema.json.d.ts +279 -0
  54. package/dist/server/src/content-types/index.d.ts +366 -0
  55. package/dist/server/src/content-types/registration/schema.json.d.ts +86 -0
  56. package/dist/server/src/controllers/admin.d.ts +17 -0
  57. package/dist/server/src/controllers/content-api.d.ts +14 -0
  58. package/dist/server/src/controllers/index.d.ts +28 -0
  59. package/dist/server/src/destroy.d.ts +5 -0
  60. package/dist/server/src/index.d.ts +551 -0
  61. package/dist/server/src/middlewares/index.d.ts +2 -0
  62. package/dist/server/src/policies/index.d.ts +2 -0
  63. package/dist/server/src/register.d.ts +5 -0
  64. package/dist/server/src/routes/admin.d.ts +17 -0
  65. package/dist/server/src/routes/content-api.d.ts +12 -0
  66. package/dist/server/src/routes/index.d.ts +30 -0
  67. package/dist/server/src/services/ical.d.ts +18 -0
  68. package/dist/server/src/services/index.d.ts +110 -0
  69. package/dist/server/src/services/occurrence.d.ts +44 -0
  70. package/dist/server/src/services/occurrenceHelpers.d.ts +16 -0
  71. package/dist/server/src/services/occurrencePayloadHelpers.d.ts +30 -0
  72. package/dist/server/src/services/occurrenceRsvpBatch.d.ts +25 -0
  73. package/dist/server/src/services/rsvp.d.ts +51 -0
  74. package/dist/server/src/services/rsvpHelpers.d.ts +52 -0
  75. package/dist/server/src/services/rsvpMutations.d.ts +34 -0
  76. package/dist/server/src/services/rsvpSecurity.d.ts +22 -0
  77. package/dist/server/src/services/settings.d.ts +10 -0
  78. package/dist/server/src/types.d.ts +171 -0
  79. package/dist/shared/calendarSystems.d.ts +6 -0
  80. package/dist/shared/icalGenerator.d.ts +25 -0
  81. package/dist/shared/index.d.ts +6 -0
  82. package/dist/shared/index.js +753 -0
  83. package/dist/shared/index.mjs +727 -0
  84. package/dist/shared/pluginId.d.ts +4 -0
  85. package/dist/shared/rruleEngine.d.ts +20 -0
  86. package/dist/shared/spotlightHelpers.d.ts +17 -0
  87. package/dist/shared/types.d.ts +309 -0
  88. package/dist/shared/urlAndPosterHelpers.d.ts +18 -0
  89. package/package.json +109 -3
@@ -0,0 +1,753 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const shared = require("@vicacha-devs/strapi-plugin-calendar/shared");
4
+ const DANGEROUS_OBJECT_KEYS = /* @__PURE__ */ new Set([
5
+ "__proto__",
6
+ "constructor",
7
+ "prototype"
8
+ ]);
9
+ function isSafeObjectKey(key) {
10
+ return typeof key === "string" && key.length > 0 && !DANGEROUS_OBJECT_KEYS.has(key);
11
+ }
12
+ function sanitizePlainRecord(raw, maxDepth = 4) {
13
+ if (!raw || typeof raw !== "object" || Array.isArray(raw) || maxDepth < 0) {
14
+ return null;
15
+ }
16
+ const clean = {};
17
+ for (const [k, v] of Object.entries(raw)) {
18
+ if (!isSafeObjectKey(k)) continue;
19
+ if (v && typeof v === "object" && !Array.isArray(v)) {
20
+ if (maxDepth === 0) continue;
21
+ clean[k] = sanitizePlainRecord(v, maxDepth - 1);
22
+ } else if (Array.isArray(v)) {
23
+ clean[k] = maxDepth === 0 ? [] : v.map(
24
+ (item) => item && typeof item === "object" && !Array.isArray(item) ? sanitizePlainRecord(item, maxDepth - 1) : item
25
+ );
26
+ } else {
27
+ clean[k] = v;
28
+ }
29
+ }
30
+ return clean;
31
+ }
32
+ const ALLOWED_ACTION_VARIANTS = /* @__PURE__ */ new Set([
33
+ "primary",
34
+ "secondary",
35
+ "outline"
36
+ ]);
37
+ const HTTP_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:"]);
38
+ const ACTION_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "mailto:"]);
39
+ function isSafeRelativeUrl(trimmed) {
40
+ return trimmed.startsWith("/") && !trimmed.startsWith("//") && !trimmed.includes("\\");
41
+ }
42
+ function isAllowedAbsoluteUrl(trimmed, allowMailto) {
43
+ try {
44
+ const parsed = new URL(trimmed);
45
+ const proto = parsed.protocol.toLowerCase();
46
+ return allowMailto ? ACTION_PROTOCOLS.has(proto) : HTTP_PROTOCOLS.has(proto);
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ function sanitizeEventUrl(rawUrl, options) {
52
+ if (typeof rawUrl !== "string") return null;
53
+ const trimmed = rawUrl.trim();
54
+ if (!trimmed || trimmed.length > 2048 || /[\u0000-\u001F\u007F]/.test(trimmed)) {
55
+ return null;
56
+ }
57
+ const allowMailto = options?.allowMailto ?? true;
58
+ if (isSafeRelativeUrl(trimmed) || isAllowedAbsoluteUrl(trimmed, allowMailto)) {
59
+ return trimmed;
60
+ }
61
+ return null;
62
+ }
63
+ function parseJsonIfNeeded(raw) {
64
+ if (typeof raw !== "string") return raw;
65
+ const trimmed = raw.trim();
66
+ const isJsonLike = trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
67
+ if (!isJsonLike) return raw;
68
+ try {
69
+ return JSON.parse(trimmed);
70
+ } catch {
71
+ return raw;
72
+ }
73
+ }
74
+ function getTrimmedString(val, maxLen) {
75
+ if (typeof val !== "string") return void 0;
76
+ const trimmed = val.trim();
77
+ if (!trimmed) return void 0;
78
+ return maxLen ? trimmed.slice(0, maxLen) : trimmed;
79
+ }
80
+ function getPositiveInteger(val) {
81
+ if (typeof val !== "number" || !Number.isFinite(val) || val <= 0) {
82
+ return void 0;
83
+ }
84
+ return Math.round(val);
85
+ }
86
+ function unwrapPosterRecord(obj) {
87
+ if (obj.data && typeof obj.data === "object" && !Array.isArray(obj.data)) {
88
+ return unwrapPosterRecord(obj.data);
89
+ }
90
+ if (obj.attributes && typeof obj.attributes === "object" && !Array.isArray(obj.attributes)) {
91
+ return unwrapPosterRecord(obj.attributes);
92
+ }
93
+ return obj;
94
+ }
95
+ function resolvePosterAltText(obj, fallbackAlt) {
96
+ return getTrimmedString(obj.alt) || getTrimmedString(obj.alternativeText) || getTrimmedString(obj.caption) || fallbackAlt || void 0;
97
+ }
98
+ function normalizeEventPoster(rawPoster, fallbackAlt) {
99
+ if (!rawPoster) return void 0;
100
+ if (typeof rawPoster === "string") {
101
+ const safeUrl2 = sanitizeEventUrl(rawPoster, { allowMailto: false });
102
+ return safeUrl2 ? { url: safeUrl2, alt: fallbackAlt || void 0 } : void 0;
103
+ }
104
+ if (Array.isArray(rawPoster)) {
105
+ return rawPoster.length > 0 ? normalizeEventPoster(rawPoster[0], fallbackAlt) : void 0;
106
+ }
107
+ if (typeof rawPoster !== "object") return void 0;
108
+ const obj = unwrapPosterRecord(rawPoster);
109
+ const safeUrl = sanitizeEventUrl(obj.url, { allowMailto: false });
110
+ if (!safeUrl) return void 0;
111
+ return {
112
+ url: safeUrl,
113
+ alt: resolvePosterAltText(obj, fallbackAlt),
114
+ mime: getTrimmedString(obj.mime),
115
+ width: getPositiveInteger(obj.width),
116
+ height: getPositiveInteger(obj.height)
117
+ };
118
+ }
119
+ function resolveActionVariant(rawVariant, index) {
120
+ const candidate = getTrimmedString(rawVariant);
121
+ if (candidate && ALLOWED_ACTION_VARIANTS.has(candidate)) {
122
+ return candidate;
123
+ }
124
+ return index === 0 ? "primary" : "outline";
125
+ }
126
+ function normalizeSingleActionLink(item, index) {
127
+ if (!item || typeof item !== "object") return null;
128
+ const rec = item;
129
+ const label = getTrimmedString(rec.label, 120);
130
+ const safeUrl = sanitizeEventUrl(rec.url);
131
+ if (!label || !safeUrl) return null;
132
+ const external = typeof rec.external === "boolean" ? rec.external : /^https?:\/\//i.test(safeUrl);
133
+ const icon = getTrimmedString(rec.icon, 60);
134
+ return {
135
+ label,
136
+ url: safeUrl,
137
+ variant: resolveActionVariant(rec.variant, index),
138
+ external,
139
+ ...icon ? { icon } : {}
140
+ };
141
+ }
142
+ function normalizeEventActionLinks(rawLinks) {
143
+ const parsed = parseJsonIfNeeded(rawLinks);
144
+ if (!Array.isArray(parsed) || parsed.length === 0) {
145
+ return void 0;
146
+ }
147
+ const normalized = parsed.map((item, idx) => normalizeSingleActionLink(item, idx)).filter((link) => link !== null);
148
+ return normalized.length > 0 ? normalized : void 0;
149
+ }
150
+ function isValidCalendarDateKey(dateStr) {
151
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
152
+ return false;
153
+ }
154
+ const parsed = /* @__PURE__ */ new Date(`${dateStr}T00:00:00.000Z`);
155
+ if (Number.isNaN(parsed.getTime())) {
156
+ return false;
157
+ }
158
+ return parsed.toISOString().slice(0, 10) === dateStr;
159
+ }
160
+ function toDateKey(dateInput) {
161
+ const d = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
162
+ if (Number.isNaN(d.getTime())) {
163
+ return "";
164
+ }
165
+ return d.toISOString().slice(0, 10);
166
+ }
167
+ function normalizeRRuleString(raw) {
168
+ if (!raw || typeof raw !== "string") {
169
+ return "";
170
+ }
171
+ return raw.replace(/[\u0000-\u001F\u007F]+/g, "").trim().replace(/^RRULE:/i, "").trim();
172
+ }
173
+ function sanitizeOccurrenceOverrides(rawOverrides) {
174
+ const clean = {};
175
+ if (!rawOverrides || typeof rawOverrides !== "object" || Array.isArray(rawOverrides)) {
176
+ return clean;
177
+ }
178
+ for (const [key, ov] of Object.entries(rawOverrides)) {
179
+ if (!isSafeObjectKey(key) || !isValidCalendarDateKey(key)) continue;
180
+ if (!ov || typeof ov !== "object" || Array.isArray(ov)) continue;
181
+ let validStart;
182
+ let startMs = null;
183
+ if (ov.startDate !== void 0 && ov.startDate !== "") {
184
+ if (typeof ov.startDate !== "string") continue;
185
+ const parsedStart = new Date(ov.startDate).getTime();
186
+ if (Number.isNaN(parsedStart)) continue;
187
+ validStart = ov.startDate;
188
+ startMs = parsedStart;
189
+ }
190
+ let validEnd;
191
+ if (ov.endDate !== void 0 && ov.endDate !== "") {
192
+ if (typeof ov.endDate !== "string") continue;
193
+ const parsedEnd = new Date(ov.endDate).getTime();
194
+ if (Number.isNaN(parsedEnd)) continue;
195
+ const minStartMs = startMs ?? (/* @__PURE__ */ new Date(`${key}T00:00:00.000Z`)).getTime();
196
+ if (parsedEnd < minStartMs) continue;
197
+ validEnd = ov.endDate;
198
+ }
199
+ clean[key] = {
200
+ ...ov,
201
+ originalDate: typeof ov.originalDate === "string" && isValidCalendarDateKey(ov.originalDate) ? ov.originalDate : key,
202
+ startDate: validStart,
203
+ endDate: validEnd
204
+ };
205
+ }
206
+ return clean;
207
+ }
208
+ function sanitizeRRuleFieldValue(rec, defaultTimezone = "UTC") {
209
+ const rrule = normalizeRRuleString(rec?.rrule);
210
+ const timezone = typeof rec?.timezone === "string" && rec.timezone.trim() ? rec.timezone.trim() : defaultTimezone;
211
+ const exdates = Array.isArray(rec?.exdates) ? rec.exdates.filter(
212
+ (d) => typeof d === "string" && isValidCalendarDateKey(d)
213
+ ) : [];
214
+ const overrides = sanitizeOccurrenceOverrides(rec?.overrides);
215
+ return {
216
+ ...rec,
217
+ rrule,
218
+ timezone,
219
+ allDay: Boolean(rec?.allDay),
220
+ exdates,
221
+ overrides
222
+ };
223
+ }
224
+ function parseRRuleFieldValue(raw, defaultTimezone = "UTC") {
225
+ const parsed = shared.parseRRuleFieldValue(raw, defaultTimezone);
226
+ return sanitizeRRuleFieldValue(parsed, defaultTimezone);
227
+ }
228
+ function buildRRule(parsed) {
229
+ return shared.buildRRule({
230
+ freq: parsed.freq ?? "NONE",
231
+ interval: parsed.interval ?? 1,
232
+ byDay: parsed.byDay ?? [],
233
+ byMonthDay: parsed.byMonthDay,
234
+ count: parsed.count,
235
+ until: parsed.until
236
+ });
237
+ }
238
+ const FREQ_UNITS = {
239
+ DAILY: { singular: "day", plural: "days" },
240
+ WEEKLY: { singular: "week", plural: "weeks" },
241
+ MONTHLY: { singular: "month", plural: "months" },
242
+ YEARLY: { singular: "year", plural: "years" }
243
+ };
244
+ function formatIntervalUnit(freq, interval) {
245
+ const unit = Object.hasOwn(FREQ_UNITS, freq) ? FREQ_UNITS[freq] : FREQ_UNITS.YEARLY;
246
+ return interval === 1 ? unit.singular : `${interval} ${unit.plural}`;
247
+ }
248
+ function describeRRule(rruleString) {
249
+ const parsed = shared.parseRRule(normalizeRRuleString(rruleString));
250
+ if (parsed.freq === "NONE") {
251
+ return "Does not repeat";
252
+ }
253
+ let summary = `Every ${formatIntervalUnit(parsed.freq, parsed.interval)}`;
254
+ if (parsed.freq === "WEEKLY" && parsed.byDay.length > 0) {
255
+ summary += ` on ${parsed.byDay.join(", ")}`;
256
+ } else if (parsed.freq === "MONTHLY" && parsed.byMonthDay) {
257
+ summary += ` on day ${parsed.byMonthDay}`;
258
+ }
259
+ if (parsed.until) {
260
+ summary += ` until ${parsed.until}`;
261
+ } else if (parsed.count) {
262
+ summary += ` (${parsed.count} occurrences)`;
263
+ }
264
+ return summary;
265
+ }
266
+ function expandRuleSlots(options) {
267
+ const baseStartMs = new Date(options.baseStartDate).getTime();
268
+ if (Number.isNaN(baseStartMs)) {
269
+ return [];
270
+ }
271
+ let safeBaseEnd = null;
272
+ if (typeof options.baseEndDate === "string" && options.baseEndDate) {
273
+ const baseEndMs = new Date(options.baseEndDate).getTime();
274
+ if (!Number.isNaN(baseEndMs) && baseEndMs >= baseStartMs) {
275
+ safeBaseEnd = options.baseEndDate;
276
+ }
277
+ }
278
+ const windowStartIso = typeof options.windowStart === "string" ? options.windowStart : options.windowStart.toISOString();
279
+ const windowEndIso = typeof options.windowEnd === "string" ? options.windowEnd : options.windowEnd.toISOString();
280
+ if (Number.isNaN(new Date(windowStartIso).getTime()) || Number.isNaN(new Date(windowEndIso).getTime())) {
281
+ return [];
282
+ }
283
+ return shared.expandRuleSlots({
284
+ baseStartDate: options.baseStartDate,
285
+ baseEndDate: safeBaseEnd,
286
+ recurrence: sanitizeRRuleFieldValue(
287
+ options.recurrence,
288
+ options.recurrence?.timezone || "UTC"
289
+ ),
290
+ windowStart: windowStartIso,
291
+ windowEnd: windowEndIso,
292
+ maxOccurrences: options.maxInstances ?? 366
293
+ });
294
+ }
295
+ const utf8Encoder = new TextEncoder();
296
+ function toIcsTimestamp(isoString, allDay = false) {
297
+ const d = new Date(isoString);
298
+ if (Number.isNaN(d.getTime())) {
299
+ return "";
300
+ }
301
+ const iso = d.toISOString();
302
+ if (allDay) {
303
+ return iso.slice(0, 10).replace(/-/g, "");
304
+ }
305
+ return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
306
+ }
307
+ function resolveExclusiveAllDayEndStamp(startDate, endDate, dtStartStamp) {
308
+ const rawEndStamp = toIcsTimestamp(endDate, true);
309
+ if (rawEndStamp && rawEndStamp > dtStartStamp) {
310
+ return rawEndStamp;
311
+ }
312
+ const startObj = new Date(startDate);
313
+ if (Number.isNaN(startObj.getTime())) {
314
+ return rawEndStamp;
315
+ }
316
+ const nextDay = new Date(
317
+ Date.UTC(
318
+ startObj.getUTCFullYear(),
319
+ startObj.getUTCMonth(),
320
+ startObj.getUTCDate() + 1
321
+ )
322
+ );
323
+ return nextDay.toISOString().slice(0, 10).replace(/-/g, "");
324
+ }
325
+ function escapeIcsText(value) {
326
+ if (!value) return "";
327
+ return String(value).replace(/\\/g, "\\\\").replace(/\r\n|\r|\n/g, "\\n").replace(/;/g, "\\;").replace(/,/g, "\\,");
328
+ }
329
+ function foldIcsLine(line) {
330
+ if (utf8Encoder.encode(line).byteLength <= 75) {
331
+ return line;
332
+ }
333
+ const chunks = [];
334
+ let currentChunk = "";
335
+ let currentBytes = 0;
336
+ let maxBytes = 75;
337
+ for (const codePoint of line) {
338
+ const byteLen = utf8Encoder.encode(codePoint).byteLength;
339
+ if (currentBytes + byteLen > maxBytes && currentChunk.length > 0) {
340
+ chunks.push(chunks.length === 0 ? currentChunk : ` ${currentChunk}`);
341
+ currentChunk = codePoint;
342
+ currentBytes = byteLen;
343
+ maxBytes = 74;
344
+ } else {
345
+ currentChunk += codePoint;
346
+ currentBytes += byteLen;
347
+ }
348
+ }
349
+ if (currentChunk.length > 0) {
350
+ chunks.push(chunks.length === 0 ? currentChunk : ` ${currentChunk}`);
351
+ }
352
+ return chunks.join("\r\n");
353
+ }
354
+ function buildCalendarLinks(params) {
355
+ const {
356
+ documentId,
357
+ title,
358
+ startDate,
359
+ endDate,
360
+ allDay = false,
361
+ summary = "",
362
+ location = "",
363
+ occurrenceDate
364
+ } = params;
365
+ const icsStart = toIcsTimestamp(startDate, allDay);
366
+ const icsEnd = allDay ? resolveExclusiveAllDayEndStamp(startDate, endDate, icsStart) : toIcsTimestamp(endDate, false);
367
+ const googleParams = new URLSearchParams({
368
+ action: "TEMPLATE",
369
+ text: title,
370
+ dates: `${icsStart}/${icsEnd}`,
371
+ details: summary,
372
+ location
373
+ });
374
+ const outlookParams = new URLSearchParams({
375
+ path: "/calendar/action/compose",
376
+ rru: "addevent",
377
+ subject: title,
378
+ startdt: startDate,
379
+ enddt: endDate,
380
+ body: summary,
381
+ location,
382
+ allday: allDay ? "true" : "false"
383
+ });
384
+ const yahooParams = new URLSearchParams({
385
+ v: "60",
386
+ title,
387
+ st: icsStart,
388
+ et: icsEnd,
389
+ desc: summary,
390
+ in_loc: location
391
+ });
392
+ const querySuffix = occurrenceDate ? `?occurrenceDate=${encodeURIComponent(occurrenceDate)}` : "";
393
+ return {
394
+ google: `https://calendar.google.com/calendar/render?${googleParams.toString()}`,
395
+ outlook: `https://outlook.live.com/calendar/0/deeplink/compose?${outlookParams.toString()}`,
396
+ yahoo: `https://calendar.yahoo.com/?${yahooParams.toString()}`,
397
+ icsPath: `/api/events/${encodeURIComponent(documentId)}/invite.ics${querySuffix}`
398
+ };
399
+ }
400
+ const ICS_STATUS_MAP = {
401
+ cancelled: "CANCELLED",
402
+ postponed: "TENTATIVE"
403
+ };
404
+ function resolveIcsStatus(status) {
405
+ return Object.hasOwn(ICS_STATUS_MAP, status) ? ICS_STATUS_MAP[status] : "CONFIRMED";
406
+ }
407
+ function appendVEventDateLines(lines, occ) {
408
+ const isAllDay = Boolean(occ.allDay);
409
+ const prefix = isAllDay ? ";VALUE=DATE" : "";
410
+ const startStamp = toIcsTimestamp(occ.startDate, isAllDay);
411
+ const endStamp = isAllDay ? resolveExclusiveAllDayEndStamp(occ.startDate, occ.endDate, startStamp) : toIcsTimestamp(occ.endDate, false);
412
+ lines.push(`DTSTART${prefix}:${startStamp}`);
413
+ lines.push(`DTEND${prefix}:${endStamp}`);
414
+ }
415
+ function appendVEventOptionalLines(lines, occ) {
416
+ if (occ.summary) {
417
+ lines.push(foldIcsLine(`DESCRIPTION:${escapeIcsText(occ.summary)}`));
418
+ }
419
+ if (occ.location) {
420
+ lines.push(foldIcsLine(`LOCATION:${escapeIcsText(occ.location)}`));
421
+ }
422
+ if (occ.virtualUrl) {
423
+ lines.push(foldIcsLine(`URL:${escapeIcsText(occ.virtualUrl)}`));
424
+ }
425
+ if (occ.isRecurring && occ.rrule) {
426
+ const normalized = normalizeRRuleString(occ.rrule).replace(/[\r\n]+/g, "");
427
+ if (normalized) {
428
+ lines.push(foldIcsLine(`RRULE:${normalized}`));
429
+ }
430
+ }
431
+ }
432
+ function generateIcsCalendar(occurrences, feedTitle = "Events Calendar") {
433
+ const nowStamp = toIcsTimestamp((/* @__PURE__ */ new Date()).toISOString(), false);
434
+ const lines = [
435
+ "BEGIN:VCALENDAR",
436
+ "VERSION:2.0",
437
+ "PRODID:-//Strapi v5//strapi-plugin-events//EN",
438
+ "CALSCALE:GREGORIAN",
439
+ "METHOD:PUBLISH",
440
+ foldIcsLine(`X-WR-CALNAME:${escapeIcsText(feedTitle)}`)
441
+ ];
442
+ for (const occ of occurrences) {
443
+ const uid = `${occ.documentId}-${occ.occurrenceDate}@strapi-plugin-events`;
444
+ lines.push("BEGIN:VEVENT");
445
+ lines.push(foldIcsLine(`UID:${escapeIcsText(uid)}`));
446
+ lines.push(`DTSTAMP:${nowStamp}`);
447
+ appendVEventDateLines(lines, occ);
448
+ lines.push(foldIcsLine(`SUMMARY:${escapeIcsText(occ.title)}`));
449
+ appendVEventOptionalLines(lines, occ);
450
+ lines.push(`STATUS:${resolveIcsStatus(occ.status)}`);
451
+ lines.push("END:VEVENT");
452
+ }
453
+ lines.push("END:VCALENDAR");
454
+ return `${lines.join("\r\n")}\r
455
+ `;
456
+ }
457
+ function sanitizeCsvCell(raw) {
458
+ if (raw === null || raw === void 0) {
459
+ return '""';
460
+ }
461
+ let text = String(raw);
462
+ if (/^[\s\u0085\u2028\u2029]*[=+\-@|]|^[\t\r\n]/.test(text)) {
463
+ text = `'${text}`;
464
+ }
465
+ const escaped = text.replace(/"/g, '""');
466
+ return `"${escaped}"`;
467
+ }
468
+ function generateRegistrationsCsv(records) {
469
+ const headers = [
470
+ "Document ID",
471
+ "Event UID",
472
+ "Event Document ID",
473
+ "Event Title",
474
+ "Occurrence Date",
475
+ "Attendee Name",
476
+ "Attendee Email",
477
+ "Attendee Phone",
478
+ "Guests Count",
479
+ "Status",
480
+ "Checked In At",
481
+ "Locale",
482
+ "Notes",
483
+ "Created At"
484
+ ];
485
+ const rows = [headers.map(sanitizeCsvCell).join(",")];
486
+ for (const r of records) {
487
+ rows.push(
488
+ [
489
+ r.documentId,
490
+ r.eventUid,
491
+ r.eventDocumentId,
492
+ r.eventTitle ?? "",
493
+ r.occurrenceDate,
494
+ r.attendeeName,
495
+ r.attendeeEmail,
496
+ r.attendeePhone ?? "",
497
+ r.guestsCount,
498
+ r.status,
499
+ r.checkedInAt ?? "",
500
+ r.locale,
501
+ r.notes ?? "",
502
+ r.createdAt ?? ""
503
+ ].map(sanitizeCsvCell).join(",")
504
+ );
505
+ }
506
+ return `${rows.join("\r\n")}\r
507
+ `;
508
+ }
509
+ const PLUGIN_ID = "events";
510
+ const RRULE_CUSTOM_FIELD_UID = `plugin::${PLUGIN_ID}.rrule`;
511
+ const EVENT_CONTENT_TYPE_UID = `plugin::${PLUGIN_ID}.event`;
512
+ const REGISTRATION_CONTENT_TYPE_UID = `plugin::${PLUGIN_ID}.registration`;
513
+ const ALLOWED_SPOTLIGHT_POSITIONS = /* @__PURE__ */ new Set([
514
+ "bottom-end",
515
+ "bottom-start"
516
+ ]);
517
+ function resolveSpotlightPosition(rawPos) {
518
+ const candidate = getTrimmedString(rawPos);
519
+ return candidate && ALLOWED_SPOTLIGHT_POSITIONS.has(candidate) ? candidate : void 0;
520
+ }
521
+ function resolveFiniteRoundNumber(val) {
522
+ return typeof val === "number" && Number.isFinite(val) ? Math.round(val) : void 0;
523
+ }
524
+ function normalizeEventSpotlightConfig(rawConfig) {
525
+ const parsed = parseJsonIfNeeded(rawConfig);
526
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
527
+ return void 0;
528
+ }
529
+ const rec = parsed;
530
+ const config = {
531
+ enabled: Boolean(rec.enabled)
532
+ };
533
+ const badgeText = getTrimmedString(rec.badgeText, 80);
534
+ if (badgeText) config.badgeText = badgeText;
535
+ const priority = resolveFiniteRoundNumber(rec.priority);
536
+ if (priority !== void 0) config.priority = priority;
537
+ const position = resolveSpotlightPosition(rec.position);
538
+ if (position) config.position = position;
539
+ if (typeof rec.showCountdown === "boolean") {
540
+ config.showCountdown = rec.showCountdown;
541
+ }
542
+ const startAt = getTrimmedString(rec.startAt);
543
+ if (startAt) config.startAt = startAt;
544
+ const endAt = getTrimmedString(rec.endAt);
545
+ if (endAt) config.endAt = endAt;
546
+ return config;
547
+ }
548
+ function toBoundaryTimestamp(rawIsoOrDate, isEndBoundary) {
549
+ const trimmed = rawIsoOrDate.trim();
550
+ const isoString = /^\d{4}-\d{2}-\d{2}$/.test(trimmed) ? `${trimmed}${isEndBoundary ? "T23:59:59.999Z" : "T00:00:00.000Z"}` : trimmed;
551
+ const ts = new Date(isoString).getTime();
552
+ return Number.isNaN(ts) ? null : ts;
553
+ }
554
+ function toReferenceTimestamp(referenceDate) {
555
+ if (referenceDate instanceof Date) {
556
+ const ts2 = referenceDate.getTime();
557
+ return Number.isNaN(ts2) ? null : ts2;
558
+ }
559
+ const str = getTrimmedString(referenceDate);
560
+ const dt = str ? new Date(str) : /* @__PURE__ */ new Date();
561
+ const ts = dt.getTime();
562
+ return Number.isNaN(ts) ? null : ts;
563
+ }
564
+ function isEventSpotlightActiveOnDate(params) {
565
+ const { spotlightConfig, eventStartDate, eventEndDate, referenceDate } = params;
566
+ if (!spotlightConfig?.enabled) return false;
567
+ const refTs = toReferenceTimestamp(referenceDate);
568
+ if (refTs === null) return false;
569
+ const startTs = toBoundaryTimestamp(
570
+ spotlightConfig.startAt || eventStartDate,
571
+ false
572
+ );
573
+ const endTs = toBoundaryTimestamp(
574
+ spotlightConfig.endAt || eventEndDate || eventStartDate,
575
+ true
576
+ );
577
+ const afterStart = startTs === null || refTs >= startTs;
578
+ const beforeEnd = endTs === null || refTs <= endTs;
579
+ return afterStart && beforeEnd;
580
+ }
581
+ function deduplicateAndSortSpotlights(occurrences) {
582
+ const byDocKey = /* @__PURE__ */ new Map();
583
+ for (const occ of occurrences) {
584
+ if (!occ.spotlightConfig?.enabled || occ.status === "cancelled") {
585
+ continue;
586
+ }
587
+ const key = `${occ.sourceId}:${occ.documentId}`;
588
+ if (!byDocKey.has(key)) {
589
+ byDocKey.set(key, occ);
590
+ }
591
+ }
592
+ const unique = Array.from(byDocKey.values());
593
+ unique.sort((a, b) => {
594
+ const prioA = a.spotlightConfig?.priority ?? 0;
595
+ const prioB = b.spotlightConfig?.priority ?? 0;
596
+ if (prioB !== prioA) {
597
+ return prioB - prioA;
598
+ }
599
+ return new Date(a.startDate).getTime() - new Date(b.startDate).getTime();
600
+ });
601
+ return unique;
602
+ }
603
+ const DEFAULT_TIME_SLOTS = {
604
+ morning: { start: "09:00:00", end: "11:00:00" },
605
+ afternoon: { start: "14:00:00", end: "16:00:00" },
606
+ evening: { start: "18:00:00", end: "20:00:00" }
607
+ };
608
+ function buildSafeSlotMap(customSlots) {
609
+ const slotMap = { ...DEFAULT_TIME_SLOTS };
610
+ if (customSlots && typeof customSlots === "object") {
611
+ for (const [k, v] of Object.entries(customSlots)) {
612
+ if (isSafeObjectKey(k) && v && typeof v === "object") {
613
+ slotMap[k] = v;
614
+ }
615
+ }
616
+ }
617
+ return slotMap;
618
+ }
619
+ function resolveDateOnlySlotWindow(rawStart, rawSlot, source) {
620
+ if (!isSafeObjectKey(rawSlot)) return null;
621
+ const slotMap = buildSafeSlotMap(source.timeSlotMap);
622
+ const mapped = Object.hasOwn(slotMap, rawSlot) ? slotMap[rawSlot] : void 0;
623
+ if (mapped) {
624
+ return {
625
+ startDate: `${rawStart}T${mapped.start}.000Z`,
626
+ endDate: `${rawStart}T${mapped.end}.000Z`,
627
+ allDay: false
628
+ };
629
+ }
630
+ if (/^\d{2}:\d{2}(:\d{2})?$/.test(rawSlot)) {
631
+ const normalizedTime = rawSlot.length === 5 ? `${rawSlot}:00` : rawSlot;
632
+ return {
633
+ startDate: `${rawStart}T${normalizedTime}.000Z`,
634
+ endDate: null,
635
+ allDay: false
636
+ };
637
+ }
638
+ return null;
639
+ }
640
+ function resolveDateOnlyEnd(rawStart, rawEnd) {
641
+ if (typeof rawEnd === "string" && rawEnd) {
642
+ return /^\d{4}-\d{2}-\d{2}$/.test(rawEnd) ? `${rawEnd}T23:59:59.000Z` : rawEnd;
643
+ }
644
+ return `${rawStart}T23:59:59.000Z`;
645
+ }
646
+ function readOptionalDocField(doc, fieldName) {
647
+ return fieldName && isSafeObjectKey(fieldName) ? doc[fieldName] : null;
648
+ }
649
+ function resolveNonEmptyEnd(rawEnd) {
650
+ return typeof rawEnd === "string" && rawEnd ? rawEnd : null;
651
+ }
652
+ function resolveStartEndFromDocument(doc, source) {
653
+ const startField = source.fields.startDate;
654
+ const rawStart = isSafeObjectKey(startField) ? doc[startField] : void 0;
655
+ if (typeof rawStart !== "string" || !rawStart) {
656
+ return null;
657
+ }
658
+ const rawEnd = readOptionalDocField(doc, source.fields.endDate);
659
+ const rawSlot = readOptionalDocField(doc, source.fields.timeSlot);
660
+ if (/^\d{4}-\d{2}-\d{2}$/.test(rawStart)) {
661
+ const slotWindow = resolveDateOnlySlotWindow(rawStart, rawSlot, source);
662
+ if (slotWindow) return slotWindow;
663
+ return {
664
+ startDate: `${rawStart}T00:00:00.000Z`,
665
+ endDate: resolveDateOnlyEnd(rawStart, rawEnd),
666
+ allDay: source.defaultAllDay ?? true
667
+ };
668
+ }
669
+ const allDay = typeof doc.allDay === "boolean" ? doc.allDay : Boolean(source.defaultAllDay);
670
+ return {
671
+ startDate: rawStart,
672
+ endDate: resolveNonEmptyEnd(rawEnd),
673
+ allDay
674
+ };
675
+ }
676
+ const EVENT_STATUS_ALIAS_MAP = {
677
+ cancelled: "cancelled",
678
+ canceled: "cancelled",
679
+ postponed: "postponed",
680
+ pending: "postponed",
681
+ rescheduled: "rescheduled"
682
+ };
683
+ function mapEventStatus(rawStatus) {
684
+ if (typeof rawStatus !== "string") return "scheduled";
685
+ const lower = rawStatus.toLowerCase();
686
+ return Object.hasOwn(EVENT_STATUS_ALIAS_MAP, lower) ? EVENT_STATUS_ALIAS_MAP[lower] : "scheduled";
687
+ }
688
+ Object.defineProperty(exports, "SUPPORTED_INTL_CALENDARS", {
689
+ enumerable: true,
690
+ get: () => shared.SUPPORTED_INTL_CALENDARS
691
+ });
692
+ Object.defineProperty(exports, "applyDayOffset", {
693
+ enumerable: true,
694
+ get: () => shared.applyDayOffset
695
+ });
696
+ Object.defineProperty(exports, "extractCalendarParts", {
697
+ enumerable: true,
698
+ get: () => shared.extractCalendarParts
699
+ });
700
+ Object.defineProperty(exports, "formatDateInCalendarSystem", {
701
+ enumerable: true,
702
+ get: () => shared.formatDateInCalendarSystem
703
+ });
704
+ Object.defineProperty(exports, "getRegisteredCalendarFormatters", {
705
+ enumerable: true,
706
+ get: () => shared.getRegisteredCalendarFormatters
707
+ });
708
+ Object.defineProperty(exports, "parseRRule", {
709
+ enumerable: true,
710
+ get: () => shared.parseRRule
711
+ });
712
+ Object.defineProperty(exports, "registerCalendarSystemFormatter", {
713
+ enumerable: true,
714
+ get: () => shared.registerCalendarSystemFormatter
715
+ });
716
+ Object.defineProperty(exports, "resolveOccurrenceAlternateDates", {
717
+ enumerable: true,
718
+ get: () => shared.resolveOccurrenceAlternateDates
719
+ });
720
+ Object.defineProperty(exports, "unregisterCalendarSystemFormatter", {
721
+ enumerable: true,
722
+ get: () => shared.unregisterCalendarSystemFormatter
723
+ });
724
+ exports.EVENT_CONTENT_TYPE_UID = EVENT_CONTENT_TYPE_UID;
725
+ exports.PLUGIN_ID = PLUGIN_ID;
726
+ exports.REGISTRATION_CONTENT_TYPE_UID = REGISTRATION_CONTENT_TYPE_UID;
727
+ exports.RRULE_CUSTOM_FIELD_UID = RRULE_CUSTOM_FIELD_UID;
728
+ exports.buildCalendarLinks = buildCalendarLinks;
729
+ exports.buildRRule = buildRRule;
730
+ exports.deduplicateAndSortSpotlights = deduplicateAndSortSpotlights;
731
+ exports.describeRRule = describeRRule;
732
+ exports.escapeIcsText = escapeIcsText;
733
+ exports.expandRuleSlots = expandRuleSlots;
734
+ exports.foldIcsLine = foldIcsLine;
735
+ exports.generateIcsCalendar = generateIcsCalendar;
736
+ exports.generateRegistrationsCsv = generateRegistrationsCsv;
737
+ exports.isEventSpotlightActiveOnDate = isEventSpotlightActiveOnDate;
738
+ exports.isSafeObjectKey = isSafeObjectKey;
739
+ exports.isValidCalendarDateKey = isValidCalendarDateKey;
740
+ exports.mapEventStatus = mapEventStatus;
741
+ exports.normalizeEventActionLinks = normalizeEventActionLinks;
742
+ exports.normalizeEventPoster = normalizeEventPoster;
743
+ exports.normalizeEventSpotlightConfig = normalizeEventSpotlightConfig;
744
+ exports.normalizeRRuleString = normalizeRRuleString;
745
+ exports.parseRRuleFieldValue = parseRRuleFieldValue;
746
+ exports.resolveStartEndFromDocument = resolveStartEndFromDocument;
747
+ exports.sanitizeCsvCell = sanitizeCsvCell;
748
+ exports.sanitizeEventUrl = sanitizeEventUrl;
749
+ exports.sanitizeOccurrenceOverrides = sanitizeOccurrenceOverrides;
750
+ exports.sanitizePlainRecord = sanitizePlainRecord;
751
+ exports.sanitizeRRuleFieldValue = sanitizeRRuleFieldValue;
752
+ exports.toDateKey = toDateKey;
753
+ exports.toIcsTimestamp = toIcsTimestamp;