diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx
index 11f2436db..ecd136b64 100644
--- a/components/calendar/calendar-app.tsx
+++ b/components/calendar/calendar-app.tsx
@@ -137,9 +137,18 @@ export function CalendarApp({ linkSegments }: CalendarAppProps = {}) {
void fetchPrincipal();
}, [accountEmails, fetchPrincipal]);
+ // The default ParticipantIdentity (draft-ietf-jmap-calendars §6) is the
+ // address new invitations are organised from, so it goes first: the event
+ // modal takes currentUserEmails[0] as organizer.
+ const participantIdentities = useCalendarStore((s) => s.participantIdentities);
const currentUserEmails = useMemo(
- () => collectUserCalendarAddresses(identities.map(id => id.email), accountEmails),
- [identities, accountEmails]
+ () => collectUserCalendarAddresses(
+ participantIdentities.filter(i => i.isDefault).map(i => i.calendarAddress.replace(/^mailto:/i, '')),
+ participantIdentities.map(i => i.calendarAddress.replace(/^mailto:/i, '')),
+ identities.map(id => id.email),
+ accountEmails,
+ ),
+ [participantIdentities, identities, accountEmails]
);
const [showEventModal, setShowEventModal] = useState(false);
diff --git a/components/settings/calendar-settings.tsx b/components/settings/calendar-settings.tsx
index 4154f17b5..c841d26fb 100644
--- a/components/settings/calendar-settings.tsx
+++ b/components/settings/calendar-settings.tsx
@@ -1,16 +1,30 @@
"use client";
+import { useEffect } from 'react';
import { useTranslations } from 'next-intl';
import { useCalendarStore, CalendarViewMode } from '@/stores/calendar-store';
import { useSettingsStore } from '@/stores/settings-store';
import { usePolicyStore } from '@/stores/policy-store';
+import { useAuthStore } from '@/stores/auth-store';
+import { toast } from '@/stores/toast-store';
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
export function CalendarSettings() {
const t = useTranslations('calendar.settings');
const tViews = useTranslations('calendar.views');
- const { viewMode, setViewMode } = useCalendarStore();
+ const { viewMode, setViewMode, participantIdentities, fetchParticipantIdentities, setDefaultParticipantIdentity } = useCalendarStore();
+ const client = useAuthStore((s) => s.client);
+
+ // ParticipantIdentity list (draft-ietf-jmap-calendars §6): which of the
+ // user's addresses organises new invitations. Loaded here because the
+ // settings page can open before the calendar app ever did.
+ useEffect(() => {
+ if (client && participantIdentities.length === 0) {
+ void fetchParticipantIdentities(client);
+ }
+ }, [client, participantIdentities.length, fetchParticipantIdentities]);
+ const defaultIdentityId = participantIdentities.find((i) => i.isDefault)?.id ?? participantIdentities[0]?.id ?? '';
const {
showTimeInMonthView,
showWeekNumbers,
@@ -37,6 +51,26 @@ export function CalendarSettings() {
/>
+ {client && participantIdentities.length > 1 && (
+
+
+ )}
+
{
});
});
+ describe('participant identities', () => {
+ it('maps ParticipantIdentity/get and sets the default via onSuccessSetIsDefault', async () => {
+ await setup({ 'urn:ietf:params:jmap:calendars': {} });
+ answer((body) => {
+ const [method] = body.methodCalls[0];
+ if (method === 'ParticipantIdentity/get') {
+ return { methodResponses: [['ParticipantIdentity/get', { list: [
+ { id: 'i1', name: 'Me', calendarAddress: 'mailto:me@example.com', isDefault: true },
+ { id: 'i2', name: '', calendarAddress: 'mailto:alias@example.com', isDefault: false },
+ ] }, '0']] };
+ }
+ return { methodResponses: [['ParticipantIdentity/set', { updated: null }, '0']] };
+ });
+
+ const identities = await client.getParticipantIdentities();
+ expect(requests[0].using).toContain('urn:ietf:params:jmap:calendars');
+ expect(identities).toEqual([
+ { id: 'i1', name: 'Me', calendarAddress: 'mailto:me@example.com', isDefault: true },
+ { id: 'i2', name: '', calendarAddress: 'mailto:alias@example.com', isDefault: false },
+ ]);
+
+ await client.setDefaultParticipantIdentity('i2');
+ expect(requests[1].methodCalls[0]).toEqual(['ParticipantIdentity/set', { accountId: 'acct-1', onSuccessSetIsDefault: 'i2' }, '0']);
+ });
+ });
+
describe('free/busy', () => {
it('needs both the principals and the availability capability', async () => {
await setup();
diff --git a/lib/jmap/client-interface.ts b/lib/jmap/client-interface.ts
index 1b761e36f..c428ba965 100644
--- a/lib/jmap/client-interface.ts
+++ b/lib/jmap/client-interface.ts
@@ -1,4 +1,4 @@
-import type { Email, Mailbox, MailboxRights, StateChange, AccountStates, CollectionChanges, ShareNotification, BusyPeriod, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CreateCalendarOptions, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, FileNode, FileNodeRights, Principal, PushSubscription, EmailPushConfig, ScheduledEmail, SendEmailResult, SharedAccount } from "./types";
+import type { Email, Mailbox, MailboxRights, StateChange, AccountStates, CollectionChanges, ShareNotification, BusyPeriod, CalendarParticipantIdentity, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CreateCalendarOptions, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, FileNode, FileNodeRights, Principal, PushSubscription, EmailPushConfig, ScheduledEmail, SendEmailResult, SharedAccount } from "./types";
import type { SieveScript, SieveCapabilities } from "./sieve-types";
import type { SortLevel } from "@/lib/message-list-order";
@@ -126,6 +126,10 @@ export interface IJMAPClient {
createMailbox(name: string, parentId?: string, accountId?: string): Promise;
updateMailbox(mailboxId: string, changes: { name?: string; parentId?: string | null; role?: string | null; sortOrder?: number }, accountId?: string): Promise;
deleteMailbox(mailboxId: string, accountId?: string): Promise;
+ // ParticipantIdentity (draft-ietf-jmap-calendars §6): the addresses the
+ // user organises events as. Optional.
+ getParticipantIdentities?(): Promise;
+ setDefaultParticipantIdentity?(id: string): Promise;
// Free/busy (Principal/getAvailability, principals:availability). Optional.
supportsAvailability?(): boolean;
getPrincipalAvailability?(principalId: string, utcStart: Date, utcEnd: Date): Promise;
diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts
index 26d63120b..d7db4c568 100644
--- a/lib/jmap/client.ts
+++ b/lib/jmap/client.ts
@@ -1,5 +1,5 @@
import { generateUUID } from '@/lib/utils';
-import type { Email, Mailbox, MailboxRights, StateChange, AccountStates, CollectionChanges, ShareNotification, BusyPeriod, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CalendarComponentType, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, CreateCalendarOptions, FileNode, FileNodeFilter, FileNodeRights, Principal, PushSubscription, EmailPushConfig, EmailSubmission, ScheduledEmail, SendEmailResult, SharedAccount } from "./types";
+import type { Email, Mailbox, MailboxRights, StateChange, AccountStates, CollectionChanges, ShareNotification, BusyPeriod, CalendarParticipantIdentity, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CalendarComponentType, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, CreateCalendarOptions, FileNode, FileNodeFilter, FileNodeRights, Principal, PushSubscription, EmailPushConfig, EmailSubmission, ScheduledEmail, SendEmailResult, SharedAccount } from "./types";
import type { SieveScript, SieveCapabilities } from "./sieve-types";
import type { IJMAPClient, KeywordDiscoveryResult, KeywordInfo, KeywordMigration } from "./client-interface";
import { toWildcardQuery } from "./search-utils";
@@ -5071,6 +5071,42 @@ export class JMAPClient implements IJMAPClient {
}
}
+ // ── ParticipantIdentity (draft-ietf-jmap-calendars §6) ─────────
+
+ /**
+ * The calendar addresses this user can organise events as, with the
+ * server's default flagged. Empty when the server has no calendars.
+ */
+ async getParticipantIdentities(): Promise {
+ if (!this.supportsCalendars()) return [];
+ const accountId = this.getCalendarsAccountId();
+ const response = await this.request([
+ ["ParticipantIdentity/get", { accountId }, "0"],
+ ], this.calendarUsing());
+ const [method, result] = response.methodResponses?.[0] ?? [];
+ if (method !== "ParticipantIdentity/get") {
+ throw new Error((result as { description?: string })?.description || "Failed to load participant identities");
+ }
+ return ((result as { list?: Array & { id: string }> }).list ?? []).map((i) => ({
+ id: i.id,
+ name: i.name ?? "",
+ calendarAddress: i.calendarAddress ?? "",
+ isDefault: i.isDefault === true,
+ }));
+ }
+
+ /** Makes `id` the default identity (ParticipantIdentity/set onSuccessSetIsDefault). */
+ async setDefaultParticipantIdentity(id: string): Promise {
+ const accountId = this.getCalendarsAccountId();
+ const response = await this.request([
+ ["ParticipantIdentity/set", { accountId, onSuccessSetIsDefault: id }, "0"],
+ ], this.calendarUsing());
+ const [method, result] = response.methodResponses?.[0] ?? [];
+ if (method !== "ParticipantIdentity/set") {
+ throw new Error((result as { description?: string })?.description || "Failed to set the default participant identity");
+ }
+ }
+
// ── Principal/getAvailability (free/busy) ──────────────────────
static readonly PRINCIPALS_AVAILABILITY_CAPABILITY = 'urn:ietf:params:jmap:principals:availability';
diff --git a/lib/jmap/types.ts b/lib/jmap/types.ts
index 303904d2a..0fffc6a22 100644
--- a/lib/jmap/types.ts
+++ b/lib/jmap/types.ts
@@ -805,11 +805,17 @@ export interface CalendarTask {
relatedTo: Record | null;
}
+/**
+ * A ParticipantIdentity (draft-ietf-jmap-calendars §6): one of the calendar
+ * addresses the user may act as when organising or replying to events. The
+ * server derives them from the account's addresses; the default one is what
+ * new invitations are sent from.
+ */
export interface CalendarParticipantIdentity {
id: string;
name: string;
- scheduleId: string;
- sendTo: Record;
+ /** `mailto:` URI of the scheduling address. */
+ calendarAddress: string;
isDefault: boolean;
}
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 440ba87d8..3967b232e 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "تأخير ثانية واحدة",
"hover_preview_delay_2s": "تأخير ثانيتين",
"hover_preview_off": "معطّل",
+ "organizer_identity": "تنظيم الدعوات باسم",
+ "organizer_identity_desc": "عنوان التقويم المستخدم كمنظّم عند دعوة الآخرين إلى الأحداث.",
+ "organizer_identity_failed": "فشل تغيير عنوان المنظّم",
"show_birthday_calendar": "تقويم أعياد ميلاد جهات الاتصال",
"show_birthday_calendar_desc": "إظهار تقويم افتراضي بأعياد ميلاد جهات اتصالك"
},
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 40b80aa8e..03fe45a58 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Retard d'1 segon",
"hover_preview_delay_2s": "Retard de 2 segons",
"hover_preview_off": "Desactivada",
+ "organizer_identity": "Organitza les invitacions com a",
+ "organizer_identity_desc": "L'adreça de calendari usada com a organitzador quan convides gent als esdeveniments.",
+ "organizer_identity_failed": "No s'ha pogut canviar l'adreça de l'organitzador",
"show_birthday_calendar": "Calendari d'aniversaris dels contactes",
"show_birthday_calendar_desc": "Mostra un calendari virtual amb els aniversaris dels vostres contactes"
},
diff --git a/locales/cs/common.json b/locales/cs/common.json
index b413a9aac..eb6b52a1c 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Zpoždění 1 sekunda",
"hover_preview_delay_2s": "Zpoždění 2 sekundy",
"hover_preview_off": "Vypnuto",
+ "organizer_identity": "Organizovat pozvánky jako",
+ "organizer_identity_desc": "Adresa kalendáře použitá jako organizátor, když zvete lidi na události.",
+ "organizer_identity_failed": "Nepodařilo se změnit adresu organizátora",
"show_birthday_calendar": "Kalendář narozenin",
"show_birthday_calendar_desc": "Zobrazovat virtuální kalendář s narozeninami z vašich kontaktů"
},
diff --git a/locales/da/common.json b/locales/da/common.json
index 843579dbc..de84dbadb 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 sekunds forsinkelse",
"hover_preview_delay_2s": "2 sekunders forsinkelse",
"hover_preview_off": "Deaktiveret",
+ "organizer_identity": "Organisér invitationer som",
+ "organizer_identity_desc": "Kalenderadressen, der bruges som arrangør, når du inviterer folk til begivenheder.",
+ "organizer_identity_failed": "Kunne ikke ændre arrangøradressen",
"show_birthday_calendar": "Kontaktfødselsdagskalender",
"show_birthday_calendar_desc": "Vis en virtuel kalender med fødselsdage fra dine kontakter"
},
diff --git a/locales/de/common.json b/locales/de/common.json
index d268b77df..e92376919 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 Sekunde Verzögerung",
"hover_preview_delay_2s": "2 Sekunden Verzögerung",
"hover_preview_off": "Deaktiviert",
+ "organizer_identity": "Einladungen organisieren als",
+ "organizer_identity_desc": "Die Kalenderadresse, die als Organisator verwendet wird, wenn du Personen zu Terminen einlädst.",
+ "organizer_identity_failed": "Organisatoradresse konnte nicht geändert werden",
"show_birthday_calendar": "Geburtstags-Kalender",
"show_birthday_calendar_desc": "Zeigt einen virtuellen Kalender mit Geburtstagen aus deinen Kontakten"
},
diff --git a/locales/en/common.json b/locales/en/common.json
index 3d20ebb93..922361bb3 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1-second delay",
"hover_preview_delay_2s": "2-second delay",
"hover_preview_off": "Disabled",
+ "organizer_identity": "Organize invitations as",
+ "organizer_identity_desc": "The calendar address used as organizer when you invite people to events.",
+ "organizer_identity_failed": "Failed to change the organizer address",
"show_birthday_calendar": "Contact birthday calendar",
"show_birthday_calendar_desc": "Show a virtual calendar with birthdays from your contacts"
},
diff --git a/locales/es/common.json b/locales/es/common.json
index 8f6e03916..0e2db5586 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Retraso de 1 segundo",
"hover_preview_delay_2s": "Retraso de 2 segundos",
"hover_preview_off": "Desactivado",
+ "organizer_identity": "Organizar invitaciones como",
+ "organizer_identity_desc": "La dirección de calendario usada como organizador al invitar a personas a eventos.",
+ "organizer_identity_failed": "No se pudo cambiar la dirección del organizador",
"show_birthday_calendar": "Calendario de cumpleaños",
"show_birthday_calendar_desc": "Mostrar un calendario virtual con los cumpleaños de tus contactos"
},
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 1cc4639ff..f9d18474e 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "تأخیر ۱ ثانیه",
"hover_preview_delay_2s": "تأخیر ۲ ثانیه",
"hover_preview_off": "غیرفعال",
+ "organizer_identity": "سازماندهی دعوتها بهعنوان",
+ "organizer_identity_desc": "آدرس تقویمی که هنگام دعوت افراد به رویدادها بهعنوان برگزارکننده استفاده میشود.",
+ "organizer_identity_failed": "تغییر آدرس برگزارکننده ناموفق بود",
"show_birthday_calendar": "تقویم تولد مخاطبین",
"show_birthday_calendar_desc": "نمایش تقویم مجازی با تولدهای مخاطبین"
},
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 63c42371e..aa88abc4e 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Délai de 1 seconde",
"hover_preview_delay_2s": "Délai de 2 secondes",
"hover_preview_off": "Désactivé",
+ "organizer_identity": "Organiser les invitations en tant que",
+ "organizer_identity_desc": "L'adresse d'agenda utilisée comme organisateur lorsque vous invitez des personnes à des événements.",
+ "organizer_identity_failed": "Impossible de modifier l'adresse de l'organisateur",
"show_birthday_calendar": "Calendrier des anniversaires",
"show_birthday_calendar_desc": "Afficher un calendrier virtuel avec les anniversaires de vos contacts"
},
diff --git a/locales/he/common.json b/locales/he/common.json
index 7fbfef3f3..8ea8a1521 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -2852,6 +2852,9 @@
"hover_preview_delay_1s": "עיכוב של שנייה",
"hover_preview_delay_2s": "עיכוב של 2 שניות",
"hover_preview_off": "מושבת",
+ "organizer_identity": "ארגון הזמנות בתור",
+ "organizer_identity_desc": "כתובת היומן שתשמש כמארגן כשמזמינים אנשים לאירועים.",
+ "organizer_identity_failed": "שינוי כתובת המארגן נכשל",
"show_birthday_calendar": "צור קשר עם יומן יום הולדת",
"show_birthday_calendar_desc": "הצג לוח שנה וירטואלי עם ימי הולדת מאנשי הקשר שלך"
},
diff --git a/locales/hu/common.json b/locales/hu/common.json
index 582eeb6d3..4597300e0 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 másodperc késleltetés",
"hover_preview_delay_2s": "2 másodperc késleltetés",
"hover_preview_off": "Letiltva",
+ "organizer_identity": "Meghívók szervezése mint",
+ "organizer_identity_desc": "A szervezőként használt naptárcím, amikor embereket hívsz meg eseményekre.",
+ "organizer_identity_failed": "Nem sikerült módosítani a szervező címét",
"show_birthday_calendar": "Névjegy születésnap naptár",
"show_birthday_calendar_desc": "Virtuális naptár mutatása a névjegyeid születésnapjaival"
},
diff --git a/locales/it/common.json b/locales/it/common.json
index 84e174a39..af76ccb2b 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Ritardo di 1 secondo",
"hover_preview_delay_2s": "Ritardo di 2 secondi",
"hover_preview_off": "Disabilitato",
+ "organizer_identity": "Organizza gli inviti come",
+ "organizer_identity_desc": "L'indirizzo del calendario usato come organizzatore quando inviti persone agli eventi.",
+ "organizer_identity_failed": "Impossibile cambiare l'indirizzo dell'organizzatore",
"show_birthday_calendar": "Calendario compleanni",
"show_birthday_calendar_desc": "Mostra un calendario virtuale con i compleanni dei tuoi contatti"
},
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 08852a66b..36fc451fb 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1秒遅延",
"hover_preview_delay_2s": "2秒遅延",
"hover_preview_off": "無効",
+ "organizer_identity": "招待の主催者アドレス",
+ "organizer_identity_desc": "イベントに人を招待する際に主催者として使用するカレンダーアドレスです。",
+ "organizer_identity_failed": "主催者アドレスを変更できませんでした",
"show_birthday_calendar": "誕生日カレンダー",
"show_birthday_calendar_desc": "連絡先の誕生日を表示する仮想カレンダーを表示します"
},
diff --git a/locales/ko/common.json b/locales/ko/common.json
index add96d9a5..7e0acda25 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1초 지연",
"hover_preview_delay_2s": "2초 지연",
"hover_preview_off": "사용 안 함",
+ "organizer_identity": "초대 주최자 주소",
+ "organizer_identity_desc": "이벤트에 사람들을 초대할 때 주최자로 사용되는 캘린더 주소입니다.",
+ "organizer_identity_failed": "주최자 주소를 변경하지 못했습니다",
"show_birthday_calendar": "연락처 생일 캘린더",
"show_birthday_calendar_desc": "연락처에 등록된 생일을 가상 캘린더로 보여줘요"
},
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 7297ca159..cd6670551 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -2924,6 +2924,9 @@
"hover_preview_delay_1s": "1 sekundes aizture",
"hover_preview_delay_2s": "2 sekunžu aizture",
"hover_preview_off": "Izslēgts",
+ "organizer_identity": "Organizēt ielūgumus kā",
+ "organizer_identity_desc": "Kalendāra adrese, ko izmanto kā organizatoru, uzaicinot cilvēkus uz notikumiem.",
+ "organizer_identity_failed": "Neizdevās mainīt organizatora adresi",
"show_birthday_calendar": "Kontaktu dzimšanas dienu kalendārs",
"show_birthday_calendar_desc": "Rādīt virtuālu kalendāru ar kontaktu dzimšanas dienām"
},
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 4fe4b23d5..da22095c1 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 секундын саатал",
"hover_preview_delay_2s": "2 секундын саатал",
"hover_preview_off": "Хөгжлийн бэрхшээлтэй",
+ "organizer_identity": "Урилгыг зохион байгуулагч",
+ "organizer_identity_desc": "Хүмүүсийг арга хэмжээнд урихад зохион байгуулагчаар ашиглах хуанлийн хаяг.",
+ "organizer_identity_failed": "Зохион байгуулагчийн хаягийг өөрчилж чадсангүй",
"show_birthday_calendar": "Холбоо барих төрсөн өдрийн хуанли",
"show_birthday_calendar_desc": "Харилцагчдаас төрсөн өдөр бүхий виртуал хуанли харуулах"
},
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 5a10901b5..1a7f4e663 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -2923,6 +2923,9 @@
"hover_preview_delay_1s": "Forsinkelse på 1 sekund",
"hover_preview_delay_2s": "Forsinkelse på 2 sekunder",
"hover_preview_off": "Deaktivert",
+ "organizer_identity": "Organiser invitasjoner som",
+ "organizer_identity_desc": "Kalenderadressen som brukes som arrangør når du inviterer folk til hendelser.",
+ "organizer_identity_failed": "Kunne ikke endre arrangøradressen",
"show_birthday_calendar": "Fødselsdager fra kontakter",
"show_birthday_calendar_desc": "Vis en virtuell kalender med bursdager fra kontaktene dine"
},
diff --git a/locales/nl/common.json b/locales/nl/common.json
index 9647edd92..edb51f6d4 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 seconde vertraging",
"hover_preview_delay_2s": "2 seconden vertraging",
"hover_preview_off": "Uitgeschakeld",
+ "organizer_identity": "Uitnodigingen organiseren als",
+ "organizer_identity_desc": "Het agenda-adres dat als organisator wordt gebruikt wanneer je mensen uitnodigt voor afspraken.",
+ "organizer_identity_failed": "Kon het organisatoradres niet wijzigen",
"show_birthday_calendar": "Verjaardagskalender",
"show_birthday_calendar_desc": "Toon een virtuele kalender met verjaardagen van je contacten"
},
diff --git a/locales/pl/common.json b/locales/pl/common.json
index 7f2d34c29..31fb66bd7 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Opóźnienie 1 sekundy",
"hover_preview_delay_2s": "Opóźnienie 2 sekund",
"hover_preview_off": "Wyłączono",
+ "organizer_identity": "Organizuj zaproszenia jako",
+ "organizer_identity_desc": "Adres kalendarza używany jako organizator, gdy zapraszasz osoby na wydarzenia.",
+ "organizer_identity_failed": "Nie udało się zmienić adresu organizatora",
"show_birthday_calendar": "Kalendarz urodzin kontaktów",
"show_birthday_calendar_desc": "Pokaż wirtualny kalendarz z urodzinami z kontaktów"
},
diff --git a/locales/pt/common.json b/locales/pt/common.json
index e62785e2f..7a4e16e39 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Atraso de 1 segundo",
"hover_preview_delay_2s": "Atraso de 2 segundos",
"hover_preview_off": "Desativado",
+ "organizer_identity": "Organizar convites como",
+ "organizer_identity_desc": "O endereço de calendário usado como organizador quando convida pessoas para eventos.",
+ "organizer_identity_failed": "Não foi possível alterar o endereço do organizador",
"show_birthday_calendar": "Calendário de aniversários",
"show_birthday_calendar_desc": "Mostrar um calendário virtual com os aniversários dos seus contatos"
},
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 8d51524b2..b35474a85 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Întârziere de 1 secundă",
"hover_preview_delay_2s": "Întârziere de 2 secunde",
"hover_preview_off": "Dezactivat",
+ "organizer_identity": "Organizează invitațiile ca",
+ "organizer_identity_desc": "Adresa de calendar folosită ca organizator când inviți persoane la evenimente.",
+ "organizer_identity_failed": "Nu s-a putut schimba adresa organizatorului",
"show_birthday_calendar": "Calendarul zilelor de naștere ale contactelor",
"show_birthday_calendar_desc": "Afișați un calendar virtual cu zilele de naștere ale contactelor dvs."
},
diff --git a/locales/ru/common.json b/locales/ru/common.json
index 1e3beb0cf..f33d0a38f 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Задержка 1 секунда",
"hover_preview_delay_2s": "Задержка 2 секунды",
"hover_preview_off": "Отключено",
+ "organizer_identity": "Организовывать приглашения как",
+ "organizer_identity_desc": "Адрес календаря, используемый как организатор при приглашении людей на события.",
+ "organizer_identity_failed": "Не удалось изменить адрес организатора",
"show_birthday_calendar": "Календарь дней рождения",
"show_birthday_calendar_desc": "Показать виртуальный календарь с днями рождения из ваших контактов"
},
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 39d967ba7..c09af2409 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "Oneskorenie 1 sekunda",
"hover_preview_delay_2s": "Oneskorenie 2 sekundy",
"hover_preview_off": "Vypnuté",
+ "organizer_identity": "Organizovať pozvánky ako",
+ "organizer_identity_desc": "Adresa kalendára použitá ako organizátor, keď pozývate ľudí na udalosti.",
+ "organizer_identity_failed": "Nepodarilo sa zmeniť adresu organizátora",
"show_birthday_calendar": "Kalendár narodenín",
"show_birthday_calendar_desc": "Zobrazovať virtuálny kalendár s narodeninami z vašich kontaktov"
},
diff --git a/locales/tr/common.json b/locales/tr/common.json
index c43f848ba..8399abf59 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1 saniyelik gecikme",
"hover_preview_delay_2s": "2 saniyelik gecikme",
"hover_preview_off": "Devre dışı",
+ "organizer_identity": "Davetleri şu kimlikle düzenle",
+ "organizer_identity_desc": "Kişileri etkinliklere davet ederken düzenleyici olarak kullanılan takvim adresi.",
+ "organizer_identity_failed": "Düzenleyici adresi değiştirilemedi",
"show_birthday_calendar": "Kişi doğum günü takvimi",
"show_birthday_calendar_desc": "Kişilerinizdeki doğum günlerini gösteren sanal takvim göster"
},
diff --git a/locales/uk/common.json b/locales/uk/common.json
index b845b40e4..6845ddd39 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1-секундна затримка",
"hover_preview_delay_2s": "2-секундна затримка",
"hover_preview_off": "Вимкнено",
+ "organizer_identity": "Організовувати запрошення як",
+ "organizer_identity_desc": "Адреса календаря, що використовується як організатор, коли ви запрошуєте людей на події.",
+ "organizer_identity_failed": "Не вдалося змінити адресу організатора",
"show_birthday_calendar": "Контактний календар днів народження",
"show_birthday_calendar_desc": "Показати віртуальний календар із днями народження ваших контактів"
},
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index c0c05499d..7cad4d4f5 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -2918,6 +2918,9 @@
"hover_preview_delay_1s": "延遲 1 秒",
"hover_preview_delay_2s": "延遲 2 秒",
"hover_preview_off": "停用",
+ "organizer_identity": "以此身分發送邀請",
+ "organizer_identity_desc": "邀請他人參加活動時用作主辦者的行事曆地址。",
+ "organizer_identity_failed": "無法變更主辦者地址",
"show_birthday_calendar": "聯絡人生日行事曆",
"show_birthday_calendar_desc": "顯示包含聯絡人生日的虛擬行事曆"
},
diff --git a/locales/zh/common.json b/locales/zh/common.json
index a4c016b70..0bbb50bcf 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -2925,6 +2925,9 @@
"hover_preview_delay_1s": "1秒延迟",
"hover_preview_delay_2s": "2秒延迟",
"hover_preview_off": "关闭",
+ "organizer_identity": "以此身份组织邀请",
+ "organizer_identity_desc": "邀请他人参加活动时用作组织者的日历地址。",
+ "organizer_identity_failed": "无法更改组织者地址",
"show_birthday_calendar": "联系人生日日历",
"show_birthday_calendar_desc": "显示包含联系人生日的虚拟日历"
},
diff --git a/stores/calendar-store.ts b/stores/calendar-store.ts
index 96c9b363a..5599f4edc 100644
--- a/stores/calendar-store.ts
+++ b/stores/calendar-store.ts
@@ -1,7 +1,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { IJMAPClient } from '@/lib/jmap/client-interface';
-import type { Calendar, CalendarEvent, CalendarParticipant, CalendarRights, CreateCalendarOptions } from '@/lib/jmap/types';
+import type { Calendar, CalendarEvent, CalendarParticipant, CalendarParticipantIdentity, CalendarRights, CreateCalendarOptions } from '@/lib/jmap/types';
import { debug } from '@/lib/debug';
import { normalizeAllDayDuration } from '@/lib/calendar-utils';
import { displayNow } from '@/lib/timezone';
@@ -427,6 +427,11 @@ interface CalendarStore {
supportsCalendar: boolean;
error: string | null;
dateRange: { start: string; end: string } | null;
+ // The user's ParticipantIdentity list (calendar addresses to organise
+ // as); the default one is used as organizer of new invitations.
+ participantIdentities: CalendarParticipantIdentity[];
+ fetchParticipantIdentities: (client: IJMAPClient) => Promise;
+ setDefaultParticipantIdentity: (client: IJMAPClient, id: string) => Promise;
setSupported: (supported: boolean) => void;
fetchCalendars: (client: IJMAPClient) => Promise;
@@ -469,6 +474,7 @@ const initialState = {
isLoading: false,
isLoadingEvents: false,
supportsCalendar: false,
+ participantIdentities: [],
error: null as string | null,
dateRange: null as { start: string; end: string } | null,
icalSubscriptions: [] as ICalSubscription[],
@@ -486,8 +492,30 @@ export const useCalendarStore = create()(
setSupported: (supported) => set({ supportsCalendar: supported }),
+ fetchParticipantIdentities: async (client) => {
+ if (!client.getParticipantIdentities) return;
+ try {
+ set({ participantIdentities: await client.getParticipantIdentities() });
+ } catch (error) {
+ // Servers without ParticipantIdentity (pre-draft) reject the method;
+ // the organizer then falls back to the login addresses.
+ debug.log('calendar', 'ParticipantIdentity/get unavailable:', error);
+ }
+ },
+
+ setDefaultParticipantIdentity: async (client, id) => {
+ if (!client.setDefaultParticipantIdentity) return;
+ await client.setDefaultParticipantIdentity(id);
+ set((state) => ({
+ participantIdentities: state.participantIdentities.map((i) => ({ ...i, isDefault: i.id === id })),
+ }));
+ },
+
fetchCalendars: async (client) => {
set({ isLoading: true, error: null });
+ // Identities are independent of the calendar list; load them
+ // alongside without blocking the grid.
+ void get().fetchParticipantIdentities(client);
try {
const calendars = await client.getAllCalendars();
const { selectedCalendarIds } = get();