@emailens/engine 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -301,7 +301,10 @@ declare function auditEmail(html: string, options?: AuditOptions): AuditReport;
301
301
 
302
302
  /**
303
303
  * Convert HTML email to plain text suitable for multipart/alternative emails.
304
- * Uses cheerio for parsing (already a dependency — no new deps needed).
304
+ *
305
+ * Walks the DOM tree node-by-node, emitting text content with proper
306
+ * line breaks for block elements. Avoids cheerio's `.text()` which
307
+ * preserves all source whitespace from deeply nested email tables.
305
308
  */
306
309
  declare function toPlainText(html: string): string;
307
310
 
package/dist/index.d.ts CHANGED
@@ -301,7 +301,10 @@ declare function auditEmail(html: string, options?: AuditOptions): AuditReport;
301
301
 
302
302
  /**
303
303
  * Convert HTML email to plain text suitable for multipart/alternative emails.
304
- * Uses cheerio for parsing (already a dependency — no new deps needed).
304
+ *
305
+ * Walks the DOM tree node-by-node, emitting text content with proper
306
+ * line breaks for block elements. Avoids cheerio's `.text()` which
307
+ * preserves all source whitespace from deeply nested email tables.
305
308
  */
306
309
  declare function toPlainText(html: string): string;
307
310
 
package/dist/index.js CHANGED
@@ -10015,45 +10015,104 @@ function auditEmail(html, options) {
10015
10015
  import * as cheerio13 from "cheerio";
10016
10016
  function toPlainText(html) {
10017
10017
  const $ = cheerio13.load(html);
10018
- $("style, script").remove();
10018
+ $("style, script, head").remove();
10019
10019
  $("[data-skip-in-text='true']").remove();
10020
- $("hr").replaceWith("\n---\n");
10021
- $("br").replaceWith("\n");
10022
- $("img").each((_, el) => {
10023
- const alt = $(el).attr("alt");
10024
- $(el).replaceWith(alt ? alt : "");
10025
- });
10026
- $("a").each((_, el) => {
10027
- const $el = $(el);
10028
- const href = $el.attr("href") || "";
10029
- const text2 = $el.text().trim();
10030
- if (!href || href === text2) {
10031
- $el.replaceWith(text2 || href);
10032
- } else if (!text2) {
10033
- $el.replaceWith(href);
10034
- } else {
10035
- $el.replaceWith(`${text2} (${href})`);
10020
+ const lines = [];
10021
+ let currentLine = "";
10022
+ const BLOCK_TAGS = /* @__PURE__ */ new Set([
10023
+ "p",
10024
+ "div",
10025
+ "h1",
10026
+ "h2",
10027
+ "h3",
10028
+ "h4",
10029
+ "h5",
10030
+ "h6",
10031
+ "tr",
10032
+ "table",
10033
+ "blockquote",
10034
+ "ul",
10035
+ "ol",
10036
+ "li",
10037
+ "section",
10038
+ "article",
10039
+ "header",
10040
+ "footer",
10041
+ "main",
10042
+ "td",
10043
+ "th"
10044
+ // treat cells as blocks to avoid run-on text
10045
+ ]);
10046
+ const SKIP_TAGS = /* @__PURE__ */ new Set(["style", "script", "head"]);
10047
+ function flushLine() {
10048
+ const trimmed = currentLine.trim();
10049
+ if (trimmed) lines.push(trimmed);
10050
+ currentLine = "";
10051
+ }
10052
+ function walk5(node) {
10053
+ var _a;
10054
+ if (node.type === "text") {
10055
+ const text = node.data.replace(/\s+/g, " ");
10056
+ if (text.trim()) {
10057
+ currentLine += text;
10058
+ }
10059
+ return;
10060
+ }
10061
+ if (node.type !== "tag") return;
10062
+ const el = node;
10063
+ const tag = el.tagName.toLowerCase();
10064
+ if (SKIP_TAGS.has(tag)) return;
10065
+ if (tag === "br") {
10066
+ flushLine();
10067
+ return;
10068
+ }
10069
+ if (tag === "hr") {
10070
+ flushLine();
10071
+ lines.push("---");
10072
+ return;
10073
+ }
10074
+ if (tag === "img") {
10075
+ const alt = (_a = $(el).attr("alt")) == null ? void 0 : _a.trim();
10076
+ if (alt) currentLine += alt;
10077
+ return;
10078
+ }
10079
+ if (tag === "a") {
10080
+ const href = $(el).attr("href") || "";
10081
+ const text = $(el).text().trim();
10082
+ if (!href || href === "#" || href === text) {
10083
+ currentLine += text || href;
10084
+ } else if (!text) {
10085
+ currentLine += href;
10086
+ } else {
10087
+ currentLine += `${text} (${href})`;
10088
+ }
10089
+ return;
10090
+ }
10091
+ const isBlock = BLOCK_TAGS.has(tag);
10092
+ if (isBlock) {
10093
+ flushLine();
10094
+ } else if (currentLine && !currentLine.endsWith(" ")) {
10095
+ currentLine += " ";
10096
+ }
10097
+ if (tag === "li") {
10098
+ currentLine = "- ";
10099
+ }
10100
+ for (const child of el.children) {
10101
+ walk5(child);
10102
+ }
10103
+ if (isBlock) flushLine();
10104
+ }
10105
+ const body = $("body");
10106
+ const root = body.length ? body[0] : $.root()[0];
10107
+ if (root && "children" in root) {
10108
+ for (const child of root.children) {
10109
+ walk5(child);
10036
10110
  }
10037
- });
10038
- $("li").each((_, el) => {
10039
- const $el = $(el);
10040
- const text2 = $el.text().trim();
10041
- $el.replaceWith(`
10042
- - ${text2}
10043
- `);
10044
- });
10045
- const blockTags = "p, div, h1, h2, h3, h4, h5, h6, tr, blockquote, ul, ol";
10046
- $(blockTags).each((_, el) => {
10047
- const $el = $(el);
10048
- $el.prepend("\n\n");
10049
- $el.append("\n\n");
10050
- });
10051
- let text = $("body").text();
10052
- if (!text.trim()) {
10053
- text = $.root().text();
10054
10111
  }
10055
- text = text.replace(/\n{3,}/g, "\n\n");
10056
- return text.trim();
10112
+ flushLine();
10113
+ let result = lines.join("\n");
10114
+ result = result.replace(/\n{3,}/g, "\n\n");
10115
+ return result.trim();
10057
10116
  }
10058
10117
 
10059
10118
  // src/session.ts