mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
A move outside the editor kept every answer. A drag or resize in the
week and day views, a drop in the month view and an applied counter
proposal wrote the new time with the attendees' stored answers:
Stalwart's update then said "accepted" for a time nobody had accepted,
and a dragged occurrence, which gets its own override, reached no
attendee who had answered (live on 1.0 and 0.16.23). When the user
organizes the meeting these moves now ask the attendees again, as the
editor does; one helper (`userOrganizes`) decides for both.
Also:
- A changed recurrence rule counts as a move for the series: a custom
rule moved from Monday to Tuesday asks again. A rule the user did not
touch is no change however the server spells it, and on one
occurrence, where no rule applies, it is none.
- An event that lasts no time (a deadline) keeps its zero length on
save instead of becoming an hour long and asking everyone again.
- An attendee's save of their own copy - an alert on one occurrence, a
drag - wrote the participants again and sent the organizer a
"Reply:" mail. It now leaves the participants alone and sends
nothing. The editor shows someone else's attendee list read-only and
no "Send invitations" box, which only ever controlled the organizer's
messages; answers go out through the answer buttons.
- An existing override written on the base event (0.16 before 0.16.20,
and ranges the browser expands) gets the series' organizer again when
it has none, as every save wrote it before 4c20b09c: 0.16.19 refuses
an answer to such an occurrence. 0.16.23 takes the key and drops it;
1.0 refuses it inside an override once, and the store writes the
override again without it (both live).
- The comment on `organizerCalendarAddress` said Stalwart takes the
ORGANIZER solely from it. Both versions take it from the owner
participant when it is missing (live).
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import type { CalendarRecurrenceRule } from '@/lib/jmap/types';
|
|
|
|
type Frequency = CalendarRecurrenceRule['frequency'];
|
|
|
|
/**
|
|
* A plain Gregorian recurrence rule with all by-parts unset. `rscale`/`skip`
|
|
* are intentionally omitted: they only matter for non-Gregorian scales /
|
|
* invalid-date handling, and defaulting them made Stalwart serialise
|
|
* `RSCALE=GREGORIAN;SKIP=OMIT` into the RRULE, which DAVx5 rejects (#805).
|
|
*/
|
|
export function createRecurrenceRule(
|
|
frequency: Frequency,
|
|
overrides: Partial<CalendarRecurrenceRule> = {},
|
|
): CalendarRecurrenceRule {
|
|
return {
|
|
'@type': 'RecurrenceRule',
|
|
frequency,
|
|
interval: 1,
|
|
firstDayOfWeek: 'mo',
|
|
byDay: null,
|
|
byMonthDay: null,
|
|
byMonth: null,
|
|
byYearDay: null,
|
|
byWeekNo: null,
|
|
byHour: null,
|
|
byMinute: null,
|
|
bySecond: null,
|
|
bySetPosition: null,
|
|
count: null,
|
|
until: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
// What a rule means when a part is left out (RFC 8984 4.3.3).
|
|
const RULE_DEFAULTS: Record<string, unknown> = { interval: 1, firstDayOfWeek: 'mo', rscale: 'gregorian', skip: 'omit' };
|
|
|
|
function ruleKey(rule: CalendarRecurrenceRule): string {
|
|
const parts = Object.entries(rule as unknown as Record<string, unknown>)
|
|
.filter(([name, value]) => name !== '@type' && value != null
|
|
&& !(Array.isArray(value) && value.length === 0) && RULE_DEFAULTS[name] !== value)
|
|
.map(([name, value]) => {
|
|
if (!Array.isArray(value)) return [name, value];
|
|
const items = value.map((item) => (item && typeof item === 'object'
|
|
? JSON.stringify([(item as { day?: string }).day, (item as { nthOfPeriod?: number | null }).nthOfPeriod ?? null])
|
|
: JSON.stringify(item)));
|
|
return [name, items.sort()];
|
|
})
|
|
.sort(([a], [b]) => (String(a) < String(b) ? -1 : 1));
|
|
return JSON.stringify(parts);
|
|
}
|
|
|
|
/**
|
|
* Whether two sets of recurrence rules describe the same occurrences as far
|
|
* as their parts go: parts left out and parts set to their default are the
|
|
* same, and so are by-lists in another order. A server returns a rule with
|
|
* its defaults dropped that the editor wrote with them.
|
|
*/
|
|
export function sameRecurrenceRules(
|
|
a: CalendarRecurrenceRule[] | null | undefined,
|
|
b: CalendarRecurrenceRule[] | null | undefined,
|
|
): boolean {
|
|
const keys = (rules: CalendarRecurrenceRule[] | null | undefined) => (rules ?? []).map(ruleKey).sort();
|
|
return JSON.stringify(keys(a)) === JSON.stringify(keys(b));
|
|
}
|