// Builds the default reply/forward quote header and runs it through the // emailHooks.onBuildQuoteHeader transform so plugins can replace it (e.g. // with an Outlook-style From/Sent/To/Cc/Subject block). // // This module is the single source of truth for the default header strings - // the composer keeps the same defaults inline as a fallback, but production // flow goes through here. import { formatDateTime } from "@/lib/utils"; import { emailHooks } from "@/lib/plugin-hooks"; import { escapeHtml } from "@/lib/email-sanitization"; import type { QuoteHeader, QuoteHeaderContext } from "@/lib/plugin-types"; // Localized label set the caller passes in. Labels live on the client where // useTranslations is available; this module stays framework-agnostic. export interface QuoteHeaderLabels { /** ICU-formatted reply line, e.g. "On {date}, {from} wrote:" with placeholders already substituted. */ formatReplyLine: (vars: { date: string; from: string }) => string; forwardedSeparator: string; fromLabel: string; dateLabel: string; subjectLabel: string; /** The forward header's To and Cc lines; left out when not given. */ toLabel?: string; ccLabel?: string; } type HeaderAddress = { email?: string; name?: string }; /** "Name
, address", as the forward header's From line writes one. */ function formatAddressList(list: HeaderAddress[] | undefined): string { return (list ?? []) .filter((r) => r.email) .map((r) => (r.name && r.name !== r.email ? `${r.name} <${r.email}>` : r.email as string)) .join(", "); } /** * The To and Cc lines of a forward header, as Gmail and Outlook write them: * one per list that has anyone on it, for a label the caller gave. Values * are plain text; escape them for HTML. */ export function forwardRecipientLines( email: { to?: HeaderAddress[]; cc?: HeaderAddress[] }, labels: { toLabel?: string; ccLabel?: string }, ): { label: string; value: string }[] { const lines: { label: string; value: string }[] = []; const to = formatAddressList(email.to); const cc = formatAddressList(email.cc); if (labels.toLabel && to) lines.push({ label: labels.toLabel, value: to }); if (labels.ccLabel && cc) lines.push({ label: labels.ccLabel, value: cc }); return lines; } interface BuildArgs { mode: "reply" | "replyAll" | "forward"; email: { from?: { email?: string; name?: string }[]; to?: { email?: string; name?: string }[]; cc?: { email?: string; name?: string }[]; subject?: string; receivedAt?: string; }; newTo: string[]; newCc: string[]; locale: string; timeFormat: "12h" | "24h"; unknownLabel: string; /** * Localized labels. Optional for backward compatibility; falls back to * English (matching the original hardcoded behaviour) when not supplied. */ labels?: QuoteHeaderLabels; } const DEFAULT_LABELS: QuoteHeaderLabels = { formatReplyLine: ({ date, from }) => `On ${date}, ${from} wrote:`, forwardedSeparator: "---------- Forwarded message ----------", fromLabel: "From", dateLabel: "Date", subjectLabel: "Subject", toLabel: "To", ccLabel: "Cc", }; function defaultHeader(args: BuildArgs): QuoteHeader { const { mode, email, timeFormat, unknownLabel } = args; const labels = args.labels ?? DEFAULT_LABELS; const date = email.receivedAt ? formatDateTime(email.receivedAt, timeFormat, { weekday: "short", year: "numeric", month: "short", day: "numeric", }) : ""; const from = email.from?.[0]; // Both the forward "From:" line and the reply "On … wrote:" line show the // full sender incl. address ("Name