- Dans un brouillon Outlook, utilisez Ctrl + clic pour tester un lien. Après envoi, un clic normal suffit.
+ Pour Outlook, ouvrez le fichier .eml téléchargé : le mail est chargé comme un vrai brouillon HTML, sans collage ni remplacement des styles. Dans le brouillon, utilisez Ctrl + clic pour tester un lien.
diff --git a/web/src/components/admin/AdminTabs.jsx b/web/src/components/admin/AdminTabs.jsx
index 4cecb7b..0840912 100644
--- a/web/src/components/admin/AdminTabs.jsx
+++ b/web/src/components/admin/AdminTabs.jsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { api, dateParts, seasonLabel, statutLabel } from '../../lib/api.js';
import { copyRichEmail } from '../../lib/emailClipboard.js';
+import { downloadOutlookDraft } from '../../lib/outlookDraft.js';
import { useAuth } from '../../lib/AuthContext.jsx';
import Modal from '../Modal.jsx';
import ImageSlot from '../ImageSlot.jsx';
@@ -392,6 +393,16 @@ function EmailModal({ renc, onClose }) {
}
};
+ const downloadOutlook = () => {
+ const slug = renc.titre.normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase();
+ downloadOutlookDraft({
+ subject: data.subject,
+ html: data.html,
+ filename: `invitation-${slug}-outlook.eml`,
+ });
+ flash('outlook');
+ };
+
const download = () => {
const blob = new Blob([data.html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
@@ -429,8 +440,11 @@ function EmailModal({ renc, onClose }) {
automatiquement. Videz un champ pour retirer ce bloc de l'email.
-
diff --git a/web/src/lib/outlookDraft.js b/web/src/lib/outlookDraft.js
new file mode 100644
index 0000000..12e8ee2
--- /dev/null
+++ b/web/src/lib/outlookDraft.js
@@ -0,0 +1,42 @@
+function utf8Base64(value) {
+ const bytes = new TextEncoder().encode(String(value ?? ''));
+ let binary = '';
+
+ for (let offset = 0; offset < bytes.length; offset += 0x8000) {
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
+ }
+
+ return globalThis.btoa(binary);
+}
+
+function wrapBase64(value) {
+ return value.match(/.{1,76}/g)?.join('\r\n') ?? '';
+}
+
+export function buildOutlookDraftMime({ subject, html }) {
+ // X-Unsent asks Outlook to open the MIME message as a message to compose.
+ // Loading HTML as a real MIME body bypasses Outlook's destructive paste merge.
+ return [
+ 'X-Unsent: 1',
+ `Subject: =?UTF-8?B?${utf8Base64(subject)}?=`,
+ 'MIME-Version: 1.0',
+ 'Content-Type: text/html; charset=UTF-8',
+ 'Content-Transfer-Encoding: base64',
+ '',
+ wrapBase64(utf8Base64(html)),
+ '',
+ ].join('\r\n');
+}
+
+export function downloadOutlookDraft({ subject, html, filename }) {
+ const mime = buildOutlookDraftMime({ subject, html });
+ const blob = new Blob([mime], { type: 'message/rfc822' });
+ const url = URL.createObjectURL(blob);
+ const anchor = document.createElement('a');
+ anchor.href = url;
+ anchor.download = filename;
+ document.body.appendChild(anchor);
+ anchor.click();
+ anchor.remove();
+ setTimeout(() => URL.revokeObjectURL(url), 2000);
+}