@templatical/quality 0.7.3 → 0.8.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/index.js CHANGED
@@ -1,41 +1,54 @@
1
- import { HEADING_LEVEL_FONT_SIZE as e, isButton as t, isImage as n, isMenu as r, isParagraph as i, isSection as a, isTable as o, isTitle as s } from "@templatical/types";
2
- import { Parser as c } from "htmlparser2";
1
+ import { HEADING_LEVEL_FONT_SIZE as e, isButton as t, isHtml as n, isImage as r, isMenu as i, isParagraph as a, isSection as o, isSocialIcons as s, isTable as ee, isTitle as c, isVideo as te } from "@templatical/types";
2
+ import { Parser as l } from "htmlparser2";
3
3
  //#region \0rolldown/runtime.js
4
- var l = Object.defineProperty, u = (e, t) => {
4
+ var u = Object.defineProperty, d = (e, t) => {
5
5
  let n = {};
6
- for (var r in e) l(n, r, {
6
+ for (var r in e) u(n, r, {
7
7
  get: e[r],
8
8
  enumerable: !0
9
9
  });
10
- return t || l(n, Symbol.toStringTag, { value: "Module" }), n;
11
- }, d = {
10
+ return t || u(n, Symbol.toStringTag, { value: "Module" }), n;
11
+ }, f = {
12
12
  altMaxLength: 125,
13
13
  minFontSize: 14,
14
14
  allCapsMinLength: 20,
15
15
  minTouchTargetPx: 44
16
- };
16
+ }, p = [
17
+ "localhost",
18
+ "127.0.0.1",
19
+ "0.0.0.0",
20
+ "*.local",
21
+ "*.staging.*",
22
+ "*.dev.*"
23
+ ];
17
24
  //#endregion
18
25
  //#region src/contrast.ts
19
- function f(e, t) {
26
+ function m(e, t) {
20
27
  let n = h(e), r = h(t);
21
28
  if (!n || !r) return NaN;
22
29
  let i = _(n), a = _(r), o = Math.max(i, a), s = Math.min(i, a);
23
30
  return (o + .05) / (s + .05);
24
31
  }
25
- var p = /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i, m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
32
+ var ne = /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i, re = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i, ie = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
26
33
  function h(e) {
27
34
  if (typeof e != "string") return null;
28
- let t = e.trim(), n = m.exec(t);
29
- if (n) return {
35
+ let t = e.trim(), n = ie.exec(t);
36
+ if (n) return n[4].toLowerCase() === "ff" ? {
30
37
  r: parseInt(n[1], 16),
31
38
  g: parseInt(n[2], 16),
32
39
  b: parseInt(n[3], 16)
40
+ } : null;
41
+ let r = re.exec(t);
42
+ if (r) return {
43
+ r: parseInt(r[1], 16),
44
+ g: parseInt(r[2], 16),
45
+ b: parseInt(r[3], 16)
33
46
  };
34
- let r = p.exec(t);
35
- return r ? {
36
- r: parseInt(r[1] + r[1], 16),
37
- g: parseInt(r[2] + r[2], 16),
38
- b: parseInt(r[3] + r[3], 16)
47
+ let i = ne.exec(t);
48
+ return i ? {
49
+ r: parseInt(i[1] + i[1], 16),
50
+ g: parseInt(i[2] + i[2], 16),
51
+ b: parseInt(i[3] + i[3], 16)
39
52
  } : null;
40
53
  }
41
54
  function g(e) {
@@ -50,18 +63,20 @@ function v(e) {
50
63
  }
51
64
  //#endregion
52
65
  //#region src/walk.ts
53
- var y = "#ffffff";
54
- function b(e, t) {
55
- let n = g(e.settings.backgroundColor) ? e.settings.backgroundColor.toLowerCase() : y, r = (e, n) => {
56
- if (t(e, n), !a(e)) return;
57
- let i = e.styles?.backgroundColor, o = g(i) ? i.toLowerCase() : n.resolvedBackgroundColor;
58
- e.children.forEach((t, i) => {
66
+ var ae = "#ffffff";
67
+ function y(e, t) {
68
+ let n = g(e.settings.backgroundColor) ? e.settings.backgroundColor.toLowerCase() : ae, r = (e, n) => {
69
+ let i = e.styles?.backgroundColor, a = g(i) ? i.toLowerCase() : n.resolvedBackgroundColor;
70
+ t(e, a === n.resolvedBackgroundColor ? n : {
71
+ ...n,
72
+ resolvedBackgroundColor: a
73
+ }), o(e) && e.children.forEach((t, i) => {
59
74
  t.forEach((t) => r(t, {
60
75
  parent: e,
61
76
  section: e,
62
77
  columnIndex: i,
63
78
  depth: n.depth + 1,
64
- resolvedBackgroundColor: o
79
+ resolvedBackgroundColor: a
65
80
  }));
66
81
  });
67
82
  };
@@ -74,122 +89,190 @@ function b(e, t) {
74
89
  });
75
90
  }
76
91
  //#endregion
92
+ //#region src/run-rules.ts
93
+ function b(e, t, n, r) {
94
+ let i = [];
95
+ function a(e, t, i) {
96
+ return {
97
+ blockId: i.blockId,
98
+ ruleId: e,
99
+ severity: t,
100
+ message: r(n.locale, e, i.params),
101
+ fix: i.fix
102
+ };
103
+ }
104
+ y(e, (e, r) => {
105
+ for (let o of t) {
106
+ let t = n.severity(o.meta.id);
107
+ if (t === "off" || !o.block) continue;
108
+ let s = o.block(e, r, n);
109
+ s !== null && i.push(a(o.meta.id, t, s));
110
+ }
111
+ });
112
+ for (let r of t) {
113
+ let t = n.severity(r.meta.id);
114
+ if (t === "off" || !r.template) continue;
115
+ let o = r.template(e, n);
116
+ for (let e of o) i.push(a(r.meta.id, t, e));
117
+ }
118
+ return i;
119
+ }
120
+ function x(e) {
121
+ let t = e.overrides ?? {}, n = {
122
+ ...f,
123
+ ...e.thresholds ?? {}
124
+ }, r = { nonProductionHosts: e.nonProductionHosts ?? p }, i = e.locale ?? "en", a = e.rules;
125
+ return {
126
+ locale: i,
127
+ rules: t,
128
+ thresholds: n,
129
+ links: r,
130
+ severity: (e) => {
131
+ let n = t[e];
132
+ return n === void 0 ? a.find((t) => t.meta.id === e)?.meta.severity ?? "warning" : n;
133
+ }
134
+ };
135
+ }
136
+ function oe(e, t, n) {
137
+ return x({
138
+ locale: e,
139
+ rules: n,
140
+ overrides: t.rules,
141
+ thresholds: t.thresholds,
142
+ nonProductionHosts: void 0
143
+ });
144
+ }
145
+ function se(e, t, n) {
146
+ return x({
147
+ locale: e,
148
+ rules: n,
149
+ overrides: t.rules,
150
+ thresholds: void 0,
151
+ nonProductionHosts: void 0
152
+ });
153
+ }
154
+ function ce(e, t, n) {
155
+ return x({
156
+ locale: e,
157
+ rules: n,
158
+ overrides: t.rules,
159
+ thresholds: void 0,
160
+ nonProductionHosts: t.nonProductionHosts
161
+ });
162
+ }
163
+ //#endregion
77
164
  //#region src/accessibility/messages/de.ts
78
- var x = /* @__PURE__ */ u({ default: () => S }), S = {
79
- "img-missing-alt": "Bild ohne Alt-Text. Füge eine kurze Beschreibung hinzu oder markiere das Bild als dekorativ.",
80
- "img-alt-is-filename": "Alt-Text sieht wie ein Dateiname aus (\"{alt}\"). Beschreibe stattdessen kurz, was das Bild zeigt.",
81
- "img-alt-too-long": "Alt-Text ist {length} Zeichen lang; bleibe unter {max}.",
82
- "img-decorative-needs-empty-alt": "Dekoratives Bild hat Alt-Text. Entferne den Alt-Text oder hebe die Markierung als dekorativ auf.",
83
- "img-linked-no-context": "Verlinktes Bild beschreibt nur das Motiv, nicht das Linkziel. Nenne das Ziel (z. B. „Frühlingssale ansehen“).",
84
- "heading-empty": "Überschrift ist leer. Füge Text hinzu oder entferne den Block.",
85
- "heading-skip-level": "Überschrift springt von H{from} auf H{to}. Eine Ebene pro Schritt.",
86
- "heading-multiple-h1": "E-Mail enthält mehr als eine H1. Verwende H1 nur einmal für die Hauptüberschrift.",
87
- "link-empty": "Ein Link in diesem Block hat keinen Text und kein beschriebenes Bild.",
88
- "link-vague-text": "Link-Text „{text}“ ist unspezifisch. Beschreibe stattdessen das Ziel.",
89
- "link-href-empty": "Ein Link in diesem Block hat ein leeres oder „#“-href.",
90
- "link-target-blank-no-rel": "Link öffnet in neuem Tab, aber rel=\"noopener\" fehlt – ergänze es, damit das Ziel nicht auf window.opener zugreifen kann.",
91
- "text-all-caps": "Längere Texte in Großbuchstaben sind schwerer lesbar. Verwende Groß- und Kleinschreibung.",
92
- "text-low-contrast": "Überschriftskontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.",
93
- "text-too-small": "Text ist {size}px; mindestens {min}px verwenden.",
94
- "button-vague-label": "Button-Beschriftung „{text}“ ist unspezifisch. Beschreibe die Aktion.",
95
- "button-touch-target": "Button ist etwa {height}px hoch; mindestens {min}px verwenden, um Fehltipper auf Mobilgeräten zu vermeiden.",
96
- "button-low-contrast": "Buttontextkontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.",
97
- "missing-preheader": "Kein Preheader-Text gesetzt. Postfächer zeigen sonst Bruchstücke des ersten Blocks an."
98
- }, C = /* @__PURE__ */ u({ default: () => w }), w = {
99
- "img-missing-alt": "Image is missing alt text. Add a short description, or mark the image as decorative.",
100
- "img-alt-is-filename": "Alt text looks like a filename (\"{alt}\"). Replace with a short description of what the image conveys.",
101
- "img-alt-too-long": "Alt text is {length} characters; aim for under {max}.",
102
- "img-decorative-needs-empty-alt": "Decorative image has alt text. Either clear the alt text or unmark the image as decorative.",
103
- "img-linked-no-context": "Linked image alt describes the image but not the link destination. Include where the link goes (e.g. 'Buy spring sale').",
104
- "heading-empty": "Heading is empty. Add text or remove the block.",
105
- "heading-skip-level": "Heading jumps from H{from} to H{to}. Step one level at a time.",
106
- "heading-multiple-h1": "Email has more than one H1. Use H1 once for the main heading.",
107
- "link-empty": "A link in this block has no text and no described image.",
108
- "link-vague-text": "Link text \"{text}\" is vague. Describe the destination instead.",
109
- "link-href-empty": "A link in this block has an empty or '#' href.",
110
- "link-target-blank-no-rel": "Link opens in a new tab but is missing rel=\"noopener\" — add it to prevent the destination from accessing window.opener.",
111
- "text-all-caps": "Long all-caps text is harder to read for everyone. Use sentence case.",
112
- "text-low-contrast": "Heading contrast is {ratio}:1; WCAG AA requires at least {required}:1.",
113
- "text-too-small": "Text is {size}px; aim for at least {min}px.",
114
- "button-vague-label": "Button label \"{text}\" is vague. Describe the action.",
115
- "button-touch-target": "Button is roughly {height}px tall; aim for at least {min}px to avoid mis-taps on mobile.",
116
- "button-low-contrast": "Button text contrast is {ratio}:1; WCAG AA requires at least {required}:1.",
117
- "missing-preheader": "No preheader text set. Inboxes will fall back to fragments of the first block."
118
- }, T = /* @__PURE__ */ Object.assign({
119
- "./de.ts": x,
120
- "./en.ts": C
121
- }), E = {};
122
- for (let e in T) {
165
+ var le = /* @__PURE__ */ d({ default: () => ue }), ue = {
166
+ "a11y.img-missing-alt": "Bild ohne Alt-Text. Füge eine kurze Beschreibung hinzu oder markiere das Bild als dekorativ.",
167
+ "a11y.img-alt-is-filename": "Alt-Text sieht wie ein Dateiname aus (\"{alt}\"). Beschreibe stattdessen kurz, was das Bild zeigt.",
168
+ "a11y.img-alt-too-long": "Alt-Text ist {length} Zeichen lang; bleibe unter {max}.",
169
+ "a11y.img-decorative-needs-empty-alt": "Dekoratives Bild hat Alt-Text. Entferne den Alt-Text oder hebe die Markierung als dekorativ auf.",
170
+ "a11y.img-linked-no-context": "Verlinktes Bild beschreibt nur das Motiv, nicht das Linkziel. Nenne das Ziel (z. B. „Frühlingssale ansehen“).",
171
+ "a11y.heading-empty": "Überschrift ist leer. Füge Text hinzu oder entferne den Block.",
172
+ "a11y.heading-skip-level": "Überschrift springt von H{from} auf H{to}. Eine Ebene pro Schritt.",
173
+ "a11y.heading-multiple-h1": "E-Mail enthält mehr als eine H1. Verwende H1 nur einmal für die Hauptüberschrift.",
174
+ "a11y.link-empty": "Ein Link in diesem Block hat keinen Text und kein beschriebenes Bild.",
175
+ "a11y.link-vague-text": "Link-Text „{text}“ ist unspezifisch. Beschreibe stattdessen das Ziel.",
176
+ "a11y.link-href-empty": "Ein Link in diesem Block hat ein leeres oder „#“-href.",
177
+ "a11y.link-target-blank-no-rel": "Link öffnet in neuem Tab, aber rel=\"noopener\" fehlt – ergänze es, damit das Ziel nicht auf window.opener zugreifen kann.",
178
+ "a11y.text-all-caps": "Längere Texte in Großbuchstaben sind schwerer lesbar. Verwende Groß- und Kleinschreibung.",
179
+ "a11y.text-low-contrast": "Überschriftskontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.",
180
+ "a11y.text-too-small": "Text ist {size}px; mindestens {min}px verwenden.",
181
+ "a11y.button-vague-label": "Button-Beschriftung „{text}“ ist unspezifisch. Beschreibe die Aktion.",
182
+ "a11y.button-touch-target": "Button ist etwa {height}px hoch; mindestens {min}px verwenden, um Fehltipper auf Mobilgeräten zu vermeiden.",
183
+ "a11y.button-low-contrast": "Buttontextkontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.",
184
+ "a11y.missing-preheader": "Kein Preheader-Text gesetzt. Postfächer zeigen sonst Bruchstücke des ersten Blocks an."
185
+ }, de = /* @__PURE__ */ d({ default: () => S }), S = {
186
+ "a11y.img-missing-alt": "Image is missing alt text. Add a short description, or mark the image as decorative.",
187
+ "a11y.img-alt-is-filename": "Alt text looks like a filename (\"{alt}\"). Replace with a short description of what the image conveys.",
188
+ "a11y.img-alt-too-long": "Alt text is {length} characters; aim for under {max}.",
189
+ "a11y.img-decorative-needs-empty-alt": "Decorative image has alt text. Either clear the alt text or unmark the image as decorative.",
190
+ "a11y.img-linked-no-context": "Linked image alt describes the image but not the link destination. Include where the link goes (e.g. 'Buy spring sale').",
191
+ "a11y.heading-empty": "Heading is empty. Add text or remove the block.",
192
+ "a11y.heading-skip-level": "Heading jumps from H{from} to H{to}. Step one level at a time.",
193
+ "a11y.heading-multiple-h1": "Email has more than one H1. Use H1 once for the main heading.",
194
+ "a11y.link-empty": "A link in this block has no text and no described image.",
195
+ "a11y.link-vague-text": "Link text \"{text}\" is vague. Describe the destination instead.",
196
+ "a11y.link-href-empty": "A link in this block has an empty or '#' href.",
197
+ "a11y.link-target-blank-no-rel": "Link opens in a new tab but is missing rel=\"noopener\" — add it to prevent the destination from accessing window.opener.",
198
+ "a11y.text-all-caps": "Long all-caps text is harder to read for everyone. Use sentence case.",
199
+ "a11y.text-low-contrast": "Heading contrast is {ratio}:1; WCAG AA requires at least {required}:1.",
200
+ "a11y.text-too-small": "Text is {size}px; aim for at least {min}px.",
201
+ "a11y.button-vague-label": "Button label \"{text}\" is vague. Describe the action.",
202
+ "a11y.button-touch-target": "Button is roughly {height}px tall; aim for at least {min}px to avoid mis-taps on mobile.",
203
+ "a11y.button-low-contrast": "Button text contrast is {ratio}:1; WCAG AA requires at least {required}:1.",
204
+ "a11y.missing-preheader": "No preheader text set. Inboxes will fall back to fragments of the first block."
205
+ }, C = /* @__PURE__ */ Object.assign({
206
+ "./de.ts": le,
207
+ "./en.ts": de
208
+ }), w = {};
209
+ for (let e in C) {
123
210
  let t = /\.\/([^/]+)\.ts$/.exec(e);
124
211
  if (!t) continue;
125
212
  let n = t[1];
126
- n !== "index" && (E[n] = T[e].default);
213
+ n !== "index" && (w[n] = C[e].default);
127
214
  }
128
- var D = Object.keys(E);
129
- function O(e) {
130
- return E[e.split("-")[0]?.toLowerCase() ?? "en"] ?? E.en ?? w;
215
+ var fe = Object.keys(w);
216
+ function T(e) {
217
+ return w[e.split("-")[0]?.toLowerCase() ?? "en"] ?? w.en ?? S;
131
218
  }
132
- function k(e, t, n) {
133
- let r = O(e)[t] ?? w[t];
219
+ function E(e, t, n) {
220
+ let r = T(e)[t] ?? S[t];
134
221
  return n ? r.replace(/\{(\w+)\}/g, (e, t) => {
135
222
  let r = n[t];
136
223
  return r === void 0 ? `{${t}}` : String(r);
137
224
  }) : r;
138
225
  }
139
- var A = {
226
+ var pe = {
140
227
  meta: {
141
- id: "img-missing-alt",
228
+ id: "a11y.img-missing-alt",
142
229
  severity: "error"
143
230
  },
144
231
  block(e) {
145
- return !n(e) || e.decorative === !0 || (e.alt?.trim() ?? "") !== "" || (e.src ?? "").trim() === "" ? null : { blockId: e.id };
232
+ return !r(e) || e.decorative === !0 || (e.alt?.trim() ?? "") !== "" || (e.src ?? "").trim() === "" ? null : { blockId: e.id };
146
233
  }
147
- }, ee = {
148
- id: "img-alt-is-filename",
234
+ }, me = {
235
+ id: "a11y.img-alt-is-filename",
149
236
  severity: "warning"
150
- }, te = [
237
+ }, he = [
151
238
  /\.(jpe?g|png|gif|webp|svg)$/i,
152
239
  /^IMG[_-]?\d+/i,
153
240
  /^Untitled/i,
154
241
  /^Screen[\s_-]?Shot/i,
155
242
  /^DSC[_-]?\d+/i
156
- ], ne = {
157
- meta: ee,
243
+ ], ge = {
244
+ meta: me,
158
245
  block(e) {
159
- if (!n(e) || e.decorative === !0) return null;
246
+ if (!r(e) || e.decorative === !0) return null;
160
247
  let t = e.alt?.trim() ?? "";
161
- return t === "" || !te.some((e) => e.test(t)) ? null : {
248
+ return t === "" || !he.some((e) => e.test(t)) ? null : {
162
249
  blockId: e.id,
163
- params: { alt: t },
164
- fix: {
165
- description: "Clear alt text",
166
- apply: (t) => t.updateBlock(e.id, { alt: "" })
167
- }
250
+ params: { alt: t }
168
251
  };
169
252
  }
170
- }, re = {
253
+ }, _e = {
171
254
  meta: {
172
- id: "img-alt-too-long",
255
+ id: "a11y.img-alt-too-long",
173
256
  severity: "warning"
174
257
  },
175
- block(e, t, r) {
176
- if (!n(e) || e.decorative === !0) return null;
258
+ block(e, t, n) {
259
+ if (!r(e) || e.decorative === !0) return null;
177
260
  let i = e.alt ?? "";
178
- return i.length <= r.thresholds.altMaxLength ? null : {
261
+ return i.length <= n.thresholds.altMaxLength ? null : {
179
262
  blockId: e.id,
180
263
  params: {
181
264
  length: i.length,
182
- max: r.thresholds.altMaxLength
265
+ max: n.thresholds.altMaxLength
183
266
  }
184
267
  };
185
268
  }
186
- }, ie = {
269
+ }, ve = {
187
270
  meta: {
188
- id: "img-decorative-needs-empty-alt",
271
+ id: "a11y.img-decorative-needs-empty-alt",
189
272
  severity: "info"
190
273
  },
191
274
  block(e) {
192
- return !n(e) || e.decorative !== !0 || (e.alt ?? "") === "" ? null : {
275
+ return !r(e) || e.decorative !== !0 || (e.alt ?? "").trim() === "" ? null : {
193
276
  blockId: e.id,
194
277
  fix: {
195
278
  description: "Clear alt text",
@@ -197,7 +280,7 @@ var A = {
197
280
  }
198
281
  };
199
282
  }
200
- }, j = /* @__PURE__ */ u({ default: () => M }), M = {
283
+ }, ye = /* @__PURE__ */ d({ default: () => be }), be = {
201
284
  vagueLinkText: [
202
285
  "hier klicken",
203
286
  "hier",
@@ -240,7 +323,7 @@ var A = {
240
323
  "anschauen",
241
324
  "jetzt"
242
325
  ]
243
- }, N = /* @__PURE__ */ u({ default: () => P }), P = {
326
+ }, xe = /* @__PURE__ */ d({ default: () => Se }), Se = {
244
327
  vagueLinkText: [
245
328
  "click here",
246
329
  "here",
@@ -281,49 +364,49 @@ var A = {
281
364
  "redeem",
282
365
  "watch"
283
366
  ]
284
- }, F = /* @__PURE__ */ Object.assign({
285
- "./de.ts": j,
286
- "./en.ts": N
287
- }), I = {};
288
- for (let e in F) {
367
+ }, D = /* @__PURE__ */ Object.assign({
368
+ "./de.ts": ye,
369
+ "./en.ts": xe
370
+ }), O = {};
371
+ for (let e in D) {
289
372
  let t = /\.\/([^/]+)\.ts$/.exec(e);
290
373
  if (!t) continue;
291
374
  let n = t[1];
292
- n !== "index" && (I[n] = F[e].default);
375
+ n !== "index" && (O[n] = D[e].default);
293
376
  }
294
- function L(e) {
295
- return z;
377
+ function k(e) {
378
+ return Ce;
296
379
  }
297
- function R(e) {
380
+ function A(e) {
298
381
  let t = /* @__PURE__ */ new Set();
299
- for (let n of Object.values(I)) for (let r of e(n)) t.add(r);
382
+ for (let n of Object.values(O)) for (let r of e(n)) t.add(r);
300
383
  return Array.from(t);
301
384
  }
302
- var z = {
303
- vagueLinkText: R((e) => e.vagueLinkText),
304
- vagueButtonLabels: R((e) => e.vagueButtonLabels),
305
- linkedImageActionHints: R((e) => e.linkedImageActionHints)
306
- }, B = Object.keys(I);
307
- function V(e) {
385
+ var Ce = {
386
+ vagueLinkText: A((e) => e.vagueLinkText),
387
+ vagueButtonLabels: A((e) => e.vagueButtonLabels),
388
+ linkedImageActionHints: A((e) => e.linkedImageActionHints)
389
+ }, we = Object.keys(O);
390
+ function j(e) {
308
391
  return e.toLowerCase().replace(/\s+/g, " ").replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "").trim();
309
392
  }
310
- var H = {
393
+ var Te = {
311
394
  meta: {
312
- id: "img-linked-no-context",
395
+ id: "a11y.img-linked-no-context",
313
396
  severity: "warning"
314
397
  },
315
- block(e, t, r) {
316
- if (!n(e) || e.decorative === !0 || !e.linkUrl || e.linkUrl.trim() === "") return null;
398
+ block(e, t, n) {
399
+ if (!r(e) || e.decorative === !0 || !e.linkUrl || e.linkUrl.trim() === "") return null;
317
400
  let i = (e.alt ?? "").trim();
318
401
  if (i === "") return null;
319
- let a = i.toLocaleLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean), o = L(r.locale).linkedImageActionHints;
402
+ let a = i.toLocaleLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean), o = k(n.locale).linkedImageActionHints;
320
403
  return a.some((e) => o.includes(e)) ? null : { blockId: e.id };
321
404
  }
322
405
  };
323
406
  //#endregion
324
407
  //#region src/html-utils.ts
325
- function U(e) {
326
- let t = [], n = [], r = new c({
408
+ function M(e) {
409
+ let t = [], n = [], r = new l({
327
410
  onopentag(e, t) {
328
411
  if (e === "a") {
329
412
  let e = {
@@ -353,38 +436,38 @@ function U(e) {
353
436
  });
354
437
  return r.write(e), r.end(), t;
355
438
  }
356
- function W(e) {
357
- let t = "", n = new c({ ontext(e) {
439
+ function N(e) {
440
+ let t = "", n = new l({ ontext(e) {
358
441
  t += e;
359
442
  } });
360
443
  return n.write(e), n.end(), t.trim();
361
444
  }
362
- var G = {
445
+ var Ee = {
363
446
  meta: {
364
- id: "heading-empty",
447
+ id: "a11y.heading-empty",
365
448
  severity: "error"
366
449
  },
367
450
  block(e) {
368
- return !s(e) || W(e.content ?? "") !== "" ? null : { blockId: e.id };
451
+ return !c(e) || N(e.content ?? "") !== "" ? null : { blockId: e.id };
369
452
  }
370
- }, K = {
371
- id: "heading-skip-level",
453
+ }, De = {
454
+ id: "a11y.heading-skip-level",
372
455
  severity: "error"
373
456
  };
374
- function q(e, t) {
457
+ function P(e, t) {
375
458
  for (let n of e) {
376
- if (s(n)) {
459
+ if (c(n)) {
377
460
  t.push(n);
378
461
  continue;
379
462
  }
380
- if (a(n)) for (let e of n.children) q(e, t);
463
+ if (o(n)) for (let e of n.children) P(e, t);
381
464
  }
382
465
  }
383
- var J = {
384
- meta: K,
466
+ var Oe = {
467
+ meta: De,
385
468
  template(e) {
386
469
  let t = [];
387
- q(e.blocks, t);
470
+ P(e.blocks, t);
388
471
  let n = [], r = 0;
389
472
  for (let e of t) r !== 0 && e.level > r + 1 && n.push({
390
473
  blockId: e.id,
@@ -395,54 +478,54 @@ var J = {
395
478
  }), r = e.level;
396
479
  return n;
397
480
  }
398
- }, Y = {
399
- id: "heading-multiple-h1",
481
+ }, ke = {
482
+ id: "a11y.heading-multiple-h1",
400
483
  severity: "warning"
401
484
  };
402
- function X(e, t) {
485
+ function F(e, t) {
403
486
  for (let n of e) {
404
- if (s(n)) {
487
+ if (c(n)) {
405
488
  t.push(n);
406
489
  continue;
407
490
  }
408
- if (a(n)) for (let e of n.children) X(e, t);
491
+ if (o(n)) for (let e of n.children) F(e, t);
409
492
  }
410
493
  }
411
- var ae = {
412
- meta: Y,
494
+ var Ae = {
495
+ meta: ke,
413
496
  template(e) {
414
497
  let t = [];
415
- X(e.blocks, t);
498
+ F(e.blocks, t);
416
499
  let n = t.filter((e) => e.level === 1);
417
500
  return n.length <= 1 ? [] : n.slice(1).map((e) => ({ blockId: e.id }));
418
501
  }
419
- }, oe = {
420
- id: "link-empty",
502
+ }, je = {
503
+ id: "a11y.link-empty",
421
504
  severity: "error"
422
505
  };
423
- function se(e) {
424
- return i(e) || s(e) ? e.content : null;
506
+ function Me(e) {
507
+ return a(e) || c(e) ? e.content : null;
425
508
  }
426
- var ce = {
427
- meta: oe,
509
+ var Ne = {
510
+ meta: je,
428
511
  block(e) {
429
- let t = se(e);
430
- return t === null || !U(t).find((e) => e.text === "" && !e.hasImageWithAlt) ? null : { blockId: e.id };
512
+ let t = Me(e);
513
+ return t === null || !M(t).find((e) => e.text === "" && !e.hasImageWithAlt) ? null : { blockId: e.id };
431
514
  }
432
- }, le = {
433
- id: "link-vague-text",
515
+ }, Pe = {
516
+ id: "a11y.link-vague-text",
434
517
  severity: "warning"
435
518
  };
436
- function ue(e) {
437
- return i(e) || s(e) ? e.content : null;
519
+ function Fe(e) {
520
+ return a(e) || c(e) ? e.content : null;
438
521
  }
439
- var de = {
440
- meta: le,
522
+ var Ie = {
523
+ meta: Pe,
441
524
  block(e, t, n) {
442
- let r = ue(e);
525
+ let r = Fe(e);
443
526
  if (r === null) return null;
444
- let i = L(n.locale).vagueLinkText, a = U(r).find((e) => {
445
- let t = V(e.text);
527
+ let i = k(n.locale).vagueLinkText, a = M(r).find((e) => {
528
+ let t = j(e.text);
446
529
  return t !== "" && i.includes(t);
447
530
  });
448
531
  return a ? {
@@ -450,53 +533,53 @@ var de = {
450
533
  params: { text: a.text }
451
534
  } : null;
452
535
  }
453
- }, fe = {
454
- id: "link-href-empty",
536
+ }, Le = {
537
+ id: "a11y.link-href-empty",
455
538
  severity: "error"
456
539
  };
457
- function pe(e) {
458
- return i(e) || s(e) ? e.content : null;
540
+ function Re(e) {
541
+ return a(e) || c(e) ? e.content : null;
459
542
  }
460
- var me = {
461
- meta: fe,
543
+ var ze = {
544
+ meta: Le,
462
545
  block(e) {
463
- let t = pe(e);
464
- return t === null || !U(t).find((e) => {
546
+ let t = Re(e);
547
+ return t === null || !M(t).find((e) => {
465
548
  let t = e.href.trim();
466
549
  return t === "" || t === "#";
467
550
  }) ? null : { blockId: e.id };
468
551
  }
469
- }, he = {
470
- id: "link-target-blank-no-rel",
552
+ }, Be = {
553
+ id: "a11y.link-target-blank-no-rel",
471
554
  severity: "warning"
472
555
  };
473
- function ge(e) {
474
- return i(e) || s(e) ? e.content : null;
556
+ function Ve(e) {
557
+ return a(e) || c(e) ? e.content : null;
475
558
  }
476
- function _e(e) {
559
+ function He(e) {
477
560
  if (e === null) return !1;
478
561
  let t = e.toLowerCase().split(/\s+/);
479
562
  return t.includes("noopener") || t.includes("noreferrer");
480
563
  }
481
- var ve = {
482
- meta: he,
564
+ var Ue = {
565
+ meta: Be,
483
566
  block(e) {
484
- let t = ge(e);
485
- return t === null || !U(t).find((e) => e.target === "_blank" && !_e(e.rel)) ? null : {
567
+ let t = Ve(e);
568
+ return t === null || !M(t).find((e) => e.target === "_blank" && !He(e.rel)) ? null : {
486
569
  blockId: e.id,
487
570
  fix: {
488
571
  description: "Add rel=\"noopener\"",
489
572
  apply: (t) => {
490
- if (!i(e) && !s(e)) return;
491
- let n = xe(e.content ?? "");
573
+ if (!a(e) && !c(e)) return;
574
+ let n = Ke(e.content ?? "");
492
575
  t.updateBlock(e.id, { content: n });
493
576
  }
494
577
  }
495
578
  };
496
579
  }
497
- }, Z = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
498
- function ye(e) {
499
- let t = [], n = new RegExp(Z.source, Z.flags), r;
580
+ }, I = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
581
+ function We(e) {
582
+ let t = [], n = new RegExp(I.source, I.flags), r;
500
583
  for (; (r = n.exec(e)) !== null;) {
501
584
  let e = r[2] ?? r[3] ?? r[4] ?? null;
502
585
  t.push({
@@ -508,13 +591,13 @@ function ye(e) {
508
591
  }
509
592
  return t;
510
593
  }
511
- function be(e) {
594
+ function Ge(e) {
512
595
  return e.some((e) => e.name.toLowerCase() === "target" && e.value !== null && e.value.toLowerCase() === "_blank");
513
596
  }
514
- function xe(e) {
597
+ function Ke(e) {
515
598
  return e.replace(/<a\b([^>]*)>/gi, (e, t) => {
516
- let n = ye(t);
517
- if (!be(n)) return e;
599
+ let n = We(t);
600
+ if (!Ge(n)) return e;
518
601
  let r = n.find((e) => e.name.toLowerCase() === "rel");
519
602
  if (r) {
520
603
  let n = (r.value ?? "").toLowerCase().split(/\s+/);
@@ -525,24 +608,24 @@ function xe(e) {
525
608
  return `<a${t} rel="noopener">`;
526
609
  });
527
610
  }
528
- var Se = {
611
+ var qe = {
529
612
  meta: {
530
- id: "text-all-caps",
613
+ id: "a11y.text-all-caps",
531
614
  severity: "warning"
532
615
  },
533
616
  block(e, t, n) {
534
- if (!i(e) && !s(e)) return null;
535
- let r = W(e.content ?? "").replace(/[^\p{L}]/gu, "");
617
+ if (!a(e) && !c(e)) return null;
618
+ let r = N(e.content ?? "").replace(/[^\p{L}]/gu, "");
536
619
  return r.length < n.thresholds.allCapsMinLength || r !== r.toLocaleUpperCase() ? null : { blockId: e.id };
537
620
  }
538
- }, Ce = {
621
+ }, Je = {
539
622
  meta: {
540
- id: "text-low-contrast",
623
+ id: "a11y.text-low-contrast",
541
624
  severity: "error"
542
625
  },
543
626
  block(t, n) {
544
- if (!s(t) || !g(t.color) || !g(n.resolvedBackgroundColor)) return null;
545
- let r = e[t.level] >= 24 ? 3 : 4.5, i = f(t.color, n.resolvedBackgroundColor);
627
+ if (!c(t) || !g(t.color) || !g(n.resolvedBackgroundColor)) return null;
628
+ let r = e[t.level] >= 24 ? 3 : 4.5, i = m(t.color, n.resolvedBackgroundColor);
546
629
  return Number.isNaN(i) || i >= r ? null : {
547
630
  blockId: t.id,
548
631
  params: {
@@ -551,34 +634,34 @@ var Se = {
551
634
  }
552
635
  };
553
636
  }
554
- }, we = {
555
- id: "text-too-small",
637
+ }, Ye = {
638
+ id: "a11y.text-too-small",
556
639
  severity: "warning"
557
640
  };
558
- function Q(e) {
559
- return r(e) || o(e) ? e.fontSize : null;
641
+ function Xe(e) {
642
+ return i(e) || ee(e) ? e.fontSize : null;
560
643
  }
561
644
  //#endregion
562
645
  //#region src/accessibility/index.ts
563
- var $ = [
564
- A,
565
- ne,
566
- re,
567
- ie,
568
- H,
569
- G,
570
- J,
571
- ae,
572
- ce,
573
- de,
574
- me,
646
+ var L = [
647
+ pe,
648
+ ge,
649
+ _e,
575
650
  ve,
576
- Se,
577
- Ce,
651
+ Te,
652
+ Ee,
653
+ Oe,
654
+ Ae,
655
+ Ne,
656
+ Ie,
657
+ ze,
658
+ Ue,
659
+ qe,
660
+ Je,
578
661
  {
579
- meta: we,
662
+ meta: Ye,
580
663
  block(e, t, n) {
581
- let r = Q(e);
664
+ let r = Xe(e);
582
665
  return r === null || r >= n.thresholds.minFontSize ? null : {
583
666
  blockId: e.id,
584
667
  params: {
@@ -590,13 +673,13 @@ var $ = [
590
673
  },
591
674
  {
592
675
  meta: {
593
- id: "button-vague-label",
676
+ id: "a11y.button-vague-label",
594
677
  severity: "warning"
595
678
  },
596
679
  block(e, n, r) {
597
680
  if (!t(e)) return null;
598
- let i = V(e.text ?? "");
599
- return i === "" || !L(r.locale).vagueButtonLabels.includes(i) ? null : {
681
+ let i = j(e.text ?? "");
682
+ return i === "" || !k(r.locale).vagueButtonLabels.includes(i) ? null : {
600
683
  blockId: e.id,
601
684
  params: { text: e.text }
602
685
  };
@@ -604,7 +687,7 @@ var $ = [
604
687
  },
605
688
  {
606
689
  meta: {
607
- id: "button-touch-target",
690
+ id: "a11y.button-touch-target",
608
691
  severity: "warning"
609
692
  },
610
693
  block(e, n, r) {
@@ -623,12 +706,12 @@ var $ = [
623
706
  },
624
707
  {
625
708
  meta: {
626
- id: "button-low-contrast",
709
+ id: "a11y.button-low-contrast",
627
710
  severity: "error"
628
711
  },
629
712
  block(e) {
630
713
  if (!t(e)) return null;
631
- let n = f(e.textColor, e.backgroundColor);
714
+ let n = m(e.textColor, e.backgroundColor);
632
715
  if (Number.isNaN(n)) return null;
633
716
  let r = e.fontSize >= 24 ? 3 : 4.5;
634
717
  return n >= r ? null : {
@@ -642,7 +725,7 @@ var $ = [
642
725
  },
643
726
  {
644
727
  meta: {
645
- id: "missing-preheader",
728
+ id: "a11y.missing-preheader",
646
729
  severity: "info"
647
730
  },
648
731
  template(e) {
@@ -650,50 +733,400 @@ var $ = [
650
733
  }
651
734
  }
652
735
  ];
653
- function Te(e, t = {}) {
654
- if (t.disabled === !0) return [];
655
- let n = Ee(t), r = [];
656
- function i(e, t, r) {
736
+ function Ze(e, t = {}) {
737
+ if (t.disabled === !0 || t.accessibility === !1) return [];
738
+ let n = t.accessibility ?? {};
739
+ return b(e, L, oe(t.locale, n, L), (e, t, n) => E(e, t, n));
740
+ }
741
+ //#endregion
742
+ //#region src/structure/messages/de.ts
743
+ var Qe = /* @__PURE__ */ d({ default: () => $e }), $e = {
744
+ "structure.duplicate-block-id": "Block-ID erscheint {count}-mal im Baum. Jeder Block muss eine eindeutige ID haben.",
745
+ "structure.section-column-mismatch": "Sektion verwendet Layout „{layout}\" (erwartet {expected} Spalten), hat aber {actual}. Deutet auf beschädigten Zustand hin.",
746
+ "structure.nested-section": "Sektion ist in einer anderen Sektion verschachtelt. Sektionen können nicht verschachtelt werden – der Renderer wird sich falsch verhalten.",
747
+ "structure.empty-section": "Sektion enthält keine Blöcke. Entferne sie oder füge Inhalt hinzu.",
748
+ "structure.empty-column": "Spalte {columnIndex} dieser Sektion ist leer. Füge Inhalt hinzu oder reduziere die Spaltenanzahl."
749
+ }, et = /* @__PURE__ */ d({ default: () => R }), R = {
750
+ "structure.duplicate-block-id": "Block id appears {count} times in the tree. Each block must have a unique id.",
751
+ "structure.section-column-mismatch": "Section uses layout \"{layout}\" (expects {expected} columns) but has {actual}. Indicates corrupted state.",
752
+ "structure.nested-section": "Section is nested inside another section. Sections cannot nest — the renderer will misbehave.",
753
+ "structure.empty-section": "Section has no blocks. Remove it or add content.",
754
+ "structure.empty-column": "Column {columnIndex} of this section is empty. Add content or reduce the column count."
755
+ }, z = /* @__PURE__ */ Object.assign({
756
+ "./de.ts": Qe,
757
+ "./en.ts": et
758
+ }), B = {};
759
+ for (let e in z) {
760
+ let t = /\.\/([^/]+)\.ts$/.exec(e);
761
+ if (!t) continue;
762
+ let n = t[1];
763
+ n !== "index" && (B[n] = z[e].default);
764
+ }
765
+ var tt = Object.keys(B);
766
+ function V(e) {
767
+ return B[e.split("-")[0]?.toLowerCase() ?? "en"] ?? B.en ?? R;
768
+ }
769
+ function H(e, t, n) {
770
+ let r = V(e)[t] ?? R[t];
771
+ return n ? r.replace(/\{(\w+)\}/g, (e, t) => {
772
+ let r = n[t];
773
+ return r === void 0 ? `{${t}}` : String(r);
774
+ }) : r;
775
+ }
776
+ //#endregion
777
+ //#region src/structure/rules/duplicate-block-id.ts
778
+ var nt = {
779
+ id: "structure.duplicate-block-id",
780
+ severity: "error"
781
+ };
782
+ function U(e, t) {
783
+ for (let n of e) if (t.set(n.id, (t.get(n.id) ?? 0) + 1), o(n)) for (let e of n.children) U(e, t);
784
+ }
785
+ var rt = {
786
+ meta: nt,
787
+ template(e) {
788
+ let t = /* @__PURE__ */ new Map();
789
+ U(e.blocks, t);
790
+ let n = [];
791
+ for (let [e, r] of t) r > 1 && n.push({
792
+ blockId: e,
793
+ params: { count: r }
794
+ });
795
+ return n;
796
+ }
797
+ }, it = {
798
+ id: "structure.empty-column",
799
+ severity: "warning"
800
+ };
801
+ function W(e, t) {
802
+ for (let n of e) {
803
+ if (!o(n)) continue;
804
+ let e = n;
805
+ e.children.length > 1 && e.children.forEach((n, r) => {
806
+ n.length === 0 && t.push({
807
+ blockId: e.id,
808
+ params: { columnIndex: r + 1 }
809
+ });
810
+ });
811
+ for (let n of e.children) W(n, t);
812
+ }
813
+ }
814
+ var at = {
815
+ meta: it,
816
+ template(e) {
817
+ let t = [];
818
+ return W(e.blocks, t), t;
819
+ }
820
+ }, ot = {
821
+ id: "structure.empty-section",
822
+ severity: "warning"
823
+ };
824
+ function st(e) {
825
+ return e.children.length === 0 ? !0 : e.children.every((e) => e.length === 0);
826
+ }
827
+ var ct = {
828
+ meta: ot,
829
+ block(e) {
830
+ if (!o(e)) return null;
831
+ let t = e;
832
+ return st(t) ? {
833
+ blockId: t.id,
834
+ fix: {
835
+ description: "Remove the empty section",
836
+ apply: (e) => {
837
+ e.removeBlock(t.id);
838
+ }
839
+ }
840
+ } : null;
841
+ }
842
+ }, lt = {
843
+ meta: {
844
+ id: "structure.nested-section",
845
+ severity: "error"
846
+ },
847
+ block(e, t) {
848
+ if (!o(e) || t.section === null) return null;
849
+ let n = t.section;
657
850
  return {
658
- blockId: r.blockId,
659
- ruleId: e,
660
- severity: t,
661
- message: k(n.locale, e, r.params),
662
- fix: r.fix
851
+ blockId: e.id,
852
+ params: { parentId: n.id }
663
853
  };
664
854
  }
665
- b(e, (e, t) => {
666
- for (let a of $) {
667
- let o = n.severity(a.meta.id);
668
- if (o === "off" || !a.block) continue;
669
- let s = a.block(e, t, n);
670
- s !== null && r.push(i(a.meta.id, o, s));
855
+ }, ut = {
856
+ id: "structure.section-column-mismatch",
857
+ severity: "error"
858
+ };
859
+ function dt(e) {
860
+ return e === "1" ? 1 : e === "3" ? 3 : 2;
861
+ }
862
+ //#endregion
863
+ //#region src/structure/index.ts
864
+ var G = [
865
+ rt,
866
+ ct,
867
+ at,
868
+ lt,
869
+ {
870
+ meta: ut,
871
+ block(e) {
872
+ if (!o(e)) return null;
873
+ let t = e, n = dt(t.columns), r = t.children.length;
874
+ return r === n ? null : {
875
+ blockId: t.id,
876
+ params: {
877
+ layout: t.columns,
878
+ expected: n,
879
+ actual: r
880
+ }
881
+ };
671
882
  }
672
- });
673
- for (let t of $) {
674
- let a = n.severity(t.meta.id);
675
- if (a === "off" || !t.template) continue;
676
- let o = t.template(e, n);
677
- for (let e of o) r.push(i(t.meta.id, a, e));
678
883
  }
679
- return r;
884
+ ];
885
+ function ft(e, t = {}) {
886
+ if (t.disabled === !0 || t.structure === !1) return [];
887
+ let n = t.structure ?? {};
888
+ return b(e, G, se(t.locale, n, G), (e, t, n) => H(e, t, n));
680
889
  }
681
- function Ee(e) {
682
- let t = e.rules ?? {}, n = {
683
- ...d,
684
- ...e.thresholds ?? {}
685
- };
686
- return {
687
- locale: e.locale ?? "en",
688
- rules: t,
689
- thresholds: n,
690
- severity: (e) => {
691
- let n = t[e];
692
- return n === void 0 ? $.find((t) => t.meta.id === e)?.meta.severity ?? "warning" : n;
890
+ //#endregion
891
+ //#region src/links/messages/de.ts
892
+ var pt = /* @__PURE__ */ d({ default: () => mt }), mt = {
893
+ "link.javascript-protocol": "Die URL verwendet das „javascript:\"-Protokoll, das aus Sicherheitsgründen beim Rendern entfernt wird. Ersetze sie durch eine echte URL oder entferne sie.",
894
+ "link.unsupported-protocol": "Die URL verwendet das Protokoll „{protocol}\", das von den meisten E-Mail-Clients nicht unterstützt wird. Verwende http, https, mailto, tel oder sms.",
895
+ "link.malformed-mailto": "Der mailto:-Link ist fehlerhaft. Erwartet wird eine einzelne Empfängeradresse vor einer eventuellen Querystring (z. B. mailto:hallo@example.com).",
896
+ "link.malformed-tel": "Der tel:-Link enthält Zeichen, die keine Ziffern, +, Leerzeichen, Bindestriche, Klammern oder Punkte sind.",
897
+ "link.localhost-or-staging": "Der URL-Host „{host}\" entspricht einem Nicht-Produktionsmuster. Ersetze ihn vor dem Versand durch die Produktions-URL."
898
+ }, ht = /* @__PURE__ */ d({ default: () => K }), K = {
899
+ "link.javascript-protocol": "URL uses the \"javascript:\" protocol, which is stripped at render time for safety. Replace it with a real link or remove the URL.",
900
+ "link.unsupported-protocol": "URL uses the \"{protocol}\" protocol, which most email clients do not support. Use http, https, mailto, tel, or sms.",
901
+ "link.malformed-mailto": "mailto: link is malformed. Expected a single recipient address before any query string (e.g. mailto:hello@example.com).",
902
+ "link.malformed-tel": "tel: link contains characters that are not digits, +, spaces, dashes, parentheses, or dots.",
903
+ "link.localhost-or-staging": "URL host \"{host}\" matches a non-production pattern. Replace with the production URL before sending."
904
+ }, q = /* @__PURE__ */ Object.assign({
905
+ "./de.ts": pt,
906
+ "./en.ts": ht
907
+ }), J = {};
908
+ for (let e in q) {
909
+ let t = /\.\/([^/]+)\.ts$/.exec(e);
910
+ if (!t) continue;
911
+ let n = t[1];
912
+ n !== "index" && (J[n] = q[e].default);
913
+ }
914
+ var gt = Object.keys(J);
915
+ function Y(e) {
916
+ return J[e.split("-")[0]?.toLowerCase() ?? "en"] ?? J.en ?? K;
917
+ }
918
+ function X(e, t, n) {
919
+ let r = Y(e)[t] ?? K[t];
920
+ return n ? r.replace(/\{(\w+)\}/g, (e, t) => {
921
+ let r = n[t];
922
+ return r === void 0 ? `{${t}}` : String(r);
923
+ }) : r;
924
+ }
925
+ //#endregion
926
+ //#region src/url-walker.ts
927
+ function Z(e) {
928
+ let o = [];
929
+ return y(e, (e) => {
930
+ if (c(e) || a(e) || n(e)) {
931
+ for (let t of M(e.content)) o.push({
932
+ url: t.href,
933
+ blockId: e.id,
934
+ source: "anchor",
935
+ label: t.text
936
+ });
937
+ return;
693
938
  }
694
- };
939
+ if (t(e)) {
940
+ o.push({
941
+ url: e.url,
942
+ blockId: e.id,
943
+ source: "button",
944
+ label: e.text
945
+ });
946
+ return;
947
+ }
948
+ if (r(e)) {
949
+ e.linkUrl && e.linkUrl !== "" && o.push({
950
+ url: e.linkUrl,
951
+ blockId: e.id,
952
+ source: "image-link",
953
+ label: e.alt || void 0
954
+ });
955
+ return;
956
+ }
957
+ if (te(e)) {
958
+ o.push({
959
+ url: e.url,
960
+ blockId: e.id,
961
+ source: "video",
962
+ label: e.alt || void 0
963
+ });
964
+ return;
965
+ }
966
+ if (i(e)) {
967
+ for (let t of e.items) o.push({
968
+ url: t.url,
969
+ blockId: e.id,
970
+ source: "menu-item",
971
+ label: t.text
972
+ });
973
+ return;
974
+ }
975
+ if (s(e)) {
976
+ for (let t of e.icons) o.push({
977
+ url: t.url,
978
+ blockId: e.id,
979
+ source: "social-icon",
980
+ label: t.platform
981
+ });
982
+ return;
983
+ }
984
+ }), o;
985
+ }
986
+ //#endregion
987
+ //#region src/links/rules/javascript-protocol.ts
988
+ var _t = {
989
+ id: "link.javascript-protocol",
990
+ severity: "error"
991
+ };
992
+ function vt(e) {
993
+ if (!e) return !1;
994
+ let t = e.replace(/\s+/g, "");
995
+ return /^javascript:/i.test(t);
996
+ }
997
+ var yt = {
998
+ meta: _t,
999
+ template(e) {
1000
+ let t = [];
1001
+ for (let n of Z(e)) vt(n.url) && t.push({ blockId: n.blockId });
1002
+ return t;
1003
+ }
1004
+ }, bt = {
1005
+ id: "link.unsupported-protocol",
1006
+ severity: "warning"
1007
+ }, xt = new Set([
1008
+ "http",
1009
+ "https",
1010
+ "mailto",
1011
+ "tel",
1012
+ "sms"
1013
+ ]);
1014
+ function St(e) {
1015
+ if (!e) return null;
1016
+ let t = e.trim(), n = /^([a-z][a-z0-9+\-.]*):/i.exec(t);
1017
+ return n ? n[1].toLowerCase() : null;
1018
+ }
1019
+ var Ct = {
1020
+ meta: bt,
1021
+ template(e) {
1022
+ let t = [];
1023
+ for (let n of Z(e)) {
1024
+ let e = St(n.url);
1025
+ e !== null && e !== "javascript" && (xt.has(e) || t.push({
1026
+ blockId: n.blockId,
1027
+ params: { protocol: e }
1028
+ }));
1029
+ }
1030
+ return t;
1031
+ }
1032
+ }, wt = {
1033
+ id: "link.malformed-mailto",
1034
+ severity: "warning"
1035
+ };
1036
+ function Tt(e) {
1037
+ let t = e.trim();
1038
+ if (!/^mailto:/i.test(t)) return !1;
1039
+ let [n] = t.slice(7).split("?", 2);
1040
+ if (n.trim() === "") return !0;
1041
+ let r = n.split(",").map((e) => e.trim());
1042
+ for (let e of r) {
1043
+ if (e === "") return !0;
1044
+ let t = e.split("@");
1045
+ if (t.length !== 2) return !0;
1046
+ let [n, r] = t;
1047
+ if (n === "" || r === "" || !r.includes(".")) return !0;
1048
+ }
1049
+ return !1;
1050
+ }
1051
+ var Et = {
1052
+ meta: wt,
1053
+ template(e) {
1054
+ let t = [];
1055
+ for (let n of Z(e)) Tt(n.url) && t.push({ blockId: n.blockId });
1056
+ return t;
1057
+ }
1058
+ }, Dt = {
1059
+ id: "link.malformed-tel",
1060
+ severity: "warning"
1061
+ }, Q = /^[+0-9\s().\-]+$/, Ot = /^[A-Za-z0-9-]+(=[^;]+)?$/;
1062
+ function kt(e) {
1063
+ let t = e.trim();
1064
+ if (!/^tel:/i.test(t)) return !1;
1065
+ let n = t.slice(4).trim();
1066
+ if (n === "") return !0;
1067
+ let [r, ...i] = n.split(";");
1068
+ return Q.test(r) ? i.some((e) => !Ot.test(e)) : !0;
1069
+ }
1070
+ var At = {
1071
+ meta: Dt,
1072
+ template(e) {
1073
+ let t = [];
1074
+ for (let n of Z(e)) kt(n.url) && t.push({ blockId: n.blockId });
1075
+ return t;
1076
+ }
1077
+ }, jt = {
1078
+ id: "link.localhost-or-staging",
1079
+ severity: "warning"
1080
+ };
1081
+ function Mt(e) {
1082
+ let t = e.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1083
+ return RegExp(`^${t}$`, "i");
1084
+ }
1085
+ function Nt(e) {
1086
+ if (!e) return null;
1087
+ let t = e.trim();
1088
+ if (!/^(https?|ftps?):\/\//i.test(t)) return null;
1089
+ try {
1090
+ return new URL(t).hostname.toLowerCase();
1091
+ } catch {
1092
+ return null;
1093
+ }
1094
+ }
1095
+ //#endregion
1096
+ //#region src/links/index.ts
1097
+ var $ = [
1098
+ yt,
1099
+ Ct,
1100
+ Et,
1101
+ At,
1102
+ {
1103
+ meta: jt,
1104
+ template(e, t) {
1105
+ let n = t.links.nonProductionHosts;
1106
+ if (n.length === 0) return [];
1107
+ let r = n.map(Mt), i = [];
1108
+ for (let t of Z(e)) {
1109
+ let e = Nt(t.url);
1110
+ e !== null && r.some((t) => t.test(e)) && i.push({
1111
+ blockId: t.blockId,
1112
+ params: { host: e }
1113
+ });
1114
+ }
1115
+ return i;
1116
+ }
1117
+ }
1118
+ ];
1119
+ function Pt(e, t = {}) {
1120
+ if (t.disabled === !0 || t.links === !1) return [];
1121
+ let n = t.links ?? {};
1122
+ return b(e, $, ce(t.locale, n, $), (e, t, n) => X(e, t, n));
1123
+ }
1124
+ //#endregion
1125
+ //#region src/util.ts
1126
+ function Ft(e) {
1127
+ return e ? e.disabled === !0 ? !0 : e.accessibility === !1 && e.structure === !1 && e.links === !1 : !1;
695
1128
  }
696
1129
  //#endregion
697
- export { d as DEFAULT_THRESHOLDS, $ as RULES, B as SUPPORTED_DICTIONARY_LOCALES, D as SUPPORTED_MESSAGE_LOCALES, U as extractAnchors, W as extractText, k as formatMessage, f as getContrastRatio, L as getDictionary, O as getMessages, g as isOpaqueHex, Te as lintAccessibility, h as parseHex, b as walkBlocks };
1130
+ export { L as ACCESSIBILITY_RULES, f as DEFAULT_A11Y_THRESHOLDS, p as DEFAULT_NON_PRODUCTION_HOSTS, $ as LINK_RULES, G as STRUCTURE_RULES, we as SUPPORTED_DICTIONARY_LOCALES, gt as SUPPORTED_LINK_MESSAGE_LOCALES, fe as SUPPORTED_MESSAGE_LOCALES, tt as SUPPORTED_STRUCTURE_MESSAGE_LOCALES, M as extractAnchors, N as extractText, X as formatLinkMessage, E as formatMessage, H as formatStructureMessage, m as getContrastRatio, k as getDictionary, Y as getLinkMessages, T as getMessages, V as getStructureMessages, Ft as isLintFullyDisabled, g as isOpaqueHex, Ze as lintAccessibility, Pt as lintLinks, ft as lintStructure, h as parseHex, y as walkBlocks, Z as walkUrls };
698
1131
 
699
1132
  //# sourceMappingURL=index.js.map