Save contact photos inline, and load cards so avatars show

Stalwart refuses a blobId in a card's media ("blobIds in media is not
supported"), so adding or changing a photo always failed. The editor now
saves the photo as a data: URI, which Stalwart accepts and returns
unchanged, and leaves the card's other media as it was. Checked live on
0.16.22; the mock now refuses a blobId the same way.

Avatars in the mail list come from the address book's cards, and nothing
loaded those at sign-in, so a photo showed only after Contacts had been
opened. The cards now load in the background at start, the avatar uses
whatever cards are held, and a shared card's photo is fetched from the
account it belongs to.

Fixes #376.
This commit is contained in:
jcoffey-dev committed 2026-09-16 11:27:47 -07:00
1 parent aa9bf1b9b2
commit d38dee7eb9
10 files changed
+150 -18

No files matched your search

+5 -2
View File
@@ -8,10 +8,13 @@ import { t } from "@/lib/i18n";
export function Avatar({ who, size, className }: { who: EmailAddress | { name?: string | null; email?: string } | string | null | undefined; size?: "sm" | "lg" | "xl"; className?: string }) {
const email = typeof who === "string" ? who : (who?.email ?? "");
const name = typeof who === "string" ? who : (who?.name ?? who?.email ?? "");
// Whatever cards are held count, the reader's own or a shared book's; the
// photo is fetched from the account the card belongs to.
const photo = useContacts((s) => {
if (!email || !s.loaded) return null;
if (!email) return null;
const c = s.lookupByEmail(email);
return c && s.accountId ? contactPhoto(c, s.accountId) : null;
const account = c ? (s.accountOfCard(c.id) ?? s.accountId) : null;
return c && account ? contactPhoto(c, account) : null;
});
return (
<span className={`avatar ${size ?? ""} ${className ?? ""}`} style={{ background: photo ? "transparent" : avatarColor(email || name) }} aria-hidden="true">