@emailens/engine 0.11.0 → 0.11.1
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/compile/index.d.cts +2 -2
- package/dist/compile/index.d.ts +2 -2
- package/dist/index.cjs +202 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -5
- package/dist/index.d.ts +25 -5
- package/dist/index.js +201 -10
- package/dist/index.js.map +1 -1
- package/dist/{react-email-D24HkW2Z.d.ts → react-email-BROBcC8u.d.ts} +1 -1
- package/dist/{react-email-BWIfwG7U.d.cts → react-email-DuZAgJtw.d.cts} +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-BFhPXh3c.d.cts → types-bvGDId6f.d.cts} +9 -1
- package/dist/{types-BFhPXh3c.d.ts → types-bvGDId6f.d.ts} +9 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as
|
|
2
|
-
export { B as BaseIssue,
|
|
1
|
+
import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.cjs';
|
|
2
|
+
export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.cjs';
|
|
3
3
|
import * as cheerio from 'cheerio';
|
|
4
|
-
export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-
|
|
4
|
+
export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-DuZAgJtw.cjs';
|
|
5
5
|
|
|
6
6
|
declare const EMAIL_CLIENTS: EmailClient[];
|
|
7
7
|
declare function getClient(id: string): EmailClient | undefined;
|
|
@@ -372,6 +372,24 @@ declare function checkTemplateVariables(html: string, options?: ParseOptions): T
|
|
|
372
372
|
*/
|
|
373
373
|
declare function checkOverflow(html: string, options?: ParseOptions): OverflowReport;
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Check hand-written VML for the mistakes Outlook punishes.
|
|
377
|
+
*
|
|
378
|
+
* This is the one part of an email the DOM analyzers structurally cannot see:
|
|
379
|
+
* VML lives inside `<!--[if mso]>` conditional comments, so to every HTML
|
|
380
|
+
* parser it is a comment node, and to a headless-Chromium screenshot it does
|
|
381
|
+
* not exist at all. An email can therefore lint clean and preview perfectly
|
|
382
|
+
* while the branch Outlook actually renders is broken.
|
|
383
|
+
*
|
|
384
|
+
* ponytail: a tag-sequence checker, not a VML renderer. It validates structure
|
|
385
|
+
* and attribute values, which is where the reported breakages cluster. It does
|
|
386
|
+
* not resolve geometry, so it can say Outlook will misplace a nested shape but
|
|
387
|
+
* not where the shape lands.
|
|
388
|
+
*/
|
|
389
|
+
declare function checkVml(html: string, options?: {
|
|
390
|
+
positions?: boolean;
|
|
391
|
+
}): VmlReport;
|
|
392
|
+
|
|
375
393
|
/**
|
|
376
394
|
* Detect probable visual bugs in a stylized email: background images/gradients
|
|
377
395
|
* with no colour fallback, and font stacks with no web-safe fallback. Each
|
|
@@ -390,7 +408,7 @@ interface AuditOptions extends ParseOptions {
|
|
|
390
408
|
/** Options for spam analysis */
|
|
391
409
|
spam?: SpamAnalysisOptions;
|
|
392
410
|
/** Skip specific checks */
|
|
393
|
-
skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design">;
|
|
411
|
+
skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design" | "vml">;
|
|
394
412
|
}
|
|
395
413
|
interface AuditReport {
|
|
396
414
|
compatibility: {
|
|
@@ -411,6 +429,8 @@ interface AuditReport {
|
|
|
411
429
|
templateVariables: TemplateReport;
|
|
412
430
|
overflow: OverflowReport;
|
|
413
431
|
visual: VisualReport;
|
|
432
|
+
/** Structural faults in Outlook-only VML, which lives inside conditional comments. */
|
|
433
|
+
vml: VmlReport;
|
|
414
434
|
/** Contrast failures present only once a client inverts colours. */
|
|
415
435
|
darkContrast: AccessibilityIssue[];
|
|
416
436
|
/** Contrast failures present only below the email's mobile breakpoint. */
|
|
@@ -654,4 +674,4 @@ declare function colorDistance(a: RGBA, b: RGBA): number;
|
|
|
654
674
|
*/
|
|
655
675
|
declare function downlevelCSS(html: string): string;
|
|
656
676
|
|
|
657
|
-
export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
|
|
677
|
+
export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, VmlReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, checkVml, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as
|
|
2
|
-
export { B as BaseIssue,
|
|
1
|
+
import { E as EmailClient, F as Framework, T as TransformResult, C as CSSWarning, a as CodeFix, D as DiffResult, b as ExportPromptOptions, A as AiProvider, c as AiFixResult, S as SupportLevel, d as SpamAnalysisOptions, e as SpamReport, L as LinkReport, f as AccessibilityReport, g as AccessibilityIssue, I as ImageReport, h as InboxPreview, i as SizeReport, j as TemplateReport, O as OverflowReport, V as VmlReport, k as VisualReport, l as DesignReport, m as DeliverabilityReport } from './types-bvGDId6f.js';
|
|
2
|
+
export { B as BaseIssue, n as ClientTruncation, o as DeliverabilityCheck, p as DeliverabilityIssue, q as DesignIssue, r as EstimateOptions, s as ExportScope, t as FixType, u as ImageInfo, v as ImageIssue, w as InboxPreviewIssue, x as InputFormat, y as LinkIssue, z as OverflowIssue, P as PreviewResult, G as Severity, H as SizeIssue, J as SourceLocation, K as SpamIssue, M as TemplateIssue, N as TokenEstimate, Q as TokenEstimateWithWarnings, R as VisualIssue, U as VmlIssue, W as estimateAiFixTokens, X as generateFixPrompt, Y as heuristicTokenCount } from './types-bvGDId6f.js';
|
|
3
3
|
import * as cheerio from 'cheerio';
|
|
4
|
-
export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-
|
|
4
|
+
export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy } from './react-email-BROBcC8u.js';
|
|
5
5
|
|
|
6
6
|
declare const EMAIL_CLIENTS: EmailClient[];
|
|
7
7
|
declare function getClient(id: string): EmailClient | undefined;
|
|
@@ -372,6 +372,24 @@ declare function checkTemplateVariables(html: string, options?: ParseOptions): T
|
|
|
372
372
|
*/
|
|
373
373
|
declare function checkOverflow(html: string, options?: ParseOptions): OverflowReport;
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Check hand-written VML for the mistakes Outlook punishes.
|
|
377
|
+
*
|
|
378
|
+
* This is the one part of an email the DOM analyzers structurally cannot see:
|
|
379
|
+
* VML lives inside `<!--[if mso]>` conditional comments, so to every HTML
|
|
380
|
+
* parser it is a comment node, and to a headless-Chromium screenshot it does
|
|
381
|
+
* not exist at all. An email can therefore lint clean and preview perfectly
|
|
382
|
+
* while the branch Outlook actually renders is broken.
|
|
383
|
+
*
|
|
384
|
+
* ponytail: a tag-sequence checker, not a VML renderer. It validates structure
|
|
385
|
+
* and attribute values, which is where the reported breakages cluster. It does
|
|
386
|
+
* not resolve geometry, so it can say Outlook will misplace a nested shape but
|
|
387
|
+
* not where the shape lands.
|
|
388
|
+
*/
|
|
389
|
+
declare function checkVml(html: string, options?: {
|
|
390
|
+
positions?: boolean;
|
|
391
|
+
}): VmlReport;
|
|
392
|
+
|
|
375
393
|
/**
|
|
376
394
|
* Detect probable visual bugs in a stylized email: background images/gradients
|
|
377
395
|
* with no colour fallback, and font stacks with no web-safe fallback. Each
|
|
@@ -390,7 +408,7 @@ interface AuditOptions extends ParseOptions {
|
|
|
390
408
|
/** Options for spam analysis */
|
|
391
409
|
spam?: SpamAnalysisOptions;
|
|
392
410
|
/** Skip specific checks */
|
|
393
|
-
skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design">;
|
|
411
|
+
skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility" | "inboxPreview" | "size" | "templateVariables" | "overflow" | "visual" | "darkContrast" | "mobileContrast" | "design" | "vml">;
|
|
394
412
|
}
|
|
395
413
|
interface AuditReport {
|
|
396
414
|
compatibility: {
|
|
@@ -411,6 +429,8 @@ interface AuditReport {
|
|
|
411
429
|
templateVariables: TemplateReport;
|
|
412
430
|
overflow: OverflowReport;
|
|
413
431
|
visual: VisualReport;
|
|
432
|
+
/** Structural faults in Outlook-only VML, which lives inside conditional comments. */
|
|
433
|
+
vml: VmlReport;
|
|
414
434
|
/** Contrast failures present only once a client inverts colours. */
|
|
415
435
|
darkContrast: AccessibilityIssue[];
|
|
416
436
|
/** Contrast failures present only below the email's mobile breakpoint. */
|
|
@@ -654,4 +674,4 @@ declare function colorDistance(a: RGBA, b: RGBA): number;
|
|
|
654
674
|
*/
|
|
655
675
|
declare function downlevelCSS(html: string): string;
|
|
656
676
|
|
|
657
|
-
export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
|
|
677
|
+
export { AI_FIX_SYSTEM_PROMPT, AT_RULE_FEATURES, AccessibilityIssue, AccessibilityReport, AiFixResult, AiProvider, type AuditOptions, type AuditReport, COMPOUND_VALUE_FEATURES, CSSWarning, CSS_FUNCTION_FEATURES, CSS_SUPPORT, CSS_SUPPORT_NOTES, CodeFix, type CreateSessionOptions, DeliverabilityReport, DesignReport, DiffResult, EMAIL_CLIENTS, EMPTY_DELIVERABILITY, EmailClient, type EmailSession, ExportPromptOptions, FEATURE_URLS, Framework, GENERIC_LINK_TEXT, type GenerateAiFixOptions, HTML_ELEMENT_FEATURES, ImageReport, InboxPreview, LinkReport, MAX_HTML_SIZE, MAX_WARNING_LOCATIONS, OverflowReport, type ParseOptions, type RGBA, type RenderContext, STRUCTURAL_FIX_PROPERTIES, SizeReport, SpamAnalysisOptions, SpamReport, SupportLevel, TemplateReport, TransformResult, VALUE_CAVEAT_PROPS, VisualReport, VmlReport, type WcagGrade, alphaBlend, analyzeEmail, analyzeImages, analyzeSpam, auditEmail, caveatApplies, checkAccessibility, checkDarkModeContrast, checkDarkStylesContrastFromDom, checkDesignConsistency, checkMobileContrast, checkOverflow, checkSize, checkTemplateVariables, checkVisual, checkVml, colorDistance, contrastRatio, createSession, diffResults, downlevelCSS, errorWarnings, extractInboxPreview, featureUrl, formatRgb, generateAiFix, generateCompatibilityScore, getClient, getCodeFix, getSuggestion, parseColor, relativeLuminance, rgbToOklab, simulateDarkMode, structuralWarnings, toPlainText, transformForAllClients, transformForClient, validateLinks, warningsForClient, wcagGrade };
|
package/dist/index.js
CHANGED
|
@@ -9793,6 +9793,7 @@ var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, pr
|
|
|
9793
9793
|
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
9794
9794
|
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
9795
9795
|
var EMPTY_OVERFLOW = { hasOverflow: false, issues: [] };
|
|
9796
|
+
var EMPTY_VML = { hasVml: false, issues: [] };
|
|
9796
9797
|
var EMPTY_VISUAL = { issues: [] };
|
|
9797
9798
|
var EMAIL_MAX_WIDTH = 600;
|
|
9798
9799
|
var UNBREAKABLE_STRING_LENGTH = 30;
|
|
@@ -11001,11 +11002,11 @@ function locOfElement(el) {
|
|
|
11001
11002
|
if (!raw) return void 0;
|
|
11002
11003
|
return toLoc((_a = raw.startTag) != null ? _a : raw);
|
|
11003
11004
|
}
|
|
11004
|
-
function locOfAttr(el,
|
|
11005
|
+
function locOfAttr(el, attr2) {
|
|
11005
11006
|
var _a, _b, _c, _d;
|
|
11006
11007
|
const raw = el == null ? void 0 : el.sourceCodeLocation;
|
|
11007
11008
|
if (!raw) return void 0;
|
|
11008
|
-
const attrLoc = (_d = (_a = raw.attrs) == null ? void 0 : _a[
|
|
11009
|
+
const attrLoc = (_d = (_a = raw.attrs) == null ? void 0 : _a[attr2]) != null ? _d : (_c = (_b = raw.startTag) == null ? void 0 : _b.attrs) == null ? void 0 : _c[attr2];
|
|
11009
11010
|
return attrLoc ? toLoc(attrLoc) : locOfElement(el);
|
|
11010
11011
|
}
|
|
11011
11012
|
function locInAttr(attrLoc, source, property, occurrence = 0) {
|
|
@@ -14148,22 +14149,22 @@ function checkTemplateVariablesFromDom($, source) {
|
|
|
14148
14149
|
for (const sel of attrSelectors) {
|
|
14149
14150
|
$(sel).each((_, el) => {
|
|
14150
14151
|
const attrs = ["href", "src", "alt"];
|
|
14151
|
-
for (const
|
|
14152
|
-
const value = $(el).attr(
|
|
14152
|
+
for (const attr2 of attrs) {
|
|
14153
|
+
const value = $(el).attr(attr2);
|
|
14153
14154
|
if (!value) continue;
|
|
14154
14155
|
for (const [pattern, label] of TEMPLATE_VARIABLE_PATTERNS) {
|
|
14155
14156
|
pattern.lastIndex = 0;
|
|
14156
14157
|
let match;
|
|
14157
14158
|
while ((match = pattern.exec(value)) !== null) {
|
|
14158
14159
|
const variable = match[0];
|
|
14159
|
-
const key = `attr:${
|
|
14160
|
+
const key = `attr:${attr2}:${variable}`;
|
|
14160
14161
|
if (seen.has(key)) continue;
|
|
14161
14162
|
seen.add(key);
|
|
14162
|
-
const loc = locOfAttr(el,
|
|
14163
|
+
const loc = locOfAttr(el, attr2);
|
|
14163
14164
|
issues.push(__spreadValues({
|
|
14164
14165
|
rule: "unresolved-variable",
|
|
14165
14166
|
severity: "error",
|
|
14166
|
-
message: `Unresolved ${label} variable "${variable}" found in ${
|
|
14167
|
+
message: `Unresolved ${label} variable "${variable}" found in ${attr2} attribute.`,
|
|
14167
14168
|
variable,
|
|
14168
14169
|
location: "attribute"
|
|
14169
14170
|
}, loc ? { loc } : {}));
|
|
@@ -14189,8 +14190,8 @@ function fixedPxWidth($el) {
|
|
|
14189
14190
|
const style = $el.attr("style") || "";
|
|
14190
14191
|
const styleMatch = style.match(/(?:^|[;\s])width\s*:\s*(\d+)px/i);
|
|
14191
14192
|
if (styleMatch) return parseInt(styleMatch[1], 10);
|
|
14192
|
-
const
|
|
14193
|
-
if (
|
|
14193
|
+
const attr2 = $el.attr("width");
|
|
14194
|
+
if (attr2 && /^\d+$/.test(attr2.trim())) return parseInt(attr2.trim(), 10);
|
|
14194
14195
|
return null;
|
|
14195
14196
|
}
|
|
14196
14197
|
function isFluid(style) {
|
|
@@ -14330,6 +14331,193 @@ function checkOverflow(html, options) {
|
|
|
14330
14331
|
);
|
|
14331
14332
|
}
|
|
14332
14333
|
|
|
14334
|
+
// src/vml-checker.ts
|
|
14335
|
+
var SHAPE_TAGS = /* @__PURE__ */ new Set([
|
|
14336
|
+
"rect",
|
|
14337
|
+
"roundrect",
|
|
14338
|
+
"oval",
|
|
14339
|
+
"line",
|
|
14340
|
+
"polyline",
|
|
14341
|
+
"curve",
|
|
14342
|
+
"arc",
|
|
14343
|
+
"shape",
|
|
14344
|
+
"image",
|
|
14345
|
+
"background"
|
|
14346
|
+
]);
|
|
14347
|
+
var GROUP_TAGS = /* @__PURE__ */ new Set(["group"]);
|
|
14348
|
+
function msoBlocks(html) {
|
|
14349
|
+
const blocks = [];
|
|
14350
|
+
const re = /<!--\[if([^\]]*)\]>([\s\S]*?)<!\[endif\]-->/g;
|
|
14351
|
+
let m;
|
|
14352
|
+
while ((m = re.exec(html)) !== null) {
|
|
14353
|
+
const condition = m[1];
|
|
14354
|
+
if (/!\s*(mso|vml)/i.test(condition)) continue;
|
|
14355
|
+
if (!/mso|vml/i.test(condition)) continue;
|
|
14356
|
+
blocks.push({ inner: m[2], offset: m.index + m[0].length - m[2].length - "<![endif]-->".length });
|
|
14357
|
+
}
|
|
14358
|
+
return blocks;
|
|
14359
|
+
}
|
|
14360
|
+
function vmlTags(blocks) {
|
|
14361
|
+
const tags = [];
|
|
14362
|
+
const re = /<(\/?)v:([a-z]+)((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>/gi;
|
|
14363
|
+
for (const block of blocks) {
|
|
14364
|
+
let m;
|
|
14365
|
+
re.lastIndex = 0;
|
|
14366
|
+
while ((m = re.exec(block.inner)) !== null) {
|
|
14367
|
+
tags.push({
|
|
14368
|
+
name: m[2].toLowerCase(),
|
|
14369
|
+
closing: m[1] === "/",
|
|
14370
|
+
selfClosing: m[4] === "/",
|
|
14371
|
+
attrs: m[3],
|
|
14372
|
+
offset: block.offset + m.index,
|
|
14373
|
+
length: m[0].length
|
|
14374
|
+
});
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14377
|
+
return tags;
|
|
14378
|
+
}
|
|
14379
|
+
function locAt(source, offset, length) {
|
|
14380
|
+
if (source === void 0) return void 0;
|
|
14381
|
+
const start = positionOf(source, offset);
|
|
14382
|
+
const end = positionOf(source, offset + length);
|
|
14383
|
+
return {
|
|
14384
|
+
line: start.line,
|
|
14385
|
+
column: start.column,
|
|
14386
|
+
endLine: end.line,
|
|
14387
|
+
endColumn: end.column,
|
|
14388
|
+
offset,
|
|
14389
|
+
length
|
|
14390
|
+
};
|
|
14391
|
+
}
|
|
14392
|
+
function attr(attrs, name) {
|
|
14393
|
+
var _a;
|
|
14394
|
+
const m = attrs.match(
|
|
14395
|
+
new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]*)"|(?:^|\\s)${name}\\s*=\\s*'([^']*)'`, "i")
|
|
14396
|
+
);
|
|
14397
|
+
return m ? (_a = m[1]) != null ? _a : m[2] : void 0;
|
|
14398
|
+
}
|
|
14399
|
+
function add(issues, seen, key, issue) {
|
|
14400
|
+
const existing = seen.get(key);
|
|
14401
|
+
if (existing) {
|
|
14402
|
+
if (!issue.loc || !existing.locs) return;
|
|
14403
|
+
if (existing.locs.some((l) => l.offset === issue.loc.offset)) return;
|
|
14404
|
+
if (existing.locs.length >= MAX_WARNING_LOCATIONS) {
|
|
14405
|
+
existing.locsTruncated = true;
|
|
14406
|
+
return;
|
|
14407
|
+
}
|
|
14408
|
+
existing.locs.push(issue.loc);
|
|
14409
|
+
return;
|
|
14410
|
+
}
|
|
14411
|
+
seen.set(key, issue);
|
|
14412
|
+
issues.push(issue);
|
|
14413
|
+
}
|
|
14414
|
+
function withLoc(issue, loc) {
|
|
14415
|
+
return loc ? __spreadProps(__spreadValues({}, issue), { loc, locs: [loc] }) : issue;
|
|
14416
|
+
}
|
|
14417
|
+
var TEXT_HOSTS = /<\s*(center|v:textbox|div|p|table|h[1-6]|span|font|a)\b/i;
|
|
14418
|
+
function checkLooseText(issues, seen, open, inner, source) {
|
|
14419
|
+
if (TEXT_HOSTS.test(inner)) return;
|
|
14420
|
+
const text = inner.replace(/<!--[\s\S]*?-->/g, "").replace(/<[^>]*>/g, "").replace(/ |\s/g, "");
|
|
14421
|
+
if (text === "") return;
|
|
14422
|
+
add(issues, seen, `loosetext:${open.name}`, withLoc({
|
|
14423
|
+
rule: "vml-unrendered-text",
|
|
14424
|
+
severity: "error",
|
|
14425
|
+
message: `<v:${open.name}> has label text with no element around it, which Outlook does not draw.`,
|
|
14426
|
+
detail: `Verified in Outlook Classic: the shape renders its fill and shape correctly and shows no text at all, so the reader gets a blank coloured block where the label should be. Nothing errors and the HTML fallback still reads correctly in every other client, so this ships unnoticed. Wrap the text in <center> (what the bulletproof-button pattern uses) or in <v:textbox>.`
|
|
14427
|
+
}, locAt(source, open.offset, open.length)));
|
|
14428
|
+
}
|
|
14429
|
+
function checkVml(html, options) {
|
|
14430
|
+
if (!html || !html.trim()) return EMPTY_VML;
|
|
14431
|
+
const source = (options == null ? void 0 : options.positions) ? html : void 0;
|
|
14432
|
+
const tags = vmlTags(msoBlocks(html));
|
|
14433
|
+
if (tags.length === 0) return EMPTY_VML;
|
|
14434
|
+
const issues = [];
|
|
14435
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14436
|
+
const openShapes = [];
|
|
14437
|
+
let groupDepth = 0;
|
|
14438
|
+
for (const tag of tags) {
|
|
14439
|
+
const { name } = tag;
|
|
14440
|
+
const isShape = SHAPE_TAGS.has(name);
|
|
14441
|
+
const isGroup = GROUP_TAGS.has(name);
|
|
14442
|
+
const loc = locAt(source, tag.offset, tag.length);
|
|
14443
|
+
if (tag.closing) {
|
|
14444
|
+
if (isGroup) groupDepth = Math.max(0, groupDepth - 1);
|
|
14445
|
+
if (isShape) {
|
|
14446
|
+
const idx = openShapes.map((t) => t.name).lastIndexOf(name);
|
|
14447
|
+
if (idx !== -1) {
|
|
14448
|
+
const open = openShapes[idx];
|
|
14449
|
+
const inner = html.slice(open.offset + open.length, tag.offset);
|
|
14450
|
+
checkLooseText(issues, seen, open, inner, source);
|
|
14451
|
+
}
|
|
14452
|
+
if (idx === -1) {
|
|
14453
|
+
add(issues, seen, `stray:${name}`, withLoc({
|
|
14454
|
+
rule: "vml-unbalanced-tag",
|
|
14455
|
+
severity: "error",
|
|
14456
|
+
message: `Closing </v:${name}> has no matching opening tag.`,
|
|
14457
|
+
detail: `Outlook stops rendering the shape when VML tags do not balance. Check that every conditional-comment block opens and closes the tags it is responsible for.`
|
|
14458
|
+
}, loc));
|
|
14459
|
+
} else {
|
|
14460
|
+
openShapes.splice(idx, 1);
|
|
14461
|
+
}
|
|
14462
|
+
}
|
|
14463
|
+
continue;
|
|
14464
|
+
}
|
|
14465
|
+
if (isGroup && !tag.selfClosing) groupDepth++;
|
|
14466
|
+
if (isShape) {
|
|
14467
|
+
const parent = openShapes[openShapes.length - 1];
|
|
14468
|
+
if (parent && groupDepth === 0) {
|
|
14469
|
+
add(issues, seen, `nested:${parent.name}>${name}`, withLoc({
|
|
14470
|
+
rule: "vml-nested-shape",
|
|
14471
|
+
severity: "error",
|
|
14472
|
+
message: `<v:${name}> is nested inside <v:${parent.name}>. Outlook does not support nesting one VML shape inside another.`,
|
|
14473
|
+
detail: `Verified in Outlook Classic, and the damage is not local. Three things happen: the containing <v:${parent.name}> does not render at all (its fill and everything inside it disappear, leaving the inner shape stranded); the table structure around it terminates early, so content *after* the shape falls out of the layout and renders outside the email frame; and every VML shape further down the email stops drawing its text, so buttons and headings after this point ship as blank coloured blocks. Confirmed with byte-identical probes either side of one nested shape: the one before it renders its labels, the one after it does not. Every other client renders the HTML fallback correctly, which is why none of this is visible outside the Word engine. Fix: lift the inner shape out of <v:${parent.name}>, or drop the container shape and keep the inner one (a framed background can degrade to a solid fill colour on the <td> instead).`
|
|
14474
|
+
}, loc));
|
|
14475
|
+
}
|
|
14476
|
+
if (!tag.selfClosing) openShapes.push(tag);
|
|
14477
|
+
}
|
|
14478
|
+
const style = attr(tag.attrs, "style");
|
|
14479
|
+
if (style && (isShape || isGroup)) {
|
|
14480
|
+
for (const prop of ["width", "height"]) {
|
|
14481
|
+
const m = style.match(new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]*)`, "i"));
|
|
14482
|
+
if (!m) continue;
|
|
14483
|
+
const value = m[1].trim();
|
|
14484
|
+
if (value === "" || /^[a-z%]+$/i.test(value)) {
|
|
14485
|
+
add(issues, seen, `dim:${name}:${prop}:${value}`, withLoc({
|
|
14486
|
+
rule: "vml-invalid-dimension",
|
|
14487
|
+
severity: "error",
|
|
14488
|
+
message: `<v:${name}> has an invalid ${prop}: "${prop}:${value}" is missing its number.`,
|
|
14489
|
+
detail: `Verified in Outlook Classic: the shape still draws, but at a size Outlook picks rather than the one you meant, and the content inside is clipped to it \u2014 roughly half the intended height in the case measured, with the text inside cut off. It fails quietly: there is no gap or broken image to notice, just content that is silently missing. Usually a template variable that resolved to an empty string. Set an explicit value in pixels, e.g. ${prop}:400px.`
|
|
14490
|
+
}, loc));
|
|
14491
|
+
}
|
|
14492
|
+
}
|
|
14493
|
+
}
|
|
14494
|
+
if (name === "roundrect") {
|
|
14495
|
+
const arcsize = attr(tag.attrs, "arcsize");
|
|
14496
|
+
if (arcsize !== void 0) {
|
|
14497
|
+
const raw = arcsize.trim();
|
|
14498
|
+
const pct = raw.endsWith("%") ? parseFloat(raw) : parseFloat(raw) * 100;
|
|
14499
|
+
if (Number.isFinite(pct) && (pct < 0 || pct > 100)) {
|
|
14500
|
+
add(issues, seen, `arcsize:${raw}`, withLoc({
|
|
14501
|
+
rule: "vml-arcsize-range",
|
|
14502
|
+
severity: "warning",
|
|
14503
|
+
message: `<v:roundrect> has arcsize="${raw}", outside the valid 0%\u2013100% range.`,
|
|
14504
|
+
detail: `arcsize is a fraction of half the shape's smaller side: 0% is square, 100% is fully circular. Verified in Outlook Classic: out-of-range values are clamped, so 120% draws exactly the same corner as 100%. Nothing visibly breaks today, which is why this is a warning and not an error \u2014 but the radius you get is the renderer's clamp rather than a value you chose, and it is not guaranteed across clients. For a fully rounded button, say arcsize="100%".`
|
|
14505
|
+
}, loc));
|
|
14506
|
+
}
|
|
14507
|
+
}
|
|
14508
|
+
}
|
|
14509
|
+
}
|
|
14510
|
+
for (const tag of openShapes) {
|
|
14511
|
+
add(issues, seen, `unclosed:${tag.name}`, withLoc({
|
|
14512
|
+
rule: "vml-unbalanced-tag",
|
|
14513
|
+
severity: "error",
|
|
14514
|
+
message: `<v:${tag.name}> is never closed.`,
|
|
14515
|
+
detail: `Outlook needs the matching </v:${tag.name}>, usually in a later conditional-comment block. Without it the shape swallows the rest of the email.`
|
|
14516
|
+
}, locAt(source, tag.offset, tag.length)));
|
|
14517
|
+
}
|
|
14518
|
+
return { hasVml: true, issues };
|
|
14519
|
+
}
|
|
14520
|
+
|
|
14333
14521
|
// src/visual-checker.ts
|
|
14334
14522
|
import * as csstree8 from "css-tree";
|
|
14335
14523
|
var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set(["inherit", "initial", "unset", "revert", "revert-layer"]);
|
|
@@ -14660,6 +14848,7 @@ var EMPTY_AUDIT = {
|
|
|
14660
14848
|
templateVariables: EMPTY_TEMPLATE,
|
|
14661
14849
|
overflow: EMPTY_OVERFLOW,
|
|
14662
14850
|
visual: EMPTY_VISUAL,
|
|
14851
|
+
vml: EMPTY_VML,
|
|
14663
14852
|
darkContrast: [],
|
|
14664
14853
|
mobileContrast: [],
|
|
14665
14854
|
design: EMPTY_DESIGN
|
|
@@ -14687,13 +14876,14 @@ function runAudit($, html, framework, options) {
|
|
|
14687
14876
|
const templateVariables = skip.has("templateVariables") ? EMPTY_TEMPLATE : checkTemplateVariablesFromDom($, source);
|
|
14688
14877
|
const overflow = skip.has("overflow") ? EMPTY_OVERFLOW : checkOverflowFromDom($, source);
|
|
14689
14878
|
const visual = skip.has("visual") ? EMPTY_VISUAL : checkVisualFromDom($, source);
|
|
14879
|
+
const vml = skip.has("vml") ? EMPTY_VML : checkVml(html, { positions: !!source });
|
|
14690
14880
|
const darkContrast = skip.has("darkContrast") || skip.has("accessibility") ? [] : dedupeByElement([
|
|
14691
14881
|
...checkDarkModeContrast(html, accessibility.issues),
|
|
14692
14882
|
...checkDarkStylesContrastFromDom($, accessibility.issues)
|
|
14693
14883
|
]);
|
|
14694
14884
|
const design = skip.has("design") ? EMPTY_DESIGN : checkDesignConsistencyFromDom($);
|
|
14695
14885
|
const mobileContrast = skip.has("mobileContrast") || skip.has("accessibility") ? [] : checkMobileContrastFromDom($, accessibility.issues);
|
|
14696
|
-
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables, overflow, visual, darkContrast, mobileContrast, design };
|
|
14886
|
+
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables, overflow, visual, vml, darkContrast, mobileContrast, design };
|
|
14697
14887
|
}
|
|
14698
14888
|
function auditEmail(html, options) {
|
|
14699
14889
|
return fromHtml(html, EMPTY_AUDIT, ($, h) => runAudit($, h, options == null ? void 0 : options.framework, options), options);
|
|
@@ -14916,6 +15106,7 @@ export {
|
|
|
14916
15106
|
checkSize,
|
|
14917
15107
|
checkTemplateVariables,
|
|
14918
15108
|
checkVisual,
|
|
15109
|
+
checkVml,
|
|
14919
15110
|
colorDistance,
|
|
14920
15111
|
contrastRatio,
|
|
14921
15112
|
createSession,
|