diff --git a/package-lock.json b/package-lock.json index 45c775797..abac02b0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "react-i18next": "^11.18.6", "react-icons": "^4.4.0", "recharts": "^2.7.2", + "rrule": "^2.8.1", "swr": "^1.3.0", "systeminformation": "^5.17.12", "tough-cookie": "^4.1.2", @@ -5501,9 +5502,9 @@ } }, "node_modules/rrule": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.7.2.tgz", - "integrity": "sha512-NkBsEEB6FIZOZ3T8frvEBOB243dm46SPufpDckY/Ap/YH24V1zLeMmDY8OA10lk452NdrF621+ynDThE7FQU2A==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", + "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", "dependencies": { "tslib": "^2.4.0" } diff --git a/package.json b/package.json index 0688bf7d2..9bea04618 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-i18next": "^11.18.6", "react-icons": "^4.4.0", "recharts": "^2.7.2", + "rrule": "^2.8.1", "swr": "^1.3.0", "systeminformation": "^5.17.12", "tough-cookie": "^4.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d272b312..ba74b36da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ dependencies: recharts: specifier: ^2.7.2 version: 2.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) + rrule: + specifier: ^2.8.1 + version: 2.8.1 swr: specifier: ^1.3.0 version: 1.3.0(react@18.2.0) @@ -871,7 +874,7 @@ packages: resolution: {integrity: sha512-wlQwcF0fl4eLclyGdncF9rcNNq0ipRYZGagG6h3LVgRXvCWE1fdMUaCLXwfC9YWoz9jKKbjQAq7TpO2Y3yrvmA==} dependencies: ical-date-parser: 4.0.0 - rrule: 2.7.2 + rrule: 2.8.1 dev: false /call-bind@1.0.2: @@ -3552,8 +3555,8 @@ packages: dependencies: glob: 7.2.3 - /rrule@2.7.2: - resolution: {integrity: sha512-NkBsEEB6FIZOZ3T8frvEBOB243dm46SPufpDckY/Ap/YH24V1zLeMmDY8OA10lk452NdrF621+ynDThE7FQU2A==} + /rrule@2.8.1: + resolution: {integrity: sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==} dependencies: tslib: 2.5.0 dev: false diff --git a/src/widgets/calendar/integrations/ical.jsx b/src/widgets/calendar/integrations/ical.jsx index b3f1f7818..f5063331a 100644 --- a/src/widgets/calendar/integrations/ical.jsx +++ b/src/widgets/calendar/integrations/ical.jsx @@ -2,6 +2,7 @@ import { DateTime } from "luxon"; import { parseString } from "cal-parser"; import { useEffect } from "react"; import { useTranslation } from "next-i18next"; +import { RRule } from "rrule"; import useWidgetAPI from "../../../utils/proxy/use-widget-api"; import Error from "../../../components/services/widget/error"; @@ -22,15 +23,15 @@ export default function Integration({ config, params, setEvents, hideErrors }) { } } - if (icalError || !parsedIcal) { + const startDate = DateTime.fromISO(params.start); + const endDate = DateTime.fromISO(params.end); + + if (icalError || !parsedIcal || !startDate.isValid || !endDate.isValid) { return; } const eventsToAdd = {}; - const events = parsedIcal?.getEventsBetweenDates( - DateTime.fromISO(params.start).toJSDate(), - DateTime.fromISO(params.end).toJSDate(), - ); + const events = parsedIcal?.getEventsBetweenDates(startDate.toJSDate(), endDate.toJSDate()); events?.forEach((event) => { let title = `${event?.summary?.value}`; @@ -38,16 +39,31 @@ export default function Integration({ config, params, setEvents, hideErrors }) { title = `${config.name}: ${title}`; } - event.matchingDates.forEach((date) => { - eventsToAdd[event?.uid?.value] = { - title, - date: DateTime.fromJSDate(date), - color: config?.color ?? "zinc", - isCompleted: DateTime.fromJSDate(date) < DateTime.now(), - additional: event.location?.value, - type: "ical", - }; - }); + const eventToAdd = (date, i, type) => { + const duration = event.dtend.value - event.dtstart.value; + const days = duration / (1000 * 60 * 60 * 24); + + for (let j = 0; j < days; j += 1) { + eventsToAdd[`${event?.uid?.value}${i}${j}${type}`] = { + title, + date: DateTime.fromJSDate(date).plus({ days: j }), + color: config?.color ?? "zinc", + isCompleted: DateTime.fromJSDate(date) < DateTime.now(), + additional: event.location?.value, + type: "ical", + }; + } + }; + + if (event?.recurrenceRule?.options) { + const rule = new RRule(event.recurrenceRule.options); + const recurringEvents = rule.between(startDate.toJSDate(), endDate.toJSDate()); + + recurringEvents.forEach((date, i) => eventToAdd(date, i, "recurring")); + return; + } + + event.matchingDates.forEach((date, i) => eventToAdd(date, i, "single")); }); setEvents((prevEvents) => ({ ...prevEvents, ...eventsToAdd }));