@maizzle/framework 6.0.0-rc.18 → 6.0.0-rc.19
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/build.d.ts.map +1 -1
- package/dist/build.js +10 -6
- package/dist/build.js.map +1 -1
- package/dist/components/Button.vue +17 -39
- package/dist/components/Container.vue +6 -6
- package/dist/components/Hr.vue +20 -120
- package/dist/components/Html.vue +19 -1
- package/dist/components/NotPlaintext.vue +14 -0
- package/dist/components/{Vml.vue → OutlookBg.vue} +1 -1
- package/dist/components/Plaintext.vue +14 -0
- package/dist/components/QrCode.vue +157 -0
- package/dist/components/Spacer.vue +28 -27
- package/dist/components/utils.js +1 -1
- package/dist/components/utils.js.map +1 -1
- package/dist/composables/defineConfig.js +1 -2
- package/dist/composables/defineConfig.js.map +1 -1
- package/dist/composables/renderContext.js +1 -1
- package/dist/composables/useBaseUrl.js +3 -4
- package/dist/composables/useBaseUrl.js.map +1 -1
- package/dist/composables/useConfig.js +1 -2
- package/dist/composables/useConfig.js.map +1 -1
- package/dist/composables/useDoctype.js +1 -2
- package/dist/composables/useDoctype.js.map +1 -1
- package/dist/composables/useEvent.js +1 -2
- package/dist/composables/useEvent.js.map +1 -1
- package/dist/composables/useFont.js +1 -2
- package/dist/composables/useFont.js.map +1 -1
- package/dist/composables/useOutlookFallback.js +1 -2
- package/dist/composables/useOutlookFallback.js.map +1 -1
- package/dist/composables/usePlaintext.d.ts +2 -0
- package/dist/composables/usePlaintext.d.ts.map +1 -1
- package/dist/composables/usePlaintext.js +2 -2
- package/dist/composables/usePlaintext.js.map +1 -1
- package/dist/composables/usePreheader.js +1 -2
- package/dist/composables/usePreheader.js.map +1 -1
- package/dist/composables/useTransformers.d.ts +3 -3
- package/dist/composables/useTransformers.js +4 -5
- package/dist/composables/useTransformers.js.map +1 -1
- package/dist/composables/useUrlQuery.js +3 -4
- package/dist/composables/useUrlQuery.js.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/index.js +1 -2
- package/dist/config/index.js.map +1 -1
- package/dist/events/index.js +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +4 -5
- package/dist/plaintext.js +3 -4
- package/dist/plaintext.js.map +1 -1
- package/dist/plugin.js +1 -2
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/postcss/mergeMediaQueries.js +1 -2
- package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -1
- package/dist/plugins/postcss/pruneVars.js +1 -1
- package/dist/plugins/postcss/pruneVars.js.map +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.js +1 -1
- package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -1
- package/dist/plugins/postcss/removeDeclarations.js +1 -1
- package/dist/plugins/postcss/removeDeclarations.js.map +1 -1
- package/dist/plugins/postcss/resolveMaizzleImports.js +1 -2
- package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -1
- package/dist/plugins/postcss/resolveProps.js +1 -1
- package/dist/plugins/postcss/resolveProps.js.map +1 -1
- package/dist/plugins/postcss/tailwindCleanup.js +1 -1
- package/dist/plugins/postcss/tailwindCleanup.js.map +1 -1
- package/dist/prepare.js +1 -2
- package/dist/prepare.js.map +1 -1
- package/dist/render/active.d.ts +8 -0
- package/dist/render/active.d.ts.map +1 -0
- package/dist/render/active.js +12 -0
- package/dist/render/active.js.map +1 -0
- package/dist/render/createRenderer.d.ts.map +1 -1
- package/dist/render/createRenderer.js +6 -9
- package/dist/render/createRenderer.js.map +1 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +13 -6
- package/dist/render/index.js.map +1 -1
- package/dist/render/injectFonts.js +1 -2
- package/dist/render/injectFonts.js.map +1 -1
- package/dist/render/plugins/codeBlockExtract.js +1 -1
- package/dist/render/plugins/codeBlockExtract.js.map +1 -1
- package/dist/render/plugins/markdownExtract.js +1 -2
- package/dist/render/plugins/markdownExtract.js.map +1 -1
- package/dist/render/plugins/rawExtract.js +1 -1
- package/dist/render/plugins/rawExtract.js.map +1 -1
- package/dist/render/plugins/rowSourceLocation.js +1 -1
- package/dist/render/plugins/rowSourceLocation.js.map +1 -1
- package/dist/serve.d.ts +2 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +12 -7
- package/dist/serve.js.map +1 -1
- package/dist/server/compatibility.js +1 -2
- package/dist/server/compatibility.js.map +1 -1
- package/dist/server/email.js +1 -2
- package/dist/server/email.js.map +1 -1
- package/dist/server/linter.js +1 -2
- package/dist/server/linter.js.map +1 -1
- package/dist/server/sfc-utils.js +1 -2
- package/dist/server/sfc-utils.js.map +1 -1
- package/dist/tests/render/_helpers.d.ts +6 -0
- package/dist/tests/render/_helpers.d.ts.map +1 -0
- package/dist/tests/render/_helpers.js +16 -0
- package/dist/tests/render/_helpers.js.map +1 -0
- package/dist/transformers/addAttributes.js +3 -4
- package/dist/transformers/addAttributes.js.map +1 -1
- package/dist/transformers/attributeToStyle.d.ts +27 -14
- package/dist/transformers/attributeToStyle.d.ts.map +1 -1
- package/dist/transformers/attributeToStyle.js +34 -20
- package/dist/transformers/attributeToStyle.js.map +1 -1
- package/dist/transformers/base.d.ts +66 -3
- package/dist/transformers/base.d.ts.map +1 -1
- package/dist/transformers/base.js +50 -24
- package/dist/transformers/base.js.map +1 -1
- package/dist/transformers/columnWidth.js +1 -2
- package/dist/transformers/columnWidth.js.map +1 -1
- package/dist/transformers/entities.d.ts +31 -2
- package/dist/transformers/entities.d.ts.map +1 -1
- package/dist/transformers/entities.js +39 -7
- package/dist/transformers/entities.js.map +1 -1
- package/dist/transformers/filters/defaults.js +1 -1
- package/dist/transformers/filters/defaults.js.map +1 -1
- package/dist/transformers/filters/index.d.ts +31 -10
- package/dist/transformers/filters/index.d.ts.map +1 -1
- package/dist/transformers/filters/index.js +36 -14
- package/dist/transformers/filters/index.js.map +1 -1
- package/dist/transformers/format.d.ts +14 -7
- package/dist/transformers/format.d.ts.map +1 -1
- package/dist/transformers/format.js +15 -11
- package/dist/transformers/format.js.map +1 -1
- package/dist/transformers/index.js +49 -29
- package/dist/transformers/index.js.map +1 -1
- package/dist/transformers/inlineCss.d.ts +84 -0
- package/dist/transformers/inlineCss.d.ts.map +1 -0
- package/dist/transformers/{inlineCSS.js → inlineCss.js} +24 -14
- package/dist/transformers/inlineCss.js.map +1 -0
- package/dist/transformers/inlineLink.d.ts +26 -5
- package/dist/transformers/inlineLink.d.ts.map +1 -1
- package/dist/transformers/inlineLink.js +31 -7
- package/dist/transformers/inlineLink.js.map +1 -1
- package/dist/transformers/minify.d.ts +13 -9
- package/dist/transformers/minify.d.ts.map +1 -1
- package/dist/transformers/minify.js +14 -13
- package/dist/transformers/minify.js.map +1 -1
- package/dist/transformers/msoPlaceholders.js +1 -2
- package/dist/transformers/msoPlaceholders.js.map +1 -1
- package/dist/transformers/purgeCss.d.ts +43 -0
- package/dist/transformers/purgeCss.d.ts.map +1 -0
- package/dist/transformers/{purgeCSS.js → purgeCss.js} +32 -25
- package/dist/transformers/purgeCss.js.map +1 -0
- package/dist/transformers/removeAttributes.d.ts +43 -20
- package/dist/transformers/removeAttributes.d.ts.map +1 -1
- package/dist/transformers/removeAttributes.js +34 -27
- package/dist/transformers/removeAttributes.js.map +1 -1
- package/dist/transformers/replaceStrings.js +1 -1
- package/dist/transformers/replaceStrings.js.map +1 -1
- package/dist/transformers/safeClassNames.js +1 -2
- package/dist/transformers/safeClassNames.js.map +1 -1
- package/dist/transformers/shorthandCss.d.ts +47 -0
- package/dist/transformers/shorthandCss.d.ts.map +1 -0
- package/dist/transformers/shorthandCss.js +61 -0
- package/dist/transformers/shorthandCss.js.map +1 -0
- package/dist/transformers/sixHex.d.ts +16 -7
- package/dist/transformers/sixHex.d.ts.map +1 -1
- package/dist/transformers/sixHex.js +21 -9
- package/dist/transformers/sixHex.js.map +1 -1
- package/dist/transformers/tailwindComponent.js +1 -2
- package/dist/transformers/tailwindComponent.js.map +1 -1
- package/dist/transformers/tailwindcss.js +1 -2
- package/dist/transformers/tailwindcss.js.map +1 -1
- package/dist/transformers/urlQuery.d.ts +26 -14
- package/dist/transformers/urlQuery.d.ts.map +1 -1
- package/dist/transformers/urlQuery.js +32 -20
- package/dist/transformers/urlQuery.js.map +1 -1
- package/dist/types/config.d.ts +71 -19
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/utils/ast/index.js +1 -2
- package/dist/utils/ast/parser.js +1 -2
- package/dist/utils/ast/parser.js.map +1 -1
- package/dist/utils/ast/serializer.js +1 -2
- package/dist/utils/ast/serializer.js.map +1 -1
- package/dist/utils/ast/walker.js +1 -1
- package/dist/utils/ast/walker.js.map +1 -1
- package/dist/utils/compileTailwindCss.js +1 -2
- package/dist/utils/compileTailwindCss.js.map +1 -1
- package/dist/utils/decodeStyleEntities.js +1 -1
- package/dist/utils/decodeStyleEntities.js.map +1 -1
- package/dist/utils/detect.js +1 -2
- package/dist/utils/detect.js.map +1 -1
- package/dist/utils/output-markers.d.ts +29 -0
- package/dist/utils/output-markers.d.ts.map +1 -0
- package/dist/utils/output-markers.js +68 -0
- package/dist/utils/output-markers.js.map +1 -0
- package/dist/utils/url.js +1 -2
- package/dist/utils/url.js.map +1 -1
- package/node_modules/maizzle/README.md +24 -0
- package/node_modules/maizzle/dist/commands/make/component.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/config.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/layout.mjs +3 -3
- package/node_modules/maizzle/dist/commands/make/scaffold.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/stubs/Layout.vue +146 -0
- package/node_modules/maizzle/dist/commands/make/stubs/component.vue +2 -4
- package/node_modules/maizzle/dist/commands/make/stubs/config.ts +1 -5
- package/node_modules/maizzle/dist/commands/make/template.mjs +1 -1
- package/node_modules/maizzle/dist/commands/new.mjs +29 -24
- package/node_modules/maizzle/dist/index.mjs +28 -8
- package/node_modules/maizzle/package.json +1 -1
- package/package.json +2 -2
- package/dist/transformers/inlineCSS.d.ts +0 -17
- package/dist/transformers/inlineCSS.d.ts.map +0 -1
- package/dist/transformers/inlineCSS.js.map +0 -1
- package/dist/transformers/purgeCSS.d.ts +0 -23
- package/dist/transformers/purgeCSS.d.ts.map +0 -1
- package/dist/transformers/purgeCSS.js.map +0 -1
- package/dist/transformers/shorthandCSS.d.ts +0 -24
- package/dist/transformers/shorthandCSS.d.ts.map +0 -1
- package/dist/transformers/shorthandCSS.js +0 -48
- package/dist/transformers/shorthandCSS.js.map +0 -1
- package/node_modules/maizzle/dist/commands/make/stubs/layout.vue +0 -39
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;UAaiB,WAAA;EACf,KAAA;EACA,MAAA,EAAQ,aAAA;AAAA;;;;;;;;;AAaV;;iBAAsB,KAAA,CAAM,WAAA,GAAc,OAAA,CAAQ,aAAA,aAA0B,OAAA,CAAQ,WAAA"}
|
package/dist/build.js
CHANGED
|
@@ -3,11 +3,12 @@ import { EventManager } from "./events/index.js";
|
|
|
3
3
|
import { runTransformers } from "./transformers/index.js";
|
|
4
4
|
import { createRenderer } from "./render/createRenderer.js";
|
|
5
5
|
import { createPlaintext } from "./plaintext.js";
|
|
6
|
+
import { stripForHtml, stripForPlaintext } from "./utils/output-markers.js";
|
|
6
7
|
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
8
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
8
9
|
import { glob } from "tinyglobby";
|
|
9
10
|
import ora from "ora";
|
|
10
|
-
|
|
11
|
+
import defu from "defu";
|
|
11
12
|
//#region src/build.ts
|
|
12
13
|
/**
|
|
13
14
|
* Build all SFC email templates to HTML files.
|
|
@@ -76,20 +77,23 @@ async function build(configInput) {
|
|
|
76
77
|
html
|
|
77
78
|
});
|
|
78
79
|
html = `${doctype}\n${html}`;
|
|
80
|
+
const htmlOut = stripForHtml(html);
|
|
79
81
|
const outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
|
|
80
82
|
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
81
|
-
writeFileSync(outputFilePath,
|
|
83
|
+
writeFileSync(outputFilePath, htmlOut);
|
|
82
84
|
outputFiles.push(outputFilePath);
|
|
83
85
|
const globalPlaintext = templateConfig.plaintext;
|
|
84
86
|
const sfcPlaintext = rendered.plaintext;
|
|
85
87
|
if (globalPlaintext || sfcPlaintext) {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
+
const globalCfg = typeof globalPlaintext === "object" ? globalPlaintext : {};
|
|
89
|
+
const stripOptions = defu(sfcPlaintext?.options, globalCfg.options);
|
|
90
|
+
const plaintext = createPlaintext(stripForPlaintext(html), stripOptions);
|
|
91
|
+
const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? "txt";
|
|
88
92
|
let ptOutputPath;
|
|
89
93
|
if (sfcPlaintext?.destination) {
|
|
90
94
|
const name = basename(templatePath).replace(/\.(vue|md)$/, "");
|
|
91
95
|
ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`);
|
|
92
|
-
} else if (
|
|
96
|
+
} else if (globalCfg.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase);
|
|
93
97
|
else ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase);
|
|
94
98
|
mkdirSync(dirname(ptOutputPath), { recursive: true });
|
|
95
99
|
writeFileSync(ptOutputPath, plaintext);
|
|
@@ -142,7 +146,7 @@ async function copyStatic(config, outputPath) {
|
|
|
142
146
|
cpSync(file, destPath);
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
|
-
|
|
146
149
|
//#endregion
|
|
147
150
|
export { build };
|
|
151
|
+
|
|
148
152
|
//# sourceMappingURL=build.js.map
|
package/dist/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","names":[],"sources":["../src/build.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join } from 'node:path'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { createPlaintext } from './plaintext.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n const outputFiles: string[] = []\n\n try {\n for (const templatePath of templateFiles) {\n const absolutePath = resolve(templatePath)\n let template = readFileSync(absolutePath, 'utf-8')\n\n template = await events.fireBeforeRender({ config, template })\n\n const rendered = await renderer.render(absolutePath, config)\n\n // Register SFC event handlers collected during render so they participate\n // in the post-render events (afterRender / afterTransform). They're cleared\n // at the end of the iteration so they don't leak into the next template.\n for (const { name, handler } of rendered.sfcEventHandlers) {\n events.on(name, handler)\n }\n\n let html = await events.fireAfterRender({ config, template, html: rendered.html })\n\n // Use the per-template merged config (from defineConfig() in the SFC) so that\n // template-level overrides like css.safe: false are respected by transformers.\n const templateConfig = rendered.templateConfig\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config, template, html })\n html = `${doctype}\\n${html}`\n\n const outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, html)\n outputFiles.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const stripOptions = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const plaintext = createPlaintext(html, stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`)\n } else if (typeof globalPlaintext === 'string') {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalPlaintext), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n\n events.clearSfcHandlers()\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * This is used to strip the content base from template paths\n * so the output preserves only the subdirectory structure.\n */\nfunction computeContentBase(patterns: string[]): string {\n // Use the first non-negated pattern\n const pattern = patterns.find(p => !p.startsWith('!')) ?? patterns[0]\n\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n}\n\nfunction resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.js","names":[],"sources":["../src/build.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join } from 'node:path'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { createPlaintext } from './plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from './utils/output-markers.ts'\nimport defu from 'defu'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n const outputFiles: string[] = []\n\n try {\n for (const templatePath of templateFiles) {\n const absolutePath = resolve(templatePath)\n let template = readFileSync(absolutePath, 'utf-8')\n\n template = await events.fireBeforeRender({ config, template })\n\n const rendered = await renderer.render(absolutePath, config)\n\n // Register SFC event handlers collected during render so they participate\n // in the post-render events (afterRender / afterTransform). They're cleared\n // at the end of the iteration so they don't leak into the next template.\n for (const { name, handler } of rendered.sfcEventHandlers) {\n events.on(name, handler)\n }\n\n let html = await events.fireAfterRender({ config, template, html: rendered.html })\n\n // Use the per-template merged config (from defineConfig() in the SFC) so that\n // template-level overrides like css.safe: false are respected by transformers.\n const templateConfig = rendered.templateConfig\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config, template, html })\n html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n outputFiles.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n\n events.clearSfcHandlers()\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * This is used to strip the content base from template paths\n * so the output preserves only the subdirectory structure.\n */\nfunction computeContentBase(patterns: string[]): string {\n // Use the first non-negated pattern\n const pattern = patterns.find(p => !p.startsWith('!')) ?? patterns[0]\n\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n}\n\nfunction resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
|
|
@@ -36,24 +36,6 @@ const props = defineProps({
|
|
|
36
36
|
type: String as PropType<'left' | 'center' | 'right' | null>,
|
|
37
37
|
default: null
|
|
38
38
|
},
|
|
39
|
-
/**
|
|
40
|
-
* Background color for `solid` and `outline` variants.
|
|
41
|
-
* Also used as the text color for `outline` and `ghost` variants when `color` is not set.
|
|
42
|
-
* @default '#4338ca'
|
|
43
|
-
*/
|
|
44
|
-
bgColor: {
|
|
45
|
-
type: String,
|
|
46
|
-
default: '#4338ca'
|
|
47
|
-
},
|
|
48
|
-
/**
|
|
49
|
-
* Explicit text color. When omitted, `solid` buttons use `#fffffe`
|
|
50
|
-
* and all other variants fall back to `bgColor`.
|
|
51
|
-
* @default null
|
|
52
|
-
*/
|
|
53
|
-
color: {
|
|
54
|
-
type: String,
|
|
55
|
-
default: null
|
|
56
|
-
},
|
|
57
39
|
/**
|
|
58
40
|
* `mso-text-raise` value applied to the inner `<span>` elements.
|
|
59
41
|
* Controls vertical text alignment inside the button in old Outlook.
|
|
@@ -147,18 +129,9 @@ const alignClass = computed(() => props.align ? ({
|
|
|
147
129
|
right: 'text-right',
|
|
148
130
|
})[props.align] || '' : '')
|
|
149
131
|
|
|
150
|
-
const textColor = computed(() => {
|
|
151
|
-
if (props.color) return props.color
|
|
152
|
-
|
|
153
|
-
return props.variant === 'solid' ? '#fffffe' : props.bgColor
|
|
154
|
-
})
|
|
155
|
-
|
|
156
132
|
const styles = computed(() => {
|
|
157
133
|
if (props.variant === 'link') {
|
|
158
|
-
return
|
|
159
|
-
'text-decoration: none;',
|
|
160
|
-
`color: ${textColor.value};`,
|
|
161
|
-
].join('')
|
|
134
|
+
return 'text-decoration: none; color: #4338ca;'
|
|
162
135
|
}
|
|
163
136
|
|
|
164
137
|
const base = [
|
|
@@ -168,18 +141,24 @@ const styles = computed(() => {
|
|
|
168
141
|
'font-size: 16px;',
|
|
169
142
|
'line-height: 1;',
|
|
170
143
|
'border-radius: 4px;',
|
|
171
|
-
`color: ${textColor.value};`,
|
|
172
144
|
]
|
|
173
145
|
|
|
174
146
|
if (props.variant === 'outline') {
|
|
175
147
|
base.push(
|
|
176
148
|
'background-color: transparent;',
|
|
177
|
-
|
|
149
|
+
'border: 1px solid #4338ca;',
|
|
150
|
+
'color: #4338ca;',
|
|
178
151
|
)
|
|
179
152
|
} else if (props.variant === 'ghost') {
|
|
180
|
-
base.push(
|
|
153
|
+
base.push(
|
|
154
|
+
'background-color: transparent;',
|
|
155
|
+
'color: #4338ca;',
|
|
156
|
+
)
|
|
181
157
|
} else {
|
|
182
|
-
base.push(
|
|
158
|
+
base.push(
|
|
159
|
+
'background-color: #4338ca;',
|
|
160
|
+
'color: #fffffe;',
|
|
161
|
+
)
|
|
183
162
|
}
|
|
184
163
|
|
|
185
164
|
return base.join('')
|
|
@@ -187,12 +166,11 @@ const styles = computed(() => {
|
|
|
187
166
|
|
|
188
167
|
const isLink = computed(() => props.variant === 'link')
|
|
189
168
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
169
|
+
const variantClasses = computed(() =>
|
|
170
|
+
props.variant === 'ghost' ? 'hover:bg-indigo-50' : '',
|
|
171
|
+
)
|
|
194
172
|
|
|
195
|
-
const mergedClass = computed(() => twMerge(
|
|
173
|
+
const mergedClass = computed(() => twMerge(variantClasses.value, attrs.class as string))
|
|
196
174
|
|
|
197
175
|
const textSpanStyle = computed(() =>
|
|
198
176
|
outlookFallback ? `mso-text-raise: ${props.msoPt};` : undefined,
|
|
@@ -229,9 +207,9 @@ const MsoIconGap = () => createStaticVNode(
|
|
|
229
207
|
<template>
|
|
230
208
|
<div :class="alignClass">
|
|
231
209
|
<a
|
|
232
|
-
v-bind="{ ...$attrs, class: undefined }"
|
|
210
|
+
v-bind="{ ...$attrs, class: undefined, style: undefined }"
|
|
233
211
|
:href="href"
|
|
234
|
-
:style="styles"
|
|
212
|
+
:style="[styles, $attrs.style as any]"
|
|
235
213
|
:class="mergedClass"
|
|
236
214
|
>
|
|
237
215
|
<template v-if="!isLink">
|
|
@@ -16,11 +16,11 @@ const props = defineProps({
|
|
|
16
16
|
* Also used as the width source for descendant `Row`/`Column`
|
|
17
17
|
* components when computing column widths.
|
|
18
18
|
*
|
|
19
|
-
* When not set, the div defaults to `w-150 mx-auto` (
|
|
20
|
-
* centered) — overridable via Tailwind
|
|
21
|
-
* `w-[400px]` or `max-w-xl`. The MSO table
|
|
22
|
-
* from the resolved width/max-width after
|
|
23
|
-
* back to 600px when unresolvable.
|
|
19
|
+
* When not set, the div defaults to `max-w-150 mx-auto` (max
|
|
20
|
+
* 600px, centered, shrinks below) — overridable via Tailwind
|
|
21
|
+
* classes such as `w-[400px]` or `max-w-xl`. The MSO table
|
|
22
|
+
* width is auto-derived from the resolved width/max-width after
|
|
23
|
+
* CSS inlining, falling back to 600px when unresolvable.
|
|
24
24
|
*/
|
|
25
25
|
width: {
|
|
26
26
|
type: [String, Number],
|
|
@@ -79,7 +79,7 @@ const mergedClass = computed(() => {
|
|
|
79
79
|
const userClass = (attrs.class as string) ?? ''
|
|
80
80
|
const defaultClass = hasWidthUtility(userClass)
|
|
81
81
|
? 'm-0 mx-auto'
|
|
82
|
-
: 'w-150 m-0 mx-auto'
|
|
82
|
+
: 'max-w-150 m-0 mx-auto'
|
|
83
83
|
return twMerge(defaultClass, userClass)
|
|
84
84
|
})
|
|
85
85
|
|
package/dist/components/Hr.vue
CHANGED
|
@@ -1,133 +1,33 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, useAttrs } from 'vue'
|
|
3
|
-
import {
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const props = defineProps({
|
|
8
|
-
/**
|
|
9
|
-
* Height (thickness) of the divider line.
|
|
10
|
-
*
|
|
11
|
-
* @default '1px'
|
|
12
|
-
*/
|
|
13
|
-
height: {
|
|
14
|
-
type: [String, Number],
|
|
15
|
-
default: '1px'
|
|
16
|
-
},
|
|
17
|
-
/**
|
|
18
|
-
* Background color of the divider line.
|
|
19
|
-
*
|
|
20
|
-
* Defaults to `#cbd5e1` when no Tailwind `bg-*` class is used.
|
|
21
|
-
*
|
|
22
|
-
* @example '#e2e8f0'
|
|
23
|
-
*/
|
|
24
|
-
color: {
|
|
25
|
-
type: String,
|
|
26
|
-
default: null
|
|
27
|
-
},
|
|
28
|
-
/**
|
|
29
|
-
* Vertical spacing (margin) above and below the divider.
|
|
30
|
-
*
|
|
31
|
-
* Overridden by `top` and `bottom` if set.
|
|
32
|
-
*
|
|
33
|
-
* @default '24px'
|
|
34
|
-
*/
|
|
35
|
-
spaceY: {
|
|
36
|
-
type: [String, Number],
|
|
37
|
-
default: '24px'
|
|
38
|
-
},
|
|
39
|
-
/**
|
|
40
|
-
* Horizontal spacing (margin) on both sides of the divider.
|
|
41
|
-
*
|
|
42
|
-
* Overridden by `left` and `right` if set.
|
|
43
|
-
*/
|
|
44
|
-
spaceX: {
|
|
45
|
-
type: [String, Number],
|
|
46
|
-
default: null
|
|
47
|
-
},
|
|
48
|
-
/** Margin above the divider. Overrides `spaceY` for the top side. */
|
|
49
|
-
top: {
|
|
50
|
-
type: [String, Number],
|
|
51
|
-
default: null
|
|
52
|
-
},
|
|
53
|
-
/** Margin below the divider. Overrides `spaceY` for the bottom side. */
|
|
54
|
-
bottom: {
|
|
55
|
-
type: [String, Number],
|
|
56
|
-
default: null
|
|
57
|
-
},
|
|
58
|
-
/** Margin to the left of the divider. Overrides `spaceX` for the left side. */
|
|
59
|
-
left: {
|
|
60
|
-
type: [String, Number],
|
|
61
|
-
default: null
|
|
62
|
-
},
|
|
63
|
-
/** Margin to the right of the divider. Overrides `spaceX` for the right side. */
|
|
64
|
-
right: {
|
|
65
|
-
type: [String, Number],
|
|
66
|
-
default: null
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const hasBgClass = computed(() =>
|
|
71
|
-
typeof attrs.class === 'string' &&
|
|
72
|
-
attrs.class.split(' ').some(c => c.startsWith('bg-'))
|
|
73
|
-
)
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
74
6
|
|
|
75
|
-
const
|
|
76
|
-
const s = []
|
|
77
|
-
const height = normalizeToPixels(props.height || '1px')
|
|
78
|
-
|
|
79
|
-
s.push(`height: ${height};`)
|
|
80
|
-
s.push(`line-height: ${height};`)
|
|
81
|
-
|
|
82
|
-
// Color
|
|
83
|
-
if (props.color) {
|
|
84
|
-
s.push(`background-color: ${props.color};`)
|
|
85
|
-
} else if (!hasBgClass.value) {
|
|
86
|
-
s.push('background-color: #cbd5e1;')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Margins reset
|
|
90
|
-
if (
|
|
91
|
-
props.top != null ||
|
|
92
|
-
props.bottom != null ||
|
|
93
|
-
props.left != null ||
|
|
94
|
-
props.right != null ||
|
|
95
|
-
props.spaceY != null ||
|
|
96
|
-
props.spaceX != null
|
|
97
|
-
) {
|
|
98
|
-
s.push('margin: 0;')
|
|
99
|
-
}
|
|
7
|
+
const attrs = useAttrs()
|
|
100
8
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const v = props.spaceY === 0 ? '0px' : normalizeToPixels(props.spaceY)
|
|
104
|
-
s.push(`margin-top: ${v}; margin-bottom: ${v};`)
|
|
105
|
-
}
|
|
9
|
+
const HEIGHT_RE = /(?:^|\s)h-([\w./\-[\]%]+)/g
|
|
10
|
+
const LEADING_RE = /(?:^|\s)leading-/
|
|
106
11
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
12
|
+
const mergedClass = computed(() => {
|
|
13
|
+
const userClass = (attrs.class as string) || ''
|
|
14
|
+
const heights = [...userClass.matchAll(HEIGHT_RE)]
|
|
15
|
+
const userHeight = heights.length ? heights[heights.length - 1][1] : null
|
|
16
|
+
const userHasLeading = LEADING_RE.test(userClass)
|
|
112
17
|
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (props.bottom != null) {
|
|
118
|
-
s.push(`margin-bottom: ${normalizeToPixels(props.bottom)};`)
|
|
119
|
-
}
|
|
120
|
-
if (props.left != null) {
|
|
121
|
-
s.push(`margin-left: ${normalizeToPixels(props.left)};`)
|
|
122
|
-
}
|
|
123
|
-
if (props.right != null) {
|
|
124
|
-
s.push(`margin-right: ${normalizeToPixels(props.right)};`)
|
|
125
|
-
}
|
|
18
|
+
const defaults = ['my-6', 'bg-slate-300']
|
|
19
|
+
if (!userHeight) defaults.push('h-px')
|
|
20
|
+
if (!userHasLeading && !userHeight) defaults.push('leading-px')
|
|
126
21
|
|
|
127
|
-
|
|
22
|
+
const derived = userHeight && !userHasLeading ? `leading-${userHeight}` : ''
|
|
23
|
+
return twMerge(defaults.join(' '), userClass, derived)
|
|
128
24
|
})
|
|
129
25
|
</script>
|
|
130
26
|
|
|
131
27
|
<template>
|
|
132
|
-
<div
|
|
28
|
+
<div
|
|
29
|
+
role="separator"
|
|
30
|
+
v-bind="{ ...$attrs, class: undefined }"
|
|
31
|
+
:class="mergedClass"
|
|
32
|
+
>‍</div>
|
|
133
33
|
</template>
|
package/dist/components/Html.vue
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { createStaticVNode, provide, useAttrs, useSlots } from 'vue'
|
|
2
|
+
import { createStaticVNode, inject, provide, useAttrs, useSlots } from 'vue'
|
|
3
3
|
import type { PropType } from 'vue'
|
|
4
4
|
import { outlookFallbackProp } from './utils.ts'
|
|
5
5
|
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
6
|
+
import { RenderContextKey } from '../composables/renderContext'
|
|
6
7
|
|
|
7
8
|
defineOptions({ inheritAttrs: false })
|
|
8
9
|
|
|
@@ -79,10 +80,27 @@ const props = defineProps({
|
|
|
79
80
|
* @default true
|
|
80
81
|
*/
|
|
81
82
|
outlookFallback: outlookFallbackProp,
|
|
83
|
+
/**
|
|
84
|
+
* DOCTYPE declaration prepended to the rendered HTML.
|
|
85
|
+
*
|
|
86
|
+
* Overrides `doctype` in the Maizzle config and any value
|
|
87
|
+
* set via `useDoctype()` in the same template.
|
|
88
|
+
*
|
|
89
|
+
* @default '<!DOCTYPE html>'
|
|
90
|
+
*/
|
|
91
|
+
doctype: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: undefined,
|
|
94
|
+
},
|
|
82
95
|
})
|
|
83
96
|
|
|
84
97
|
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
85
98
|
|
|
99
|
+
if (props.doctype !== undefined) {
|
|
100
|
+
const ctx = inject(RenderContextKey, undefined)
|
|
101
|
+
if (ctx) ctx.doctype = props.doctype
|
|
102
|
+
}
|
|
103
|
+
|
|
86
104
|
provide('htmlLang', props.lang)
|
|
87
105
|
|
|
88
106
|
const render = () => {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode, type PropType } from 'vue'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import { encode } from 'uqr'
|
|
5
|
+
|
|
6
|
+
type Ecc = 'L' | 'M' | 'Q' | 'H'
|
|
7
|
+
|
|
8
|
+
const escapeAttr = (v: string) =>
|
|
9
|
+
v
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse a Tailwind sizing token's pixel equivalent.
|
|
17
|
+
*
|
|
18
|
+
* Assumes the default v4 spacing scale (1 unit = 0.25rem),
|
|
19
|
+
* which resolves to 4px at the standard 16px root font.
|
|
20
|
+
* Arbitrary values accept `px` and `rem`; other units
|
|
21
|
+
* fall through to the caller's default.
|
|
22
|
+
*/
|
|
23
|
+
function tokenToPx(token: string): number {
|
|
24
|
+
const seg = token.split(':').at(-1) ?? ''
|
|
25
|
+
const m = seg.match(/^(?:size|w|h)-(.+)$/)
|
|
26
|
+
if (!m) return 0
|
|
27
|
+
const v = m[1]
|
|
28
|
+
if (v.startsWith('[') && v.endsWith(']')) {
|
|
29
|
+
const um = v.slice(1, -1).match(/^([\d.]+)(px|rem)?$/)
|
|
30
|
+
if (!um) return 0
|
|
31
|
+
const n = Number.parseFloat(um[1])
|
|
32
|
+
return um[2] === 'rem' ? n * 16 : n
|
|
33
|
+
}
|
|
34
|
+
if (/^\d+(?:\.\d+)?$/.test(v)) return Number.parseFloat(v) * 4
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Partition a class string into two buckets:
|
|
40
|
+
*
|
|
41
|
+
* - `sizing` — width/height/size utilities used for cell px math.
|
|
42
|
+
* - `neutral` — everything else; lands on the table verbatim.
|
|
43
|
+
*/
|
|
44
|
+
function partition(cls: string): { neutral: string[]; sizing: string[] } {
|
|
45
|
+
const neutral: string[] = []
|
|
46
|
+
const sizing: string[] = []
|
|
47
|
+
for (const t of cls.split(/\s+/).filter(Boolean)) {
|
|
48
|
+
const last = t.split(':').at(-1) ?? ''
|
|
49
|
+
if (/^(?:size|w|h|min-w|min-h|max-w|max-h)-/.test(last)) sizing.push(t)
|
|
50
|
+
else neutral.push(t)
|
|
51
|
+
}
|
|
52
|
+
return { neutral, sizing }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
name: 'QrCode',
|
|
57
|
+
inheritAttrs: false,
|
|
58
|
+
props: {
|
|
59
|
+
/** Data to encode (URL or arbitrary text). */
|
|
60
|
+
value: {
|
|
61
|
+
type: String,
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Error correction level: redundancy that keeps the code
|
|
66
|
+
* scannable when partially obscured (e.g. a logo overlay),
|
|
67
|
+
* at the cost of a larger matrix.
|
|
68
|
+
*
|
|
69
|
+
* - `L` ~7% recovery
|
|
70
|
+
* - `M` ~15% recovery (default, fine for on-screen display)
|
|
71
|
+
* - `Q` ~25% recovery
|
|
72
|
+
* - `H` ~30% recovery
|
|
73
|
+
*/
|
|
74
|
+
ecc: {
|
|
75
|
+
type: String as PropType<Ecc>,
|
|
76
|
+
default: 'M',
|
|
77
|
+
validator: (v: string) => ['L', 'M', 'Q', 'H'].includes(v),
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Width of the light "quiet zone" around the code, in modules.
|
|
81
|
+
* Spec recommends ≥ 4; 1 is plenty for on-screen scans.
|
|
82
|
+
*/
|
|
83
|
+
border: {
|
|
84
|
+
type: Number,
|
|
85
|
+
default: 1,
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Accessible label exposed via `aria-label` on the table.
|
|
89
|
+
*/
|
|
90
|
+
alt: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: '',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
setup(props, { attrs }) {
|
|
96
|
+
const userClass = String(attrs.class ?? '')
|
|
97
|
+
const { neutral, sizing } = partition(userClass)
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Effective pixel size from the user's sizing token, else
|
|
101
|
+
* 120px (= `size-30` on the default v4 spacing scale).
|
|
102
|
+
*/
|
|
103
|
+
const sizingToken = sizing[0]
|
|
104
|
+
const effectivePx = sizingToken ? (tokenToPx(sizingToken) || 120) : 120
|
|
105
|
+
|
|
106
|
+
const result = encode(props.value, {
|
|
107
|
+
ecc: props.ecc,
|
|
108
|
+
border: props.border,
|
|
109
|
+
boostEcc: true,
|
|
110
|
+
})
|
|
111
|
+
const matrix = result.data
|
|
112
|
+
const dim = matrix.length
|
|
113
|
+
const cellPx = Math.max(1, Math.floor(effectivePx / dim))
|
|
114
|
+
const totalPx = cellPx * dim
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Table base classes:
|
|
118
|
+
*
|
|
119
|
+
* - `size-[Npx]` matches the outer to cell math (no stripe).
|
|
120
|
+
* - `[&_td]:*` sizes each cell and zeroes its font-size.
|
|
121
|
+
* - `bg-*` paints the table; light cells stay transparent.
|
|
122
|
+
* - `qr:*` paints dark cells via the registered variant.
|
|
123
|
+
*
|
|
124
|
+
* Defaults and user tokens share each form, so `twMerge`
|
|
125
|
+
* resolves overrides cleanly.
|
|
126
|
+
*/
|
|
127
|
+
const baseTable = [
|
|
128
|
+
`size-[${totalPx}px]`,
|
|
129
|
+
`[&_td]:w-[${cellPx}px]`,
|
|
130
|
+
`[&_td]:h-[${cellPx}px]`,
|
|
131
|
+
'[&_td]:text-[0px]',
|
|
132
|
+
'bg-white',
|
|
133
|
+
'dark:bg-gray-950',
|
|
134
|
+
'qr:bg-gray-950',
|
|
135
|
+
'dark:qr:bg-white',
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
const merged = twMerge([...baseTable, ...neutral].join(' '))
|
|
139
|
+
|
|
140
|
+
let rows = ''
|
|
141
|
+
for (let y = 0; y < dim; y++) {
|
|
142
|
+
let cells = ''
|
|
143
|
+
const row = matrix[y]
|
|
144
|
+
for (let x = 0; x < dim; x++) {
|
|
145
|
+
cells += row[x] ? '<td class="qd"></td>' : '<td></td>'
|
|
146
|
+
}
|
|
147
|
+
rows += `<tr>${cells}</tr>`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const altAttr = props.alt ? ` aria-label="${escapeAttr(props.alt)}"` : ''
|
|
151
|
+
const styleAttr = attrs.style ? ` style="${String(attrs.style)}"` : ''
|
|
152
|
+
const html = `<table class="${escapeAttr(merged)}" role="img"${altAttr} cellpadding="0" cellspacing="0" border="0"${styleAttr}>${rows}</table>`
|
|
153
|
+
|
|
154
|
+
return () => createStaticVNode(html, 1)
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
</script>
|