feat: add repeat/recurrence dropdown to calendar event modal (#25865)

* feat(calendar): add repeat/recurrence dropdown to event modal

* fix(calendar): anchor recurring event expansion to event start date

The expand_recurring_event() utility used the view range start as dtstart,
causing FREQ=WEEKLY events to land on the wrong day of the week. Use the
event's original start date instead so recurrence patterns stay correct.
This commit is contained in:
G30
2026-06-29 02:16:19 -05:00
committed by GitHub
parent 01198eaeef
commit ff25ccca65
2 changed files with 49 additions and 5 deletions
@@ -33,9 +33,31 @@
let allDay = false;
let location = '';
let alertMinutes: number = 10;
let repeatFrequency = '';
let loading = false;
let showDeleteConfirmDialog = false;
const REPEAT_RRULE_MAP: Record<string, string> = {
daily: 'FREQ=DAILY',
weekdays: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
weekly: 'FREQ=WEEKLY',
monthly: 'FREQ=MONTHLY',
yearly: 'FREQ=YEARLY'
};
function getRepeatRrule(): string | undefined {
return REPEAT_RRULE_MAP[repeatFrequency] || undefined;
}
function parseRepeatFromRrule(rrule: string | null): string {
if (!rrule) return '';
const normalized = rrule.toUpperCase().replace(/\s/g, '');
for (const [key, value] of Object.entries(REPEAT_RRULE_MAP)) {
if (normalized === value) return key;
}
return '';
}
const NS = 1_000_000;
function nsToDateStr(ns: number): string {
@@ -66,6 +88,7 @@
allDay = event.all_day;
location = event.location || '';
alertMinutes = event.meta?.alert_minutes ?? 10;
repeatFrequency = parseRepeatFromRrule(event.rrule);
} else {
title = '';
description = '';
@@ -87,6 +110,7 @@
allDay = false;
location = '';
alertMinutes = 10;
repeatFrequency = '';
}
}
@@ -107,11 +131,12 @@
const result = await updateCalendarEvent(localStorage.token, event.id, {
calendar_id: calendarId,
title: title.trim(),
description: description.trim() || null,
description: description.trim() || undefined,
start_at: startNs,
end_at: endNs,
all_day: allDay,
location: location.trim() || null,
rrule: getRepeatRrule(),
location: location.trim() || undefined,
meta: { alert_minutes: alertMinutes }
});
if (result) {
@@ -127,6 +152,7 @@
start_at: startNs,
end_at: endNs,
all_day: allDay,
rrule: getRepeatRrule(),
location: location.trim() || undefined,
meta: { alert_minutes: alertMinutes }
};
@@ -238,6 +264,22 @@
</select>
</div>
<!-- Repeat -->
<div>
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Repeat')}</div>
<select
class="w-full text-sm bg-transparent outline-hidden cursor-pointer"
bind:value={repeatFrequency}
>
<option value="">{$i18n.t('No Repeat')}</option>
<option value="daily">{$i18n.t('Daily')}</option>
<option value="weekdays">{$i18n.t('Monday – Friday')}</option>
<option value="weekly">{$i18n.t('Weekly')}</option>
<option value="monthly">{$i18n.t('Monthly')}</option>
<option value="yearly">{$i18n.t('Yearly')}</option>
</select>
</div>
<!-- Description -->
<div>
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>