@maizzle/framework 6.0.0-rc.13 → 6.0.0-rc.14
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/components/Button.vue +2 -2
- package/dist/components/CodeBlock.vue +2 -1
- package/dist/components/Column.vue +28 -22
- package/dist/components/Container.vue +47 -9
- package/dist/components/Font.vue +96 -0
- package/dist/components/Layout.vue +9 -4
- package/dist/components/Overlap.vue +75 -18
- package/dist/components/Row.vue +40 -19
- package/dist/components/Section.vue +35 -8
- package/dist/components/utils.d.mts +14 -1
- package/dist/components/utils.d.mts.map +1 -1
- package/dist/components/utils.mjs +32 -1
- package/dist/components/utils.mjs.map +1 -1
- package/dist/components/utils.ts +39 -0
- package/dist/composables/renderContext.d.mts +8 -1
- package/dist/composables/renderContext.d.mts.map +1 -1
- package/dist/composables/renderContext.mjs.map +1 -1
- package/dist/composables/useFont.d.mts +50 -0
- package/dist/composables/useFont.d.mts.map +1 -0
- package/dist/composables/useFont.mjs +93 -0
- package/dist/composables/useFont.mjs.map +1 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/plugins/postcss/quoteFontFamilies.d.mts +13 -0
- package/dist/plugins/postcss/quoteFontFamilies.d.mts.map +1 -0
- package/dist/plugins/postcss/quoteFontFamilies.mjs +84 -0
- package/dist/plugins/postcss/quoteFontFamilies.mjs.map +1 -0
- package/dist/render/createRenderer.mjs +8 -2
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/render/injectFonts.d.mts +15 -0
- package/dist/render/injectFonts.d.mts.map +1 -0
- package/dist/render/injectFonts.mjs +46 -0
- package/dist/render/injectFonts.mjs.map +1 -0
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +6 -2
- package/dist/serve.mjs.map +1 -1
- package/dist/server/ui/App.vue +25 -11
- package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
- package/dist/server/ui/pages/Preview.vue +24 -5
- package/dist/transformers/columnWidth.d.mts +31 -0
- package/dist/transformers/columnWidth.d.mts.map +1 -0
- package/dist/transformers/columnWidth.mjs +166 -0
- package/dist/transformers/columnWidth.mjs.map +1 -0
- package/dist/transformers/index.d.mts.map +1 -1
- package/dist/transformers/index.mjs +4 -0
- package/dist/transformers/index.mjs.map +1 -1
- package/dist/transformers/msoWidthFromClass.d.mts +19 -0
- package/dist/transformers/msoWidthFromClass.d.mts.map +1 -0
- package/dist/transformers/msoWidthFromClass.mjs +61 -0
- package/dist/transformers/msoWidthFromClass.mjs.map +1 -0
- package/dist/transformers/tailwindcss.d.mts.map +1 -1
- package/dist/transformers/tailwindcss.mjs +4 -12
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/utils/decodeStyleEntities.d.mts +15 -0
- package/dist/utils/decodeStyleEntities.d.mts.map +1 -0
- package/dist/utils/decodeStyleEntities.mjs +18 -0
- package/dist/utils/decodeStyleEntities.mjs.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRenderer.mjs","names":["merge"],"sources":["../../src/render/createRenderer.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { isLaravel } from '../utils/detect.ts'\nimport { createServer, mergeConfig, type InlineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport Markdown from 'unplugin-vue-markdown/vite'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport Components from 'unplugin-vue-components/vite'\nimport { unheadVueComposablesImports } from '@unhead/vue'\nimport { defu as merge } from 'defu'\nimport { createSSRApp } from 'vue'\nimport { renderToString } from 'vue/server-renderer'\nimport { createHead, renderSSRHead } from '@unhead/vue/server'\nimport { MaizzleConfigKey } from '../composables/useConfig.ts'\nimport { RenderContextKey } from '../composables/renderContext.ts'\nimport type { Component, InjectionKey } from 'vue'\nimport type { MaizzleConfig, MarkdownConfig } from '../types/index.ts'\nimport type { MarkdownExit } from 'markdown-exit'\nimport type { RenderContext } from '../composables/renderContext.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Vite plugin that extracts raw slot content from <CodeBlock> tags\n * and passes it as a :code prop before Vue compiles the template.\n *\n * This lets users write HTML naturally inside CodeBlock slots without\n * Vue attempting to compile it as template syntax.\n */\nfunction codeBlockExtract() {\n // Matches <CodeBlock ...>content</CodeBlock> (and kebab-case <code-block>)\n const re = /<(CodeBlock|code-block)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n\n return {\n name: 'maizzle:code-block-extract',\n enforce: 'pre' as const,\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('CodeBlock') && !code.includes('code-block')) return\n\n const transformed = code.replace(re, (_match, tag, attrs, content) => {\n // Skip if already has a :code or v-bind:code prop\n if (/(?:^|\\s):code\\b/.test(attrs) || /v-bind:code\\b/.test(attrs)) return _match\n\n // Strip leading/trailing blank lines, then dedent based on\n // the minimum indent of non-empty lines (à la min-indent)\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min, ws) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n // HTML-escape for safe embedding in a static attribute value.\n const escaped = dedented\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${attrs} code=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n\n/**\n * Vite plugin that pre-processes <Markdown> tags:\n * - Extracts slot content, dedents it, and passes as :content prop\n * - Resolves `src` prop to read file contents at build time\n */\nfunction markdownExtract() {\n const re = /<(Markdown|markdown)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n const selfClosingRe = /<(Markdown|markdown)((?:\\s[^>]*?\\bsrc\\s*=\\s*\"[^\"]*\"[^>]*?))\\/>/g\n\n return {\n name: 'maizzle:markdown-extract',\n enforce: 'pre' as const,\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('Markdown') && !code.includes('markdown')) return\n\n let transformed = code\n\n // Handle <Markdown>content</Markdown>\n transformed = transformed.replace(re, (_match, tag, attrs, content) => {\n if (/(?:^|\\s):content\\b/.test(attrs) || /v-bind:content\\b/.test(attrs)) return _match\n\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min: number, ws: string) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n const escaped = dedented\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${attrs} content=\"${escaped}\" />`\n })\n\n // Handle <Markdown src=\"./file.md\" /> — resolve and inline file content\n transformed = transformed.replace(selfClosingRe, (_match, tag, attrs) => {\n const srcMatch = attrs.match(/\\bsrc\\s*=\\s*\"([^\"]*)\"/)\n if (!srcMatch) return _match\n\n const srcPath = srcMatch[1]\n const resolvedPath = resolve(dirname(id), srcPath)\n\n let fileContent: string\n try {\n fileContent = readFileSync(resolvedPath, 'utf-8').trim()\n } catch {\n return _match\n }\n\n // Remove src prop, add content prop\n const cleanAttrs = attrs.replace(/\\s*\\bsrc\\s*=\\s*\"[^\"]*\"/, '')\n const escaped = fileContent\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${cleanAttrs} content=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n\nconst vuePkgDir = dirname(fileURLToPath(import.meta.resolve('vue/package.json')))\nconst vueServerRendererPkgDir = dirname(fileURLToPath(import.meta.resolve('@vue/server-renderer/package.json')))\nconst unheadVuePkgDir = resolve(dirname(fileURLToPath(import.meta.resolve('@unhead/vue'))), '..')\nconst vueRouterPkgDir = dirname(fileURLToPath(import.meta.resolve('vue-router/package.json')))\n\nexport interface RenderedTemplate {\n html: string\n doctype?: string\n templateConfig: MaizzleConfig\n sfcEventHandlers: RenderContext['sfcEventHandlers']\n plaintext?: RenderContext['plaintext']\n}\n\nexport interface Renderer {\n render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate>\n invalidate(filePath: string): Promise<void>\n invalidateAll(): Promise<void>\n close(): Promise<void>\n}\n\nexport interface CreateRendererOptions {\n /** Generate .d.ts files for auto-imports and components (default: false) */\n dts?: boolean\n /** Options passed to unplugin-vue-markdown */\n markdown?: MarkdownConfig\n /** Root directory for resolving user component dirs and .d.ts output */\n root?: string\n /** Additional component directories to register for auto-import */\n componentDirs?: string[]\n /** User Vite config options to merge into the internal SSR server */\n vite?: InlineConfig\n}\n\n/**\n * Lightweight Vite SSR loader for rendering Vue SFC email templates.\n *\n * Uses only Vue + unplugin for component/auto-import resolution.\n * Tailwind CSS compilation is handled by the transformer pipeline.\n */\nexport async function createRenderer(\n options: CreateRendererOptions = {},\n): Promise<Renderer> {\n const { dts = false, markdown: markdownOptionsRaw, root = process.cwd(), componentDirs = [], vite: userViteConfig } = options\n const { shikiTheme = 'github-light', ...markdownOptions } = markdownOptionsRaw ?? {}\n\n const dtsDir = isLaravel()\n ? resolve(process.cwd(), 'resources/js/types/maizzle')\n : resolve(root, '.maizzle')\n\n const VIRTUAL_SFC_ID = 'virtual:maizzle-sfc.vue'\n let virtualSfcSource = ''\n\n // Check for a user vite.config file in the project root\n const viteConfigFile = ['vite.config.ts', 'vite.config.js']\n .map(f => resolve(root, f))\n .find(f => existsSync(f))\n\n const maizzleConfig: InlineConfig = {\n configFile: viteConfigFile ?? false,\n plugins: [\n codeBlockExtract(),\n markdownExtract(),\n {\n name: 'maizzle:virtual-sfc',\n resolveId(id) {\n if (id === VIRTUAL_SFC_ID) return id\n },\n load(id) {\n if (id === VIRTUAL_SFC_ID) return virtualSfcSource\n },\n },\n vue({\n include: [/\\.vue$/, /\\.md$/],\n template: {\n transformAssetUrls: false,\n },\n }),\n Markdown(merge(markdownOptions ?? {}, {\n headEnabled: true,\n wrapperDiv: false,\n wrapperClasses: 'prose',\n markdownOptions: {\n async highlight(code: string, lang: string) {\n const { codeToHtml } = await import('shiki')\n return codeToHtml(code, { lang, theme: shikiTheme })\n },\n },\n markdownSetup(md: MarkdownExit) {\n const wrapPre = (html: string) =>\n `<table class=\"w-full\"><tr><td class=\"max-w-0 mso-padding-alt-4\">${html}</td></tr></table>\\n`\n\n const defaultFence = md.renderer.rules.fence!\n md.renderer.rules.fence = (...args) => {\n const result = defaultFence(...args)\n if (typeof result === 'string') return wrapPre(result)\n return result.then(wrapPre)\n }\n\n const defaultCodeBlock = md.renderer.rules.code_block!\n md.renderer.rules.code_block = (...args) => wrapPre(defaultCodeBlock(...args) as string)\n },\n })),\n AutoImport({\n dirs: [\n resolve(__dirname, '../composables'),\n resolve(__dirname, '../filters'),\n ],\n imports: ['vue', unheadVueComposablesImports],\n dts: dts ? resolve(dtsDir, 'auto-imports.d.ts') : false,\n }),\n Components({\n extensions: ['vue', 'md'],\n include: [/\\.vue$/, /\\.vue\\?vue/, /\\.md$/],\n dirs: [\n resolve(__dirname, '../components'),\n resolve(root, 'components'),\n ...componentDirs,\n ],\n dts: dts ? resolve(dtsDir, 'components.d.ts') : false,\n }),\n ],\n resolve: {\n alias: {\n 'vue/server-renderer': resolve(vueServerRendererPkgDir, 'dist/server-renderer.esm-bundler.js'),\n 'vue': resolve(vuePkgDir, 'dist/vue.runtime.esm-bundler.js'),\n 'vue-router': vueRouterPkgDir,\n '@unhead/vue/server': resolve(unheadVuePkgDir, 'dist/server.mjs'),\n '@unhead/vue': resolve(unheadVuePkgDir, 'dist/index.mjs'),\n },\n },\n server: {\n middlewareMode: true,\n hmr: false,\n watch: null,\n fs: {\n allow: [process.cwd(), root, ...componentDirs, vuePkgDir, vueServerRendererPkgDir, unheadVuePkgDir, vueRouterPkgDir],\n },\n },\n appType: 'custom',\n logLevel: 'silent',\n optimizeDeps: {\n noDiscovery: true,\n },\n }\n\n // Merge user's vite config (from config.vite) under Maizzle's config.\n // mergeConfig(a, b) → b overrides a for scalars, arrays are concatenated.\n // This ensures Maizzle's critical settings (middlewareMode, appType, etc.) always win,\n // while user plugins and other options are included.\n const finalConfig = userViteConfig && !viteConfigFile\n ? mergeConfig(userViteConfig, maizzleConfig)\n : maizzleConfig\n\n const server = await createServer(finalConfig)\n\n return {\n async render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate> {\n let component: Component\n let configKey: InjectionKey<MaizzleConfig>\n let contextKey: InjectionKey<RenderContext>\n\n if (typeof input === 'string') {\n // String input goes through Vite — must use ssrLoadModule for injection keys\n // so they share the same module instance as the SFC\n const configModule = await server.ssrLoadModule(resolve(__dirname, '../composables/useConfig'))\n const contextModule = await server.ssrLoadModule(resolve(__dirname, '../composables/renderContext'))\n configKey = configModule.MaizzleConfigKey\n contextKey = contextModule.RenderContextKey\n\n if (input.includes('<template') || input.includes('<script')) {\n virtualSfcSource = input\n const mod = server.moduleGraph.getModuleById(VIRTUAL_SFC_ID)\n if (mod) server.moduleGraph.invalidateModule(mod)\n component = (await server.ssrLoadModule(VIRTUAL_SFC_ID)).default\n } else {\n component = (await server.ssrLoadModule(input)).default\n }\n } else {\n // Pre-compiled component — use directly imported keys\n component = input\n configKey = MaizzleConfigKey\n contextKey = RenderContextKey\n }\n\n const renderContext: RenderContext = {\n doctype: undefined,\n sfcConfig: undefined,\n sfcEventHandlers: [],\n }\n\n const head = createHead({ disableDefaults: true })\n const app = createSSRApp(component)\n app.use(head)\n\n // Register user Vue plugins, directives, and global properties\n if (config.vue) {\n for (const plugin of config.vue.plugins ?? []) {\n app.use(plugin)\n }\n for (const [name, directive] of Object.entries(config.vue.directives ?? {})) {\n app.directive(name, directive)\n }\n Object.assign(app.config.globalProperties, config.vue.globalProperties)\n }\n\n app.provide(configKey, config)\n app.provide(contextKey, renderContext)\n\n const ssrContext: Record<string, any> = {}\n let html: string = await renderToString(app, ssrContext)\n\n const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = await renderSSRHead(head)\n\n // Inject head entries into the rendered HTML\n if (htmlAttrs) {\n html = html.replace(/<html([^>]*)>/, `<html$1 ${htmlAttrs}>`)\n }\n if (headTags) {\n html = html.replace('</head>', `${headTags}\\n</head>`)\n }\n if (bodyAttrs) {\n html = html.replace(/<body([^>]*)>/, `<body$1 ${bodyAttrs}>`)\n }\n if (bodyTagsOpen) {\n html = html.replace(/<body([^>]*)>/, `<body$1>\\n${bodyTagsOpen}`)\n }\n if (bodyTags) {\n html = html.replace('</body>', `${bodyTags}\\n</body>`)\n }\n\n // Inject SSR teleport content into their target elements\n if (ssrContext.teleports) {\n const { parse: parseDom, serialize: serializeDom, walk } = await import('../utils/ast/index.ts')\n let dom = parseDom(html)\n\n for (const [rawTarget, content] of Object.entries(ssrContext.teleports) as [string, string][]) {\n if (!content) continue\n\n const prepend = rawTarget.endsWith(':start')\n const target = prepend ? rawTarget.slice(0, -6) : rawTarget\n const targetChildren = parseDom(content)\n\n walk(dom, (node) => {\n const el = node as import('domhandler').Element\n\n if (!el.name) return\n\n const matched\n = target === el.name\n || (target.startsWith('#') && el.attribs?.id === target.slice(1))\n || (target.startsWith('.') && el.attribs?.class?.split(/\\s+/).includes(target.slice(1)))\n\n if (matched) {\n for (const child of targetChildren) {\n child.parent = el as any\n }\n\n el.children = prepend\n ? [...targetChildren, ...(el.children || [])] as any\n : [...(el.children || []), ...targetChildren] as any\n }\n })\n }\n\n html = serializeDom(dom)\n }\n\n // Inject preheader text from usePreheader() composable\n if (renderContext.preheader) {\n const { text, fillerCount, shyCount } = renderContext.preheader\n const filler = '\\u2007\\u034F '.repeat(fillerCount)\n const shys = '\\u00AD '.repeat(shyCount)\n const previewHtml = `<div style=\"display:none\">${text}${filler}${shys}\\u00A0</div>`\n html = html.replace(/<body([^>]*)>/, `<body$1>${previewHtml}`)\n }\n\n return {\n html,\n doctype: renderContext.doctype,\n templateConfig: renderContext.sfcConfig ?? config,\n sfcEventHandlers: renderContext.sfcEventHandlers,\n plaintext: renderContext.plaintext,\n }\n },\n\n async invalidate(filePath: string): Promise<void> {\n const mod = await server.moduleGraph.getModuleByUrl(filePath)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n },\n\n async invalidateAll(): Promise<void> {\n for (const mod of server.moduleGraph.idToModuleMap.values()) {\n server.moduleGraph.invalidateModule(mod)\n }\n },\n\n async close(): Promise<void> {\n await server.close()\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createRenderer.mjs","names":["merge"],"sources":["../../src/render/createRenderer.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { isLaravel } from '../utils/detect.ts'\nimport { createServer, mergeConfig, type InlineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport Markdown from 'unplugin-vue-markdown/vite'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport Components from 'unplugin-vue-components/vite'\nimport { unheadVueComposablesImports } from '@unhead/vue'\nimport { defu as merge } from 'defu'\nimport { createSSRApp } from 'vue'\nimport { renderToString } from 'vue/server-renderer'\nimport { createHead, renderSSRHead } from '@unhead/vue/server'\nimport { MaizzleConfigKey } from '../composables/useConfig.ts'\nimport { RenderContextKey } from '../composables/renderContext.ts'\nimport type { Component, InjectionKey } from 'vue'\nimport type { MaizzleConfig, MarkdownConfig } from '../types/index.ts'\nimport type { MarkdownExit } from 'markdown-exit'\nimport type { RenderContext } from '../composables/renderContext.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Vite plugin that extracts raw slot content from <CodeBlock> tags\n * and passes it as a :code prop before Vue compiles the template.\n *\n * This lets users write HTML naturally inside CodeBlock slots without\n * Vue attempting to compile it as template syntax.\n */\nfunction codeBlockExtract() {\n // Matches <CodeBlock ...>content</CodeBlock> (and kebab-case <code-block>)\n const re = /<(CodeBlock|code-block)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n\n return {\n name: 'maizzle:code-block-extract',\n enforce: 'pre' as const,\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('CodeBlock') && !code.includes('code-block')) return\n\n const transformed = code.replace(re, (_match, tag, attrs, content) => {\n // Skip if already has a :code or v-bind:code prop\n if (/(?:^|\\s):code\\b/.test(attrs) || /v-bind:code\\b/.test(attrs)) return _match\n\n // Strip leading/trailing blank lines, then dedent based on\n // the minimum indent of non-empty lines (à la min-indent)\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min, ws) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n // HTML-escape for safe embedding in a static attribute value.\n const escaped = dedented\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${attrs} code=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n\n/**\n * Vite plugin that pre-processes <Markdown> tags:\n * - Extracts slot content, dedents it, and passes as :content prop\n * - Resolves `src` prop to read file contents at build time\n */\nfunction markdownExtract() {\n const re = /<(Markdown|markdown)((?:\\s[^>]*?)?)>([\\s\\S]*?)<\\/\\1>/g\n const selfClosingRe = /<(Markdown|markdown)((?:\\s[^>]*?\\bsrc\\s*=\\s*\"[^\"]*\"[^>]*?))\\/>/g\n\n return {\n name: 'maizzle:markdown-extract',\n enforce: 'pre' as const,\n transform(code: string, id: string) {\n if (!id.endsWith('.vue') && !id.endsWith('.md')) return\n if (!code.includes('Markdown') && !code.includes('markdown')) return\n\n let transformed = code\n\n // Handle <Markdown>content</Markdown>\n transformed = transformed.replace(re, (_match, tag, attrs, content) => {\n if (/(?:^|\\s):content\\b/.test(attrs) || /v-bind:content\\b/.test(attrs)) return _match\n\n const stripped = content.replace(/^\\n+/, '').replace(/\\s+$/, '')\n if (!stripped) return _match\n\n const minIndent = stripped.match(/^[ \\t]*(?=\\S)/gm)\n ?.reduce((min: number, ws: string) => Math.min(min, ws.length), Infinity) ?? 0\n\n const dedented = minIndent > 0\n ? stripped.replace(new RegExp(`^[ \\\\t]{${minIndent}}`, 'gm'), '')\n : stripped\n\n const escaped = dedented\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${attrs} content=\"${escaped}\" />`\n })\n\n // Handle <Markdown src=\"./file.md\" /> — resolve and inline file content\n transformed = transformed.replace(selfClosingRe, (_match, tag, attrs) => {\n const srcMatch = attrs.match(/\\bsrc\\s*=\\s*\"([^\"]*)\"/)\n if (!srcMatch) return _match\n\n const srcPath = srcMatch[1]\n const resolvedPath = resolve(dirname(id), srcPath)\n\n let fileContent: string\n try {\n fileContent = readFileSync(resolvedPath, 'utf-8').trim()\n } catch {\n return _match\n }\n\n // Remove src prop, add content prop\n const cleanAttrs = attrs.replace(/\\s*\\bsrc\\s*=\\s*\"[^\"]*\"/, '')\n const escaped = fileContent\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n\n return `<${tag}${cleanAttrs} content=\"${escaped}\" />`\n })\n\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n }\n}\n\nconst vuePkgDir = dirname(fileURLToPath(import.meta.resolve('vue/package.json')))\nconst vueServerRendererPkgDir = dirname(fileURLToPath(import.meta.resolve('@vue/server-renderer/package.json')))\nconst unheadVuePkgDir = resolve(dirname(fileURLToPath(import.meta.resolve('@unhead/vue'))), '..')\nconst vueRouterPkgDir = dirname(fileURLToPath(import.meta.resolve('vue-router/package.json')))\n\nexport interface RenderedTemplate {\n html: string\n doctype?: string\n templateConfig: MaizzleConfig\n sfcEventHandlers: RenderContext['sfcEventHandlers']\n plaintext?: RenderContext['plaintext']\n}\n\nexport interface Renderer {\n render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate>\n invalidate(filePath: string): Promise<void>\n invalidateAll(): Promise<void>\n close(): Promise<void>\n}\n\nexport interface CreateRendererOptions {\n /** Generate .d.ts files for auto-imports and components (default: false) */\n dts?: boolean\n /** Options passed to unplugin-vue-markdown */\n markdown?: MarkdownConfig\n /** Root directory for resolving user component dirs and .d.ts output */\n root?: string\n /** Additional component directories to register for auto-import */\n componentDirs?: string[]\n /** User Vite config options to merge into the internal SSR server */\n vite?: InlineConfig\n}\n\n/**\n * Lightweight Vite SSR loader for rendering Vue SFC email templates.\n *\n * Uses only Vue + unplugin for component/auto-import resolution.\n * Tailwind CSS compilation is handled by the transformer pipeline.\n */\nexport async function createRenderer(\n options: CreateRendererOptions = {},\n): Promise<Renderer> {\n const { dts = false, markdown: markdownOptionsRaw, root = process.cwd(), componentDirs = [], vite: userViteConfig } = options\n const { shikiTheme = 'github-light', ...markdownOptions } = markdownOptionsRaw ?? {}\n\n const dtsDir = isLaravel()\n ? resolve(process.cwd(), 'resources/js/types/maizzle')\n : resolve(root, '.maizzle')\n\n const VIRTUAL_SFC_ID = 'virtual:maizzle-sfc.vue'\n let virtualSfcSource = ''\n\n // Check for a user vite.config file in the project root\n const viteConfigFile = ['vite.config.ts', 'vite.config.js']\n .map(f => resolve(root, f))\n .find(f => existsSync(f))\n\n const maizzleConfig: InlineConfig = {\n configFile: viteConfigFile ?? false,\n plugins: [\n codeBlockExtract(),\n markdownExtract(),\n {\n name: 'maizzle:virtual-sfc',\n resolveId(id) {\n if (id === VIRTUAL_SFC_ID) return id\n },\n load(id) {\n if (id === VIRTUAL_SFC_ID) return virtualSfcSource\n },\n },\n vue({\n include: [/\\.vue$/, /\\.md$/],\n template: {\n transformAssetUrls: false,\n },\n }),\n Markdown(merge(markdownOptions ?? {}, {\n headEnabled: true,\n wrapperDiv: false,\n wrapperClasses: 'prose',\n markdownOptions: {\n async highlight(code: string, lang: string) {\n const { codeToHtml } = await import('shiki')\n return codeToHtml(code, { lang, theme: shikiTheme })\n },\n },\n markdownSetup(md: MarkdownExit) {\n const wrapPre = (html: string) =>\n `<table class=\"w-full\"><tr><td class=\"max-w-0 mso-padding-alt-4\">${html}</td></tr></table>\\n`\n\n const defaultFence = md.renderer.rules.fence!\n md.renderer.rules.fence = (...args) => {\n const result = defaultFence(...args)\n if (typeof result === 'string') return wrapPre(result)\n return result.then(wrapPre)\n }\n\n const defaultCodeBlock = md.renderer.rules.code_block!\n md.renderer.rules.code_block = (...args) => wrapPre(defaultCodeBlock(...args) as string)\n },\n })),\n AutoImport({\n dirs: [\n resolve(__dirname, '../composables'),\n resolve(__dirname, '../filters'),\n ],\n imports: ['vue', unheadVueComposablesImports],\n dts: dts ? resolve(dtsDir, 'auto-imports.d.ts') : false,\n }),\n Components({\n extensions: ['vue', 'md'],\n include: [/\\.vue$/, /\\.vue\\?vue/, /\\.md$/],\n dirs: [\n resolve(__dirname, '../components'),\n resolve(root, 'components'),\n ...componentDirs,\n ],\n dts: dts ? resolve(dtsDir, 'components.d.ts') : false,\n }),\n ],\n resolve: {\n alias: {\n 'vue/server-renderer': resolve(vueServerRendererPkgDir, 'dist/server-renderer.esm-bundler.js'),\n 'vue': resolve(vuePkgDir, 'dist/vue.runtime.esm-bundler.js'),\n 'vue-router': vueRouterPkgDir,\n '@unhead/vue/server': resolve(unheadVuePkgDir, 'dist/server.mjs'),\n '@unhead/vue': resolve(unheadVuePkgDir, 'dist/index.mjs'),\n },\n },\n server: {\n middlewareMode: true,\n hmr: false,\n watch: null,\n fs: {\n allow: [process.cwd(), root, ...componentDirs, vuePkgDir, vueServerRendererPkgDir, unheadVuePkgDir, vueRouterPkgDir],\n },\n },\n appType: 'custom',\n logLevel: 'silent',\n optimizeDeps: {\n noDiscovery: true,\n },\n }\n\n // Merge user's vite config (from config.vite) under Maizzle's config.\n // mergeConfig(a, b) → b overrides a for scalars, arrays are concatenated.\n // This ensures Maizzle's critical settings (middlewareMode, appType, etc.) always win,\n // while user plugins and other options are included.\n const finalConfig = userViteConfig && !viteConfigFile\n ? mergeConfig(userViteConfig, maizzleConfig)\n : maizzleConfig\n\n const server = await createServer(finalConfig)\n\n return {\n async render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate> {\n let component: Component\n let configKey: InjectionKey<MaizzleConfig>\n let contextKey: InjectionKey<RenderContext>\n\n if (typeof input === 'string') {\n // String input goes through Vite — must use ssrLoadModule for injection keys\n // so they share the same module instance as the SFC\n const configModule = await server.ssrLoadModule(resolve(__dirname, '../composables/useConfig'))\n const contextModule = await server.ssrLoadModule(resolve(__dirname, '../composables/renderContext'))\n configKey = configModule.MaizzleConfigKey\n contextKey = contextModule.RenderContextKey\n\n if (input.includes('<template') || input.includes('<script')) {\n virtualSfcSource = input\n const mod = server.moduleGraph.getModuleById(VIRTUAL_SFC_ID)\n if (mod) server.moduleGraph.invalidateModule(mod)\n component = (await server.ssrLoadModule(VIRTUAL_SFC_ID)).default\n } else {\n component = (await server.ssrLoadModule(input)).default\n }\n } else {\n // Pre-compiled component — use directly imported keys\n component = input\n configKey = MaizzleConfigKey\n contextKey = RenderContextKey\n }\n\n const renderContext: RenderContext = {\n doctype: undefined,\n sfcConfig: undefined,\n sfcEventHandlers: [],\n }\n\n const head = createHead({ disableDefaults: true })\n const app = createSSRApp(component)\n app.use(head)\n\n // Register user Vue plugins, directives, and global properties\n if (config.vue) {\n for (const plugin of config.vue.plugins ?? []) {\n app.use(plugin)\n }\n for (const [name, directive] of Object.entries(config.vue.directives ?? {})) {\n app.directive(name, directive)\n }\n Object.assign(app.config.globalProperties, config.vue.globalProperties)\n }\n\n app.provide(configKey, config)\n app.provide(contextKey, renderContext)\n\n const ssrContext: Record<string, any> = {}\n let html: string = await renderToString(app, ssrContext)\n\n const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = await renderSSRHead(head)\n\n // Inject head entries into the rendered HTML\n if (htmlAttrs) {\n html = html.replace(/<html([^>]*)>/, `<html$1 ${htmlAttrs}>`)\n }\n if (headTags) {\n html = html.replace('</head>', `${headTags}\\n</head>`)\n }\n if (bodyAttrs) {\n html = html.replace(/<body([^>]*)>/, `<body$1 ${bodyAttrs}>`)\n }\n if (bodyTagsOpen) {\n html = html.replace(/<body([^>]*)>/, `<body$1>\\n${bodyTagsOpen}`)\n }\n if (bodyTags) {\n html = html.replace('</body>', `${bodyTags}\\n</body>`)\n }\n\n // Inject SSR teleport content into their target elements\n const hasTeleports = ssrContext.teleports && Object.keys(ssrContext.teleports).length > 0\n const hasFonts = (renderContext.fonts?.length ?? 0) > 0\n\n if (hasTeleports || hasFonts) {\n const { parse: parseDom, serialize: serializeDom, walk } = await import('../utils/ast/index.ts')\n let dom = parseDom(html)\n\n if (hasTeleports) {\n for (const [rawTarget, content] of Object.entries(ssrContext.teleports) as [string, string][]) {\n if (!content) continue\n\n const prepend = rawTarget.endsWith(':start')\n const target = prepend ? rawTarget.slice(0, -6) : rawTarget\n const targetChildren = parseDom(content)\n\n walk(dom, (node) => {\n const el = node as import('domhandler').Element\n\n if (!el.name) return\n\n const matched\n = target === el.name\n || (target.startsWith('#') && el.attribs?.id === target.slice(1))\n || (target.startsWith('.') && el.attribs?.class?.split(/\\s+/).includes(target.slice(1)))\n\n if (matched) {\n for (const child of targetChildren) {\n child.parent = el as any\n }\n\n el.children = prepend\n ? [...targetChildren, ...(el.children || [])] as any\n : [...(el.children || []), ...targetChildren] as any\n }\n })\n }\n }\n\n if (hasFonts) {\n const { injectFonts } = await import('./injectFonts.ts')\n injectFonts(dom, renderContext.fonts!, parseDom, walk)\n }\n\n html = serializeDom(dom)\n }\n\n // Inject preheader text from usePreheader() composable\n if (renderContext.preheader) {\n const { text, fillerCount, shyCount } = renderContext.preheader\n const filler = '\\u2007\\u034F '.repeat(fillerCount)\n const shys = '\\u00AD '.repeat(shyCount)\n const previewHtml = `<div style=\"display:none\">${text}${filler}${shys}\\u00A0</div>`\n html = html.replace(/<body([^>]*)>/, `<body$1>${previewHtml}`)\n }\n\n return {\n html,\n doctype: renderContext.doctype,\n templateConfig: renderContext.sfcConfig ?? config,\n sfcEventHandlers: renderContext.sfcEventHandlers,\n plaintext: renderContext.plaintext,\n }\n },\n\n async invalidate(filePath: string): Promise<void> {\n const mod = await server.moduleGraph.getModuleByUrl(filePath)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n },\n\n async invalidateAll(): Promise<void> {\n for (const mod of server.moduleGraph.idToModuleMap.values()) {\n server.moduleGraph.invalidateModule(mod)\n }\n },\n\n async close(): Promise<void> {\n await server.close()\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FontRegistration } from "../composables/renderContext.mjs";
|
|
2
|
+
import { ChildNode } from "domhandler";
|
|
3
|
+
|
|
4
|
+
//#region src/render/injectFonts.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Inject font `<link>` tags into `<head>` and merge `@theme` declarations
|
|
7
|
+
* into the template's existing Tailwind `<style>` block (so the
|
|
8
|
+
* `font-{slug}` utilities are generated in the same compilation unit
|
|
9
|
+
* as the Tailwind import). Without a Tailwind import, emits plain
|
|
10
|
+
* `.font-{slug}` class rules so the utility still works.
|
|
11
|
+
*/
|
|
12
|
+
declare function injectFonts(dom: ChildNode[], fonts: FontRegistration[], parseDom: (html: string) => ChildNode[], walk: (ast: ChildNode[], cb: (n: ChildNode) => void) => void): void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { injectFonts };
|
|
15
|
+
//# sourceMappingURL=injectFonts.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectFonts.d.mts","names":[],"sources":["../../src/render/injectFonts.ts"],"mappings":";;;;;;AAoBA;;;;;iBAAgB,WAAA,CACd,GAAA,EAAK,SAAA,IACL,KAAA,EAAO,gBAAA,IACP,QAAA,GAAW,IAAA,aAAiB,SAAA,IAC5B,IAAA,GAAO,GAAA,EAAK,SAAA,IAAa,EAAA,GAAK,CAAA,EAAG,SAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { decodeStyleEntities } from "../utils/decodeStyleEntities.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/render/injectFonts.ts
|
|
4
|
+
const TAILWIND_IMPORT_RE = /((@import|@reference)\s+["'](tailwindcss|@maizzle\/tailwindcss)|@tailwind\s)/;
|
|
5
|
+
function getText(el) {
|
|
6
|
+
return (el.children || []).filter((c) => c.type === "text").map((c) => c.data).join("");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Inject font `<link>` tags into `<head>` and merge `@theme` declarations
|
|
10
|
+
* into the template's existing Tailwind `<style>` block (so the
|
|
11
|
+
* `font-{slug}` utilities are generated in the same compilation unit
|
|
12
|
+
* as the Tailwind import). Without a Tailwind import, emits plain
|
|
13
|
+
* `.font-{slug}` class rules so the utility still works.
|
|
14
|
+
*/
|
|
15
|
+
function injectFonts(dom, fonts, parseDom, walk) {
|
|
16
|
+
if (!fonts.length) return;
|
|
17
|
+
let head;
|
|
18
|
+
let tailwindStyle;
|
|
19
|
+
walk(dom, (node) => {
|
|
20
|
+
const el = node;
|
|
21
|
+
if (!el.name) return;
|
|
22
|
+
if (el.name === "head" && !head) head = el;
|
|
23
|
+
if (el.name === "style" && !tailwindStyle && TAILWIND_IMPORT_RE.test(decodeStyleEntities(getText(el)))) tailwindStyle = el;
|
|
24
|
+
});
|
|
25
|
+
if (!head) return;
|
|
26
|
+
const linkNodes = parseDom(fonts.map((f) => `<link href="${f.url}" rel="stylesheet" media="screen">`).join(""));
|
|
27
|
+
for (const child of linkNodes) child.parent = head;
|
|
28
|
+
head.children = [...head.children || [], ...linkNodes];
|
|
29
|
+
if (tailwindStyle) {
|
|
30
|
+
const themeDecls = fonts.map((f) => ` --font-${f.slug}: ${f.declaration};`).join("\n");
|
|
31
|
+
const existing = getText(tailwindStyle);
|
|
32
|
+
tailwindStyle.children = [{
|
|
33
|
+
type: "text",
|
|
34
|
+
data: `${existing}\n@theme {\n${themeDecls}\n}\n`,
|
|
35
|
+
parent: tailwindStyle
|
|
36
|
+
}];
|
|
37
|
+
} else {
|
|
38
|
+
const styleNodes = parseDom(`<style>\n${fonts.map((f) => `.font-${f.slug} { font-family: ${f.declaration}; }`).join("\n")}\n</style>`);
|
|
39
|
+
for (const child of styleNodes) child.parent = head;
|
|
40
|
+
head.children = [...head.children || [], ...styleNodes];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { injectFonts };
|
|
46
|
+
//# sourceMappingURL=injectFonts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectFonts.mjs","names":[],"sources":["../../src/render/injectFonts.ts"],"sourcesContent":["import type { ChildNode, Element } from 'domhandler'\nimport type { FontRegistration } from '../composables/renderContext.ts'\nimport { decodeStyleEntities } from '../utils/decodeStyleEntities.ts'\n\nconst TAILWIND_IMPORT_RE = /((@import|@reference)\\s+[\"'](tailwindcss|@maizzle\\/tailwindcss)|@tailwind\\s)/\n\nfunction getText(el: Element): string {\n return (el.children || [])\n .filter((c: any) => c.type === 'text')\n .map((c: any) => c.data)\n .join('')\n}\n\n/**\n * Inject font `<link>` tags into `<head>` and merge `@theme` declarations\n * into the template's existing Tailwind `<style>` block (so the\n * `font-{slug}` utilities are generated in the same compilation unit\n * as the Tailwind import). Without a Tailwind import, emits plain\n * `.font-{slug}` class rules so the utility still works.\n */\nexport function injectFonts(\n dom: ChildNode[],\n fonts: FontRegistration[],\n parseDom: (html: string) => ChildNode[],\n walk: (ast: ChildNode[], cb: (n: ChildNode) => void) => void,\n): void {\n if (!fonts.length) return\n\n let head: Element | undefined\n let tailwindStyle: Element | undefined\n\n walk(dom, (node) => {\n const el = node as Element\n if (!el.name) return\n if (el.name === 'head' && !head) head = el\n if (el.name === 'style' && !tailwindStyle && TAILWIND_IMPORT_RE.test(decodeStyleEntities(getText(el)))) {\n tailwindStyle = el\n }\n })\n\n if (!head) return\n\n const linkHtml = fonts\n .map(f => `<link href=\"${f.url}\" rel=\"stylesheet\" media=\"screen\">`)\n .join('')\n const linkNodes = parseDom(linkHtml)\n for (const child of linkNodes) {\n (child as any).parent = head\n }\n head.children = [...(head.children || []), ...linkNodes] as any\n\n if (tailwindStyle) {\n const themeDecls = fonts\n .map(f => ` --font-${f.slug}: ${f.declaration};`)\n .join('\\n')\n const existing = getText(tailwindStyle)\n tailwindStyle.children = [{\n type: 'text',\n data: `${existing}\\n@theme {\\n${themeDecls}\\n}\\n`,\n parent: tailwindStyle,\n } as any]\n } else {\n const classRules = fonts\n .map(f => `.font-${f.slug} { font-family: ${f.declaration}; }`)\n .join('\\n')\n const styleNodes = parseDom(`<style>\\n${classRules}\\n</style>`)\n for (const child of styleNodes) {\n (child as any).parent = head\n }\n head.children = [...(head.children || []), ...styleNodes] as any\n }\n}\n"],"mappings":";;;AAIA,MAAM,qBAAqB;AAE3B,SAAS,QAAQ,IAAqB;AACpC,SAAQ,GAAG,YAAY,EAAE,EACtB,QAAQ,MAAW,EAAE,SAAS,OAAO,CACrC,KAAK,MAAW,EAAE,KAAK,CACvB,KAAK,GAAG;;;;;;;;;AAUb,SAAgB,YACd,KACA,OACA,UACA,MACM;AACN,KAAI,CAAC,MAAM,OAAQ;CAEnB,IAAI;CACJ,IAAI;AAEJ,MAAK,MAAM,SAAS;EAClB,MAAM,KAAK;AACX,MAAI,CAAC,GAAG,KAAM;AACd,MAAI,GAAG,SAAS,UAAU,CAAC,KAAM,QAAO;AACxC,MAAI,GAAG,SAAS,WAAW,CAAC,iBAAiB,mBAAmB,KAAK,oBAAoB,QAAQ,GAAG,CAAC,CAAC,CACpG,iBAAgB;GAElB;AAEF,KAAI,CAAC,KAAM;CAKX,MAAM,YAAY,SAHD,MACd,KAAI,MAAK,eAAe,EAAE,IAAI,oCAAoC,CAClE,KAAK,GAAG,CACyB;AACpC,MAAK,MAAM,SAAS,UAClB,CAAC,MAAc,SAAS;AAE1B,MAAK,WAAW,CAAC,GAAI,KAAK,YAAY,EAAE,EAAG,GAAG,UAAU;AAExD,KAAI,eAAe;EACjB,MAAM,aAAa,MAChB,KAAI,MAAK,YAAY,EAAE,KAAK,IAAI,EAAE,YAAY,GAAG,CACjD,KAAK,KAAK;EACb,MAAM,WAAW,QAAQ,cAAc;AACvC,gBAAc,WAAW,CAAC;GACxB,MAAM;GACN,MAAM,GAAG,SAAS,cAAc,WAAW;GAC3C,QAAQ;GACT,CAAQ;QACJ;EAIL,MAAM,aAAa,SAAS,YAHT,MAChB,KAAI,MAAK,SAAS,EAAE,KAAK,kBAAkB,EAAE,YAAY,KAAK,CAC9D,KAAK,KAAK,CACsC,YAAY;AAC/D,OAAK,MAAM,SAAS,WAClB,CAAC,MAAc,SAAS;AAE1B,OAAK,WAAW,CAAC,GAAI,KAAK,YAAY,EAAE,EAAG,GAAG,WAAW"}
|
package/dist/serve.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.mts","names":[],"sources":["../src/serve.ts"],"mappings":";;;;UA8BiB,YAAA;EACf,MAAA,GAAS,OAAA,CAAQ,aAAA;;EAEjB,IAAA;EAH2B;EAK3B,MAAA;AAAA;;;;;;;;AAYF;;iBAAsB,KAAA,CAAM,OAAA,GAAS,YAAA,GAAiB,OAAA,CAAA,aAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"serve.d.mts","names":[],"sources":["../src/serve.ts"],"mappings":";;;;UA8BiB,YAAA;EACf,MAAA,GAAS,OAAA,CAAQ,aAAA;;EAEjB,IAAA;EAH2B;EAK3B,MAAA;AAAA;;;;;;;;AAYF;;iBAAsB,KAAA,CAAM,OAAA,GAAS,YAAA,GAAiB,OAAA,CAAA,aAAA;AAAA,iBAujBtC,WAAA,CAAY,MAAA,EAAQ,aAAA,EAAe,WAAA"}
|
package/dist/serve.mjs
CHANGED
|
@@ -89,7 +89,9 @@ async function serve(options = {}) {
|
|
|
89
89
|
"reka-ui",
|
|
90
90
|
"class-variance-authority",
|
|
91
91
|
"clsx",
|
|
92
|
-
"tailwind-merge"
|
|
92
|
+
"tailwind-merge",
|
|
93
|
+
"culori",
|
|
94
|
+
"postcss-safe-parser"
|
|
93
95
|
]
|
|
94
96
|
},
|
|
95
97
|
server: {
|
|
@@ -108,7 +110,9 @@ async function serve(options = {}) {
|
|
|
108
110
|
"lucide-vue-next",
|
|
109
111
|
"class-variance-authority",
|
|
110
112
|
"clsx",
|
|
111
|
-
"tailwind-merge"
|
|
113
|
+
"tailwind-merge",
|
|
114
|
+
"culori",
|
|
115
|
+
"postcss-safe-parser"
|
|
112
116
|
].map(pkg)
|
|
113
117
|
] }
|
|
114
118
|
},
|
package/dist/serve.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.mjs","names":[],"sources":["../src/serve.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve, basename, matchesGlob } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { createRequire } from 'node:module'\nimport { createServer, createLogger, type ViteDevServer } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport tailwindcss from '@tailwindcss/vite'\nimport { glob } from 'tinyglobby'\nimport { createHighlighter, type Highlighter } from 'shiki'\nimport { createPlaintext } from './plaintext.ts'\nimport { resolveConfig } from './config/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer, type Renderer } from './render/createRenderer.ts'\nimport { serveCompatibility } from './server/compatibility.ts'\nimport { serveLint } from './server/linter.ts'\nimport { sendEmail } from './server/email.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst devUIDir = resolve(__dirname, 'server/ui')\n\nconst require = createRequire(import.meta.url)\nconst pkg = (name: string) => {\n const resolved = require.resolve(name).replace(/\\\\/g, '/')\n const marker = `node_modules/${name}`\n const idx = resolved.lastIndexOf(marker)\n\n return resolved.slice(0, idx + marker.length)\n}\n\nexport interface ServeOptions {\n config?: Partial<MaizzleConfig> | string\n /** Expose the server on the network (e.g. --host) */\n host?: boolean | string\n /** When true, suppresses the banner/URL output (used by the Vite plugin, which prints its own) */\n silent?: boolean\n}\n\n/**\n * Start the Maizzle dev server.\n *\n * Creates two things:\n * 1. A Vite dev server for the dev UI (sidebar + preview, with Vue + Tailwind for the UI itself)\n * 2. A Renderer instance for SSR rendering email templates\n *\n * Template rendering goes through the Renderer, not the Vite dev server.\n */\nexport async function serve(options: ServeOptions = {}) {\n const start = performance.now()\n\n let config = await resolveConfig(options.config)\n const port = config.server?.port ?? 3000\n\n // Create a renderer for SSR rendering email templates (with dts for dev)\n let renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n\n const server = await createServer({\n configFile: false,\n plugins: [\n // Vue and Tailwind are only for the dev UI SPA, not for email templates\n vue(),\n tailwindcss(),\n maizzleDevPlugin(config, renderer, options.config),\n ],\n resolve: {\n dedupe: ['vue'],\n alias: [\n { find: '@', replacement: devUIDir },\n { find: 'vue', replacement: resolve(pkg('vue'), 'dist/vue.runtime.esm-bundler.js') },\n ...['vue-router', 'reka-ui', '@vueuse/core', '@vueuse/shared', 'lucide-vue-next', 'class-variance-authority', 'clsx', 'tailwind-merge']\n .map(name => ({ find: name, replacement: pkg(name) })),\n ],\n },\n cacheDir: resolve(devUIDir, '.vite'),\n optimizeDeps: {\n noDiscovery: true,\n include: [\n 'vue',\n 'vue-router',\n 'lucide-vue-next',\n '@vueuse/core',\n '@vueuse/shared',\n 'reka-ui',\n 'class-variance-authority',\n 'clsx',\n 'tailwind-merge',\n ],\n },\n server: {\n port,\n host: options.host,\n fs: {\n allow: [process.cwd(), config.root ?? process.cwd(), devUIDir, ...['vue', 'vue-router', 'reka-ui', '@vueuse/core', '@vueuse/shared', 'lucide-vue-next', 'class-variance-authority', 'clsx', 'tailwind-merge'].map(pkg)],\n },\n },\n customLogger: customLogger(),\n })\n\n // Store renderer ref on server for cleanup\n const originalClose = server.close.bind(server)\n server.close = async () => {\n await renderer.close()\n return originalClose()\n }\n\n await server.listen()\n\n const startupTime = Math.round(performance.now() - start)\n\n if (!options.silent) {\n printBanner(server, startupTime)\n }\n\n // Expose startup time so the plugin can print it later\n ; (server as any)._maizzleStartupTime = startupTime\n\n return server\n}\n\n/**\n * Internal Vite plugin that adds Maizzle middleware and file watching to the dev UI server.\n */\nfunction maizzleDevPlugin(\n config: MaizzleConfig,\n renderer: Renderer,\n configInput: Partial<MaizzleConfig> | string | undefined,\n) {\n return {\n name: 'maizzle:dev',\n enforce: 'pre' as const,\n\n hotUpdate: {\n order: 'pre' as const,\n handler({ file }: { file: string }) {\n // Prevent Tailwind/Vue from triggering a full reload for email template files.\n // Maizzle handles these via custom HMR events in the watcher below.\n if (isTemplateFile(file)) {\n return []\n }\n },\n },\n\n configureServer(server: ViteDevServer) {\n // File watching\n const defaultWatchPaths = [\n 'maizzle.config.js',\n 'maizzle.config.ts',\n 'tailwind.config.js',\n 'tailwind.config.ts',\n 'locales/**',\n ]\n\n const userWatchPaths = config.server?.watch ?? []\n const watchPaths = [...defaultWatchPaths, ...userWatchPaths]\n const isWatchedFile = (file: string) => watchPaths.some(p => matchesGlob(file, p))\n\n for (const watchPath of watchPaths) {\n server.watcher.add(watchPath)\n }\n\n server.watcher.on('add', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('unlink', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('change', async (file) => {\n if (isWatchedFile(file)) {\n config = await resolveConfig(configInput)\n\n // Recreate the renderer so config changes (e.g. markdown.shikiTheme) take effect\n await renderer.close()\n renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n\n // Push UI-relevant config bits so the dev UI reacts to live edits\n // without a page reload. Uses the same shape as the initial inject.\n server.ws.send({ type: 'custom', event: 'maizzle:config-updated', data: buildUiConfig(config) })\n }\n\n // Invalidate all renderer modules so component and config changes\n // are picked up on the next render (Tailwind recompiles with fresh content)\n await renderer.invalidateAll()\n\n if (\n isTemplateFile(file)\n || isWatchedFile(file)\n ) {\n server.ws.send({ type: 'custom', event: 'maizzle:template-updated', data: { file } })\n }\n })\n\n // API middleware (before Vite's middleware)\n server.middlewares.use(async (req: any, res: any, next: any) => {\n const url = req.url || '/'\n\n if (url === '/__maizzle/templates') {\n return serveTemplateList(config, res)\n }\n\n if (url.startsWith('/__maizzle/render/')) {\n return await serveRenderedTemplate(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/source/')) {\n return await serveHighlightedSource(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/compatibility/')) {\n return await serveCompatibility(url, res, config, [config.components?.source ?? []].flat())\n }\n\n if (url.startsWith('/__maizzle/lint/')) {\n return await serveLint(url, res, config, [config.components?.source ?? []].flat())\n }\n\n if (url.startsWith('/__maizzle/vue-source/')) {\n return await serveVueSource(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/plaintext/')) {\n return await servePlaintext(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/stats/')) {\n return await serveStats(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/email/') && req.method === 'POST') {\n return await serveEmailEndpoint(url, req, res, config, renderer)\n }\n\n if (url === '/__maizzle/email-config') {\n return serveEmailConfig(config, res)\n }\n\n next()\n })\n\n // Dev UI fallback (after Vite's middleware)\n return () => {\n server.middlewares.use(async (req: any, res: any, next: any) => {\n if (isNavigationRequest(req)) {\n return await serveDevUI(server, res, req.url || '/', config)\n }\n\n next()\n })\n }\n },\n }\n}\n\nfunction isTemplateFile(file: string): boolean {\n return (file.endsWith('.vue') || file.endsWith('.md')) && !file.includes('server/ui')\n}\n\nfunction isNavigationRequest(req: any): boolean {\n const accept = req.headers?.accept || ''\n return req.method === 'GET' && accept.includes('text/html')\n}\n\n/**\n * Shape exposed to the dev UI both at initial HTML load (as\n * `window.__MAIZZLE_CONFIG__`) and on the `maizzle:config-updated` HMR event.\n * Add UI-visible config bits here; consumers on both ends pick up automatically.\n */\nfunction buildUiConfig(config: MaizzleConfig) {\n return {\n checks: config.server?.checks ?? true,\n }\n}\n\nasync function serveDevUI(server: ViteDevServer, res: any, url: string, config: MaizzleConfig) {\n let indexHtml = readFileSync(resolve(devUIDir, 'index.html'), 'utf-8')\n\n indexHtml = indexHtml.replace('./main.ts', `/@fs/${resolve(devUIDir, 'main.ts')}`)\n indexHtml = indexHtml.replace('./favicon.svg', `/@fs/${resolve(devUIDir, 'favicon.svg')}`)\n\n const configScript = `<script>window.__MAIZZLE_CONFIG__ = ${JSON.stringify(buildUiConfig(config))};</script>`\n indexHtml = indexHtml.replace('</head>', `${configScript}</head>`)\n\n const transformed = await server.transformIndexHtml(url, indexHtml)\n\n res.setHeader('Content-Type', 'text/html')\n res.end(transformed)\n}\n\nasync function serveTemplateList(config: MaizzleConfig, res: any) {\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n\n const data = templates.map(t => ({\n name: basename(t).replace(/\\.(vue|md)$/, ''),\n path: t,\n href: '/' + t.replace(/\\.(vue|md)$/, ''),\n }))\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(data))\n}\n\n/**\n * SSR render a .vue template using the Renderer (not the dev UI server).\n */\nasync function serveRenderedTemplate(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/render/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n // Invalidate all modules so template + component changes are picked up\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n html = `${doctype}\\n${html}`\n\n res.setHeader('Content-Type', 'text/html')\n res.end(html)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nlet highlighter: Highlighter | null = null\n\nasync function getHighlighter() {\n if (!highlighter) {\n highlighter = await createHighlighter({\n themes: ['laserwave'],\n langs: ['html', 'vue'],\n })\n }\n return highlighter\n}\n\nasync function serveHighlightedSource(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n html = `${doctype}\\n${html}`\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(html, {\n lang: 'html',\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function serveVueSource(url: string, config: MaizzleConfig, res: any) {\n const templateSlug = url.replace('/__maizzle/vue-source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const source = readFileSync(resolve(match), 'utf-8')\n const lang = match.endsWith('.md') ? 'html' : 'vue'\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(source, {\n lang,\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function servePlaintext(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/plaintext/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const plaintext = createPlaintext(html)\n\n res.setHeader('Content-Type', 'text/plain')\n res.end(plaintext)\n } catch (error: any) {\n res.statusCode = 500\n res.end(error.message)\n }\n}\n\nfunction humanFileSize(bytes: number, si = false, dp = 2) {\n const threshold = si ? 1000 : 1024\n\n if (Math.abs(bytes) < threshold) {\n return bytes + ' B'\n }\n\n const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n let u = -1\n const r = 10 ** dp\n\n do {\n bytes /= threshold\n ++u\n } while (Math.round(Math.abs(bytes) * r) / r >= threshold && u < units.length - 1)\n\n return bytes.toFixed(dp) + ' ' + units[u]\n}\n\nasync function serveStats(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/stats/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ error: 'Template not found' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const sizeBytes = Buffer.byteLength(html, 'utf-8')\n\n // Count images: <img> tags and CSS background images\n const imgTags = (html.match(/<img\\b[^>]*>/gi) || []).length\n const bgImages = (html.match(/url\\s*\\([^)]+\\)/gi) || []).length\n const totalImages = imgTags + bgImages\n\n // Count links\n const links = (html.match(/<a\\b[^>]*href\\s*=/gi) || []).length\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n size: {\n bytes: sizeBytes,\n formatted: humanFileSize(sizeBytes),\n },\n images: totalImages,\n links,\n }))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nasync function serveEmailEndpoint(url: string, req: any, res: any, config: MaizzleConfig, renderer: Renderer) {\n const templateSlug = url.replace('/__maizzle/email/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ success: false, message: 'Template not found' }))\n return\n }\n\n let body = ''\n for await (const chunk of req) body += chunk\n\n let payload: { to: string[]; subject: string }\n\n try {\n payload = JSON.parse(body)\n } catch {\n res.statusCode = 400\n res.end(JSON.stringify({ success: false, message: 'Invalid JSON' }))\n return\n }\n\n if (!payload.to?.length) {\n res.statusCode = 400\n res.end(JSON.stringify({ success: false, message: 'Missing recipients' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n html = `${doctype}\\n${html}`\n\n const text = createPlaintext(html)\n\n const result = await sendEmail(\n { to: payload.to, subject: payload.subject, html, text },\n config,\n templateConfig,\n )\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(result))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ success: false, message: error.message }))\n }\n}\n\nfunction serveEmailConfig(config: MaizzleConfig, res: any) {\n const emailConfig = config.server?.email\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n to: emailConfig?.to ? (Array.isArray(emailConfig.to) ? emailConfig.to : [emailConfig.to]) : [],\n from: emailConfig?.from ?? '',\n subject: emailConfig?.subject ?? '',\n hasTransport: !!emailConfig?.transport,\n }))\n}\n\nexport function printBanner(server: ViteDevServer, startupTime?: number) {\n const info = server.config.logger.info\n const time = startupTime ?? (server as any)._maizzleStartupTime\n\n info('')\n info(` \\x1b[32m\\x1b[1mMAIZZLE\\x1b[0m\\x1b[32m v6.0.0\\x1b[0m \\x1b[2mready in\\x1b[0m \\x1b[1m${time}\\x1b[0m ms`)\n info('')\n server.printUrls()\n info('')\n}\n\nfunction customLogger() {\n const logger = createLogger('info')\n const warn = logger.warn\n\n logger.warn = (message, options) => {\n if (typeof message === 'string' && message.includes('<tr> cannot be child of <table>')) {\n return\n }\n\n warn(message, options)\n }\n\n return logger\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serve.mjs","names":[],"sources":["../src/serve.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve, basename, matchesGlob } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { createRequire } from 'node:module'\nimport { createServer, createLogger, type ViteDevServer } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport tailwindcss from '@tailwindcss/vite'\nimport { glob } from 'tinyglobby'\nimport { createHighlighter, type Highlighter } from 'shiki'\nimport { createPlaintext } from './plaintext.ts'\nimport { resolveConfig } from './config/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer, type Renderer } from './render/createRenderer.ts'\nimport { serveCompatibility } from './server/compatibility.ts'\nimport { serveLint } from './server/linter.ts'\nimport { sendEmail } from './server/email.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst devUIDir = resolve(__dirname, 'server/ui')\n\nconst require = createRequire(import.meta.url)\nconst pkg = (name: string) => {\n const resolved = require.resolve(name).replace(/\\\\/g, '/')\n const marker = `node_modules/${name}`\n const idx = resolved.lastIndexOf(marker)\n\n return resolved.slice(0, idx + marker.length)\n}\n\nexport interface ServeOptions {\n config?: Partial<MaizzleConfig> | string\n /** Expose the server on the network (e.g. --host) */\n host?: boolean | string\n /** When true, suppresses the banner/URL output (used by the Vite plugin, which prints its own) */\n silent?: boolean\n}\n\n/**\n * Start the Maizzle dev server.\n *\n * Creates two things:\n * 1. A Vite dev server for the dev UI (sidebar + preview, with Vue + Tailwind for the UI itself)\n * 2. A Renderer instance for SSR rendering email templates\n *\n * Template rendering goes through the Renderer, not the Vite dev server.\n */\nexport async function serve(options: ServeOptions = {}) {\n const start = performance.now()\n\n let config = await resolveConfig(options.config)\n const port = config.server?.port ?? 3000\n\n // Create a renderer for SSR rendering email templates (with dts for dev)\n let renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n\n const server = await createServer({\n configFile: false,\n plugins: [\n // Vue and Tailwind are only for the dev UI SPA, not for email templates\n vue(),\n tailwindcss(),\n maizzleDevPlugin(config, renderer, options.config),\n ],\n resolve: {\n dedupe: ['vue'],\n alias: [\n { find: '@', replacement: devUIDir },\n { find: 'vue', replacement: resolve(pkg('vue'), 'dist/vue.runtime.esm-bundler.js') },\n ...['vue-router', 'reka-ui', '@vueuse/core', '@vueuse/shared', 'lucide-vue-next', 'class-variance-authority', 'clsx', 'tailwind-merge']\n .map(name => ({ find: name, replacement: pkg(name) })),\n ],\n },\n cacheDir: resolve(devUIDir, '.vite'),\n optimizeDeps: {\n noDiscovery: true,\n include: [\n 'vue',\n 'vue-router',\n 'lucide-vue-next',\n '@vueuse/core',\n '@vueuse/shared',\n 'reka-ui',\n 'class-variance-authority',\n 'clsx',\n 'tailwind-merge',\n 'culori',\n 'postcss-safe-parser',\n ],\n },\n server: {\n port,\n host: options.host,\n fs: {\n allow: [process.cwd(), config.root ?? process.cwd(), devUIDir, ...['vue', 'vue-router', 'reka-ui', '@vueuse/core', '@vueuse/shared', 'lucide-vue-next', 'class-variance-authority', 'clsx', 'tailwind-merge', 'culori', 'postcss-safe-parser'].map(pkg)],\n },\n },\n customLogger: customLogger(),\n })\n\n // Store renderer ref on server for cleanup\n const originalClose = server.close.bind(server)\n server.close = async () => {\n await renderer.close()\n return originalClose()\n }\n\n await server.listen()\n\n const startupTime = Math.round(performance.now() - start)\n\n if (!options.silent) {\n printBanner(server, startupTime)\n }\n\n // Expose startup time so the plugin can print it later\n ; (server as any)._maizzleStartupTime = startupTime\n\n return server\n}\n\n/**\n * Internal Vite plugin that adds Maizzle middleware and file watching to the dev UI server.\n */\nfunction maizzleDevPlugin(\n config: MaizzleConfig,\n renderer: Renderer,\n configInput: Partial<MaizzleConfig> | string | undefined,\n) {\n return {\n name: 'maizzle:dev',\n enforce: 'pre' as const,\n\n hotUpdate: {\n order: 'pre' as const,\n handler({ file }: { file: string }) {\n // Prevent Tailwind/Vue from triggering a full reload for email template files.\n // Maizzle handles these via custom HMR events in the watcher below.\n if (isTemplateFile(file)) {\n return []\n }\n },\n },\n\n configureServer(server: ViteDevServer) {\n // File watching\n const defaultWatchPaths = [\n 'maizzle.config.js',\n 'maizzle.config.ts',\n 'tailwind.config.js',\n 'tailwind.config.ts',\n 'locales/**',\n ]\n\n const userWatchPaths = config.server?.watch ?? []\n const watchPaths = [...defaultWatchPaths, ...userWatchPaths]\n const isWatchedFile = (file: string) => watchPaths.some(p => matchesGlob(file, p))\n\n for (const watchPath of watchPaths) {\n server.watcher.add(watchPath)\n }\n\n server.watcher.on('add', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('unlink', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('change', async (file) => {\n if (isWatchedFile(file)) {\n config = await resolveConfig(configInput)\n\n // Recreate the renderer so config changes (e.g. markdown.shikiTheme) take effect\n await renderer.close()\n renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat(), vite: config.vite })\n\n // Push UI-relevant config bits so the dev UI reacts to live edits\n // without a page reload. Uses the same shape as the initial inject.\n server.ws.send({ type: 'custom', event: 'maizzle:config-updated', data: buildUiConfig(config) })\n }\n\n // Invalidate all renderer modules so component and config changes\n // are picked up on the next render (Tailwind recompiles with fresh content)\n await renderer.invalidateAll()\n\n if (\n isTemplateFile(file)\n || isWatchedFile(file)\n ) {\n server.ws.send({ type: 'custom', event: 'maizzle:template-updated', data: { file } })\n }\n })\n\n // API middleware (before Vite's middleware)\n server.middlewares.use(async (req: any, res: any, next: any) => {\n const url = req.url || '/'\n\n if (url === '/__maizzle/templates') {\n return serveTemplateList(config, res)\n }\n\n if (url.startsWith('/__maizzle/render/')) {\n return await serveRenderedTemplate(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/source/')) {\n return await serveHighlightedSource(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/compatibility/')) {\n return await serveCompatibility(url, res, config, [config.components?.source ?? []].flat())\n }\n\n if (url.startsWith('/__maizzle/lint/')) {\n return await serveLint(url, res, config, [config.components?.source ?? []].flat())\n }\n\n if (url.startsWith('/__maizzle/vue-source/')) {\n return await serveVueSource(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/plaintext/')) {\n return await servePlaintext(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/stats/')) {\n return await serveStats(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/email/') && req.method === 'POST') {\n return await serveEmailEndpoint(url, req, res, config, renderer)\n }\n\n if (url === '/__maizzle/email-config') {\n return serveEmailConfig(config, res)\n }\n\n next()\n })\n\n // Dev UI fallback (after Vite's middleware)\n return () => {\n server.middlewares.use(async (req: any, res: any, next: any) => {\n if (isNavigationRequest(req)) {\n return await serveDevUI(server, res, req.url || '/', config)\n }\n\n next()\n })\n }\n },\n }\n}\n\nfunction isTemplateFile(file: string): boolean {\n return (file.endsWith('.vue') || file.endsWith('.md')) && !file.includes('server/ui')\n}\n\nfunction isNavigationRequest(req: any): boolean {\n const accept = req.headers?.accept || ''\n return req.method === 'GET' && accept.includes('text/html')\n}\n\n/**\n * Shape exposed to the dev UI both at initial HTML load (as\n * `window.__MAIZZLE_CONFIG__`) and on the `maizzle:config-updated` HMR event.\n * Add UI-visible config bits here; consumers on both ends pick up automatically.\n */\nfunction buildUiConfig(config: MaizzleConfig) {\n return {\n checks: config.server?.checks ?? true,\n }\n}\n\nasync function serveDevUI(server: ViteDevServer, res: any, url: string, config: MaizzleConfig) {\n let indexHtml = readFileSync(resolve(devUIDir, 'index.html'), 'utf-8')\n\n indexHtml = indexHtml.replace('./main.ts', `/@fs/${resolve(devUIDir, 'main.ts')}`)\n indexHtml = indexHtml.replace('./favicon.svg', `/@fs/${resolve(devUIDir, 'favicon.svg')}`)\n\n const configScript = `<script>window.__MAIZZLE_CONFIG__ = ${JSON.stringify(buildUiConfig(config))};</script>`\n indexHtml = indexHtml.replace('</head>', `${configScript}</head>`)\n\n const transformed = await server.transformIndexHtml(url, indexHtml)\n\n res.setHeader('Content-Type', 'text/html')\n res.end(transformed)\n}\n\nasync function serveTemplateList(config: MaizzleConfig, res: any) {\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n\n const data = templates.map(t => ({\n name: basename(t).replace(/\\.(vue|md)$/, ''),\n path: t,\n href: '/' + t.replace(/\\.(vue|md)$/, ''),\n }))\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(data))\n}\n\n/**\n * SSR render a .vue template using the Renderer (not the dev UI server).\n */\nasync function serveRenderedTemplate(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/render/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n // Invalidate all modules so template + component changes are picked up\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n html = `${doctype}\\n${html}`\n\n res.setHeader('Content-Type', 'text/html')\n res.end(html)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nlet highlighter: Highlighter | null = null\n\nasync function getHighlighter() {\n if (!highlighter) {\n highlighter = await createHighlighter({\n themes: ['laserwave'],\n langs: ['html', 'vue'],\n })\n }\n return highlighter\n}\n\nasync function serveHighlightedSource(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n html = `${doctype}\\n${html}`\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(html, {\n lang: 'html',\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function serveVueSource(url: string, config: MaizzleConfig, res: any) {\n const templateSlug = url.replace('/__maizzle/vue-source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const source = readFileSync(resolve(match), 'utf-8')\n const lang = match.endsWith('.md') ? 'html' : 'vue'\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(source, {\n lang,\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function servePlaintext(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/plaintext/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const plaintext = createPlaintext(html)\n\n res.setHeader('Content-Type', 'text/plain')\n res.end(plaintext)\n } catch (error: any) {\n res.statusCode = 500\n res.end(error.message)\n }\n}\n\nfunction humanFileSize(bytes: number, si = false, dp = 2) {\n const threshold = si ? 1000 : 1024\n\n if (Math.abs(bytes) < threshold) {\n return bytes + ' B'\n }\n\n const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n let u = -1\n const r = 10 ** dp\n\n do {\n bytes /= threshold\n ++u\n } while (Math.round(Math.abs(bytes) * r) / r >= threshold && u < units.length - 1)\n\n return bytes.toFixed(dp) + ' ' + units[u]\n}\n\nasync function serveStats(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/stats/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ error: 'Template not found' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const sizeBytes = Buffer.byteLength(html, 'utf-8')\n\n // Count images: <img> tags and CSS background images\n const imgTags = (html.match(/<img\\b[^>]*>/gi) || []).length\n const bgImages = (html.match(/url\\s*\\([^)]+\\)/gi) || []).length\n const totalImages = imgTags + bgImages\n\n // Count links\n const links = (html.match(/<a\\b[^>]*href\\s*=/gi) || []).length\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n size: {\n bytes: sizeBytes,\n formatted: humanFileSize(sizeBytes),\n },\n images: totalImages,\n links,\n }))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nasync function serveEmailEndpoint(url: string, req: any, res: any, config: MaizzleConfig, renderer: Renderer) {\n const templateSlug = url.replace('/__maizzle/email/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ success: false, message: 'Template not found' }))\n return\n }\n\n let body = ''\n for await (const chunk of req) body += chunk\n\n let payload: { to: string[]; subject: string }\n\n try {\n payload = JSON.parse(body)\n } catch {\n res.statusCode = 400\n res.end(JSON.stringify({ success: false, message: 'Invalid JSON' }))\n return\n }\n\n if (!payload.to?.length) {\n res.statusCode = 400\n res.end(JSON.stringify({ success: false, message: 'Missing recipients' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n html = `${doctype}\\n${html}`\n\n const text = createPlaintext(html)\n\n const result = await sendEmail(\n { to: payload.to, subject: payload.subject, html, text },\n config,\n templateConfig,\n )\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(result))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ success: false, message: error.message }))\n }\n}\n\nfunction serveEmailConfig(config: MaizzleConfig, res: any) {\n const emailConfig = config.server?.email\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n to: emailConfig?.to ? (Array.isArray(emailConfig.to) ? emailConfig.to : [emailConfig.to]) : [],\n from: emailConfig?.from ?? '',\n subject: emailConfig?.subject ?? '',\n hasTransport: !!emailConfig?.transport,\n }))\n}\n\nexport function printBanner(server: ViteDevServer, startupTime?: number) {\n const info = server.config.logger.info\n const time = startupTime ?? (server as any)._maizzleStartupTime\n\n info('')\n info(` \\x1b[32m\\x1b[1mMAIZZLE\\x1b[0m\\x1b[32m v6.0.0\\x1b[0m \\x1b[2mready in\\x1b[0m \\x1b[1m${time}\\x1b[0m ms`)\n info('')\n server.printUrls()\n info('')\n}\n\nfunction customLogger() {\n const logger = createLogger('info')\n const warn = logger.warn\n\n logger.warn = (message, options) => {\n if (typeof message === 'string' && message.includes('<tr> cannot be child of <table>')) {\n return\n }\n\n warn(message, options)\n }\n\n return logger\n}\n"],"mappings":"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"}
|
package/dist/server/ui/App.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed, onMounted, onUnmounted, watch, watchEffect } from 'vue'
|
|
3
3
|
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
|
|
4
|
-
import { Monitor, CodeXml, Smartphone, ChevronDown, ArrowUp, ArrowDown, CornerDownLeft, Check, Search, FileCode, FileText, Code, BookText, MailQuestion } from 'lucide-vue-next'
|
|
4
|
+
import { Monitor, CodeXml, Smartphone, ChevronDown, ArrowUp, ArrowDown, CornerDownLeft, Check, Search, FileCode, FileText, Code, BookText, MailQuestion, Moon, Sun } from 'lucide-vue-next'
|
|
5
5
|
import SidebarClose from '@/components/SidebarClose.vue'
|
|
6
6
|
import logoUrl from '@/logo.svg'
|
|
7
7
|
import logoGradientUrl from '@/logo-gradient.svg'
|
|
@@ -77,6 +77,7 @@ const devicePresets: DevicePreset[] = [
|
|
|
77
77
|
|
|
78
78
|
const selectedDevice = ref<DevicePreset | null>(null)
|
|
79
79
|
const deviceMenuOpen = ref(false)
|
|
80
|
+
const darkMode = ref(false)
|
|
80
81
|
const panelWidth = ref(0)
|
|
81
82
|
const panelHeight = ref(0)
|
|
82
83
|
const isDragging = ref(false)
|
|
@@ -199,12 +200,6 @@ function onKeydown(e: KeyboardEvent) {
|
|
|
199
200
|
return
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
|
|
203
|
-
e.preventDefault()
|
|
204
|
-
sidebarOpen.value = !sidebarOpen.value
|
|
205
|
-
return
|
|
206
|
-
}
|
|
207
|
-
|
|
208
203
|
if (e.key === '/' && !isInputFocused()) {
|
|
209
204
|
e.preventDefault()
|
|
210
205
|
commandOpen.value = true
|
|
@@ -241,6 +236,11 @@ function onWindowBlur() {
|
|
|
241
236
|
deviceMenuOpen.value = false
|
|
242
237
|
}
|
|
243
238
|
|
|
239
|
+
function toggleDarkMode() {
|
|
240
|
+
commandOpen.value = false
|
|
241
|
+
darkMode.value = !darkMode.value
|
|
242
|
+
}
|
|
243
|
+
|
|
244
244
|
onMounted(() => {
|
|
245
245
|
document.addEventListener('keydown', onKeydown)
|
|
246
246
|
window.addEventListener('blur', onWindowBlur)
|
|
@@ -334,7 +334,8 @@ onUnmounted(() => {
|
|
|
334
334
|
>
|
|
335
335
|
{{ panelWidth }} <button class="hover:text-gray-700 dark:hover:text-gray-300" @click="selectedDevice = null; isFullSize = true; viewMode = 'preview'; resetKey++">×</button> {{ panelHeight }}
|
|
336
336
|
</span>
|
|
337
|
-
<
|
|
337
|
+
<div v-if="isPreviewRoute" class="flex items-center gap-1">
|
|
338
|
+
<DropdownMenu v-model:open="deviceMenuOpen" :modal="false">
|
|
338
339
|
<DropdownMenuTrigger as-child>
|
|
339
340
|
<Button variant="ghost" size="sm" class="hidden min-[430px]:inline-flex gap-1.5 shadow-none border-none hover:bg-transparent">
|
|
340
341
|
<Smartphone class="size-4 dark:text-gray-400" :stroke-width="1" />
|
|
@@ -358,23 +359,36 @@ onUnmounted(() => {
|
|
|
358
359
|
<span class="ml-auto text-[11px] text-gray-400 dark:text-gray-500 tabular-nums tracking-tight">{{ device.width }}×{{ device.height }}</span>
|
|
359
360
|
</DropdownMenuItem>
|
|
360
361
|
</DropdownMenuContent>
|
|
361
|
-
|
|
362
|
+
</DropdownMenu>
|
|
363
|
+
</div>
|
|
362
364
|
</div>
|
|
363
365
|
</header>
|
|
364
366
|
|
|
365
367
|
<!-- Main content -->
|
|
366
368
|
<div class="flex-1 overflow-hidden">
|
|
367
369
|
<RouterView v-slot="{ Component }">
|
|
368
|
-
<component :is="Component" v-model:view-mode="viewMode" :device="selectedDevice" :reset-key="resetKey" :templates="templates" v-model:panel-width="panelWidth" v-model:panel-height="panelHeight" v-model:is-dragging="isDragging" v-model:is-full-size="isFullSize" @clear-device="selectedDevice = null; isFullSize = false" />
|
|
370
|
+
<component :is="Component" v-model:view-mode="viewMode" :device="selectedDevice" :reset-key="resetKey" :templates="templates" v-model:panel-width="panelWidth" v-model:panel-height="panelHeight" v-model:is-dragging="isDragging" v-model:is-full-size="isFullSize" v-model:dark-mode="darkMode" @clear-device="selectedDevice = null; isFullSize = false" />
|
|
369
371
|
</RouterView>
|
|
370
372
|
</div>
|
|
371
373
|
</SidebarInset>
|
|
372
374
|
|
|
373
375
|
<CommandDialog v-model:open="commandOpen" title="Command palette" description="Run commands or search emails">
|
|
374
376
|
<CommandInput v-model="commandSearch" placeholder="Type a command or find an email..." />
|
|
375
|
-
<CommandList>
|
|
377
|
+
<CommandList class="max-h-[400px]">
|
|
376
378
|
<CommandEmpty>No results found.</CommandEmpty>
|
|
377
379
|
|
|
380
|
+
<!-- Preview commands -->
|
|
381
|
+
<CommandGroup v-if="isPreviewRoute && viewMode === 'preview'" heading="Preview">
|
|
382
|
+
<CommandItem
|
|
383
|
+
:value="darkMode ? 'Disable dark mode' : 'Emulate dark mode'"
|
|
384
|
+
@select="toggleDarkMode"
|
|
385
|
+
>
|
|
386
|
+
<Sun v-if="darkMode" class="size-3 shrink-0 opacity-50" />
|
|
387
|
+
<Moon v-else class="size-3 shrink-0 opacity-50" />
|
|
388
|
+
<span>{{ darkMode ? 'Disable dark mode' : 'Emulate dark mode' }}</span>
|
|
389
|
+
</CommandItem>
|
|
390
|
+
</CommandGroup>
|
|
391
|
+
|
|
378
392
|
<!-- Copy to clipboard commands -->
|
|
379
393
|
<CommandGroup v-if="isPreviewRoute" heading="Copy to clipboard">
|
|
380
394
|
<CommandItem
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { parse, converter, formatRgb } from 'culori'
|
|
2
|
+
import safeParser from 'postcss-safe-parser'
|
|
3
|
+
|
|
4
|
+
const toLch = converter('lch')
|
|
5
|
+
|
|
6
|
+
type Mode = 'background' | 'foreground'
|
|
7
|
+
|
|
8
|
+
// CSS properties whose values contain colors we should invert, mapped to
|
|
9
|
+
// the inversion mode. Kebab-case for <style> decls; inline styles are
|
|
10
|
+
// handled via the same lookup after lowercasing.
|
|
11
|
+
const styleProps = new Map<string, Mode>([
|
|
12
|
+
['background', 'background'],
|
|
13
|
+
['background-color', 'background'],
|
|
14
|
+
['border', 'foreground'],
|
|
15
|
+
['border-color', 'foreground'],
|
|
16
|
+
['border-top', 'foreground'],
|
|
17
|
+
['border-right', 'foreground'],
|
|
18
|
+
['border-bottom', 'foreground'],
|
|
19
|
+
['border-left', 'foreground'],
|
|
20
|
+
['border-top-color', 'foreground'],
|
|
21
|
+
['border-right-color', 'foreground'],
|
|
22
|
+
['border-bottom-color', 'foreground'],
|
|
23
|
+
['border-left-color', 'foreground'],
|
|
24
|
+
['color', 'foreground'],
|
|
25
|
+
['outline', 'foreground'],
|
|
26
|
+
['outline-color', 'foreground'],
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
// Hex | color function with balanced-enough parens | bare word (possible named color).
|
|
30
|
+
const COLOR_TOKEN = /#[a-f0-9]{3,8}\b|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^)]*\)|\b[a-z]{3,20}\b/gi
|
|
31
|
+
|
|
32
|
+
// Words that look color-shaped but aren't — skip to avoid a wasted parse().
|
|
33
|
+
const NON_COLOR_KEYWORDS = /^(?:none|transparent|inherit|initial|unset|revert|currentcolor|auto|normal|solid|dashed|dotted|double|groove|ridge|inset|outset|hidden|thin|thick|medium|center|left|right|top|bottom|cover|contain|repeat|no-repeat|fixed|scroll|local|url|var|calc|linear|radial|conic|gradient)$/i
|
|
34
|
+
|
|
35
|
+
function invertOne(color: string, mode: Mode): string {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = parse(color)
|
|
38
|
+
if (!parsed) return color
|
|
39
|
+
const lch = toLch(parsed) as any
|
|
40
|
+
if (!lch || typeof lch.l !== 'number' || Number.isNaN(lch.l)) return color
|
|
41
|
+
|
|
42
|
+
if (mode === 'background' && lch.l >= 50) lch.l = 50 - (lch.l - 50) * 0.75
|
|
43
|
+
if (mode === 'foreground' && lch.l < 50) lch.l = 50 - (lch.l - 50) * 0.75
|
|
44
|
+
if (typeof lch.c === 'number' && !Number.isNaN(lch.c)) lch.c *= 0.8
|
|
45
|
+
|
|
46
|
+
return formatRgb(lch) || color
|
|
47
|
+
} catch {
|
|
48
|
+
return color
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function invertValue(value: string, mode: Mode): string {
|
|
53
|
+
return value.replace(COLOR_TOKEN, (tok) => {
|
|
54
|
+
if (NON_COLOR_KEYWORDS.test(tok)) return tok
|
|
55
|
+
if (!parse(tok)) return tok
|
|
56
|
+
return invertOne(tok, mode)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Splits an inline style attr by `;` (safe — color functions use `,` not `;`).
|
|
61
|
+
function invertInlineStyle(style: string): string {
|
|
62
|
+
return style.split(';').map((decl) => {
|
|
63
|
+
const i = decl.indexOf(':')
|
|
64
|
+
if (i === -1) return decl
|
|
65
|
+
const prop = decl.slice(0, i).trim().toLowerCase()
|
|
66
|
+
const mode = styleProps.get(prop)
|
|
67
|
+
if (!mode) return decl
|
|
68
|
+
return decl.slice(0, i + 1) + invertValue(decl.slice(i + 1), mode)
|
|
69
|
+
}).join(';')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function invertStyleTag(css: string): string {
|
|
73
|
+
try {
|
|
74
|
+
const root = safeParser(css)
|
|
75
|
+
root.walkDecls((decl) => {
|
|
76
|
+
const mode = styleProps.get(decl.prop.toLowerCase())
|
|
77
|
+
if (mode) decl.value = invertValue(decl.value, mode)
|
|
78
|
+
})
|
|
79
|
+
return root.toString()
|
|
80
|
+
} catch {
|
|
81
|
+
return css
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ORIG_INLINE = 'data-maizzle-orig-style'
|
|
86
|
+
const ORIG_STYLE_TAG = 'data-maizzle-orig-style-content'
|
|
87
|
+
const APPLIED_FLAG = 'data-maizzle-dark-applied'
|
|
88
|
+
|
|
89
|
+
function* walk(root: Node): Generator<Element> {
|
|
90
|
+
if (root.nodeType === 1) yield root as Element
|
|
91
|
+
for (const child of Array.from(root.childNodes)) yield* walk(child)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function applyColorInversion(iframe: HTMLIFrameElement): void {
|
|
95
|
+
const doc = iframe.contentDocument
|
|
96
|
+
if (!doc || !doc.body || doc.body.hasAttribute(APPLIED_FLAG)) return
|
|
97
|
+
|
|
98
|
+
for (const el of walk(doc.documentElement)) {
|
|
99
|
+
const inline = el.getAttribute('style')
|
|
100
|
+
if (inline) {
|
|
101
|
+
el.setAttribute(ORIG_INLINE, inline)
|
|
102
|
+
el.setAttribute('style', invertInlineStyle(inline))
|
|
103
|
+
}
|
|
104
|
+
if (el.tagName === 'STYLE') {
|
|
105
|
+
const original = el.textContent ?? ''
|
|
106
|
+
el.setAttribute(ORIG_STYLE_TAG, original)
|
|
107
|
+
el.textContent = invertStyleTag(original)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
doc.body.setAttribute(APPLIED_FLAG, '')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function undoColorInversion(iframe: HTMLIFrameElement): void {
|
|
115
|
+
const doc = iframe.contentDocument
|
|
116
|
+
if (!doc || !doc.body || !doc.body.hasAttribute(APPLIED_FLAG)) return
|
|
117
|
+
|
|
118
|
+
for (const el of walk(doc.documentElement)) {
|
|
119
|
+
const origInline = el.getAttribute(ORIG_INLINE)
|
|
120
|
+
if (origInline !== null) {
|
|
121
|
+
el.setAttribute('style', origInline)
|
|
122
|
+
el.removeAttribute(ORIG_INLINE)
|
|
123
|
+
}
|
|
124
|
+
if (el.tagName === 'STYLE' && el.hasAttribute(ORIG_STYLE_TAG)) {
|
|
125
|
+
el.textContent = el.getAttribute(ORIG_STYLE_TAG) ?? ''
|
|
126
|
+
el.removeAttribute(ORIG_STYLE_TAG)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
doc.body.removeAttribute(APPLIED_FLAG)
|
|
131
|
+
}
|