@lupinum/nuxt-email 0.1.0

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +172 -0
  4. package/THIRD_PARTY_NOTICES +75 -0
  5. package/dist/chunks/setup.mjs +149 -0
  6. package/dist/module.d.mts +15 -0
  7. package/dist/module.json +12 -0
  8. package/dist/module.mjs +300 -0
  9. package/dist/runtime/code-block/create-component.d.ts +16 -0
  10. package/dist/runtime/code-block/create-component.js +106 -0
  11. package/dist/runtime/components/EBody.d.ts +4 -0
  12. package/dist/runtime/components/EBody.js +75 -0
  13. package/dist/runtime/components/EButton.d.ts +11 -0
  14. package/dist/runtime/components/EButton.js +89 -0
  15. package/dist/runtime/components/EButton.padding.d.ts +12 -0
  16. package/dist/runtime/components/EButton.padding.js +118 -0
  17. package/dist/runtime/components/ECodeInline.d.ts +13 -0
  18. package/dist/runtime/components/ECodeInline.js +43 -0
  19. package/dist/runtime/components/EColumn.d.ts +4 -0
  20. package/dist/runtime/components/EColumn.js +12 -0
  21. package/dist/runtime/components/EContainer.d.ts +4 -0
  22. package/dist/runtime/components/EContainer.js +35 -0
  23. package/dist/runtime/components/EFont.d.ts +24 -0
  24. package/dist/runtime/components/EFont.js +124 -0
  25. package/dist/runtime/components/EHead.d.ts +4 -0
  26. package/dist/runtime/components/EHead.js +17 -0
  27. package/dist/runtime/components/EHeading.d.ts +7 -0
  28. package/dist/runtime/components/EHeading.js +23 -0
  29. package/dist/runtime/components/EHr.d.ts +4 -0
  30. package/dist/runtime/components/EHr.js +26 -0
  31. package/dist/runtime/components/EHtml.d.ts +4 -0
  32. package/dist/runtime/components/EHtml.js +17 -0
  33. package/dist/runtime/components/EImg.d.ts +7 -0
  34. package/dist/runtime/components/EImg.js +44 -0
  35. package/dist/runtime/components/ELink.d.ts +6 -0
  36. package/dist/runtime/components/ELink.js +35 -0
  37. package/dist/runtime/components/EMarkdown.d.ts +8 -0
  38. package/dist/runtime/components/EMarkdown.js +64 -0
  39. package/dist/runtime/components/EPreview.d.ts +8 -0
  40. package/dist/runtime/components/EPreview.js +85 -0
  41. package/dist/runtime/components/ERow.d.ts +4 -0
  42. package/dist/runtime/components/ERow.js +53 -0
  43. package/dist/runtime/components/ESection.d.ts +4 -0
  44. package/dist/runtime/components/ESection.js +35 -0
  45. package/dist/runtime/components/ETailwind.d.ts +24 -0
  46. package/dist/runtime/components/ETailwind.js +49 -0
  47. package/dist/runtime/components/EText.d.ts +4 -0
  48. package/dist/runtime/components/EText.js +20 -0
  49. package/dist/runtime/components/EText.margins.d.ts +18 -0
  50. package/dist/runtime/components/EText.margins.js +81 -0
  51. package/dist/runtime/components/attributes.d.ts +4 -0
  52. package/dist/runtime/components/attributes.js +11 -0
  53. package/dist/runtime/components/email-component-names.d.ts +2 -0
  54. package/dist/runtime/components/email-component-names.js +20 -0
  55. package/dist/runtime/components/email-component-registry.d.ts +8 -0
  56. package/dist/runtime/components/email-component-registry.js +2 -0
  57. package/dist/runtime/components/index.d.ts +23 -0
  58. package/dist/runtime/components/index.js +18 -0
  59. package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.d.ts +2 -0
  60. package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.js +60 -0
  61. package/dist/runtime/components/markdown/render-markdown.d.ts +2 -0
  62. package/dist/runtime/components/markdown/render-markdown.js +137 -0
  63. package/dist/runtime/components/markdown/styles.d.ts +30 -0
  64. package/dist/runtime/components/markdown/styles.js +91 -0
  65. package/dist/runtime/components/presentation-table.d.ts +7 -0
  66. package/dist/runtime/components/presentation-table.js +11 -0
  67. package/dist/runtime/components/style.d.ts +5 -0
  68. package/dist/runtime/components/style.js +32 -0
  69. package/dist/runtime/components/table-padding.d.ts +6 -0
  70. package/dist/runtime/components/table-padding.js +20 -0
  71. package/dist/runtime/define-email.d.ts +2 -0
  72. package/dist/runtime/define-email.js +5 -0
  73. package/dist/runtime/dev-preview/page-script.d.ts +1 -0
  74. package/dist/runtime/dev-preview/page-script.js +403 -0
  75. package/dist/runtime/dev-preview/page.css.d.ts +1 -0
  76. package/dist/runtime/dev-preview/page.css.js +602 -0
  77. package/dist/runtime/dev-preview/page.get.d.ts +4 -0
  78. package/dist/runtime/dev-preview/page.get.js +119 -0
  79. package/dist/runtime/dev-preview/render.get.d.ts +3 -0
  80. package/dist/runtime/dev-preview/render.get.js +123 -0
  81. package/dist/runtime/dev-preview/templates.get.d.ts +7 -0
  82. package/dist/runtime/dev-preview/templates.get.js +14 -0
  83. package/dist/runtime/errors.d.ts +4 -0
  84. package/dist/runtime/errors.js +7 -0
  85. package/dist/runtime/render/define-email.d.ts +56 -0
  86. package/dist/runtime/render/define-email.js +40 -0
  87. package/dist/runtime/render/document.d.ts +4 -0
  88. package/dist/runtime/render/document.js +75 -0
  89. package/dist/runtime/render/errors.d.ts +4 -0
  90. package/dist/runtime/render/errors.js +8 -0
  91. package/dist/runtime/render/plain-text.d.ts +1 -0
  92. package/dist/runtime/render/plain-text.js +15 -0
  93. package/dist/runtime/render/render-component.d.ts +4 -0
  94. package/dist/runtime/render/render-component.js +46 -0
  95. package/dist/runtime/render/render-email-component.d.ts +16 -0
  96. package/dist/runtime/render/render-email-component.js +46 -0
  97. package/dist/runtime/render/types.d.ts +9 -0
  98. package/dist/runtime/render/types.js +0 -0
  99. package/dist/runtime/tailwind/engine/compatibility/escape-class-name.d.ts +8 -0
  100. package/dist/runtime/tailwind/engine/compatibility/escape-class-name.js +10 -0
  101. package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.d.ts +7 -0
  102. package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.js +17 -0
  103. package/dist/runtime/tailwind/engine/compatibility/unescape-class.d.ts +1 -0
  104. package/dist/runtime/tailwind/engine/compatibility/unescape-class.js +3 -0
  105. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +63 -0
  106. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +107 -0
  107. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +6 -0
  108. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +54 -0
  109. package/dist/runtime/tailwind/engine/css/get-css-property.d.ts +17 -0
  110. package/dist/runtime/tailwind/engine/css/get-css-property.js +3 -0
  111. package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +8 -0
  112. package/dist/runtime/tailwind/engine/css/get-custom-properties.js +37 -0
  113. package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +2 -0
  114. package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +9 -0
  115. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +2 -0
  116. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +9 -0
  117. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +21 -0
  118. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +62 -0
  119. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +8 -0
  120. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +138 -0
  121. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +5 -0
  122. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +101 -0
  123. package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +15 -0
  124. package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +358 -0
  125. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +17 -0
  126. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +30 -0
  127. package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +12 -0
  128. package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +37 -0
  129. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +19 -0
  130. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +44 -0
  131. package/dist/runtime/tailwind/engine/css/unwrap-value.d.ts +2 -0
  132. package/dist/runtime/tailwind/engine/css/unwrap-value.js +6 -0
  133. package/dist/runtime/tailwind/engine/index.d.ts +63 -0
  134. package/dist/runtime/tailwind/engine/index.js +53 -0
  135. package/dist/runtime/tailwind/engine/pixel-based-preset.d.ts +9 -0
  136. package/dist/runtime/tailwind/engine/pixel-based-preset.js +58 -0
  137. package/dist/runtime/tailwind/engine/sanitize-stylesheet.d.ts +10 -0
  138. package/dist/runtime/tailwind/engine/sanitize-stylesheet.js +8 -0
  139. package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +23 -0
  140. package/dist/runtime/tailwind/engine/setup-tailwind.js +98 -0
  141. package/dist/runtime/tailwind/engine/stylesheets.d.ts +4 -0
  142. package/dist/runtime/tailwind/engine/stylesheets.js +1756 -0
  143. package/dist/runtime/tailwind/engine/types.d.ts +25 -0
  144. package/dist/runtime/tailwind/engine/types.js +0 -0
  145. package/dist/runtime/tailwind/errors.d.ts +7 -0
  146. package/dist/runtime/tailwind/errors.js +9 -0
  147. package/dist/runtime/tailwind/inline-utils.d.ts +19 -0
  148. package/dist/runtime/tailwind/inline-utils.js +24 -0
  149. package/dist/runtime/tailwind/nested.d.ts +59 -0
  150. package/dist/runtime/tailwind/nested.js +47 -0
  151. package/dist/runtime/tailwind/post-render.d.ts +7 -0
  152. package/dist/runtime/tailwind/post-render.js +156 -0
  153. package/dist/runtime/template-registry/errors.d.ts +5 -0
  154. package/dist/runtime/template-registry/errors.js +11 -0
  155. package/dist/runtime/template-registry/registry.d.ts +13 -0
  156. package/dist/runtime/testing/index.d.ts +13 -0
  157. package/dist/runtime/testing/index.js +2 -0
  158. package/dist/types.d.mts +5 -0
  159. package/package.json +122 -0
