diff --git a/frontend/src/components/list/MessageList.tsx b/frontend/src/components/list/MessageList.tsx index ff1e0fb..83a9061 100644 --- a/frontend/src/components/list/MessageList.tsx +++ b/frontend/src/components/list/MessageList.tsx @@ -33,6 +33,28 @@ function initials(p: Address | undefined): string { return name.slice(0, 2).toUpperCase(); } +// Past this many characters the sender column switches to first names only. +const SENDERS_FULL_MAX = 25; + +// The sender column's text: full names joined, or — when that would run past +// SENDERS_FULL_MAX — each sender's first word ("Alice Smith" -> "Alice", a bare +// address -> its local part). The user's own accounts are always `me`. +export function senderNames( + senders: Address[], + isMe: (a: Address) => boolean, + me: string, +): string { + const full = senders.map((a) => (isMe(a) ? me : a.name || a.email)).join(", "); + if (full.length <= SENDERS_FULL_MAX) return full; + return senders + .map((a) => { + if (isMe(a)) return me; + const name = a.name.trim(); + return name ? name.split(/\s+/)[0] : a.email.split("@")[0]; + }) + .join(", "); +} + // Monocolor paperclip (inherits currentColor) — replaces the old emoji. function Paperclip() { return ( @@ -137,9 +159,7 @@ export function MessageList({ // with the user's own accounts shown as "me" — so a thread you started // and someone answered reads "me, Alice". Recipients are not listed. const senders = thread.senders.length ? thread.senders : thread.participants.slice(0, 1); - const names = senders.length - ? senders.map((a) => (isMe(a) ? t("mail.me") : a.name || a.email)).join(", ") - : "(no one)"; + const names = senders.length ? senderNames(senders, isMe, t("mail.me")) : "(no one)"; // The avatar is the other side of the conversation where there is one. const primary = senders.find((a) => !isMe(a)) ?? senders[0]; // The badge still counts everyone involved — the name is narrowed, the diff --git a/frontend/src/test/senderNames.test.ts b/frontend/src/test/senderNames.test.ts new file mode 100644 index 0000000..4d11ab5 --- /dev/null +++ b/frontend/src/test/senderNames.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { senderNames } from "../components/list/MessageList"; +import type { Address } from "../lib/types"; + +const me = (a: Address) => a.email === "me@example.com"; +const addr = (name: string, email: string): Address => ({ name, email }); + +describe("senderNames", () => { + it("keeps full names while they fit in 25 characters", () => { + const out = senderNames( + [addr("Alice Smith", "alice@x.com"), addr("", "me@example.com")], + me, + "me", + ); + expect(out).toBe("Alice Smith, me"); + }); + + it("falls back to first words past 25 characters, keeping me", () => { + const out = senderNames( + [ + addr("Alice Smith", "alice@x.com"), + addr("Lukas R", "me@example.com"), + addr("Bob van der Berg", "bob@x.com"), + addr("", "carol.jones@y.com"), + ], + me, + "me", + ); + expect(out).toBe("Alice, me, Bob, carol.jones"); + }); +});