@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.
- package/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/THIRD_PARTY_NOTICES +75 -0
- package/dist/chunks/setup.mjs +149 -0
- package/dist/module.d.mts +15 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +300 -0
- package/dist/runtime/code-block/create-component.d.ts +16 -0
- package/dist/runtime/code-block/create-component.js +106 -0
- package/dist/runtime/components/EBody.d.ts +4 -0
- package/dist/runtime/components/EBody.js +75 -0
- package/dist/runtime/components/EButton.d.ts +11 -0
- package/dist/runtime/components/EButton.js +89 -0
- package/dist/runtime/components/EButton.padding.d.ts +12 -0
- package/dist/runtime/components/EButton.padding.js +118 -0
- package/dist/runtime/components/ECodeInline.d.ts +13 -0
- package/dist/runtime/components/ECodeInline.js +43 -0
- package/dist/runtime/components/EColumn.d.ts +4 -0
- package/dist/runtime/components/EColumn.js +12 -0
- package/dist/runtime/components/EContainer.d.ts +4 -0
- package/dist/runtime/components/EContainer.js +35 -0
- package/dist/runtime/components/EFont.d.ts +24 -0
- package/dist/runtime/components/EFont.js +124 -0
- package/dist/runtime/components/EHead.d.ts +4 -0
- package/dist/runtime/components/EHead.js +17 -0
- package/dist/runtime/components/EHeading.d.ts +7 -0
- package/dist/runtime/components/EHeading.js +23 -0
- package/dist/runtime/components/EHr.d.ts +4 -0
- package/dist/runtime/components/EHr.js +26 -0
- package/dist/runtime/components/EHtml.d.ts +4 -0
- package/dist/runtime/components/EHtml.js +17 -0
- package/dist/runtime/components/EImg.d.ts +7 -0
- package/dist/runtime/components/EImg.js +44 -0
- package/dist/runtime/components/ELink.d.ts +6 -0
- package/dist/runtime/components/ELink.js +35 -0
- package/dist/runtime/components/EMarkdown.d.ts +8 -0
- package/dist/runtime/components/EMarkdown.js +64 -0
- package/dist/runtime/components/EPreview.d.ts +8 -0
- package/dist/runtime/components/EPreview.js +85 -0
- package/dist/runtime/components/ERow.d.ts +4 -0
- package/dist/runtime/components/ERow.js +53 -0
- package/dist/runtime/components/ESection.d.ts +4 -0
- package/dist/runtime/components/ESection.js +35 -0
- package/dist/runtime/components/ETailwind.d.ts +24 -0
- package/dist/runtime/components/ETailwind.js +49 -0
- package/dist/runtime/components/EText.d.ts +4 -0
- package/dist/runtime/components/EText.js +20 -0
- package/dist/runtime/components/EText.margins.d.ts +18 -0
- package/dist/runtime/components/EText.margins.js +81 -0
- package/dist/runtime/components/attributes.d.ts +4 -0
- package/dist/runtime/components/attributes.js +11 -0
- package/dist/runtime/components/email-component-names.d.ts +2 -0
- package/dist/runtime/components/email-component-names.js +20 -0
- package/dist/runtime/components/email-component-registry.d.ts +8 -0
- package/dist/runtime/components/email-component-registry.js +2 -0
- package/dist/runtime/components/index.d.ts +23 -0
- package/dist/runtime/components/index.js +18 -0
- package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.d.ts +2 -0
- package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.js +60 -0
- package/dist/runtime/components/markdown/render-markdown.d.ts +2 -0
- package/dist/runtime/components/markdown/render-markdown.js +137 -0
- package/dist/runtime/components/markdown/styles.d.ts +30 -0
- package/dist/runtime/components/markdown/styles.js +91 -0
- package/dist/runtime/components/presentation-table.d.ts +7 -0
- package/dist/runtime/components/presentation-table.js +11 -0
- package/dist/runtime/components/style.d.ts +5 -0
- package/dist/runtime/components/style.js +32 -0
- package/dist/runtime/components/table-padding.d.ts +6 -0
- package/dist/runtime/components/table-padding.js +20 -0
- package/dist/runtime/define-email.d.ts +2 -0
- package/dist/runtime/define-email.js +5 -0
- package/dist/runtime/dev-preview/page-script.d.ts +1 -0
- package/dist/runtime/dev-preview/page-script.js +403 -0
- package/dist/runtime/dev-preview/page.css.d.ts +1 -0
- package/dist/runtime/dev-preview/page.css.js +602 -0
- package/dist/runtime/dev-preview/page.get.d.ts +4 -0
- package/dist/runtime/dev-preview/page.get.js +119 -0
- package/dist/runtime/dev-preview/render.get.d.ts +3 -0
- package/dist/runtime/dev-preview/render.get.js +123 -0
- package/dist/runtime/dev-preview/templates.get.d.ts +7 -0
- package/dist/runtime/dev-preview/templates.get.js +14 -0
- package/dist/runtime/errors.d.ts +4 -0
- package/dist/runtime/errors.js +7 -0
- package/dist/runtime/render/define-email.d.ts +56 -0
- package/dist/runtime/render/define-email.js +40 -0
- package/dist/runtime/render/document.d.ts +4 -0
- package/dist/runtime/render/document.js +75 -0
- package/dist/runtime/render/errors.d.ts +4 -0
- package/dist/runtime/render/errors.js +8 -0
- package/dist/runtime/render/plain-text.d.ts +1 -0
- package/dist/runtime/render/plain-text.js +15 -0
- package/dist/runtime/render/render-component.d.ts +4 -0
- package/dist/runtime/render/render-component.js +46 -0
- package/dist/runtime/render/render-email-component.d.ts +16 -0
- package/dist/runtime/render/render-email-component.js +46 -0
- package/dist/runtime/render/types.d.ts +9 -0
- package/dist/runtime/render/types.js +0 -0
- package/dist/runtime/tailwind/engine/compatibility/escape-class-name.d.ts +8 -0
- package/dist/runtime/tailwind/engine/compatibility/escape-class-name.js +10 -0
- package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.d.ts +7 -0
- package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.js +17 -0
- package/dist/runtime/tailwind/engine/compatibility/unescape-class.d.ts +1 -0
- package/dist/runtime/tailwind/engine/compatibility/unescape-class.js +3 -0
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +63 -0
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +107 -0
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +6 -0
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +54 -0
- package/dist/runtime/tailwind/engine/css/get-css-property.d.ts +17 -0
- package/dist/runtime/tailwind/engine/css/get-css-property.js +3 -0
- package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +8 -0
- package/dist/runtime/tailwind/engine/css/get-custom-properties.js +37 -0
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +9 -0
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +9 -0
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +21 -0
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +62 -0
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +8 -0
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +138 -0
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +5 -0
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +101 -0
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +15 -0
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +358 -0
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +17 -0
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +30 -0
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +12 -0
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +37 -0
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +19 -0
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +44 -0
- package/dist/runtime/tailwind/engine/css/unwrap-value.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/unwrap-value.js +6 -0
- package/dist/runtime/tailwind/engine/index.d.ts +63 -0
- package/dist/runtime/tailwind/engine/index.js +53 -0
- package/dist/runtime/tailwind/engine/pixel-based-preset.d.ts +9 -0
- package/dist/runtime/tailwind/engine/pixel-based-preset.js +58 -0
- package/dist/runtime/tailwind/engine/sanitize-stylesheet.d.ts +10 -0
- package/dist/runtime/tailwind/engine/sanitize-stylesheet.js +8 -0
- package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +23 -0
- package/dist/runtime/tailwind/engine/setup-tailwind.js +98 -0
- package/dist/runtime/tailwind/engine/stylesheets.d.ts +4 -0
- package/dist/runtime/tailwind/engine/stylesheets.js +1756 -0
- package/dist/runtime/tailwind/engine/types.d.ts +25 -0
- package/dist/runtime/tailwind/engine/types.js +0 -0
- package/dist/runtime/tailwind/errors.d.ts +7 -0
- package/dist/runtime/tailwind/errors.js +9 -0
- package/dist/runtime/tailwind/inline-utils.d.ts +19 -0
- package/dist/runtime/tailwind/inline-utils.js +24 -0
- package/dist/runtime/tailwind/nested.d.ts +59 -0
- package/dist/runtime/tailwind/nested.js +47 -0
- package/dist/runtime/tailwind/post-render.d.ts +7 -0
- package/dist/runtime/tailwind/post-render.js +156 -0
- package/dist/runtime/template-registry/errors.d.ts +5 -0
- package/dist/runtime/template-registry/errors.js +11 -0
- package/dist/runtime/template-registry/registry.d.ts +13 -0
- package/dist/runtime/testing/index.d.ts +13 -0
- package/dist/runtime/testing/index.js +2 -0
- package/dist/types.d.mts +5 -0
- package/package.json +122 -0
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { relative, isAbsolute, sep, resolve } from 'node:path';
|
|
2
|
+
import { resolveFiles, defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, addServerImports, addComponent, addServerHandler, useNitro, updateTemplates } from '@nuxt/kit';
|
|
3
|
+
import vueRollup from 'unplugin-vue/rollup';
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
import { EMAIL_COMPONENT_NAMES } from '../dist/runtime/components/email-component-names.js';
|
|
6
|
+
|
|
7
|
+
function previewFixturePath(sourcePath) {
|
|
8
|
+
return `${sourcePath.slice(0, -".vue".length)}.fixtures.ts`;
|
|
9
|
+
}
|
|
10
|
+
async function existingPreviewFixturePath(sourcePath) {
|
|
11
|
+
const fixturePath = previewFixturePath(sourcePath);
|
|
12
|
+
try {
|
|
13
|
+
const fixture = await stat(fixturePath);
|
|
14
|
+
return fixture.isFile() ? fixturePath : void 0;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`Failed to inspect preview fixture ${fixturePath}`, { cause: error });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function attachPreviewFixtures(templates) {
|
|
23
|
+
return Promise.all(templates.map(async (template) => {
|
|
24
|
+
const fixturePath = await existingPreviewFixturePath(template.sourcePath);
|
|
25
|
+
return fixturePath ? { ...template, fixturePath } : { ...template };
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class DuplicateEmailTemplateError extends Error {
|
|
30
|
+
templateName;
|
|
31
|
+
sourcePaths;
|
|
32
|
+
constructor(templateName, sourcePaths) {
|
|
33
|
+
const sortedSourcePaths = [...sourcePaths].sort();
|
|
34
|
+
super(`Duplicate email template name "${templateName}" from: ${sortedSourcePaths.join(", ")}`);
|
|
35
|
+
this.name = "DuplicateEmailTemplateError";
|
|
36
|
+
this.templateName = templateName;
|
|
37
|
+
this.sourcePaths = sortedSourcePaths;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class EmailTemplateDiscoveryError extends Error {
|
|
41
|
+
sourcePath;
|
|
42
|
+
constructor(sourcePath, cause) {
|
|
43
|
+
super(`Failed to discover email templates under ${sourcePath}`, { cause });
|
|
44
|
+
this.name = "EmailTemplateDiscoveryError";
|
|
45
|
+
this.sourcePath = sourcePath;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function compareCodePoints(left, right) {
|
|
50
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
51
|
+
}
|
|
52
|
+
function normalizeSeparators(value) {
|
|
53
|
+
return value.split(sep).join("/").replaceAll("\\", "/");
|
|
54
|
+
}
|
|
55
|
+
function templatesFromSourcePaths(emailDirectory, sourcePaths) {
|
|
56
|
+
const templates = sourcePaths.map((sourcePath) => {
|
|
57
|
+
const relativePath = normalizeSeparators(relative(emailDirectory, sourcePath));
|
|
58
|
+
if (relativePath.startsWith("../") || relativePath === ".." || isAbsolute(relativePath)) {
|
|
59
|
+
throw new EmailTemplateDiscoveryError(sourcePath, new TypeError("Template path is outside app/emails"));
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
name: relativePath.slice(0, -".vue".length),
|
|
63
|
+
sourcePath: normalizeSeparators(sourcePath)
|
|
64
|
+
};
|
|
65
|
+
}).sort((left, right) => {
|
|
66
|
+
return compareCodePoints(left.name, right.name) || compareCodePoints(left.sourcePath, right.sourcePath);
|
|
67
|
+
});
|
|
68
|
+
const pathsByName = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const template of templates) {
|
|
70
|
+
const paths = pathsByName.get(template.name) ?? [];
|
|
71
|
+
paths.push(template.sourcePath);
|
|
72
|
+
pathsByName.set(template.name, paths);
|
|
73
|
+
}
|
|
74
|
+
const duplicate = [...pathsByName.entries()].filter(([, paths]) => paths.length > 1).sort(([left], [right]) => compareCodePoints(left, right))[0];
|
|
75
|
+
if (duplicate) {
|
|
76
|
+
throw new DuplicateEmailTemplateError(duplicate[0], duplicate[1]);
|
|
77
|
+
}
|
|
78
|
+
return templates;
|
|
79
|
+
}
|
|
80
|
+
async function discoverEmailTemplates(emailDirectory) {
|
|
81
|
+
let sourcePaths;
|
|
82
|
+
try {
|
|
83
|
+
sourcePaths = await resolveFiles(emailDirectory, "**/*.vue", {
|
|
84
|
+
followSymbolicLinks: false,
|
|
85
|
+
ignore: ["components/**"]
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new EmailTemplateDiscoveryError(emailDirectory, error);
|
|
89
|
+
}
|
|
90
|
+
return templatesFromSourcePaths(emailDirectory, sourcePaths);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function importPath(path) {
|
|
94
|
+
return JSON.stringify(path.replaceAll("\\", "/"));
|
|
95
|
+
}
|
|
96
|
+
function generateEmailRegistry(templates, runtimePaths) {
|
|
97
|
+
const entries = templates.map((template) => {
|
|
98
|
+
const fixture = template.fixturePath ? `
|
|
99
|
+
fixture: () => import(${importPath(template.fixturePath)}),` : "";
|
|
100
|
+
return ` [${JSON.stringify(template.name)}]: {
|
|
101
|
+
component: () => import(${importPath(template.sourcePath)}),${fixture}
|
|
102
|
+
},`;
|
|
103
|
+
});
|
|
104
|
+
return `import { EmailRenderError } from ${importPath(runtimePaths.emailRenderError)}
|
|
105
|
+
import { renderEmailComponent } from ${importPath(runtimePaths.renderEmailComponent)}
|
|
106
|
+
import { UnknownEmailTemplateError } from ${importPath(runtimePaths.templateRegistryErrors)}
|
|
107
|
+
|
|
108
|
+
export const emailTemplates = Object.freeze({
|
|
109
|
+
${entries.join("\n")}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export async function renderEmail(name, props) {
|
|
113
|
+
const loader = typeof name === 'string' && Object.hasOwn(emailTemplates, name)
|
|
114
|
+
? emailTemplates[name]
|
|
115
|
+
: undefined
|
|
116
|
+
if (!loader) {
|
|
117
|
+
throw new UnknownEmailTemplateError(String(name), Object.keys(emailTemplates))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const templateModule = await loader.component()
|
|
122
|
+
return await renderEmailComponent(templateModule.default, props)
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw new EmailRenderError(name, error instanceof EmailRenderError ? error.cause : error)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
function generateEmailTypes(templates, typePaths) {
|
|
131
|
+
const entries = templates.map((template) => {
|
|
132
|
+
const fixture = template.fixturePath ? `
|
|
133
|
+
fixture: () => Promise<typeof import(${importPath(template.fixturePath)})>` : "";
|
|
134
|
+
return ` ${JSON.stringify(template.name)}: {
|
|
135
|
+
component: () => Promise<typeof import(${importPath(template.sourcePath)})>${fixture}
|
|
136
|
+
}`;
|
|
137
|
+
});
|
|
138
|
+
return `export const emailTemplates: Readonly<{
|
|
139
|
+
${entries.join("\n")}
|
|
140
|
+
}>
|
|
141
|
+
|
|
142
|
+
export type EmailTemplateName = keyof typeof emailTemplates
|
|
143
|
+
type _EmailComponent = abstract new (...args: never[]) => { $props: object }
|
|
144
|
+
type _DeclaredEmailProps<Component extends _EmailComponent> = Omit<
|
|
145
|
+
InstanceType<Component>['$props'],
|
|
146
|
+
keyof import('vue').PublicProps
|
|
147
|
+
>
|
|
148
|
+
type _EmailProps<Component extends _EmailComponent> = keyof _DeclaredEmailProps<Component> extends never
|
|
149
|
+
? Record<string, never>
|
|
150
|
+
: _DeclaredEmailProps<Component>
|
|
151
|
+
|
|
152
|
+
export type EmailTemplateProps = {
|
|
153
|
+
[Name in EmailTemplateName]: _EmailProps<Awaited<ReturnType<(typeof emailTemplates)[Name]['component']>>['default']>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function renderEmail<Name extends EmailTemplateName>(
|
|
157
|
+
name: Name,
|
|
158
|
+
props: NoInfer<EmailTemplateProps[Name]>,
|
|
159
|
+
): Promise<import(${importPath(typePaths.renderedEmail)}).RenderedEmail>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const module$1 = defineNuxtModule({
|
|
164
|
+
meta: {
|
|
165
|
+
name: "@lupinum/nuxt-email",
|
|
166
|
+
configKey: "nuxtEmail",
|
|
167
|
+
compatibility: {
|
|
168
|
+
nuxt: ">=4.5.1 <5"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
async setup(options, nuxt) {
|
|
172
|
+
const optionNames = Object.keys(options).filter((name) => name !== "codeBlock").sort();
|
|
173
|
+
if (optionNames.length > 0) {
|
|
174
|
+
throw new TypeError(`nuxt-email received unsupported module options: ${optionNames.join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
const resolver = createResolver(import.meta.url);
|
|
177
|
+
const defineEmailPublicPath = await resolver.resolvePath("./runtime/define-email");
|
|
178
|
+
const errorsPublicPath = await resolver.resolvePath("./runtime/errors");
|
|
179
|
+
let renderEmailComponentPath = await resolver.resolvePath("./runtime/testing");
|
|
180
|
+
if (options.codeBlock !== void 0) {
|
|
181
|
+
const { setupCodeBlock } = await import('./chunks/setup.mjs');
|
|
182
|
+
renderEmailComponentPath = setupCodeBlock(options.codeBlock, resolver);
|
|
183
|
+
}
|
|
184
|
+
const emailDirectory = resolve(nuxt.options.srcDir, "emails");
|
|
185
|
+
const loadTemplates = async () => {
|
|
186
|
+
const discoveredTemplates = await discoverEmailTemplates(emailDirectory);
|
|
187
|
+
return nuxt.options.dev ? attachPreviewFixtures(discoveredTemplates) : discoveredTemplates;
|
|
188
|
+
};
|
|
189
|
+
let templates = await loadTemplates();
|
|
190
|
+
const registryRuntimePaths = {
|
|
191
|
+
emailRenderError: resolver.resolve("./runtime/render/errors"),
|
|
192
|
+
renderEmailComponent: renderEmailComponentPath,
|
|
193
|
+
templateRegistryErrors: resolver.resolve("./runtime/template-registry/errors")
|
|
194
|
+
};
|
|
195
|
+
const registryTypePaths = {
|
|
196
|
+
renderedEmail: resolver.resolve("./runtime/render/types")
|
|
197
|
+
};
|
|
198
|
+
const registryId = "#nuxt-email/registry";
|
|
199
|
+
addServerTemplate({
|
|
200
|
+
filename: registryId,
|
|
201
|
+
getContents: () => generateEmailRegistry(templates, registryRuntimePaths)
|
|
202
|
+
});
|
|
203
|
+
const typeTemplate = addTypeTemplate({
|
|
204
|
+
filename: "types/nuxt-email.d.ts",
|
|
205
|
+
write: true,
|
|
206
|
+
getContents: () => generateEmailTypes(templates, registryTypePaths)
|
|
207
|
+
}, { nitro: true });
|
|
208
|
+
addServerImports([
|
|
209
|
+
{
|
|
210
|
+
name: "renderEmail",
|
|
211
|
+
from: registryId
|
|
212
|
+
}
|
|
213
|
+
]);
|
|
214
|
+
for (const name of EMAIL_COMPONENT_NAMES) {
|
|
215
|
+
addComponent({
|
|
216
|
+
name,
|
|
217
|
+
export: name,
|
|
218
|
+
filePath: resolver.resolve(`./runtime/components/${name}`),
|
|
219
|
+
mode: "server"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (nuxt.options.dev) {
|
|
223
|
+
addServerHandler({
|
|
224
|
+
route: "/__email",
|
|
225
|
+
method: "get",
|
|
226
|
+
env: "dev",
|
|
227
|
+
handler: resolver.resolve("./runtime/dev-preview/page.get")
|
|
228
|
+
});
|
|
229
|
+
addServerHandler({
|
|
230
|
+
route: "/__email/api/templates",
|
|
231
|
+
method: "get",
|
|
232
|
+
env: "dev",
|
|
233
|
+
handler: resolver.resolve("./runtime/dev-preview/templates.get")
|
|
234
|
+
});
|
|
235
|
+
addServerHandler({
|
|
236
|
+
route: "/__email/render",
|
|
237
|
+
method: "get",
|
|
238
|
+
env: "dev",
|
|
239
|
+
handler: resolver.resolve("./runtime/dev-preview/render.get")
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
let reloadRegistry = async () => {
|
|
243
|
+
};
|
|
244
|
+
nuxt.hook("ready", () => {
|
|
245
|
+
const nitro2 = useNitro();
|
|
246
|
+
reloadRegistry = async () => {
|
|
247
|
+
await nitro2.hooks.callHook("rollup:reload");
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
let registryUpdates = Promise.resolve();
|
|
251
|
+
nuxt.hook("builder:watch", (event, path) => {
|
|
252
|
+
const structureChanged = ["add", "unlink", "addDir", "unlinkDir"].includes(event);
|
|
253
|
+
if (event !== "change" && !structureChanged) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const absolutePath = isAbsolute(path) ? path : resolve(nuxt.options.srcDir, path);
|
|
257
|
+
const relativePath = relative(emailDirectory, absolutePath).replaceAll("\\", "/");
|
|
258
|
+
if (relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const update = async () => {
|
|
262
|
+
if (structureChanged) {
|
|
263
|
+
templates = await loadTemplates();
|
|
264
|
+
await updateTemplates({
|
|
265
|
+
filter: (template) => template.filename === typeTemplate.filename
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
await reloadRegistry();
|
|
269
|
+
};
|
|
270
|
+
const queuedUpdate = registryUpdates.then(update, update);
|
|
271
|
+
registryUpdates = queuedUpdate.catch(() => {
|
|
272
|
+
});
|
|
273
|
+
return queuedUpdate;
|
|
274
|
+
});
|
|
275
|
+
const nuxtOptions = nuxt.options;
|
|
276
|
+
const nitro = nuxtOptions.nitro ??= {};
|
|
277
|
+
const alias = nitro.alias ??= {};
|
|
278
|
+
alias["#nuxt-email/testing"] = renderEmailComponentPath;
|
|
279
|
+
alias["@lupinum/nuxt-email/define-email"] = defineEmailPublicPath;
|
|
280
|
+
alias["@lupinum/nuxt-email/errors"] = errorsPublicPath;
|
|
281
|
+
const externals = nitro.externals ??= {};
|
|
282
|
+
const inline = externals.inline ??= [];
|
|
283
|
+
for (const publicRuntimePath of [defineEmailPublicPath, errorsPublicPath]) {
|
|
284
|
+
if (!inline.includes(publicRuntimePath)) {
|
|
285
|
+
inline.push(publicRuntimePath);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const typescript = nitro.typescript ??= {};
|
|
289
|
+
const tsConfig = typescript.tsConfig ??= {};
|
|
290
|
+
const compilerOptions = tsConfig.compilerOptions ??= {};
|
|
291
|
+
const paths = compilerOptions.paths ??= {};
|
|
292
|
+
paths[registryId] = [typeTemplate.dst];
|
|
293
|
+
paths["#nuxt-email/testing"] = [renderEmailComponentPath];
|
|
294
|
+
nitro.rollupConfig ??= {};
|
|
295
|
+
const existingPlugins = nitro.rollupConfig.plugins;
|
|
296
|
+
nitro.rollupConfig.plugins = existingPlugins ? [existingPlugins, vueRollup()] : [vueRollup()];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TokensResult } from '@shikijs/core';
|
|
2
|
+
import type { DefineComponent, HTMLAttributes } from 'vue';
|
|
3
|
+
import type { SafeEmailAttributes } from '../components/attributes.js';
|
|
4
|
+
export type ECodeBlockProps<Language extends string> = {
|
|
5
|
+
code: string;
|
|
6
|
+
language: Language;
|
|
7
|
+
lineNumbers?: boolean;
|
|
8
|
+
fontFamily?: string;
|
|
9
|
+
} & SafeEmailAttributes<HTMLAttributes>;
|
|
10
|
+
interface CodeBlockComponentOptions<Language extends string> {
|
|
11
|
+
enabledLanguages: readonly Language[];
|
|
12
|
+
theme: string;
|
|
13
|
+
highlight: (code: string, language: Language) => Promise<TokensResult>;
|
|
14
|
+
}
|
|
15
|
+
export declare function createCodeBlockComponent<const Language extends string>(options: CodeBlockComponentOptions<Language>): DefineComponent<ECodeBlockProps<Language>>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { defineComponent, h } from "vue";
|
|
2
|
+
import { assertSafeEmailAttributes } from "../components/attributes.js";
|
|
3
|
+
import { mergeEmailStyles } from "../components/style.js";
|
|
4
|
+
const FONT_STYLE_ITALIC = 1;
|
|
5
|
+
const FONT_STYLE_BOLD = 2;
|
|
6
|
+
const FONT_STYLE_UNDERLINE = 4;
|
|
7
|
+
const FONT_STYLE_STRIKETHROUGH = 8;
|
|
8
|
+
function tokenStyle(token) {
|
|
9
|
+
if (token.htmlStyle !== void 0) {
|
|
10
|
+
return { ...token.htmlStyle };
|
|
11
|
+
}
|
|
12
|
+
const style = {
|
|
13
|
+
color: token.color,
|
|
14
|
+
backgroundColor: token.bgColor
|
|
15
|
+
};
|
|
16
|
+
const fontStyle = token.fontStyle ?? 0;
|
|
17
|
+
if (fontStyle > 0) {
|
|
18
|
+
if ((fontStyle & FONT_STYLE_ITALIC) !== 0) {
|
|
19
|
+
style.fontStyle = "italic";
|
|
20
|
+
}
|
|
21
|
+
if ((fontStyle & FONT_STYLE_BOLD) !== 0) {
|
|
22
|
+
style.fontWeight = "bold";
|
|
23
|
+
}
|
|
24
|
+
const decorations = [];
|
|
25
|
+
if ((fontStyle & FONT_STYLE_UNDERLINE) !== 0) {
|
|
26
|
+
decorations.push("underline");
|
|
27
|
+
}
|
|
28
|
+
if ((fontStyle & FONT_STYLE_STRIKETHROUGH) !== 0) {
|
|
29
|
+
decorations.push("line-through");
|
|
30
|
+
}
|
|
31
|
+
if (decorations.length > 0) {
|
|
32
|
+
style.textDecoration = decorations.join(" ");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return style;
|
|
36
|
+
}
|
|
37
|
+
function renderTokens(tokens, lineNumbers) {
|
|
38
|
+
const children = [];
|
|
39
|
+
const lineNumberWidth = `${String(tokens.length).length + 1}ch`;
|
|
40
|
+
for (const [lineIndex, line] of tokens.entries()) {
|
|
41
|
+
if (lineNumbers) {
|
|
42
|
+
children.push(h("span", {
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
"data-skip-in-text": "true",
|
|
45
|
+
"style": {
|
|
46
|
+
display: "inline-block",
|
|
47
|
+
userSelect: "none",
|
|
48
|
+
width: lineNumberWidth
|
|
49
|
+
}
|
|
50
|
+
}, String(lineIndex + 1)));
|
|
51
|
+
}
|
|
52
|
+
for (const token of line) {
|
|
53
|
+
children.push(h("span", { style: tokenStyle(token) }, token.content));
|
|
54
|
+
}
|
|
55
|
+
if (lineIndex < tokens.length - 1) {
|
|
56
|
+
children.push("\n");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return children;
|
|
60
|
+
}
|
|
61
|
+
export function createCodeBlockComponent(options) {
|
|
62
|
+
const enabledLanguages = new Set(options.enabledLanguages);
|
|
63
|
+
return defineComponent({
|
|
64
|
+
name: "ECodeBlock",
|
|
65
|
+
inheritAttrs: false,
|
|
66
|
+
props: {
|
|
67
|
+
code: { type: String, required: true },
|
|
68
|
+
language: { type: String, required: true },
|
|
69
|
+
lineNumbers: { type: Boolean, default: false },
|
|
70
|
+
fontFamily: { type: String, default: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" }
|
|
71
|
+
},
|
|
72
|
+
async setup(props, { attrs }) {
|
|
73
|
+
assertSafeEmailAttributes("ECodeBlock", attrs);
|
|
74
|
+
const language = props.language;
|
|
75
|
+
if (!enabledLanguages.has(language)) {
|
|
76
|
+
throw new TypeError(
|
|
77
|
+
`ECodeBlock language ${JSON.stringify(language)} is not enabled. Enabled languages: ${options.enabledLanguages.join(", ")}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const highlighted = await options.highlight(props.code, language);
|
|
81
|
+
return () => {
|
|
82
|
+
const { class: className, style, ...attributes } = attrs;
|
|
83
|
+
const preStyle = {
|
|
84
|
+
backgroundColor: highlighted.bg,
|
|
85
|
+
color: highlighted.fg,
|
|
86
|
+
fontFamily: props.fontFamily,
|
|
87
|
+
margin: 0,
|
|
88
|
+
overflowWrap: "break-word",
|
|
89
|
+
whiteSpace: "pre-wrap"
|
|
90
|
+
};
|
|
91
|
+
return h("pre", {
|
|
92
|
+
...attributes,
|
|
93
|
+
"class": className,
|
|
94
|
+
"data-code-theme": options.theme,
|
|
95
|
+
"style": mergeEmailStyles(preStyle, style),
|
|
96
|
+
"tabindex": attributes.tabindex ?? 0
|
|
97
|
+
}, [
|
|
98
|
+
h("code", { style: { fontFamily: props.fontFamily } }, renderTokens(
|
|
99
|
+
highlighted.tokens,
|
|
100
|
+
props.lineNumbers === true
|
|
101
|
+
))
|
|
102
|
+
]);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineComponent, h, inject } from "vue";
|
|
2
|
+
import { resolveNestedTailwindStyle, TAILWIND_NESTED_KEY } from "../tailwind/nested.js";
|
|
3
|
+
import { assertSafeEmailAttributes } from "./attributes.js";
|
|
4
|
+
import { normalizeEmailStyle } from "./style.js";
|
|
5
|
+
const RESET_PROPERTIES = [
|
|
6
|
+
"margin",
|
|
7
|
+
"marginTop",
|
|
8
|
+
"marginBottom",
|
|
9
|
+
"marginRight",
|
|
10
|
+
"marginLeft",
|
|
11
|
+
"marginInline",
|
|
12
|
+
"marginBlock",
|
|
13
|
+
"marginBlockStart",
|
|
14
|
+
"marginBlockEnd",
|
|
15
|
+
"marginInlineStart",
|
|
16
|
+
"marginInlineEnd",
|
|
17
|
+
"padding",
|
|
18
|
+
"paddingTop",
|
|
19
|
+
"paddingBottom",
|
|
20
|
+
"paddingRight",
|
|
21
|
+
"paddingLeft",
|
|
22
|
+
"paddingInline",
|
|
23
|
+
"paddingBlock",
|
|
24
|
+
"paddingBlockStart",
|
|
25
|
+
"paddingBlockEnd",
|
|
26
|
+
"paddingInlineStart",
|
|
27
|
+
"paddingInlineEnd"
|
|
28
|
+
];
|
|
29
|
+
export const EBody = defineComponent({
|
|
30
|
+
name: "EBody",
|
|
31
|
+
inheritAttrs: false,
|
|
32
|
+
setup(_props, { attrs, slots }) {
|
|
33
|
+
const holder = inject(TAILWIND_NESTED_KEY, null);
|
|
34
|
+
return () => {
|
|
35
|
+
assertSafeEmailAttributes("EBody", attrs);
|
|
36
|
+
const { style, ...bodyAttributes } = attrs;
|
|
37
|
+
const effectiveStyle = resolveNestedTailwindStyle(holder, bodyAttributes, style).style;
|
|
38
|
+
const normalizedStyle = normalizeEmailStyle(effectiveStyle);
|
|
39
|
+
const hasUserStyle = normalizedStyle !== void 0 && Object.keys(normalizedStyle).length > 0;
|
|
40
|
+
const dir = attrs.dir ?? "ltr";
|
|
41
|
+
const lang = attrs.lang ?? "en";
|
|
42
|
+
const bodyStyle = {};
|
|
43
|
+
if (hasUserStyle) {
|
|
44
|
+
bodyStyle.background = normalizedStyle.background;
|
|
45
|
+
bodyStyle.backgroundColor = normalizedStyle.backgroundColor;
|
|
46
|
+
for (const property of RESET_PROPERTIES) {
|
|
47
|
+
if (normalizedStyle[property] !== void 0) {
|
|
48
|
+
bodyStyle[property] = 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return h("body", {
|
|
53
|
+
...bodyAttributes,
|
|
54
|
+
dir,
|
|
55
|
+
lang,
|
|
56
|
+
...Object.values(bodyStyle).some((value) => value !== void 0) ? { style: bodyStyle } : {}
|
|
57
|
+
}, [
|
|
58
|
+
h("table", {
|
|
59
|
+
border: 0,
|
|
60
|
+
width: "100%",
|
|
61
|
+
cellpadding: "0",
|
|
62
|
+
cellspacing: "0",
|
|
63
|
+
role: "presentation",
|
|
64
|
+
align: "center"
|
|
65
|
+
}, [
|
|
66
|
+
h("tbody", [
|
|
67
|
+
h("tr", [
|
|
68
|
+
h("td", { dir, lang, ...hasUserStyle ? { style: effectiveStyle } : {} }, slots.default?.())
|
|
69
|
+
])
|
|
70
|
+
])
|
|
71
|
+
])
|
|
72
|
+
]);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, DefineComponent } from 'vue';
|
|
2
|
+
import type { SafeEmailAttributes } from './attributes.js';
|
|
3
|
+
export type EButtonProps = Omit<SafeEmailAttributes<AnchorHTMLAttributes>, 'href'> & {
|
|
4
|
+
href: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Outlook spacing behavior adapted from React Email's MIT-licensed Button.
|
|
8
|
+
* Copyright 2024 Plus Five Five, Inc; pinned source commit 6eb428924c4c2774228a07cbec1977ad8898f143.
|
|
9
|
+
*/
|
|
10
|
+
export declare function computeMsoFontWidthAndSpaceCount(expectedWidth: number): readonly [number, number];
|
|
11
|
+
export declare const EButton: DefineComponent<EButtonProps>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createCommentVNode, defineComponent, h, inject } from "vue";
|
|
2
|
+
import { resolveNestedTailwindStyle, TAILWIND_NESTED_KEY } from "../tailwind/nested.js";
|
|
3
|
+
import { assertSafeEmailAttributes } from "./attributes.js";
|
|
4
|
+
import { parseButtonPadding, pixelsToPoints, pixelStyle } from "./EButton.padding.js";
|
|
5
|
+
import { normalizeEmailStyle } from "./style.js";
|
|
6
|
+
const MAX_MSO_FONT_WIDTH = 5;
|
|
7
|
+
const MAX_MSO_SPACE_COUNT = 1e3;
|
|
8
|
+
export function computeMsoFontWidthAndSpaceCount(expectedWidth) {
|
|
9
|
+
if (!Number.isFinite(expectedWidth)) {
|
|
10
|
+
throw new TypeError(`EButton padding must resolve to finite pixels; received ${String(expectedWidth)}`);
|
|
11
|
+
}
|
|
12
|
+
if (expectedWidth === 0) {
|
|
13
|
+
return [0, 0];
|
|
14
|
+
}
|
|
15
|
+
if (expectedWidth < 0) {
|
|
16
|
+
throw new TypeError(`EButton padding must be a finite non-negative value; received ${String(expectedWidth)}`);
|
|
17
|
+
}
|
|
18
|
+
const spaceCount = Math.max(1, Math.ceil(expectedWidth / (MAX_MSO_FONT_WIDTH * 2)));
|
|
19
|
+
if (spaceCount > MAX_MSO_SPACE_COUNT) {
|
|
20
|
+
throw new TypeError(
|
|
21
|
+
`EButton horizontal padding requires ${spaceCount} Outlook spacer characters; maximum is ${MAX_MSO_SPACE_COUNT}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return [expectedWidth / spaceCount / 2, spaceCount];
|
|
25
|
+
}
|
|
26
|
+
export const EButton = defineComponent({
|
|
27
|
+
name: "EButton",
|
|
28
|
+
inheritAttrs: false,
|
|
29
|
+
props: {
|
|
30
|
+
href: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
setup(props, { attrs, slots }) {
|
|
36
|
+
const holder = inject(TAILWIND_NESTED_KEY, null);
|
|
37
|
+
return () => {
|
|
38
|
+
assertSafeEmailAttributes("EButton", attrs);
|
|
39
|
+
if (typeof props.href !== "string" || props.href.length === 0) {
|
|
40
|
+
throw new TypeError("EButton href must be a non-empty string");
|
|
41
|
+
}
|
|
42
|
+
const { style, target: requestedTarget, ...attributes } = attrs;
|
|
43
|
+
const effectiveStyle = resolveNestedTailwindStyle(holder, attributes, style).style;
|
|
44
|
+
const normalizedStyle = normalizeEmailStyle(effectiveStyle) ?? {};
|
|
45
|
+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } = parseButtonPadding(normalizedStyle);
|
|
46
|
+
const textRaise = pixelsToPoints((paddingTop ?? 0) + (paddingBottom ?? 0));
|
|
47
|
+
const [leftFontWidth, leftSpaceCount] = computeMsoFontWidthAndSpaceCount(paddingLeft ?? 0);
|
|
48
|
+
const [rightFontWidth, rightSpaceCount] = computeMsoFontWidthAndSpaceCount(paddingRight ?? 0);
|
|
49
|
+
const target = requestedTarget ?? "_blank";
|
|
50
|
+
return h("a", {
|
|
51
|
+
...attributes,
|
|
52
|
+
href: props.href,
|
|
53
|
+
style: {
|
|
54
|
+
lineHeight: "100%",
|
|
55
|
+
textDecoration: "none",
|
|
56
|
+
display: "inline-block",
|
|
57
|
+
maxWidth: "100%",
|
|
58
|
+
msoPaddingAlt: "0px",
|
|
59
|
+
...normalizedStyle,
|
|
60
|
+
paddingTop: pixelStyle(paddingTop),
|
|
61
|
+
paddingRight: pixelStyle(paddingRight),
|
|
62
|
+
paddingBottom: pixelStyle(paddingBottom),
|
|
63
|
+
paddingLeft: pixelStyle(paddingLeft)
|
|
64
|
+
},
|
|
65
|
+
target
|
|
66
|
+
}, [
|
|
67
|
+
h("span", [
|
|
68
|
+
createCommentVNode(
|
|
69
|
+
`[if mso]><i style="mso-font-width:${leftFontWidth * 100}%;mso-text-raise:${textRaise}px" hidden>${" ".repeat(leftSpaceCount)}</i><![endif]`
|
|
70
|
+
)
|
|
71
|
+
]),
|
|
72
|
+
h("span", {
|
|
73
|
+
style: {
|
|
74
|
+
maxWidth: "100%",
|
|
75
|
+
display: "inline-block",
|
|
76
|
+
lineHeight: "120%",
|
|
77
|
+
msoPaddingAlt: "0px",
|
|
78
|
+
msoTextRaise: pixelStyle(pixelsToPoints(paddingBottom))
|
|
79
|
+
}
|
|
80
|
+
}, slots.default?.()),
|
|
81
|
+
h("span", [
|
|
82
|
+
createCommentVNode(
|
|
83
|
+
`[if mso]><i style="mso-font-width:${rightFontWidth * 100}%" hidden>${" ".repeat(rightSpaceCount)}​</i><![endif]`
|
|
84
|
+
)
|
|
85
|
+
])
|
|
86
|
+
]);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type PaddingValue = unknown;
|
|
2
|
+
export interface ParsedPadding {
|
|
3
|
+
paddingTop: number | undefined;
|
|
4
|
+
paddingRight: number | undefined;
|
|
5
|
+
paddingBottom: number | undefined;
|
|
6
|
+
paddingLeft: number | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function convertToPixels(value: PaddingValue): number;
|
|
9
|
+
export declare function parseButtonPadding(style: unknown): ParsedPadding;
|
|
10
|
+
export declare function pixelsToPoints(pixels: number | undefined): number | undefined;
|
|
11
|
+
export declare function pixelStyle(value: number | undefined): string | number | undefined;
|
|
12
|
+
export {};
|