@@ -0,0 +1,137 @@
1
+ import { parseDocument } from "htmlparser2";
2
+ import { marked, Renderer } from "marked";
3
+ import { parseCssInJsToInlineCss } from "./parse-css-in-js-to-inline-css.js";
4
+ import { styles } from "./styles.js";
5
+ const SAFE_MARKDOWN_URL_SCHEMES = /* @__PURE__ */ new Set(["cid", "http", "https", "mailto", "tel"]);
6
+ function escapeText(value) {
7
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
8
+ }
9
+ function decodeAttributeEntities(value) {
10
+ const serialized = value.replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
11
+ const document = parseDocument(`<a href="${serialized}"></a>`);
12
+ const anchor = document.children[0];
13
+ return anchor && "attribs" in anchor ? anchor.attribs.href ?? value : value;
14
+ }
15
+ function assertSafeUrl(kind, value) {
16
+ const decoded = decodeAttributeEntities(value);
17
+ const canonical = [...decoded.trim()].filter((character) => {
18
+ const codePoint = character.codePointAt(0);
19
+ return codePoint > 32 && codePoint !== 127;
20
+ }).join("");
21
+ const scheme = /^([a-z][a-z\d+.-]*):/i.exec(canonical)?.[1]?.toLowerCase();
22
+ if (scheme !== void 0 && !SAFE_MARKDOWN_URL_SCHEMES.has(scheme)) {
23
+ throw new TypeError(`EMarkdown ${kind} URL uses an unsupported scheme: ${scheme}`);
24
+ }
25
+ }
26
+ export function renderMarkdown(source, markdownCustomStyles) {
27
+ const finalStyles = { ...styles, ...markdownCustomStyles };
28
+ const styleAttribute = (style) => {
29
+ const inline = parseCssInJsToInlineCss(style);
30
+ return inline !== "" ? ` style="${inline}"` : "";
31
+ };
32
+ const renderer = new Renderer();
33
+ renderer.blockquote = ({ tokens }) => {
34
+ const text = renderer.parser.parse(tokens);
35
+ return `<blockquote${styleAttribute(finalStyles.blockQuote)}>
36
+ ${text}</blockquote>
37
+ `;
38
+ };
39
+ renderer.br = () => {
40
+ return `<br${styleAttribute(finalStyles.br)} />`;
41
+ };
42
+ renderer.code = ({ text }) => {
43
+ text = `${escapeText(text.replace(/\n$/, ""))}
44
+ `;
45
+ return `<pre${styleAttribute(finalStyles.codeBlock)}><code>${text}</code></pre>
46
+ `;
47
+ };
48
+ renderer.codespan = ({ text }) => {
49
+ return `<code${styleAttribute(finalStyles.codeInline)}>${escapeText(text)}</code>`;
50
+ };
51
+ renderer.del = ({ tokens }) => {
52
+ const text = renderer.parser.parseInline(tokens);
53
+ return `<del${styleAttribute(finalStyles.strikethrough)}>${text}</del>`;
54
+ };
55
+ renderer.em = ({ tokens }) => {
56
+ const text = renderer.parser.parseInline(tokens);
57
+ return `<em${styleAttribute(finalStyles.italic)}>${text}</em>`;
58
+ };
59
+ renderer.heading = ({ tokens, depth }) => {
60
+ const text = renderer.parser.parseInline(tokens);
61
+ return `<h${depth}${styleAttribute(finalStyles[`h${depth}`])}>${text}</h${depth}>`;
62
+ };
63
+ renderer.hr = () => {
64
+ return `<hr${styleAttribute(finalStyles.hr)} />
65
+ `;
66
+ };
67
+ renderer.image = ({ href, text, title }) => {
68
+ assertSafeUrl("image", href);
69
+ return `<img src="${href.replaceAll('"', "&quot;")}" alt="${text.replaceAll('"', "&quot;")}"${title ? ` title="${title.replaceAll('"', "&quot;")}"` : ""}${styleAttribute(finalStyles.image)}>`;
70
+ };
71
+ renderer.link = ({ href, title, tokens }) => {
72
+ assertSafeUrl("link", href);
73
+ const text = renderer.parser.parseInline(tokens);
74
+ return `<a href="${href.replaceAll('"', "&quot;")}" target="_blank"${title ? ` title="${title.replaceAll('"', "&quot;")}"` : ""}${styleAttribute(finalStyles.link)}>${text}</a>`;
75
+ };
76
+ renderer.listitem = ({ tokens, loose }) => {
77
+ const hasNestedList = tokens.some((token) => token.type === "list");
78
+ const text = loose || hasNestedList ? renderer.parser.parse(tokens) : renderer.parser.parseInline(tokens);
79
+ return `<li${styleAttribute(finalStyles.li)}>${text}</li>
80
+ `;
81
+ };
82
+ renderer.html = () => {
83
+ throw new TypeError("EMarkdown does not support raw HTML");
84
+ };
85
+ renderer.list = ({ items, ordered, start }) => {
86
+ const type = ordered ? "ol" : "ul";
87
+ const startAt = ordered && start !== 1 ? ` start="${start}"` : "";
88
+ const inline = parseCssInJsToInlineCss(finalStyles[ordered ? "ol" : "ul"]);
89
+ return `<${type}${startAt}${inline !== "" ? ` style="${inline}"` : ""}>
90
+ ${items.map((item) => renderer.listitem(item)).join("")}</${type}>
91
+ `;
92
+ };
93
+ renderer.paragraph = ({ tokens }) => {
94
+ const text = renderer.parser.parseInline(tokens);
95
+ return `<p${styleAttribute(finalStyles.p)}>${text}</p>
96
+ `;
97
+ };
98
+ renderer.strong = ({ tokens }) => {
99
+ const text = renderer.parser.parseInline(tokens);
100
+ return `<strong${styleAttribute(finalStyles.bold)}>${text}</strong>`;
101
+ };
102
+ renderer.table = ({ header, rows }) => {
103
+ const styleTable = parseCssInJsToInlineCss(finalStyles.table);
104
+ const styleThead = parseCssInJsToInlineCss(finalStyles.thead);
105
+ const styleTbody = parseCssInJsToInlineCss(finalStyles.tbody);
106
+ const theadRow = renderer.tablerow({
107
+ text: header.map((cell) => renderer.tablecell(cell)).join("")
108
+ });
109
+ const tbodyRows = rows.map((row) => renderer.tablerow({
110
+ text: row.map((cell) => renderer.tablecell(cell)).join("")
111
+ })).join("");
112
+ const thead = `<thead${styleThead ? ` style="${styleThead}"` : ""}>
113
+ ${theadRow}</thead>`;
114
+ const tbody = `<tbody${styleTbody ? ` style="${styleTbody}"` : ""}>${tbodyRows}</tbody>`;
115
+ return `<table role="presentation"${styleTable ? ` style="${styleTable}"` : ""}>
116
+ ${thead}
117
+ ${tbody}</table>
118
+ `;
119
+ };
120
+ renderer.tablecell = ({ tokens, align, header }) => {
121
+ const text = renderer.parser.parseInline(tokens);
122
+ const type = header ? "th" : "td";
123
+ const cellStyle = styleAttribute(header ? finalStyles.th : finalStyles.td);
124
+ const tag = align ? `<${type} align="${align}"${cellStyle}>` : `<${type}${cellStyle}>`;
125
+ return `${tag}${text}</${type}>
126
+ `;
127
+ };
128
+ renderer.tablerow = ({ text }) => {
129
+ return `<tr${styleAttribute(finalStyles.tr)}>
130
+ ${text}</tr>
131
+ `;
132
+ };
133
+ return marked.parse(source, {
134
+ renderer,
135
+ async: false
136
+ });
137
+ }
@@ -0,0 +1,30 @@
1
+ import type { MarkdownStyle } from './parse-css-in-js-to-inline-css.js';
2
+ export type StylesType = {
3
+ h1?: MarkdownStyle;
4
+ h2?: MarkdownStyle;
5
+ h3?: MarkdownStyle;
6
+ h4?: MarkdownStyle;
7
+ h5?: MarkdownStyle;
8
+ h6?: MarkdownStyle;
9
+ blockQuote?: MarkdownStyle;
10
+ bold?: MarkdownStyle;
11
+ italic?: MarkdownStyle;
12
+ link?: MarkdownStyle;
13
+ codeBlock?: MarkdownStyle;
14
+ codeInline?: MarkdownStyle;
15
+ p?: MarkdownStyle;
16
+ li?: MarkdownStyle;
17
+ ul?: MarkdownStyle;
18
+ ol?: MarkdownStyle;
19
+ image?: MarkdownStyle;
20
+ br?: MarkdownStyle;
21
+ hr?: MarkdownStyle;
22
+ table?: MarkdownStyle;
23
+ thead?: MarkdownStyle;
24
+ tbody?: MarkdownStyle;
25
+ tr?: MarkdownStyle;
26
+ th?: MarkdownStyle;
27
+ td?: MarkdownStyle;
28
+ strikethrough?: MarkdownStyle;
29
+ };
30
+ export declare const styles: StylesType;
@@ -0,0 +1,91 @@
1
+ const emptyStyle = {};
2
+ const baseHeaderStyles = {
3
+ fontWeight: "500",
4
+ paddingTop: 20
5
+ };
6
+ const h1 = {
7
+ ...baseHeaderStyles,
8
+ fontSize: "2.5rem"
9
+ };
10
+ const h2 = {
11
+ ...baseHeaderStyles,
12
+ fontSize: "2rem"
13
+ };
14
+ const h3 = {
15
+ ...baseHeaderStyles,
16
+ fontSize: "1.75rem"
17
+ };
18
+ const h4 = {
19
+ ...baseHeaderStyles,
20
+ fontSize: "1.5rem"
21
+ };
22
+ const h5 = {
23
+ ...baseHeaderStyles,
24
+ fontSize: "1.25rem"
25
+ };
26
+ const h6 = {
27
+ ...baseHeaderStyles,
28
+ fontSize: "1rem"
29
+ };
30
+ const bold = {
31
+ fontWeight: "bold"
32
+ };
33
+ const italic = {
34
+ fontStyle: "italic"
35
+ };
36
+ const blockQuote = {
37
+ background: "#f9f9f9",
38
+ borderLeft: "10px solid #ccc",
39
+ margin: "1.5em 10px",
40
+ padding: "1em 10px"
41
+ };
42
+ const codeInline = {
43
+ color: "#212529",
44
+ fontSize: "87.5%",
45
+ display: "inline",
46
+ background: " #f8f8f8",
47
+ fontFamily: "SFMono-Regular,Menlo,Monaco,Consolas,monospace"
48
+ };
49
+ const codeBlock = {
50
+ ...codeInline,
51
+ display: "block",
52
+ paddingTop: 10,
53
+ paddingRight: 10,
54
+ paddingLeft: 10,
55
+ paddingBottom: 1,
56
+ marginBottom: 20,
57
+ background: " #f8f8f8"
58
+ };
59
+ const link = {
60
+ color: "#007bff",
61
+ textDecoration: "underline",
62
+ backgroundColor: "transparent"
63
+ };
64
+ export const styles = {
65
+ h1,
66
+ h2,
67
+ h3,
68
+ h4,
69
+ h5,
70
+ h6,
71
+ blockQuote,
72
+ bold,
73
+ italic,
74
+ link,
75
+ codeBlock: { ...codeBlock, wordWrap: "break-word" },
76
+ codeInline: { ...codeInline, wordWrap: "break-word" },
77
+ p: emptyStyle,
78
+ li: emptyStyle,
79
+ ul: emptyStyle,
80
+ ol: emptyStyle,
81
+ image: emptyStyle,
82
+ br: emptyStyle,
83
+ hr: emptyStyle,
84
+ table: emptyStyle,
85
+ thead: emptyStyle,
86
+ tbody: emptyStyle,
87
+ th: emptyStyle,
88
+ td: emptyStyle,
89
+ tr: emptyStyle,
90
+ strikethrough: emptyStyle
91
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The presentation-table primitives (ERow, ESection, EContainer) hard-code
3
+ * border, cellpadding, cellspacing, and role to keep the email-client-safe
4
+ * layout invariant. React Email lets callers override them (props spread after
5
+ * the defaults); nuxt-email throws instead of silently discarding the override.
6
+ */
7
+ export declare function assertFixedPresentationTable(componentName: string, attributes: Readonly<Record<string, unknown>>): void;
@@ -0,0 +1,11 @@
1
+ const FIXED_PRESENTATION_TABLE_ATTRIBUTES = /* @__PURE__ */ new Set(["border", "cellpadding", "cellspacing", "role"]);
2
+ export function assertFixedPresentationTable(componentName, attributes) {
3
+ const overrides = Object.keys(attributes).filter(
4
+ (name) => FIXED_PRESENTATION_TABLE_ATTRIBUTES.has(name.toLowerCase())
5
+ );
6
+ if (overrides.length > 0) {
7
+ throw new TypeError(
8
+ `${componentName} does not allow overriding fixed presentation-table attribute${overrides.length === 1 ? "" : "s"}: ${overrides.join(", ")}; border, cellpadding, cellspacing, and role are fixed to preserve the email-client-safe table layout`
9
+ );
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ import type { StyleValue } from 'vue';
2
+ export type EmailStyle = Record<string, string | number | undefined>;
3
+ export declare function isEmailStyle(style: unknown): style is EmailStyle;
4
+ export declare function normalizeEmailStyle(style: unknown): EmailStyle | undefined;
5
+ export declare function mergeEmailStyles(defaults: EmailStyle, style: unknown): StyleValue;
@@ -0,0 +1,32 @@
1
+ import { camelize, normalizeStyle } from "vue";
2
+ export function isEmailStyle(style) {
3
+ return typeof style === "object" && style !== null && !Array.isArray(style);
4
+ }
5
+ export function normalizeEmailStyle(style) {
6
+ if (style === void 0 || style === null) {
7
+ return void 0;
8
+ }
9
+ const normalized = normalizeStyle([style]);
10
+ if (!isEmailStyle(normalized)) {
11
+ return void 0;
12
+ }
13
+ const emailStyle = {};
14
+ for (const [property, value] of Object.entries(normalized)) {
15
+ if (value !== void 0) {
16
+ emailStyle[property.startsWith("--") ? property : camelize(property)] = value;
17
+ }
18
+ }
19
+ return emailStyle;
20
+ }
21
+ export function mergeEmailStyles(defaults, style) {
22
+ if (isEmailStyle(style)) {
23
+ return { ...defaults, ...style };
24
+ }
25
+ if (Array.isArray(style)) {
26
+ return [defaults, ...style];
27
+ }
28
+ if (typeof style === "string") {
29
+ return [defaults, style];
30
+ }
31
+ return defaults;
32
+ }
@@ -0,0 +1,6 @@
1
+ import type { EmailStyle } from './style.js';
2
+ export interface SplitTableStyle {
3
+ tableStyle: EmailStyle;
4
+ cellStyle: EmailStyle;
5
+ }
6
+ export declare function splitTablePadding(style: unknown): SplitTableStyle;
@@ -0,0 +1,20 @@
1
+ import { normalizeEmailStyle } from "./style.js";
2
+ const CELL_PADDING_PROPERTIES = /* @__PURE__ */ new Set([
3
+ "padding",
4
+ "paddingTop",
5
+ "paddingRight",
6
+ "paddingBottom",
7
+ "paddingLeft"
8
+ ]);
9
+ export function splitTablePadding(style) {
10
+ const tableStyle = {};
11
+ const cellStyle = {};
12
+ for (const [property, value] of Object.entries(normalizeEmailStyle(style) ?? {})) {
13
+ if (CELL_PADDING_PROPERTIES.has(property)) {
14
+ cellStyle[property] = value;
15
+ } else {
16
+ tableStyle[property] = value;
17
+ }
18
+ }
19
+ return { tableStyle, cellStyle };
20
+ }
@@ -0,0 +1,2 @@
1
+ export { defineEmail, DefineEmailOutsideRenderError, DuplicateEmailDefinitionError, } from './render/define-email.js';
2
+ export type { DefineEmailOptions } from './render/define-email.js';
@@ -0,0 +1,5 @@
1
+ export {
2
+ defineEmail,
3
+ DefineEmailOutsideRenderError,
4
+ DuplicateEmailDefinitionError
5
+ } from "./render/define-email.js";
@@ -0,0 +1 @@
1
+ export declare const PREVIEW_PAGE_CLIENT = "\n (function () {\n 'use strict'\n\n var state = {\n activeView: 'preview',\n failed: false,\n html: '',\n pendingRefresh: false,\n text: '',\n selectedName: '',\n signature: '',\n revision: 0,\n refreshing: false,\n viewport: '600'\n }\n var elements = {\n copy: document.getElementById('copy-button'),\n empty: document.getElementById('empty'),\n error: document.getElementById('error'),\n fixtureNote: document.getElementById('fixture-note'),\n html: document.getElementById('panel-html'),\n iframe: document.getElementById('panel-preview'),\n open: document.getElementById('open-link'),\n previewControls: document.getElementById('preview-controls'),\n select: document.getElementById('template-select'),\n sizeBadge: document.getElementById('size-badge'),\n sizeValue: document.getElementById('size-value'),\n status: document.getElementById('status'),\n statusText: document.getElementById('status-text'),\n subject: document.getElementById('subject'),\n subjectValue: document.getElementById('subject-value'),\n tabs: Array.from(document.querySelectorAll('[role=\"tab\"]')),\n text: document.getElementById('panel-text'),\n title: document.getElementById('viewer-title'),\n viewportButtons: Array.from(document.querySelectorAll('.viewport-toggle .segment'))\n }\n var previewRoot = window.location.pathname\n while (previewRoot.endsWith('/')) {\n previewRoot = previewRoot.slice(0, -1)\n }\n\n function previewUrl(path) {\n return previewRoot + path\n }\n\n function setStatus(message, statusState) {\n if (elements.status.dataset.state === statusState && elements.statusText.textContent === message) {\n return\n }\n elements.status.dataset.state = statusState\n elements.statusText.textContent = message\n }\n\n function rawRenderUrl(name) {\n var query = new URLSearchParams({ name: name, revision: String(state.revision) })\n return previewUrl('/render?' + query.toString())\n }\n\n function jsonRenderUrl(name) {\n var query = new URLSearchParams({ name: name, format: 'json' })\n return previewUrl('/render?' + query.toString())\n }\n\n function updatePreviewFrame() {\n if (!state.selectedName || !state.html || state.failed) {\n return\n }\n var next = rawRenderUrl(state.selectedName)\n if (elements.iframe.getAttribute('src') !== next) {\n elements.iframe.src = next\n }\n }\n\n function applyViewport(width) {\n state.viewport = width\n elements.iframe.dataset.width = width\n elements.viewportButtons.forEach(function (button) {\n button.setAttribute('aria-pressed', String(button.dataset.width === width))\n })\n }\n\n function updateSubject(subject) {\n var hasSubject = typeof subject === 'string' && subject.length > 0\n elements.subject.dataset.state = hasSubject ? 'set' : 'none'\n elements.subjectValue.textContent = hasSubject ? subject : 'No subject defined'\n elements.subjectValue.title = hasSubject ? subject : ''\n }\n\n function formatKilobytes(bytes) {\n if (bytes < 1024) {\n return bytes + ' B'\n }\n return (bytes / 1024).toFixed(bytes < 102400 ? 1 : 0) + ' KB'\n }\n\n function updateSize(bytes) {\n if (typeof bytes !== 'number' || !Number.isFinite(bytes)) {\n elements.sizeBadge.hidden = true\n return\n }\n elements.sizeBadge.hidden = false\n var level = bytes > 102400 ? 'over' : bytes >= 81920 ? 'warn' : 'ok'\n elements.sizeBadge.dataset.level = level\n var suffix = level === 'over'\n ? ' \u00B7 Gmail will clip'\n : level === 'warn' ? ' \u00B7 near Gmail limit' : ''\n elements.sizeValue.textContent = formatKilobytes(bytes) + suffix\n elements.sizeBadge.title = bytes.toLocaleString() + ' bytes (UTF-8). Gmail clips messages larger than 102,400 bytes; warnings begin at 81,920 bytes.'\n }\n\n async function fetchJson(url) {\n var response = await fetch(url, { cache: 'no-store' })\n var payload = await response.json()\n if (!response.ok) {\n var error = new Error(payload.statusMessage || payload.message || 'Preview request failed')\n error.details = JSON.stringify(payload.data || payload, null, 2)\n throw error\n }\n return payload\n }\n\n async function writeClipboard(value) {\n if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {\n try {\n await navigator.clipboard.writeText(value)\n return\n }\n catch {\n // LAN preview URLs are not secure contexts. Fall through to the\n // selection-based browser API so copying still works on dev Wi-Fi.\n }\n }\n\n var textarea = document.createElement('textarea')\n textarea.value = value\n textarea.setAttribute('readonly', '')\n textarea.style.position = 'fixed'\n textarea.style.opacity = '0'\n document.body.appendChild(textarea)\n textarea.select()\n textarea.setSelectionRange(0, value.length)\n var copied = document.execCommand('copy')\n textarea.remove()\n if (!copied) {\n throw new Error('Clipboard unavailable')\n }\n }\n\n function updateTemplateOptions(templates) {\n var previousName = state.selectedName\n var fragment = document.createDocumentFragment()\n templates.forEach(function (template) {\n var option = document.createElement('option')\n option.value = template.name\n option.disabled = !template.hasFixture\n option.textContent = template.hasFixture ? template.name : template.name + ' \u2014 no fixture'\n fragment.appendChild(option)\n })\n elements.select.replaceChildren(fragment)\n\n var fixtureTemplates = templates.filter(function (template) { return template.hasFixture })\n var previousStillExists = fixtureTemplates.some(function (template) {\n return template.name === previousName\n })\n state.selectedName = previousStillExists\n ? previousName\n : (fixtureTemplates[0] ? fixtureTemplates[0].name : '')\n\n elements.select.disabled = fixtureTemplates.length === 0\n if (state.selectedName) {\n elements.select.value = state.selectedName\n elements.fixtureNote.textContent = 'Using ' + state.selectedName + '.fixtures.ts'\n }\n else {\n elements.fixtureNote.textContent = templates.length === 0\n ? 'Add a Vue SFC under app/emails to begin.'\n : 'Add a colocated .fixtures.ts file to enable preview.'\n }\n }\n\n function updateActions() {\n var hasOutput = Boolean(state.selectedName && state.html && !state.failed)\n elements.copy.disabled = !hasOutput\n elements.copy.textContent = state.activeView === 'text' ? 'Copy text' : 'Copy HTML'\n elements.open.setAttribute('aria-disabled', hasOutput ? 'false' : 'true')\n elements.open.tabIndex = hasOutput ? 0 : -1\n if (hasOutput) {\n elements.open.href = rawRenderUrl(state.selectedName)\n }\n else {\n elements.open.removeAttribute('href')\n }\n }\n\n function showView(view) {\n state.activeView = view\n elements.tabs.forEach(function (tab) {\n var selected = tab.dataset.view === view\n tab.setAttribute('aria-selected', String(selected))\n tab.tabIndex = selected ? 0 : -1\n })\n elements.iframe.hidden = view !== 'preview'\n elements.html.hidden = view !== 'html'\n elements.text.hidden = view !== 'text'\n elements.previewControls.hidden = view !== 'preview'\n updateActions()\n }\n\n function showError(error) {\n var message = error.message || String(error)\n elements.error.textContent = error.details ? message + '\\n\\n' + error.details : (error.stack || message)\n elements.error.hidden = false\n elements.empty.hidden = true\n state.failed = true\n state.html = ''\n state.text = ''\n state.signature = ''\n elements.iframe.hidden = true\n elements.iframe.removeAttribute('src')\n elements.html.hidden = true\n elements.html.textContent = ''\n elements.text.hidden = true\n elements.text.textContent = ''\n updateActions()\n setStatus('Render failed', 'error')\n }\n\n function clearError() {\n elements.error.textContent = ''\n elements.error.hidden = true\n state.failed = false\n }\n\n async function refresh(announceActivity) {\n if (state.refreshing) {\n if (announceActivity) {\n state.pendingRefresh = true\n setStatus('Refreshing\u2026', 'loading')\n }\n return\n }\n state.refreshing = true\n if (announceActivity) {\n setStatus('Refreshing\u2026', 'loading')\n }\n\n try {\n var list = await fetchJson(previewUrl('/api/templates'))\n var previousName = state.selectedName\n updateTemplateOptions(list.templates)\n if (previousName !== state.selectedName) {\n state.signature = ''\n }\n\n if (!state.selectedName) {\n state.html = ''\n state.text = ''\n elements.title.textContent = 'No fixture-backed templates'\n elements.empty.hidden = false\n updateSubject(undefined)\n elements.subjectValue.textContent = 'No template selected'\n updateSize(undefined)\n clearError()\n updateActions()\n setStatus('Waiting for fixture', 'ready')\n return\n }\n\n var requestedName = state.selectedName\n var output = await fetchJson(jsonRenderUrl(requestedName))\n if (requestedName !== state.selectedName) {\n return\n }\n var nextSignature = JSON.stringify([output.name, output.html, output.text, output.subject || null])\n var outputChanged = nextSignature !== state.signature\n if (outputChanged) {\n state.signature = nextSignature\n state.html = output.html\n state.text = output.text\n state.revision += 1\n elements.html.textContent = output.html\n elements.text.textContent = output.text\n elements.iframe.title = 'Email preview: ' + output.name\n }\n updateSubject(output.subject)\n updateSize(output.bytes)\n\n elements.title.textContent = output.name\n elements.empty.hidden = true\n var recoveredFromError = state.failed\n clearError()\n if (outputChanged) {\n updatePreviewFrame()\n }\n showView(state.activeView)\n if (announceActivity || recoveredFromError) {\n setStatus('Up to date', 'ready')\n }\n else if (outputChanged) {\n setStatus('Updated', 'ready')\n }\n }\n catch (error) {\n showError(error)\n }\n finally {\n state.refreshing = false\n if (state.pendingRefresh) {\n state.pendingRefresh = false\n void refresh(true)\n }\n }\n }\n\n elements.select.addEventListener('change', function () {\n state.selectedName = elements.select.value\n state.signature = ''\n void refresh(true)\n })\n\n elements.tabs.forEach(function (tab, index) {\n tab.addEventListener('click', function () {\n showView(tab.dataset.view)\n })\n tab.addEventListener('keydown', function (event) {\n var nextIndex = index\n if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n nextIndex = (index + 1) % elements.tabs.length\n }\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n nextIndex = (index - 1 + elements.tabs.length) % elements.tabs.length\n }\n else if (event.key === 'Home') {\n nextIndex = 0\n }\n else if (event.key === 'End') {\n nextIndex = elements.tabs.length - 1\n }\n else {\n return\n }\n event.preventDefault()\n elements.tabs[nextIndex].focus()\n showView(elements.tabs[nextIndex].dataset.view)\n })\n })\n\n document.addEventListener('keydown', function (event) {\n if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {\n return\n }\n var index = Number(event.key) - 1\n if (index >= 0 && index < elements.tabs.length && document.activeElement !== elements.select) {\n showView(elements.tabs[index].dataset.view)\n }\n })\n\n elements.copy.addEventListener('click', async function () {\n var value = state.activeView === 'text' ? state.text : state.html\n try {\n await writeClipboard(value)\n var copiedMessage = state.activeView === 'text' ? 'Text copied' : 'HTML copied'\n setStatus(copiedMessage, 'ready')\n window.setTimeout(function () {\n if (elements.statusText.textContent === copiedMessage) {\n setStatus('Up to date', 'ready')\n }\n }, 1600)\n }\n catch (error) {\n setStatus('Clipboard unavailable', 'error')\n }\n })\n\n elements.open.addEventListener('click', function (event) {\n if (elements.open.getAttribute('aria-disabled') === 'true') {\n event.preventDefault()\n }\n })\n\n elements.viewportButtons.forEach(function (button) {\n button.addEventListener('click', function () {\n applyViewport(button.dataset.width)\n })\n })\n\n document.addEventListener('visibilitychange', function () {\n if (document.visibilityState === 'visible') {\n void refresh(false)\n }\n })\n\n applyViewport(state.viewport)\n showView('preview')\n void refresh(true)\n window.setInterval(function () {\n if (document.visibilityState === 'visible') {\n void refresh(false)\n }\n }, 1000)\n })()\n";