mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-14 02:39:22 +02:00
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
function bytesBase64(bytes: Uint8Array) {
|
|
let binary = "";
|
|
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
|
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
}
|
|
return globalThis.btoa(binary);
|
|
}
|
|
|
|
function utf8Base64(value: string) {
|
|
return bytesBase64(new TextEncoder().encode(value));
|
|
}
|
|
|
|
function wrapBase64(value: string) {
|
|
return value.match(/.{1,76}/g)?.join("\r\n") ?? "";
|
|
}
|
|
|
|
function slug(value: string) {
|
|
return value
|
|
.normalize("NFD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
.replace(/^-|-$/g, "")
|
|
.toLowerCase() || "email-plein-r";
|
|
}
|
|
|
|
function emailBody(html: string) {
|
|
return new DOMParser().parseFromString(html, "text/html").body.innerHTML.trim();
|
|
}
|
|
|
|
export async function copyRichEmail(html: string, plainText: string) {
|
|
const holder = document.createElement("div");
|
|
holder.contentEditable = "true";
|
|
holder.setAttribute("aria-hidden", "true");
|
|
holder.style.cssText = "position:fixed;left:-10000px;top:0;width:700px;background:#F6F2E8;pointer-events:none;z-index:-1";
|
|
holder.innerHTML = emailBody(html);
|
|
document.body.appendChild(holder);
|
|
const selection = window.getSelection();
|
|
const range = document.createRange();
|
|
try {
|
|
holder.focus();
|
|
range.selectNodeContents(holder);
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
if (!document.execCommand("copy")) throw new Error("Copie refusée");
|
|
} catch (error) {
|
|
if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") throw error;
|
|
await navigator.clipboard.write([
|
|
new ClipboardItem({
|
|
"text/html": new Blob([emailBody(html)], { type: "text/html" }),
|
|
"text/plain": new Blob([plainText], { type: "text/plain" }),
|
|
}),
|
|
]);
|
|
} finally {
|
|
selection?.removeAllRanges();
|
|
holder.remove();
|
|
}
|
|
}
|
|
|
|
export async function downloadOutlookDraft(subject: string, html: string, prefix = "email") {
|
|
const logoUrl = html.match(/\bsrc=["']([^"']*\/assets\/logo\.png(?:\?[^"']*)?)["']/i)?.[1];
|
|
let renderedHtml = html;
|
|
let logoPart = "";
|
|
const boundary = "----=_PleinR_Outlook_Draft";
|
|
|
|
if (logoUrl) {
|
|
const response = await fetch(logoUrl, { cache: "force-cache" });
|
|
if (!response.ok) throw new Error("Logo Plein R introuvable.");
|
|
const base64 = bytesBase64(new Uint8Array(await response.arrayBuffer()));
|
|
renderedHtml = html.replace(/(\bsrc\s*=\s*)(["'])[^"']*\/assets\/logo\.png(?:\?[^"']*)?\2/gi, "$1$2cid:plein-r-logo$2");
|
|
logoPart = [`--${boundary}`, "Content-Type: image/png; name=\"logo.png\"", "Content-Transfer-Encoding: base64", "Content-ID: <plein-r-logo>", "Content-Disposition: inline; filename=\"logo.png\"", "", wrapBase64(base64)].join("\r\n");
|
|
}
|
|
|
|
const mime = logoPart
|
|
? ["X-Unsent: 1", `Subject: =?UTF-8?B?${utf8Base64(subject)}?=`, "MIME-Version: 1.0", `Content-Type: multipart/related; type=\"text/html\"; boundary=\"${boundary}\"`, "", `--${boundary}`, "Content-Type: text/html; charset=UTF-8", "Content-Transfer-Encoding: base64", "", wrapBase64(utf8Base64(renderedHtml)), logoPart, `--${boundary}--`, ""].join("\r\n")
|
|
: ["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");
|
|
downloadBlob(new Blob([mime], { type: "message/rfc822" }), `${prefix}-${slug(subject)}-outlook.eml`);
|
|
}
|
|
|
|
export function downloadHtml(subject: string, html: string, prefix = "email") {
|
|
downloadBlob(new Blob([html], { type: "text/html;charset=utf-8" }), `${prefix}-${slug(subject)}.html`);
|
|
}
|
|
|
|
function downloadBlob(blob: Blob, filename: string) {
|
|
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);
|
|
}
|