ultisuite-client/lib/strip-hidden-email-html.ts
2026-05-25 13:52:40 +02:00

44 lines
1.4 KiB
TypeScript

/** Remove marketing preheader blocks before display (bluemonday strips display:none). */
const HIDDEN_STYLE =
/display\s*:\s*none|mso-hide\s*:\s*all|max-height\s*:\s*0|opacity\s*:\s*0|font-size\s*:\s*0|visibility\s*:\s*hidden/i
const HIDDEN_CLASS = /mcnPreviewText|preheader|preview-text/i
const INVISIBLE_RUN = /[\u034f\u200b-\u200f\ufeff\u00a0]{4,}/g
function shouldStripElement(el: Element): boolean {
if (el.hasAttribute("hidden")) return true
if (el.getAttribute("aria-hidden") === "true") return true
const cls = el.className?.toString() ?? ""
if (HIDDEN_CLASS.test(cls)) return true
const style = el.getAttribute("style") ?? ""
if (!style) return false
const compact = style.replace(/\s+/g, "")
return (
HIDDEN_STYLE.test(style) ||
(compact.includes("overflow:hidden") && /max-height\s*:\s*0/.test(style))
)
}
export function stripHiddenEmailHtml(html: string): string {
if (!html.trim() || typeof DOMParser === "undefined") {
return html
}
try {
const doc = new DOMParser().parseFromString(html, "text/html")
const remove: Element[] = []
doc.body.querySelectorAll("*").forEach((el) => {
if (shouldStripElement(el)) remove.push(el)
})
for (const el of remove) el.remove()
return doc.body.innerHTML
} catch {
return html
}
}
export function stripInvisibleTextRuns(text: string): string {
return text.replace(INVISIBLE_RUN, " ").replace(/\s+/g, " ").trim()
}