176 lines
4.7 KiB
TypeScript
176 lines
4.7 KiB
TypeScript
import type { Email } from "@/lib/email-data"
|
||
import { formatMailDetailDate } from "@/lib/mail-date"
|
||
import { cleanSenderName } from "@/lib/sender-display"
|
||
|
||
function escapeHtml(s: string): string {
|
||
return s
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
}
|
||
|
||
type PrintSegment = {
|
||
fromName: string
|
||
fromEmail: string
|
||
date: string
|
||
bodyHtml: string
|
||
}
|
||
|
||
function buildSegments(email: Email): PrintSegment[] {
|
||
const conv = email.conversation ?? []
|
||
const segments: PrintSegment[] = []
|
||
|
||
for (const msg of conv) {
|
||
segments.push({
|
||
fromName: cleanSenderName(msg.sender),
|
||
fromEmail: msg.senderEmail,
|
||
date: formatMailDetailDate(msg.date),
|
||
bodyHtml: msg.body,
|
||
})
|
||
}
|
||
|
||
const mainName = cleanSenderName(email.sender)
|
||
const mainEmail =
|
||
email.senderEmail ||
|
||
`${mainName.toLowerCase().replace(/\s+/g, ".")}@example.com`
|
||
|
||
segments.push({
|
||
fromName: mainName,
|
||
fromEmail: mainEmail,
|
||
date: formatMailDetailDate(email.date),
|
||
bodyHtml:
|
||
email.body ??
|
||
`<p style="color:#5f6368;margin:0;">${escapeHtml(email.preview)}</p>`,
|
||
})
|
||
|
||
return segments
|
||
}
|
||
|
||
function buildPrintHtml(email: Email): string {
|
||
const subject = escapeHtml(email.subject)
|
||
const segments = buildSegments(email)
|
||
|
||
const blocks = segments
|
||
.map(
|
||
(seg) => `
|
||
<article class="msg">
|
||
<header class="msg-hdr">
|
||
<div class="row"><span class="lbl">De</span><span class="val">${escapeHtml(seg.fromName)} <${escapeHtml(seg.fromEmail)}></span></div>
|
||
<div class="row"><span class="lbl">À</span><span class="val">moi</span></div>
|
||
<div class="row"><span class="lbl">Date</span><span class="val">${escapeHtml(seg.date)}</span></div>
|
||
</header>
|
||
<div class="msg-body">${seg.bodyHtml}</div>
|
||
</article>`
|
||
)
|
||
.join("")
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>${subject}</title>
|
||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src https: data:;">
|
||
<style>
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
padding: 20px 24px 32px;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: #202124;
|
||
background: #fff;
|
||
}
|
||
.thread-subject {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 20px;
|
||
padding-bottom: 14px;
|
||
border-bottom: 1px solid #dadce0;
|
||
}
|
||
.msg {
|
||
margin: 0 0 28px;
|
||
padding: 0 0 22px;
|
||
border-bottom: 1px solid #eceff1;
|
||
page-break-inside: avoid;
|
||
}
|
||
.msg:last-of-type { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
|
||
.msg-hdr {
|
||
font-size: 12px;
|
||
color: #5f6368;
|
||
margin-bottom: 14px;
|
||
}
|
||
.msg-hdr .row {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 4px;
|
||
align-items: baseline;
|
||
}
|
||
.msg-hdr .lbl {
|
||
flex: 0 0 52px;
|
||
font-weight: 600;
|
||
color: #3c4043;
|
||
}
|
||
.msg-hdr .val { flex: 1; min-width: 0; word-break: break-word; }
|
||
.msg-body {
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
color: #202124;
|
||
}
|
||
.msg-body a { color: #1a73e8; }
|
||
.msg-body img { max-width: 100%; height: auto; }
|
||
.msg-body pre, .msg-body code { background: #f6f8fa; border-radius: 3px; font-size: 13px; }
|
||
.msg-body pre { padding: 10px; overflow-x: auto; }
|
||
.msg-body code { padding: 2px 5px; }
|
||
@media print {
|
||
body { padding: 12px 16px; }
|
||
.msg-body a { color: #000; text-decoration: underline; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1 class="thread-subject">${subject}</h1>
|
||
${blocks}
|
||
</body>
|
||
</html>`
|
||
}
|
||
|
||
/**
|
||
* Ouvre un nouvel onglet avec la conversation en mise en page imprimable
|
||
* (en-têtes De / À / Date par message), puis déclenche la boîte d’impression du système.
|
||
*/
|
||
export function openConversationPrint(email: Email): void {
|
||
if (typeof window === "undefined") return
|
||
|
||
const html = buildPrintHtml(email)
|
||
const win = window.open("", "_blank", "noopener,noreferrer")
|
||
|
||
if (!win) {
|
||
window.alert(
|
||
"Impossible d’ouvrir la fenêtre d’impression. Vérifiez que les fenêtres pop-up ne sont pas bloquées pour ce site."
|
||
)
|
||
return
|
||
}
|
||
|
||
win.document.open()
|
||
win.document.write(html)
|
||
win.document.close()
|
||
|
||
const runPrint = () => {
|
||
try {
|
||
win.focus()
|
||
win.print()
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
if (win.document.readyState === "complete") {
|
||
window.setTimeout(runPrint, 0)
|
||
} else {
|
||
win.addEventListener("load", () => window.setTimeout(runPrint, 0), { once: true })
|
||
}
|
||
}
|