mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
feat: organize invitations as the default ParticipantIdentity
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
{client && participantIdentities.length > 1 && (
|
||||
<SettingItem
|
||||
label={t('organizer_identity')}
|
||||
description={t('organizer_identity_desc')}
|
||||
>
|
||||
<Select
|
||||
value={defaultIdentityId}
|
||||
onChange={(value) => {
|
||||
setDefaultParticipantIdentity(client, value).catch((err) => {
|
||||
toast.error(err instanceof Error ? err.message : t('organizer_identity_failed'));
|
||||
});
|
||||
}}
|
||||
options={participantIdentities.map((i) => {
|
||||
const address = i.calendarAddress.replace(/^mailto:/i, '');
|
||||
return { value: i.id, label: i.name && i.name !== address ? `${i.name} <${address}>` : address };
|
||||
})}
|
||||
/>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
label={t('show_time_in_month_view')}
|
||||
description={t('show_time_in_month_view_desc')}
|
||||
|
||||
@@ -188,6 +188,32 @@ describe('JMAP client extensions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
@@ -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<Mailbox>;
|
||||
updateMailbox(mailboxId: string, changes: { name?: string; parentId?: string | null; role?: string | null; sortOrder?: number }, accountId?: string): Promise<void>;
|
||||
deleteMailbox(mailboxId: string, accountId?: string): Promise<void>;
|
||||
// ParticipantIdentity (draft-ietf-jmap-calendars §6): the addresses the
|
||||
// user organises events as. Optional.
|
||||
getParticipantIdentities?(): Promise<CalendarParticipantIdentity[]>;
|
||||
setDefaultParticipantIdentity?(id: string): Promise<void>;
|
||||
// Free/busy (Principal/getAvailability, principals:availability). Optional.
|
||||
supportsAvailability?(): boolean;
|
||||
getPrincipalAvailability?(principalId: string, utcStart: Date, utcEnd: Date): Promise<BusyPeriod[]>;
|
||||
|
||||
+37
-1
@@ -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<CalendarParticipantIdentity[]> {
|
||||
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<Partial<CalendarParticipantIdentity> & { 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<void> {
|
||||
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';
|
||||
|
||||
+8
-2
@@ -805,11 +805,17 @@ export interface CalendarTask {
|
||||
relatedTo: Record<string, CalendarRelation> | 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<string, string>;
|
||||
/** `mailto:` URI of the scheduling address. */
|
||||
calendarAddress: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -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": "إظهار تقويم افتراضي بأعياد ميلاد جهات اتصالك"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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ů"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": "نمایش تقویم مجازی با تولدهای مخاطبین"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": "הצג לוח שנה וירטואלי עם ימי הולדת מאנשי הקשר שלך"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": "連絡先の誕生日を表示する仮想カレンダーを表示します"
|
||||
},
|
||||
|
||||
@@ -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": "연락처에 등록된 생일을 가상 캘린더로 보여줘요"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": "Харилцагчдаас төрсөн өдөр бүхий виртуал хуанли харуулах"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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": "Показать виртуальный календарь с днями рождения из ваших контактов"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": "Показати віртуальний календар із днями народження ваших контактів"
|
||||
},
|
||||
|
||||
@@ -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": "顯示包含聯絡人生日的虛擬行事曆"
|
||||
},
|
||||
|
||||
@@ -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": "显示包含联系人生日的虚拟日历"
|
||||
},
|
||||
|
||||
@@ -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<void>;
|
||||
setDefaultParticipantIdentity: (client: IJMAPClient, id: string) => Promise<void>;
|
||||
|
||||
setSupported: (supported: boolean) => void;
|
||||
fetchCalendars: (client: IJMAPClient) => Promise<void>;
|
||||
@@ -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<CalendarStore>()(
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user