mirror of
https://github.com/suitenumerique/messages.git
synced 2026-09-30 21:55:00 +02:00
✨(print) add Print button in messages context menu (#518)
This commit is contained in:
@@ -349,6 +349,7 @@
|
||||
"Please enter a valid email address.": "Please enter a valid email address.",
|
||||
"Prefix can only contain letters, numbers, dots, underscores and hyphens.": "Prefix can only contain letters, numbers, dots, underscores and hyphens.",
|
||||
"Prefix is required.": "Prefix is required.",
|
||||
"Print": "Print",
|
||||
"Read": "Read",
|
||||
"Read state": "Read state",
|
||||
"Recurring": "Recurring",
|
||||
|
||||
@@ -349,6 +349,7 @@
|
||||
"Please enter a valid email address.": "Veuillez saisir une adresse email valide.",
|
||||
"Prefix can only contain letters, numbers, dots, underscores and hyphens.": "Le préfixe ne peut contenir que des lettres, chiffres, points, tirets bas et tirets.",
|
||||
"Prefix is required.": "Le préfixe est requis.",
|
||||
"Print": "Imprimer",
|
||||
"Read": "Lu",
|
||||
"Read state": "État de lecture",
|
||||
"Recurring": "Récurrent",
|
||||
|
||||
@@ -274,6 +274,7 @@
|
||||
"Please enter a valid email address.": "Vul een correct email-adres in.",
|
||||
"Prefix can only contain letters, numbers, dots, underscores and hyphens.": "Voorvoegsel kan alleen letters, cijfers, punten, onderstrepingstekens en koppeltekens bevatten.",
|
||||
"Prefix is required.": "Voorvoegsel is vereist.",
|
||||
"Print": "Afdrukken",
|
||||
"Read": "Lees",
|
||||
"Read state": "Lees status",
|
||||
"Redirection": "Omleiding",
|
||||
|
||||
+7
@@ -5,6 +5,7 @@ import { DropdownMenu, Icon, IconType } from "@gouvfr-lasuite/ui-kit";
|
||||
import { getMessagesEmlRetrieveUrl } from "@/features/api/gen/messages/messages";
|
||||
import { getRequestUrl } from "@/features/api/utils";
|
||||
import { useMailboxContext } from "@/features/providers/mailbox";
|
||||
import usePrint from "@/features/message/use-print";
|
||||
import useRead from "@/features/message/use-read";
|
||||
import useTrash from "@/features/message/use-trash";
|
||||
import { ThreadMessageActionsProps } from "./types";
|
||||
@@ -25,6 +26,7 @@ const ThreadMessageActions = ({
|
||||
const { unselectThread, selectedThread, messages } = useMailboxContext();
|
||||
const { markAsUnread, markAsRead } = useRead();
|
||||
const { markAsTrashed } = useTrash();
|
||||
const { print } = usePrint();
|
||||
|
||||
const hasSiblingMessages = useMemo(() => {
|
||||
if (!selectedThread) return false;
|
||||
@@ -80,6 +82,11 @@ const ThreadMessageActions = ({
|
||||
icon: <Icon type={IconType.FILLED} name="mark_email_unread" />,
|
||||
callback: () => toggleReadStateFrom(true)
|
||||
}]),
|
||||
{
|
||||
label: t('Print'),
|
||||
icon: <Icon type={IconType.FILLED} name="print" />,
|
||||
callback: () => print(message)
|
||||
},
|
||||
{
|
||||
label: t('Download raw email'),
|
||||
icon: <Icon type={IconType.FILLED} name="download" />,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useCallback } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Message } from "@/features/api/gen/models";
|
||||
|
||||
const formatContact = (c: { name?: string | null; email: string }) =>
|
||||
c.name ? `${c.name} <${c.email}>` : c.email;
|
||||
|
||||
/**
|
||||
* Hook to print a message in a new browser window
|
||||
*/
|
||||
const usePrint = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const print = useCallback((message: Message) => {
|
||||
const iframe = document
|
||||
.querySelector(`#thread-message-${message.id} .thread-message__body`) as HTMLIFrameElement | null;
|
||||
const bodyHtml = iframe?.contentDocument?.body?.innerHTML;
|
||||
if (!bodyHtml) return;
|
||||
|
||||
const date = new Date(message.sent_at ?? message.created_at);
|
||||
const formattedDate = date.toLocaleString(i18n.resolvedLanguage, {
|
||||
dateStyle: 'full',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
|
||||
const headers = [
|
||||
{ label: t('From'), value: formatContact(message.sender) },
|
||||
{ label: t('To'), value: message.to.map((r) => formatContact(r.contact)).join(', ') },
|
||||
...(message.cc.length ? [{ label: t('Cc'), value: message.cc.map((r) => formatContact(r.contact)).join(', ') }] : []),
|
||||
...(message.bcc.length ? [{ label: t('Bcc'), value: message.bcc.map((r) => formatContact(r.contact)).join(', ') }] : []),
|
||||
{ label: t('Date'), value: formattedDate },
|
||||
{ label: t('Subject'), value: message.subject ?? '' },
|
||||
];
|
||||
|
||||
const html = '<!DOCTYPE html>' + renderToStaticMarkup(
|
||||
<html>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<title>{message.subject ?? ''}</title>
|
||||
<style>{`
|
||||
body { font-family: system-ui, -apple-system, sans-serif; margin: 2em; color: #000; }
|
||||
table { font-size: 14px; border-collapse: collapse; }
|
||||
hr { border: none; border-top: 1px solid #ccc; margin: 16px 0; }
|
||||
.body { font-size: 14px; }
|
||||
.body img { max-width: 100%; }
|
||||
@media print { body { margin: 0; } }
|
||||
`}</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
{headers.map((h, i) => (
|
||||
<tr key={i}>
|
||||
<td style={{ fontWeight: 'bold', padding: '2px 12px 2px 0', whiteSpace: 'nowrap', verticalAlign: 'top' }}>{h.label}</td>
|
||||
<td style={{ padding: '2px 0' }}>{h.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
<hr />
|
||||
<div className="body" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
const printWindow = window.open('');
|
||||
if (!printWindow) return;
|
||||
|
||||
printWindow.document.write(html);
|
||||
printWindow.document.close();
|
||||
printWindow.addEventListener('afterprint', () => printWindow.close());
|
||||
printWindow.onload = () => printWindow.print();
|
||||
}, [t, i18n.resolvedLanguage]);
|
||||
|
||||
return { print };
|
||||
};
|
||||
|
||||
export default usePrint;
|
||||
Reference in New Issue
Block a user