Files
webmail/hooks/use-share-recipient-labels.ts
T
lucletoffeandClaude Opus 5.5 8cf6783cdb feat(calendar): say whom a calendar is shared with
The sidebar showed the same people icon for any calendar with a share,
labelled "Share calendar", and the settings badge only gave a count, so
there was no way to tell from either who could see the calendar. A share
granted to a service account read like a share with other people.

Both now carry a hint listing the recipients ("Shared with
bot@example.com (Booking robot)"), resolved from the principals the
server lists. Until every recipient is known the hint falls back to the
count. Principals are fetched only when a calendar on screen is shared.

The per-client principal cache of the attendee availability check moves
to lib/jmap/principal-cache.ts so both use it. It no longer keeps an
empty or failed answer: the next caller asks again instead of seeing
no principals for the rest of the session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 22:19:53 +02:00

45 lines
1.7 KiB
TypeScript

import { useEffect, useState } from 'react';
import type { IJMAPClient } from '@/lib/jmap/client-interface';
import type { Principal } from '@/lib/jmap/types';
import { cachedPrincipals } from '@/lib/jmap/principal-cache';
/** How a share recipient is named: its address, then its description. */
export function principalLabel(principal: Principal): string {
const address = principal.email || principal.name || principal.id;
return principal.description && principal.description !== address
? `${address} (${principal.description})`
: address;
}
/**
* The names of the principals in `shareWith`, or null while any of them is
* unknown (principals not loaded yet, or not listed by the server).
*/
export function shareRecipientNames(
shareWith: Record<string, unknown> | null | undefined,
labels: ReadonlyMap<string, string>,
): string[] | null {
const names = Object.keys(shareWith ?? {}).map((id) => labels.get(id));
return names.every((name): name is string => !!name) ? names : null;
}
/**
* Labels of the server's principals by id, so a shared collection can say
* whom it is shared with. Nothing is fetched until `enabled`, i.e. until
* something on screen is shared.
*/
export function useShareRecipientLabels(client: IJMAPClient | null | undefined, enabled: boolean): ReadonlyMap<string, string> {
const [labels, setLabels] = useState<ReadonlyMap<string, string>>(() => new Map());
useEffect(() => {
if (!client || !enabled) return;
let cancelled = false;
void cachedPrincipals(client).then((principals) => {
if (!cancelled) setLabels(new Map(principals.map((p) => [p.id, principalLabel(p)])));
});
return () => { cancelled = true; };
}, [client, enabled]);
return labels;
}