@maizzle/framework 6.0.0-rc.2 → 6.0.0-rc.21
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/README.md +3 -3
- package/bin/maizzle.mjs +1 -1
- package/dist/build.d.ts +20 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +163 -0
- package/dist/build.js.map +1 -0
- package/dist/components/Body.vue +128 -0
- package/dist/components/Button.vue +148 -52
- package/dist/components/CodeBlock.vue +69 -0
- package/dist/components/CodeInline.vue +49 -0
- package/dist/components/Column.vue +108 -0
- package/dist/components/Container.vue +123 -0
- package/dist/components/Font.vue +96 -0
- package/dist/components/Head.vue +30 -0
- package/dist/components/Heading.vue +28 -0
- package/dist/components/Hr.vue +33 -0
- package/dist/components/Html.vue +137 -0
- package/dist/components/Img.vue +70 -0
- package/dist/components/Layout.vue +143 -0
- package/dist/components/Link.vue +26 -0
- package/dist/components/Markdown.vue +89 -0
- package/dist/components/MarkdownLayout.vue +39 -0
- package/dist/components/NotPlaintext.vue +14 -0
- package/dist/components/Outlook.vue +74 -11
- package/dist/components/OutlookBg.vue +241 -0
- package/dist/components/Overlap.vue +156 -0
- package/dist/components/Plaintext.vue +14 -0
- package/dist/components/Preheader.vue +15 -0
- package/dist/components/QrCode.vue +157 -0
- package/dist/components/Raw.vue +28 -0
- package/dist/components/Row.vue +184 -0
- package/dist/components/Section.vue +124 -0
- package/dist/components/Spacer.vue +70 -21
- package/dist/components/Tailwind.vue +43 -0
- package/dist/components/Text.vue +29 -0
- package/dist/components/utils.d.ts +28 -0
- package/dist/components/utils.d.ts.map +1 -0
- package/dist/components/utils.js +50 -0
- package/dist/components/utils.js.map +1 -0
- package/dist/components/utils.ts +51 -0
- package/dist/composables/{defineConfig.d.mts → defineConfig.d.ts} +2 -2
- package/dist/composables/defineConfig.d.ts.map +1 -0
- package/dist/composables/{defineConfig.mjs → defineConfig.js} +4 -5
- package/dist/composables/defineConfig.js.map +1 -0
- package/dist/composables/renderContext.d.ts +37 -0
- package/dist/composables/renderContext.d.ts.map +1 -0
- package/dist/composables/{renderContext.mjs → renderContext.js} +2 -2
- package/dist/composables/renderContext.js.map +1 -0
- package/dist/composables/useBaseUrl.d.ts +19 -0
- package/dist/composables/useBaseUrl.d.ts.map +1 -0
- package/dist/composables/useBaseUrl.js +26 -0
- package/dist/composables/useBaseUrl.js.map +1 -0
- package/dist/composables/{useConfig.d.mts → useConfig.d.ts} +2 -2
- package/dist/composables/useConfig.d.ts.map +1 -0
- package/dist/composables/{useConfig.mjs → useConfig.js} +2 -3
- package/dist/composables/useConfig.js.map +1 -0
- package/dist/composables/useCurrentTemplate.d.ts +31 -0
- package/dist/composables/useCurrentTemplate.d.ts.map +1 -0
- package/dist/composables/useCurrentTemplate.js +35 -0
- package/dist/composables/useCurrentTemplate.js.map +1 -0
- package/dist/composables/{useDoctype.d.mts → useDoctype.d.ts} +1 -1
- package/dist/composables/useDoctype.d.ts.map +1 -0
- package/dist/composables/{useDoctype.mjs → useDoctype.js} +3 -4
- package/dist/composables/useDoctype.js.map +1 -0
- package/dist/composables/{useEvent.d.mts → useEvent.d.ts} +3 -3
- package/dist/composables/useEvent.d.ts.map +1 -0
- package/dist/composables/{useEvent.mjs → useEvent.js} +4 -5
- package/dist/composables/useEvent.js.map +1 -0
- package/dist/composables/useFont.d.ts +50 -0
- package/dist/composables/useFont.d.ts.map +1 -0
- package/dist/composables/useFont.js +92 -0
- package/dist/composables/useFont.js.map +1 -0
- package/dist/composables/useOutlookFallback.d.ts +21 -0
- package/dist/composables/useOutlookFallback.d.ts.map +1 -0
- package/dist/composables/useOutlookFallback.js +29 -0
- package/dist/composables/useOutlookFallback.js.map +1 -0
- package/dist/composables/{usePlaintext.d.mts → usePlaintext.d.ts} +3 -1
- package/dist/composables/usePlaintext.d.ts.map +1 -0
- package/dist/composables/{usePlaintext.mjs → usePlaintext.js} +4 -4
- package/dist/composables/usePlaintext.js.map +1 -0
- package/dist/composables/usePreheader.d.ts +24 -0
- package/dist/composables/usePreheader.d.ts.map +1 -0
- package/dist/composables/usePreheader.js +28 -0
- package/dist/composables/usePreheader.js.map +1 -0
- package/dist/composables/useTransformers.d.ts +34 -0
- package/dist/composables/useTransformers.d.ts.map +1 -0
- package/dist/composables/useTransformers.js +48 -0
- package/dist/composables/useTransformers.js.map +1 -0
- package/dist/composables/useUrlQuery.d.ts +19 -0
- package/dist/composables/useUrlQuery.d.ts.map +1 -0
- package/dist/composables/useUrlQuery.js +26 -0
- package/dist/composables/useUrlQuery.js.map +1 -0
- package/dist/config/{defaults.d.mts → defaults.d.ts} +2 -2
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/{defaults.mjs → defaults.js} +10 -6
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/{index.d.mts → index.d.ts} +4 -4
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/{index.mjs → index.js} +12 -10
- package/dist/config/index.js.map +1 -0
- package/dist/events/{index.d.mts → index.d.ts} +30 -12
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/{index.mjs → index.js} +26 -13
- package/dist/events/index.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +38 -0
- package/dist/{plaintext.d.mts → plaintext.d.ts} +1 -1
- package/dist/plaintext.d.ts.map +1 -0
- package/dist/{plaintext.mjs → plaintext.js} +4 -5
- package/dist/plaintext.js.map +1 -0
- package/dist/{plugin.d.mts → plugin.d.ts} +2 -2
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +57 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/postcss/{mergeMediaQueries.d.mts → mergeMediaQueries.d.ts} +2 -2
- package/dist/plugins/postcss/mergeMediaQueries.d.ts.map +1 -0
- package/dist/plugins/postcss/{mergeMediaQueries.mjs → mergeMediaQueries.js} +2 -3
- package/dist/plugins/postcss/mergeMediaQueries.js.map +1 -0
- package/dist/plugins/postcss/{pruneVars.d.mts → pruneVars.d.ts} +1 -1
- package/dist/plugins/postcss/pruneVars.d.ts.map +1 -0
- package/dist/plugins/postcss/{pruneVars.mjs → pruneVars.js} +2 -2
- package/dist/plugins/postcss/pruneVars.js.map +1 -0
- package/dist/plugins/postcss/quoteFontFamilies.d.ts +13 -0
- package/dist/plugins/postcss/quoteFontFamilies.d.ts.map +1 -0
- package/dist/plugins/postcss/quoteFontFamilies.js +84 -0
- package/dist/plugins/postcss/quoteFontFamilies.js.map +1 -0
- package/dist/plugins/postcss/{removeDeclarations.d.mts → removeDeclarations.d.ts} +1 -1
- package/dist/plugins/postcss/removeDeclarations.d.ts.map +1 -0
- package/dist/plugins/postcss/{removeDeclarations.mjs → removeDeclarations.js} +2 -2
- package/dist/plugins/postcss/removeDeclarations.js.map +1 -0
- package/dist/plugins/postcss/resolveMaizzleImports.d.ts +16 -0
- package/dist/plugins/postcss/resolveMaizzleImports.d.ts.map +1 -0
- package/dist/plugins/postcss/resolveMaizzleImports.js +39 -0
- package/dist/plugins/postcss/resolveMaizzleImports.js.map +1 -0
- package/dist/plugins/postcss/resolveProps.d.ts +8 -0
- package/dist/plugins/postcss/resolveProps.d.ts.map +1 -0
- package/dist/plugins/postcss/resolveProps.js +144 -0
- package/dist/plugins/postcss/resolveProps.js.map +1 -0
- package/dist/plugins/postcss/{tailwindCleanup.d.mts → tailwindCleanup.d.ts} +2 -2
- package/dist/plugins/postcss/tailwindCleanup.d.ts.map +1 -0
- package/dist/plugins/postcss/tailwindCleanup.js +68 -0
- package/dist/plugins/postcss/tailwindCleanup.js.map +1 -0
- package/dist/prepare.d.ts +17 -0
- package/dist/prepare.d.ts.map +1 -0
- package/dist/prepare.js +44 -0
- package/dist/prepare.js.map +1 -0
- package/dist/render/active.d.ts +8 -0
- package/dist/render/active.d.ts.map +1 -0
- package/dist/render/active.js +12 -0
- package/dist/render/active.js.map +1 -0
- package/dist/render/{createRenderer.d.mts → createRenderer.d.ts} +15 -7
- package/dist/render/createRenderer.d.ts.map +1 -0
- package/dist/render/createRenderer.js +320 -0
- package/dist/render/createRenderer.js.map +1 -0
- package/dist/render/index.d.ts +18 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +53 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/injectFonts.d.ts +15 -0
- package/dist/render/injectFonts.d.ts.map +1 -0
- package/dist/render/injectFonts.js +45 -0
- package/dist/render/injectFonts.js.map +1 -0
- package/dist/render/plugins/codeBlockExtract.d.ts +14 -0
- package/dist/render/plugins/codeBlockExtract.d.ts.map +1 -0
- package/dist/render/plugins/codeBlockExtract.js +34 -0
- package/dist/render/plugins/codeBlockExtract.js.map +1 -0
- package/dist/render/plugins/markdownExtract.d.ts +12 -0
- package/dist/render/plugins/markdownExtract.d.ts.map +1 -0
- package/dist/render/plugins/markdownExtract.js +49 -0
- package/dist/render/plugins/markdownExtract.js.map +1 -0
- package/dist/render/plugins/rawExtract.d.ts +14 -0
- package/dist/render/plugins/rawExtract.d.ts.map +1 -0
- package/dist/render/plugins/rawExtract.js +34 -0
- package/dist/render/plugins/rawExtract.js.map +1 -0
- package/dist/render/plugins/rowSourceLocation.d.ts +18 -0
- package/dist/render/plugins/rowSourceLocation.d.ts.map +1 -0
- package/dist/render/plugins/rowSourceLocation.js +45 -0
- package/dist/render/plugins/rowSourceLocation.js.map +1 -0
- package/dist/{serve.d.mts → serve.d.ts} +4 -2
- package/dist/serve.d.ts.map +1 -0
- package/dist/{serve.mjs → serve.js} +203 -78
- package/dist/serve.js.map +1 -0
- package/dist/server/compatibility.d.ts +59 -0
- package/dist/server/compatibility.d.ts.map +1 -0
- package/dist/server/compatibility.js +911 -0
- package/dist/server/compatibility.js.map +1 -0
- package/dist/server/email.d.ts +17 -0
- package/dist/server/email.d.ts.map +1 -0
- package/dist/server/email.js +40 -0
- package/dist/server/email.js.map +1 -0
- package/dist/server/linter.d.ts +20 -0
- package/dist/server/linter.d.ts.map +1 -0
- package/dist/server/linter.js +339 -0
- package/dist/server/linter.js.map +1 -0
- package/dist/server/sfc-utils.d.ts +21 -0
- package/dist/server/sfc-utils.d.ts.map +1 -0
- package/dist/server/sfc-utils.js +198 -0
- package/dist/server/sfc-utils.js.map +1 -0
- package/dist/server/ui/App.vue +253 -77
- package/dist/server/ui/components/SidebarClose.vue +12 -0
- package/dist/server/ui/components/ui/checkbox/Checkbox.vue +35 -0
- package/dist/server/ui/components/ui/checkbox/index.ts +1 -0
- package/dist/server/ui/components/ui/command/Command.vue +5 -1
- package/dist/server/ui/components/ui/command/CommandDialog.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandInput.vue +19 -1
- package/dist/server/ui/components/ui/command/CommandItem.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandList.vue +1 -1
- package/dist/server/ui/components/ui/command/CommandShortcut.vue +1 -1
- package/dist/server/ui/components/ui/dialog/DialogOverlay.vue +9 -1
- package/dist/server/ui/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/dist/server/ui/components/ui/input/Input.vue +1 -1
- package/dist/server/ui/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/dist/server/ui/components/ui/sheet/SheetContent.vue +1 -1
- package/dist/server/ui/components/ui/sheet/SheetOverlay.vue +9 -1
- package/dist/server/ui/components/ui/sidebar/Sidebar.vue +8 -1
- package/dist/server/ui/components/ui/sidebar/SidebarProvider.vue +1 -1
- package/dist/server/ui/components/ui/sidebar/SidebarTrigger.vue +5 -4
- package/dist/server/ui/components/ui/tags-input/TagsInput.vue +26 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputInput.vue +17 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItem.vue +19 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItemDelete.vue +22 -0
- package/dist/server/ui/components/ui/tags-input/TagsInputItemText.vue +17 -0
- package/dist/server/ui/components/ui/tags-input/index.ts +5 -0
- package/dist/server/ui/components/ui/toggle/index.ts +3 -3
- package/dist/server/ui/components/ui/toggle-group/ToggleGroup.vue +1 -1
- package/dist/server/ui/components/ui/toggle-group/ToggleGroupItem.vue +2 -2
- package/dist/server/ui/lib/emulated-dark-mode.ts +131 -0
- package/dist/server/ui/main.css +20 -20
- package/dist/server/ui/pages/Home.vue +12 -5
- package/dist/server/ui/pages/Preview.vue +716 -276
- package/dist/tests/render/_helpers.d.ts +6 -0
- package/dist/tests/render/_helpers.d.ts.map +1 -0
- package/dist/tests/render/_helpers.js +16 -0
- package/dist/tests/render/_helpers.js.map +1 -0
- package/dist/transformers/{addAttributes.d.mts → addAttributes.d.ts} +2 -2
- package/dist/transformers/addAttributes.d.ts.map +1 -0
- package/dist/transformers/{addAttributes.mjs → addAttributes.js} +16 -13
- package/dist/transformers/addAttributes.js.map +1 -0
- package/dist/transformers/attributeToStyle.d.ts +38 -0
- package/dist/transformers/attributeToStyle.d.ts.map +1 -0
- package/dist/transformers/attributeToStyle.js +94 -0
- package/dist/transformers/attributeToStyle.js.map +1 -0
- package/dist/transformers/base.d.ts +71 -0
- package/dist/transformers/base.d.ts.map +1 -0
- package/dist/transformers/{base.mjs → base.js} +56 -30
- package/dist/transformers/base.js.map +1 -0
- package/dist/transformers/columnWidth.d.ts +31 -0
- package/dist/transformers/columnWidth.d.ts.map +1 -0
- package/dist/transformers/columnWidth.js +546 -0
- package/dist/transformers/columnWidth.js.map +1 -0
- package/dist/transformers/entities.d.ts +37 -0
- package/dist/transformers/entities.d.ts.map +1 -0
- package/dist/transformers/entities.js +73 -0
- package/dist/transformers/entities.js.map +1 -0
- package/dist/transformers/filters/defaults.d.ts +6 -0
- package/dist/transformers/filters/defaults.d.ts.map +1 -0
- package/dist/transformers/filters/defaults.js +78 -0
- package/dist/transformers/filters/defaults.js.map +1 -0
- package/dist/transformers/filters/index.d.ts +43 -0
- package/dist/transformers/filters/index.d.ts.map +1 -0
- package/dist/transformers/filters/index.js +89 -0
- package/dist/transformers/filters/index.js.map +1 -0
- package/dist/transformers/format.d.ts +22 -0
- package/dist/transformers/format.d.ts.map +1 -0
- package/dist/transformers/format.js +30 -0
- package/dist/transformers/format.js.map +1 -0
- package/dist/transformers/{index.d.mts → index.d.ts} +14 -11
- package/dist/transformers/index.d.ts.map +1 -0
- package/dist/transformers/index.js +133 -0
- package/dist/transformers/index.js.map +1 -0
- package/dist/transformers/inlineCss.d.ts +84 -0
- package/dist/transformers/inlineCss.d.ts.map +1 -0
- package/dist/transformers/inlineCss.js +91 -0
- package/dist/transformers/inlineCss.js.map +1 -0
- package/dist/transformers/inlineLink.d.ts +35 -0
- package/dist/transformers/inlineLink.d.ts.map +1 -0
- package/dist/transformers/{inlineLink.mjs → inlineLink.js} +34 -10
- package/dist/transformers/inlineLink.js.map +1 -0
- package/dist/transformers/minify.d.ts +21 -0
- package/dist/transformers/minify.d.ts.map +1 -0
- package/dist/transformers/minify.js +25 -0
- package/dist/transformers/minify.js.map +1 -0
- package/dist/transformers/msoPlaceholders.d.ts +28 -0
- package/dist/transformers/msoPlaceholders.d.ts.map +1 -0
- package/dist/transformers/msoPlaceholders.js +88 -0
- package/dist/transformers/msoPlaceholders.js.map +1 -0
- package/dist/transformers/purgeCss.d.ts +43 -0
- package/dist/transformers/purgeCss.d.ts.map +1 -0
- package/dist/transformers/purgeCss.js +181 -0
- package/dist/transformers/purgeCss.js.map +1 -0
- package/dist/transformers/removeAttributes.d.ts +54 -0
- package/dist/transformers/removeAttributes.d.ts.map +1 -0
- package/dist/transformers/removeAttributes.js +70 -0
- package/dist/transformers/removeAttributes.js.map +1 -0
- package/dist/transformers/{replaceStrings.d.mts → replaceStrings.d.ts} +2 -2
- package/dist/transformers/replaceStrings.d.ts.map +1 -0
- package/dist/transformers/{replaceStrings.mjs → replaceStrings.js} +2 -2
- package/dist/transformers/replaceStrings.js.map +1 -0
- package/dist/transformers/{safeClassNames.d.mts → safeClassNames.d.ts} +2 -2
- package/dist/transformers/safeClassNames.d.ts.map +1 -0
- package/dist/transformers/{safeClassNames.mjs → safeClassNames.js} +4 -5
- package/dist/transformers/safeClassNames.js.map +1 -0
- package/dist/transformers/shorthandCss.d.ts +47 -0
- package/dist/transformers/shorthandCss.d.ts.map +1 -0
- package/dist/transformers/shorthandCss.js +61 -0
- package/dist/transformers/shorthandCss.js.map +1 -0
- package/dist/transformers/sixHex.d.ts +25 -0
- package/dist/transformers/sixHex.d.ts.map +1 -0
- package/dist/transformers/sixHex.js +42 -0
- package/dist/transformers/sixHex.js.map +1 -0
- package/dist/transformers/tailwindComponent.d.ts +16 -0
- package/dist/transformers/tailwindComponent.d.ts.map +1 -0
- package/dist/transformers/tailwindComponent.js +92 -0
- package/dist/transformers/tailwindComponent.js.map +1 -0
- package/dist/transformers/{tailwindcss.d.mts → tailwindcss.d.ts} +8 -4
- package/dist/transformers/tailwindcss.d.ts.map +1 -0
- package/dist/transformers/tailwindcss.js +97 -0
- package/dist/transformers/tailwindcss.js.map +1 -0
- package/dist/transformers/urlQuery.d.ts +36 -0
- package/dist/transformers/urlQuery.d.ts.map +1 -0
- package/dist/transformers/urlQuery.js +77 -0
- package/dist/transformers/urlQuery.js.map +1 -0
- package/dist/types/config.d.ts +737 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/ast/index.d.ts +4 -0
- package/dist/utils/ast/index.js +4 -0
- package/dist/utils/ast/{parser.d.mts → parser.d.ts} +1 -1
- package/dist/utils/ast/parser.d.ts.map +1 -0
- package/dist/utils/ast/{parser.mjs → parser.js} +2 -3
- package/dist/utils/ast/parser.js.map +1 -0
- package/dist/utils/ast/serializer.d.ts +8 -0
- package/dist/utils/ast/serializer.d.ts.map +1 -0
- package/dist/utils/ast/serializer.js +36 -0
- package/dist/utils/ast/serializer.js.map +1 -0
- package/dist/utils/ast/{walker.d.mts → walker.d.ts} +1 -1
- package/dist/utils/ast/walker.d.ts.map +1 -0
- package/dist/utils/ast/{walker.mjs → walker.js} +2 -2
- package/dist/utils/ast/walker.js.map +1 -0
- package/dist/utils/compileTailwindCss.d.ts +16 -0
- package/dist/utils/compileTailwindCss.d.ts.map +1 -0
- package/dist/utils/compileTailwindCss.js +54 -0
- package/dist/utils/compileTailwindCss.js.map +1 -0
- package/dist/utils/componentSources.d.ts +50 -0
- package/dist/utils/componentSources.d.ts.map +1 -0
- package/dist/utils/componentSources.js +50 -0
- package/dist/utils/componentSources.js.map +1 -0
- package/dist/utils/decodeStyleEntities.d.ts +15 -0
- package/dist/utils/decodeStyleEntities.d.ts.map +1 -0
- package/dist/utils/decodeStyleEntities.js +18 -0
- package/dist/utils/decodeStyleEntities.js.map +1 -0
- package/dist/utils/detect.d.ts +5 -0
- package/dist/utils/detect.d.ts.map +1 -0
- package/dist/utils/detect.js +10 -0
- package/dist/utils/detect.js.map +1 -0
- package/dist/utils/output-markers.d.ts +29 -0
- package/dist/utils/output-markers.d.ts.map +1 -0
- package/dist/utils/output-markers.js +68 -0
- package/dist/utils/output-markers.js.map +1 -0
- package/dist/utils/{url.d.mts → url.d.ts} +1 -1
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/{url.mjs → url.js} +2 -3
- package/dist/utils/url.js.map +1 -0
- package/dist/utils/watchPaths.d.ts +11 -0
- package/dist/utils/watchPaths.d.ts.map +1 -0
- package/dist/utils/watchPaths.js +19 -0
- package/dist/utils/watchPaths.js.map +1 -0
- package/node_modules/@clack/core/CHANGELOG.md +8 -0
- package/node_modules/@clack/core/dist/index.d.mts +18 -4
- package/node_modules/@clack/core/dist/index.mjs +16 -10
- package/node_modules/@clack/core/dist/index.mjs.map +1 -1
- package/node_modules/@clack/core/package.json +5 -2
- package/node_modules/@clack/prompts/CHANGELOG.md +15 -0
- package/node_modules/@clack/prompts/README.md +107 -2
- package/node_modules/@clack/prompts/dist/index.d.mts +16 -11
- package/node_modules/@clack/prompts/dist/index.mjs +114 -107
- package/node_modules/@clack/prompts/dist/index.mjs.map +1 -1
- package/node_modules/@clack/prompts/package.json +7 -4
- package/node_modules/fast-string-truncated-width/dist/index.js +36 -96
- package/node_modules/fast-string-truncated-width/dist/types.d.ts +0 -3
- package/node_modules/fast-string-truncated-width/dist/utils.d.ts +3 -3
- package/node_modules/fast-string-truncated-width/dist/utils.js +14 -9
- package/node_modules/fast-string-truncated-width/package.json +1 -1
- package/node_modules/fast-string-truncated-width/readme.md +2 -3
- package/node_modules/fast-string-width/package.json +2 -2
- package/node_modules/fast-string-width/readme.md +0 -3
- package/node_modules/fast-wrap-ansi/lib/main.js +4 -1
- package/node_modules/fast-wrap-ansi/lib/main.js.map +1 -1
- package/node_modules/fast-wrap-ansi/package.json +2 -2
- package/node_modules/maizzle/README.md +24 -0
- package/node_modules/maizzle/dist/commands/make/component.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/config.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/layout.mjs +3 -3
- package/node_modules/maizzle/dist/commands/make/scaffold.mjs +1 -1
- package/node_modules/maizzle/dist/commands/make/stubs/Layout.vue +146 -0
- package/node_modules/maizzle/dist/commands/make/stubs/component.vue +2 -4
- package/node_modules/maizzle/dist/commands/make/stubs/config.ts +1 -5
- package/node_modules/maizzle/dist/commands/make/template.mjs +1 -1
- package/node_modules/maizzle/dist/commands/new.mjs +32 -52
- package/node_modules/maizzle/dist/index.d.mts +1 -0
- package/node_modules/maizzle/dist/index.mjs +30 -7
- package/node_modules/maizzle/package.json +4 -3
- package/node_modules/nypm/dist/cli.mjs +28 -5
- package/node_modules/nypm/dist/index.d.mts +0 -8
- package/node_modules/nypm/dist/index.mjs +27 -4
- package/node_modules/nypm/package.json +12 -12
- package/node_modules/tinyexec/README.md +1 -1
- package/node_modules/tinyexec/dist/main.d.mts +6 -6
- package/node_modules/tinyexec/dist/main.mjs +126 -134
- package/node_modules/tinyexec/package.json +9 -9
- package/package.json +31 -21
- package/dist/build.d.mts +0 -19
- package/dist/build.d.mts.map +0 -1
- package/dist/build.mjs +0 -139
- package/dist/build.mjs.map +0 -1
- package/dist/components/Divider.vue +0 -105
- package/dist/components/Vml.vue +0 -89
- package/dist/components/utils.d.mts +0 -5
- package/dist/components/utils.d.mts.map +0 -1
- package/dist/components/utils.mjs +0 -9
- package/dist/components/utils.mjs.map +0 -1
- package/dist/composables/defineConfig.d.mts.map +0 -1
- package/dist/composables/defineConfig.mjs.map +0 -1
- package/dist/composables/renderContext.d.mts +0 -19
- package/dist/composables/renderContext.d.mts.map +0 -1
- package/dist/composables/renderContext.mjs.map +0 -1
- package/dist/composables/useConfig.d.mts.map +0 -1
- package/dist/composables/useConfig.mjs.map +0 -1
- package/dist/composables/useDoctype.d.mts.map +0 -1
- package/dist/composables/useDoctype.mjs.map +0 -1
- package/dist/composables/useEvent.d.mts.map +0 -1
- package/dist/composables/useEvent.mjs.map +0 -1
- package/dist/composables/usePlaintext.d.mts.map +0 -1
- package/dist/composables/usePlaintext.mjs.map +0 -1
- package/dist/config/defaults.d.mts.map +0 -1
- package/dist/config/defaults.mjs.map +0 -1
- package/dist/config/index.d.mts.map +0 -1
- package/dist/config/index.mjs.map +0 -1
- package/dist/events/index.d.mts.map +0 -1
- package/dist/events/index.mjs.map +0 -1
- package/dist/index.d.mts +0 -29
- package/dist/index.mjs +0 -29
- package/dist/plaintext.d.mts.map +0 -1
- package/dist/plaintext.mjs.map +0 -1
- package/dist/plugin.d.mts.map +0 -1
- package/dist/plugin.mjs +0 -41
- package/dist/plugin.mjs.map +0 -1
- package/dist/plugins/postcss/mergeMediaQueries.d.mts.map +0 -1
- package/dist/plugins/postcss/mergeMediaQueries.mjs.map +0 -1
- package/dist/plugins/postcss/pruneVars.d.mts.map +0 -1
- package/dist/plugins/postcss/pruneVars.mjs.map +0 -1
- package/dist/plugins/postcss/removeDeclarations.d.mts.map +0 -1
- package/dist/plugins/postcss/removeDeclarations.mjs.map +0 -1
- package/dist/plugins/postcss/tailwindCleanup.d.mts.map +0 -1
- package/dist/plugins/postcss/tailwindCleanup.mjs +0 -35
- package/dist/plugins/postcss/tailwindCleanup.mjs.map +0 -1
- package/dist/render/createRenderer.d.mts.map +0 -1
- package/dist/render/createRenderer.mjs +0 -155
- package/dist/render/createRenderer.mjs.map +0 -1
- package/dist/render/index.d.mts +0 -26
- package/dist/render/index.d.mts.map +0 -1
- package/dist/render/index.mjs +0 -44
- package/dist/render/index.mjs.map +0 -1
- package/dist/serve.d.mts.map +0 -1
- package/dist/serve.mjs.map +0 -1
- package/dist/server/compatibility.d.mts +0 -6
- package/dist/server/compatibility.d.mts.map +0 -1
- package/dist/server/compatibility.mjs +0 -83
- package/dist/server/compatibility.mjs.map +0 -1
- package/dist/server/linter.d.mts +0 -6
- package/dist/server/linter.d.mts.map +0 -1
- package/dist/server/linter.mjs +0 -200
- package/dist/server/linter.mjs.map +0 -1
- package/dist/server/ui/components/ui/resizable/ResizableHandle.vue +0 -30
- package/dist/server/ui/components/ui/resizable/ResizablePanel.vue +0 -21
- package/dist/server/ui/components/ui/resizable/ResizablePanelGroup.vue +0 -25
- package/dist/server/ui/components/ui/resizable/index.ts +0 -3
- package/dist/transformers/addAttributes.d.mts.map +0 -1
- package/dist/transformers/addAttributes.mjs.map +0 -1
- package/dist/transformers/attributeToStyle.d.mts +0 -25
- package/dist/transformers/attributeToStyle.d.mts.map +0 -1
- package/dist/transformers/attributeToStyle.mjs +0 -80
- package/dist/transformers/attributeToStyle.mjs.map +0 -1
- package/dist/transformers/base.d.mts +0 -8
- package/dist/transformers/base.d.mts.map +0 -1
- package/dist/transformers/base.mjs.map +0 -1
- package/dist/transformers/entities.d.mts +0 -8
- package/dist/transformers/entities.d.mts.map +0 -1
- package/dist/transformers/entities.mjs +0 -38
- package/dist/transformers/entities.mjs.map +0 -1
- package/dist/transformers/format.d.mts +0 -15
- package/dist/transformers/format.d.mts.map +0 -1
- package/dist/transformers/format.mjs +0 -26
- package/dist/transformers/format.mjs.map +0 -1
- package/dist/transformers/index.d.mts.map +0 -1
- package/dist/transformers/index.mjs +0 -73
- package/dist/transformers/index.mjs.map +0 -1
- package/dist/transformers/inlineCSS.d.mts +0 -30
- package/dist/transformers/inlineCSS.d.mts.map +0 -1
- package/dist/transformers/inlineCSS.mjs +0 -79
- package/dist/transformers/inlineCSS.mjs.map +0 -1
- package/dist/transformers/inlineLink.d.mts +0 -14
- package/dist/transformers/inlineLink.d.mts.map +0 -1
- package/dist/transformers/inlineLink.mjs.map +0 -1
- package/dist/transformers/minify.d.mts +0 -17
- package/dist/transformers/minify.d.mts.map +0 -1
- package/dist/transformers/minify.mjs +0 -24
- package/dist/transformers/minify.mjs.map +0 -1
- package/dist/transformers/purgeCSS.d.mts +0 -23
- package/dist/transformers/purgeCSS.d.mts.map +0 -1
- package/dist/transformers/purgeCSS.mjs +0 -66
- package/dist/transformers/purgeCSS.mjs.map +0 -1
- package/dist/transformers/removeAttributes.d.mts +0 -31
- package/dist/transformers/removeAttributes.d.mts.map +0 -1
- package/dist/transformers/removeAttributes.mjs +0 -63
- package/dist/transformers/removeAttributes.mjs.map +0 -1
- package/dist/transformers/replaceStrings.d.mts.map +0 -1
- package/dist/transformers/replaceStrings.mjs.map +0 -1
- package/dist/transformers/safeClassNames.d.mts.map +0 -1
- package/dist/transformers/safeClassNames.mjs.map +0 -1
- package/dist/transformers/shorthandCSS.d.mts +0 -24
- package/dist/transformers/shorthandCSS.d.mts.map +0 -1
- package/dist/transformers/shorthandCSS.mjs +0 -48
- package/dist/transformers/shorthandCSS.mjs.map +0 -1
- package/dist/transformers/tailwindcss.d.mts.map +0 -1
- package/dist/transformers/tailwindcss.mjs +0 -136
- package/dist/transformers/tailwindcss.mjs.map +0 -1
- package/dist/transformers/urlQuery.d.mts +0 -24
- package/dist/transformers/urlQuery.d.mts.map +0 -1
- package/dist/transformers/urlQuery.mjs +0 -65
- package/dist/transformers/urlQuery.mjs.map +0 -1
- package/dist/types/config.d.mts +0 -149
- package/dist/types/config.d.mts.map +0 -1
- package/dist/types/config.mjs +0 -1
- package/dist/types/index.d.mts +0 -2
- package/dist/types/index.mjs +0 -1
- package/dist/utils/ast/index.d.mts +0 -4
- package/dist/utils/ast/index.mjs +0 -5
- package/dist/utils/ast/parser.d.mts.map +0 -1
- package/dist/utils/ast/parser.mjs.map +0 -1
- package/dist/utils/ast/serializer.d.mts +0 -7
- package/dist/utils/ast/serializer.d.mts.map +0 -1
- package/dist/utils/ast/serializer.mjs +0 -13
- package/dist/utils/ast/serializer.mjs.map +0 -1
- package/dist/utils/ast/walker.d.mts.map +0 -1
- package/dist/utils/ast/walker.mjs.map +0 -1
- package/dist/utils/url.d.mts.map +0 -1
- package/dist/utils/url.mjs.map +0 -1
- package/node_modules/maizzle/dist/commands/make/stubs/layout.vue +0 -39
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
|
2
|
+
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
|
3
3
|
import { useRoute } from 'vue-router'
|
|
4
4
|
import { ChevronUp, ChevronDown, Check } from 'lucide-vue-next'
|
|
5
5
|
import {
|
|
@@ -8,15 +8,21 @@ import {
|
|
|
8
8
|
DropdownMenuItem,
|
|
9
9
|
DropdownMenuTrigger,
|
|
10
10
|
} from '@/components/ui/dropdown-menu'
|
|
11
|
-
import {
|
|
12
|
-
ResizableHandle,
|
|
13
|
-
ResizablePanel,
|
|
14
|
-
ResizablePanelGroup,
|
|
15
|
-
} from '@/components/ui/resizable'
|
|
16
11
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
17
12
|
import { Button } from '@/components/ui/button'
|
|
13
|
+
import { Input } from '@/components/ui/input'
|
|
14
|
+
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
|
15
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
|
16
|
+
import {
|
|
17
|
+
TagsInput,
|
|
18
|
+
TagsInputInput,
|
|
19
|
+
TagsInputItem,
|
|
20
|
+
TagsInputItemDelete,
|
|
21
|
+
TagsInputItemText,
|
|
22
|
+
} from '@/components/ui/tags-input'
|
|
18
23
|
|
|
19
24
|
import stripesUrl from '../stripes.svg'
|
|
25
|
+
import { applyColorInversion, undoColorInversion } from '@/lib/emulated-dark-mode'
|
|
20
26
|
|
|
21
27
|
interface Device {
|
|
22
28
|
name: string
|
|
@@ -24,12 +30,20 @@ interface Device {
|
|
|
24
30
|
height: number
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
interface Template {
|
|
34
|
+
name: string
|
|
35
|
+
path: string
|
|
36
|
+
href: string
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
const props = defineProps<{
|
|
28
40
|
device?: Device | null
|
|
29
41
|
resetKey?: number
|
|
42
|
+
templates?: Template[]
|
|
30
43
|
}>()
|
|
31
44
|
|
|
32
45
|
const viewMode = defineModel<'preview' | 'source'>('viewMode', { default: 'preview' })
|
|
46
|
+
const darkMode = defineModel<boolean>('darkMode', { default: false })
|
|
33
47
|
|
|
34
48
|
const route = useRoute()
|
|
35
49
|
const srcdoc = ref('')
|
|
@@ -40,55 +54,100 @@ const sourceView = ref<'compiled' | 'vue' | 'plaintext'>('compiled')
|
|
|
40
54
|
const copied = ref(false)
|
|
41
55
|
|
|
42
56
|
const iframeEl = ref<HTMLIFrameElement>()
|
|
57
|
+
const compiledSourceEl = ref<HTMLElement>()
|
|
43
58
|
const vueSourceEl = ref<HTMLElement>()
|
|
59
|
+
const plaintextEl = ref<HTMLElement>()
|
|
44
60
|
const containerEl = ref<HTMLElement>()
|
|
45
|
-
const
|
|
46
|
-
const leftPanel = ref<InstanceType<typeof ResizablePanel>>()
|
|
47
|
-
const rightPanel = ref<InstanceType<typeof ResizablePanel>>()
|
|
48
|
-
const topPanel = ref<InstanceType<typeof ResizablePanel>>()
|
|
49
|
-
const bottomPanel = ref<InstanceType<typeof ResizablePanel>>()
|
|
61
|
+
const wrapperEl = ref<HTMLElement>()
|
|
50
62
|
|
|
51
63
|
const panelWidth = defineModel<number>('panelWidth', { default: 0 })
|
|
52
64
|
const panelHeight = defineModel<number>('panelHeight', { default: 0 })
|
|
65
|
+
/**
|
|
66
|
+
* Container's available area, exposed to the toolbar so size inputs can
|
|
67
|
+
* clamp typed values without paying a layout-recalc cost on
|
|
68
|
+
* every drag tick. Kept in sync via a ResizeObserver.
|
|
69
|
+
*/
|
|
70
|
+
const maxIframeWidth = defineModel<number>('maxIframeWidth', { default: 0 })
|
|
71
|
+
const maxIframeHeight = defineModel<number>('maxIframeHeight', { default: 0 })
|
|
53
72
|
const isDragging = defineModel<boolean>('isDragging', { default: false })
|
|
54
73
|
const isFullSize = defineModel<boolean>('isFullSize', { default: true })
|
|
55
74
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Custom resizable: width/height of the iframe wrapper (null = fill the
|
|
77
|
+
* container). Exposed as v-models so the toolbar's size indicator
|
|
78
|
+
* can drive these too, alongside the drag handles.
|
|
79
|
+
*/
|
|
80
|
+
const iframeWidth = defineModel<number | null>('iframeWidth', { default: null })
|
|
81
|
+
const iframeHeight = defineModel<number | null>('iframeHeight', { default: null })
|
|
82
|
+
const iframeContentHeight = ref<number | null>(null)
|
|
83
|
+
|
|
84
|
+
function copySource() {
|
|
85
|
+
let text: string
|
|
66
86
|
if (sourceView.value === 'compiled') {
|
|
67
|
-
|
|
87
|
+
// `renderedHtml` holds the raw compiled HTML (srcdoc is only populated
|
|
88
|
+
// for the initial iframe load; subsequent renders use doc.write).
|
|
89
|
+
text = renderedHtml || srcdoc.value
|
|
68
90
|
} else if (sourceView.value === 'plaintext') {
|
|
69
|
-
|
|
91
|
+
text = plaintextContent.value
|
|
70
92
|
} else {
|
|
71
93
|
const el = document.createElement('div')
|
|
72
94
|
el.innerHTML = vueSourceHtml.value
|
|
73
|
-
|
|
95
|
+
text = el.textContent || ''
|
|
74
96
|
}
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
|
|
98
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
99
|
+
copied.value = true
|
|
100
|
+
setTimeout(() => { copied.value = false }, 2000)
|
|
101
|
+
}).catch((err) => {
|
|
102
|
+
console.error('Copy failed:', err)
|
|
103
|
+
})
|
|
77
104
|
}
|
|
78
105
|
|
|
79
|
-
interface
|
|
80
|
-
|
|
106
|
+
interface CheckIssue {
|
|
107
|
+
kind: 'compat' | 'lint'
|
|
108
|
+
slug?: string
|
|
81
109
|
title: string
|
|
82
|
-
clients: Array<{ name: string, notes: string[] }>
|
|
83
110
|
url?: string
|
|
111
|
+
category: string
|
|
84
112
|
line?: number
|
|
113
|
+
file: string
|
|
114
|
+
// compat-only
|
|
115
|
+
supportLevel?: 'unsupported' | 'mitigated' | 'unknown'
|
|
116
|
+
supportLabel?: string
|
|
117
|
+
affectedClients?: string[]
|
|
118
|
+
// lint-only
|
|
119
|
+
severity?: 'error' | 'warning'
|
|
120
|
+
message?: string
|
|
85
121
|
}
|
|
86
122
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
123
|
+
function supportPrefix(issue: CheckIssue): string {
|
|
124
|
+
if (issue.supportLevel === 'unsupported') return 'Not supported in'
|
|
125
|
+
if (issue.supportLevel === 'mitigated') return 'Partial support in'
|
|
126
|
+
return 'Support unknown in'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Split a message on backtick-delimited code spans. Returns alternating
|
|
131
|
+
* { text } and { code } segments so the template can render <code> inline
|
|
132
|
+
* without needing v-html.
|
|
133
|
+
*/
|
|
134
|
+
function messageSegments(raw: string | undefined): Array<{ code: boolean, text: string }> {
|
|
135
|
+
if (!raw) return []
|
|
136
|
+
const out: Array<{ code: boolean, text: string }> = []
|
|
137
|
+
const parts = raw.split('`')
|
|
138
|
+
for (let i = 0; i < parts.length; i++) {
|
|
139
|
+
if (parts[i]) out.push({ code: i % 2 === 1, text: parts[i] })
|
|
140
|
+
}
|
|
141
|
+
return out
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function issueColorClass(issue: CheckIssue): string {
|
|
145
|
+
if (issue.kind === 'lint') {
|
|
146
|
+
return issue.severity === 'error' ? 'text-rose-600' : 'text-amber-600'
|
|
147
|
+
}
|
|
148
|
+
if (issue.supportLevel === 'unsupported') return 'text-rose-600'
|
|
149
|
+
if (issue.supportLevel === 'mitigated') return 'text-amber-600'
|
|
150
|
+
return 'text-gray-500 dark:text-gray-400'
|
|
92
151
|
}
|
|
93
152
|
|
|
94
153
|
interface TemplateStats {
|
|
@@ -97,31 +156,179 @@ interface TemplateStats {
|
|
|
97
156
|
links: number
|
|
98
157
|
}
|
|
99
158
|
|
|
100
|
-
const compatibilityIssues = ref<
|
|
159
|
+
const compatibilityIssues = ref<CheckIssue[]>([])
|
|
101
160
|
const compatibilityLoading = ref(false)
|
|
102
|
-
const
|
|
103
|
-
const
|
|
161
|
+
const compatibilityError = ref('')
|
|
162
|
+
const compatibilityCategory = ref('')
|
|
163
|
+
// Injected by serveDevUI into index.html — synchronous, available before
|
|
164
|
+
// any HTTP calls, so the Checks tab never flashes in when disabled.
|
|
165
|
+
const checksConfig = (window as any).__MAIZZLE_CONFIG__?.checks
|
|
166
|
+
const compatibilityDisabled = ref(checksConfig === false)
|
|
167
|
+
const expandedIssueKeys = ref(new Set<string>())
|
|
168
|
+
const issueKey = (issue: CheckIssue, i: number): string => `${issue.file}|${issue.line ?? 0}|${issue.slug ?? issue.title}|${i}`
|
|
169
|
+
const compatibilityCategories = ['css', 'html', 'image', 'others'] as const
|
|
170
|
+
const activeCompatibilityCategories = computed(() =>
|
|
171
|
+
compatibilityCategories.filter(cat => compatibilityIssues.value.some(i => i.category === cat))
|
|
172
|
+
)
|
|
173
|
+
const filteredCompatibilityIssues = computed(() => {
|
|
174
|
+
if (!compatibilityCategory.value) return compatibilityIssues.value
|
|
175
|
+
return compatibilityIssues.value.filter(i => i.category === compatibilityCategory.value)
|
|
176
|
+
})
|
|
104
177
|
const stats = ref<TemplateStats | null>(null)
|
|
105
178
|
const statsLoading = ref(false)
|
|
106
179
|
|
|
180
|
+
// Email test state
|
|
181
|
+
const emailTo = ref<string[]>([])
|
|
182
|
+
const emailSubject = ref('')
|
|
183
|
+
const emailSending = ref(false)
|
|
184
|
+
const emailPreventThreading = ref(true)
|
|
185
|
+
const emailResult = ref<{ success: boolean; message: string; previewUrl?: string } | null>(null)
|
|
186
|
+
|
|
187
|
+
async function fetchEmailConfig() {
|
|
188
|
+
try {
|
|
189
|
+
const res = await fetch('/__maizzle/email-config')
|
|
190
|
+
const data = await res.json()
|
|
191
|
+
if (data.to?.length && !emailTo.value.length) emailTo.value = data.to
|
|
192
|
+
if (data.subject && !emailSubject.value) emailSubject.value = data.subject
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function sendTestEmail() {
|
|
197
|
+
if (!emailTo.value.length) return
|
|
198
|
+
emailSending.value = true
|
|
199
|
+
emailResult.value = null
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const res = await fetch(`/__maizzle/email/${route.params.template}`, {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers: { 'Content-Type': 'application/json' },
|
|
205
|
+
body: JSON.stringify({
|
|
206
|
+
to: emailTo.value,
|
|
207
|
+
subject: (() => {
|
|
208
|
+
let subj = emailSubject.value || String(route.params.template)
|
|
209
|
+
if (emailPreventThreading.value) {
|
|
210
|
+
subj += ` | ${new Date().toISOString().slice(0, 19)}`
|
|
211
|
+
}
|
|
212
|
+
return subj
|
|
213
|
+
})(),
|
|
214
|
+
}),
|
|
215
|
+
})
|
|
216
|
+
emailResult.value = await res.json()
|
|
217
|
+
} catch (error: any) {
|
|
218
|
+
emailResult.value = { success: false, message: error.message }
|
|
219
|
+
} finally {
|
|
220
|
+
emailSending.value = false
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let renderedHtml = ''
|
|
225
|
+
|
|
226
|
+
function updateIframeContentHeight() {
|
|
227
|
+
const iframe = iframeEl.value
|
|
228
|
+
const doc = iframe?.contentDocument
|
|
229
|
+
if (!iframe || !doc?.documentElement) return
|
|
230
|
+
|
|
231
|
+
// Hide iframe body overflow — scrolling is handled by the outer ScrollArea
|
|
232
|
+
if (doc.body) doc.body.style.overflow = 'hidden'
|
|
233
|
+
|
|
234
|
+
// Save scroll position of the ScrollArea viewport
|
|
235
|
+
const viewport = wrapperEl.value?.querySelector('[data-slot="scroll-area-viewport"]')
|
|
236
|
+
const scrollTop = viewport?.scrollTop ?? 0
|
|
237
|
+
|
|
238
|
+
// Temporarily collapse to measure true content height
|
|
239
|
+
iframe.style.height = '0'
|
|
240
|
+
iframeContentHeight.value = doc.documentElement.scrollHeight
|
|
241
|
+
iframe.style.height = `${iframeContentHeight.value}px`
|
|
242
|
+
|
|
243
|
+
// Restore scroll position
|
|
244
|
+
if (viewport) {
|
|
245
|
+
viewport.scrollTop = scrollTop
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function onIframeLoad() {
|
|
250
|
+
updateIframeContentHeight()
|
|
251
|
+
const iframe = iframeEl.value
|
|
252
|
+
if (darkMode.value && iframe) applyColorInversion(iframe)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
watch(darkMode, (on) => {
|
|
256
|
+
const iframe = iframeEl.value
|
|
257
|
+
if (!iframe) return
|
|
258
|
+
if (on) applyColorInversion(iframe)
|
|
259
|
+
else undoColorInversion(iframe)
|
|
260
|
+
})
|
|
261
|
+
|
|
107
262
|
async function fetchTemplate() {
|
|
108
263
|
const res = await fetch(`/__maizzle/render/${route.params.template}`)
|
|
109
|
-
|
|
264
|
+
renderedHtml = await res.text()
|
|
265
|
+
|
|
266
|
+
const iframe = iframeEl.value
|
|
267
|
+
const doc = iframe?.contentDocument
|
|
268
|
+
|
|
269
|
+
// Write directly into the iframe document to avoid a full reload,
|
|
270
|
+
// which preserves scroll position natively.
|
|
271
|
+
if (doc) {
|
|
272
|
+
doc.open()
|
|
273
|
+
doc.write(renderedHtml)
|
|
274
|
+
doc.close()
|
|
275
|
+
// Hide iframe body overflow — scrolling is handled by the outer ScrollArea
|
|
276
|
+
if (doc.body) doc.body.style.overflow = 'hidden'
|
|
277
|
+
if (darkMode.value && iframe) applyColorInversion(iframe)
|
|
278
|
+
await nextTick()
|
|
279
|
+
updateIframeContentHeight()
|
|
280
|
+
} else {
|
|
281
|
+
// Fallback for initial load
|
|
282
|
+
srcdoc.value = renderedHtml
|
|
283
|
+
}
|
|
110
284
|
}
|
|
111
285
|
|
|
286
|
+
const sourceLoading = ref(false)
|
|
287
|
+
const vueSourceLoading = ref(false)
|
|
288
|
+
const plaintextLoading = ref(false)
|
|
289
|
+
|
|
112
290
|
async function fetchSource() {
|
|
113
|
-
|
|
114
|
-
|
|
291
|
+
if (sourceLoading.value) return
|
|
292
|
+
sourceLoading.value = true
|
|
293
|
+
try {
|
|
294
|
+
const res = await fetch(`/__maizzle/source/${route.params.template}`)
|
|
295
|
+
sourceHtml.value = await res.text()
|
|
296
|
+
} finally {
|
|
297
|
+
sourceLoading.value = false
|
|
298
|
+
}
|
|
115
299
|
}
|
|
116
300
|
|
|
117
301
|
async function fetchVueSource() {
|
|
118
|
-
|
|
119
|
-
|
|
302
|
+
if (vueSourceLoading.value) return
|
|
303
|
+
vueSourceLoading.value = true
|
|
304
|
+
try {
|
|
305
|
+
const res = await fetch(`/__maizzle/vue-source/${route.params.template}`)
|
|
306
|
+
vueSourceHtml.value = await res.text()
|
|
307
|
+
} finally {
|
|
308
|
+
vueSourceLoading.value = false
|
|
309
|
+
}
|
|
120
310
|
}
|
|
121
311
|
|
|
122
312
|
async function fetchPlaintext() {
|
|
123
|
-
|
|
124
|
-
|
|
313
|
+
if (plaintextLoading.value) return
|
|
314
|
+
plaintextLoading.value = true
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch(`/__maizzle/plaintext/${route.params.template}`)
|
|
317
|
+
plaintextContent.value = await res.text()
|
|
318
|
+
} finally {
|
|
319
|
+
plaintextLoading.value = false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Warm the three source views in the background so switching from the
|
|
325
|
+
* preview is instant. Single-flight guards above prevent duplication
|
|
326
|
+
* with any in-flight fetch from a view-switch watcher.
|
|
327
|
+
*/
|
|
328
|
+
function prefetchSources() {
|
|
329
|
+
if (!sourceHtml.value) fetchSource()
|
|
330
|
+
if (!vueSourceHtml.value) fetchVueSource()
|
|
331
|
+
if (!plaintextContent.value) fetchPlaintext()
|
|
125
332
|
}
|
|
126
333
|
|
|
127
334
|
async function fetchStats() {
|
|
@@ -137,10 +344,31 @@ async function fetchStats() {
|
|
|
137
344
|
}
|
|
138
345
|
|
|
139
346
|
async function fetchCompatibility() {
|
|
347
|
+
if (compatibilityDisabled.value) return
|
|
348
|
+
const template = props.templates?.find(t => t.href === '/' + route.params.template)
|
|
349
|
+
if (!template) return
|
|
350
|
+
|
|
140
351
|
compatibilityLoading.value = true
|
|
352
|
+
compatibilityError.value = ''
|
|
141
353
|
try {
|
|
142
|
-
const res = await fetch(`/__maizzle/compatibility/${
|
|
143
|
-
|
|
354
|
+
const res = await fetch(`/__maizzle/compatibility/${template.path}`)
|
|
355
|
+
const data = await res.json()
|
|
356
|
+
if (!Array.isArray(data) && data?.error) {
|
|
357
|
+
compatibilityError.value = data.error
|
|
358
|
+
compatibilityIssues.value = []
|
|
359
|
+
} else {
|
|
360
|
+
const issues: CheckIssue[] = Array.isArray(data) ? data : []
|
|
361
|
+
compatibilityIssues.value = issues
|
|
362
|
+
// Keep the current category if it still has issues; otherwise fall
|
|
363
|
+
// back to the first category that does. Prevents a "refresh" during
|
|
364
|
+
// edits from snapping back to CSS when the user is on HTML/Image.
|
|
365
|
+
const current = compatibilityCategory.value
|
|
366
|
+
const currentStillActive = current && issues.some((i) => i.category === current)
|
|
367
|
+
if (!currentStillActive) {
|
|
368
|
+
const firstCat = compatibilityCategories.find(cat => issues.some((i) => i.category === cat))
|
|
369
|
+
compatibilityCategory.value = firstCat || ''
|
|
370
|
+
}
|
|
371
|
+
}
|
|
144
372
|
} catch {
|
|
145
373
|
compatibilityIssues.value = []
|
|
146
374
|
} finally {
|
|
@@ -148,16 +376,21 @@ async function fetchCompatibility() {
|
|
|
148
376
|
}
|
|
149
377
|
}
|
|
150
378
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
379
|
+
/** Check if an issue is from the currently viewed template file */
|
|
380
|
+
function isCurrentFile(issue: { file: string }): boolean {
|
|
381
|
+
const template = props.templates?.find(t => t.href === '/' + route.params.template)
|
|
382
|
+
if (!template) return true
|
|
383
|
+
return issue.file.endsWith(template.path)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Get a short display name for a component file path */
|
|
387
|
+
function componentName(filePath: string): string {
|
|
388
|
+
const parts = filePath.replace(/\\/g, '/').split('/')
|
|
389
|
+
return parts[parts.length - 1]?.replace(/\.vue$/, '') ?? filePath
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function openInEditor(file: string, line: number) {
|
|
393
|
+
fetch(`/__open-in-editor?file=${encodeURIComponent(file + ':' + line)}`)
|
|
161
394
|
}
|
|
162
395
|
|
|
163
396
|
watch(() => route.params.template, () => {
|
|
@@ -165,18 +398,30 @@ watch(() => route.params.template, () => {
|
|
|
165
398
|
vueSourceHtml.value = ''
|
|
166
399
|
plaintextContent.value = ''
|
|
167
400
|
compatibilityIssues.value = []
|
|
168
|
-
|
|
401
|
+
compatibilityError.value = ''
|
|
169
402
|
stats.value = null
|
|
403
|
+
emailResult.value = null
|
|
170
404
|
sourceView.value = 'compiled'
|
|
171
|
-
fetchTemplate()
|
|
405
|
+
fetchTemplate().then(prefetchSources)
|
|
172
406
|
fetchCompatibility()
|
|
173
|
-
fetchLint()
|
|
174
407
|
fetchStats()
|
|
408
|
+
fetchEmailConfig()
|
|
175
409
|
if (viewMode.value === 'source') fetchSource()
|
|
176
410
|
}, { immediate: true })
|
|
177
411
|
|
|
412
|
+
// Templates list loads async from App.vue — re-trigger once available
|
|
413
|
+
watch(() => props.templates, (templates) => {
|
|
414
|
+
if (templates?.length && !compatibilityIssues.value.length && !compatibilityLoading.value) {
|
|
415
|
+
fetchCompatibility()
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
|
|
178
419
|
watch(viewMode, (mode) => {
|
|
179
|
-
if (mode === 'source'
|
|
420
|
+
if (mode === 'source') {
|
|
421
|
+
if (sourceView.value === 'compiled' && !sourceHtml.value) fetchSource()
|
|
422
|
+
if (sourceView.value === 'vue' && !vueSourceHtml.value) fetchVueSource()
|
|
423
|
+
if (sourceView.value === 'plaintext' && !plaintextContent.value) fetchPlaintext()
|
|
424
|
+
}
|
|
180
425
|
})
|
|
181
426
|
|
|
182
427
|
watch(sourceView, (view) => {
|
|
@@ -185,45 +430,81 @@ watch(sourceView, (view) => {
|
|
|
185
430
|
if (view === 'plaintext' && !plaintextContent.value) fetchPlaintext()
|
|
186
431
|
})
|
|
187
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Preserve scrollTop across in-place content updates (HMR refetch).
|
|
435
|
+
* Vue's default `flush: 'pre'` runs the watcher BEFORE the DOM is
|
|
436
|
+
* updated — so we read the current scrollTop, then restore it on the
|
|
437
|
+
* next tick after the new content has rendered. Skip the case where
|
|
438
|
+
* the value transitions from empty (first paint / route change) so a
|
|
439
|
+
* fresh template doesn't snap to a stale offset.
|
|
440
|
+
*/
|
|
441
|
+
function viewportFor(el: HTMLElement | undefined): HTMLElement | null {
|
|
442
|
+
return (el?.closest('[data-slot="scroll-area-viewport"]') as HTMLElement | null) ?? null
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function preserveScroll(getEl: () => HTMLElement | undefined) {
|
|
446
|
+
return async (newVal: string, oldVal: string) => {
|
|
447
|
+
if (!oldVal || !newVal) return
|
|
448
|
+
const vp = viewportFor(getEl())
|
|
449
|
+
if (!vp) return
|
|
450
|
+
const top = vp.scrollTop
|
|
451
|
+
await nextTick()
|
|
452
|
+
vp.scrollTop = top
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
watch(sourceHtml, preserveScroll(() => compiledSourceEl.value))
|
|
457
|
+
watch(vueSourceHtml, preserveScroll(() => vueSourceEl.value))
|
|
458
|
+
watch(plaintextContent, preserveScroll(() => plaintextEl.value))
|
|
459
|
+
|
|
188
460
|
if ((import.meta as any).hot) {
|
|
189
461
|
;(import.meta as any).hot.on('maizzle:template-updated', () => {
|
|
190
|
-
fetchTemplate()
|
|
191
462
|
fetchCompatibility()
|
|
192
|
-
fetchLint()
|
|
193
463
|
fetchStats()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
464
|
+
|
|
465
|
+
// Refetch in place — don't clear the previous values first. v-html
|
|
466
|
+
// replaces the highlighted block atomically when the new content
|
|
467
|
+
// arrives, and the ScrollArea viewport keeps its scrollTop as long
|
|
468
|
+
// as the new content's height is similar. Plaintext interpolation
|
|
469
|
+
// updates a single text node, so scroll is naturally preserved.
|
|
470
|
+
fetchTemplate()
|
|
471
|
+
fetchSource()
|
|
472
|
+
fetchVueSource()
|
|
473
|
+
fetchPlaintext()
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
// Keep the UI in sync with live config edits. Payload is the same shape
|
|
477
|
+
// as the initial `window.__MAIZZLE_CONFIG__` inject — we replace it and
|
|
478
|
+
// derive per-feature flags from there.
|
|
479
|
+
;(import.meta as any).hot.on('maizzle:config-updated', (data: Record<string, unknown>) => {
|
|
480
|
+
;(window as any).__MAIZZLE_CONFIG__ = data
|
|
481
|
+
const wasDisabled = compatibilityDisabled.value
|
|
482
|
+
const nowDisabled = data?.checks === false
|
|
483
|
+
compatibilityDisabled.value = nowDisabled
|
|
484
|
+
if (nowDisabled) {
|
|
485
|
+
compatibilityIssues.value = []
|
|
486
|
+
if (activeTab.value === 'compatibility') activeTab.value = 'stats'
|
|
487
|
+
} else if (wasDisabled) {
|
|
488
|
+
fetchCompatibility()
|
|
203
489
|
}
|
|
204
490
|
})
|
|
205
491
|
}
|
|
206
492
|
|
|
207
|
-
|
|
208
|
-
async function goToLine(line: number) {
|
|
209
|
-
// Switch to source view showing Vue source
|
|
493
|
+
async function goToCompiledLine(line: number) {
|
|
210
494
|
viewMode.value = 'source'
|
|
211
|
-
sourceView.value = '
|
|
495
|
+
sourceView.value = 'compiled'
|
|
212
496
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
await fetchVueSource()
|
|
497
|
+
if (!sourceHtml.value) {
|
|
498
|
+
await fetchSource()
|
|
216
499
|
}
|
|
217
500
|
|
|
218
501
|
await nextTick()
|
|
219
502
|
|
|
220
|
-
const el =
|
|
503
|
+
const el = compiledSourceEl.value
|
|
221
504
|
if (!el) return
|
|
222
505
|
|
|
223
|
-
// Remove previous highlight
|
|
224
506
|
el.querySelectorAll('.shiki-highlight-line').forEach(l => l.classList.remove('shiki-highlight-line'))
|
|
225
507
|
|
|
226
|
-
// Find and highlight the line
|
|
227
508
|
const lineEl = el.querySelector(`[data-line="${line}"]`)
|
|
228
509
|
if (lineEl) {
|
|
229
510
|
lineEl.classList.add('shiki-highlight-line')
|
|
@@ -231,74 +512,94 @@ async function goToLine(line: number) {
|
|
|
231
512
|
}
|
|
232
513
|
}
|
|
233
514
|
|
|
234
|
-
// Track which axis is being user-dragged so we can sync the opposite panel
|
|
235
|
-
let hDragging = false
|
|
236
|
-
let vDragging = false
|
|
237
|
-
|
|
238
515
|
const emit = defineEmits<{ 'clear-device': [] }>()
|
|
239
516
|
|
|
240
|
-
|
|
241
|
-
function onHDragEnd() { setTimeout(() => { hDragging = false }, 50); isDragging.value = false }
|
|
242
|
-
function onVDragStart() { vDragging = true; isDragging.value = true; emit('clear-device') }
|
|
243
|
-
function onVDragEnd() { setTimeout(() => { vDragging = false }, 50); isDragging.value = false }
|
|
517
|
+
type Edge = 'left' | 'right' | 'top' | 'bottom'
|
|
244
518
|
|
|
245
|
-
function
|
|
246
|
-
|
|
519
|
+
function onEdgeDrag(e: MouseEvent | TouchEvent, edge: Edge) {
|
|
520
|
+
e.preventDefault()
|
|
521
|
+
isDragging.value = true
|
|
522
|
+
emit('clear-device')
|
|
523
|
+
|
|
524
|
+
const container = containerEl.value
|
|
525
|
+
if (!container) return
|
|
526
|
+
|
|
527
|
+
const isTouch = e.type === 'touchstart'
|
|
528
|
+
const startPoint = isTouch ? (e as TouchEvent).touches[0] : (e as MouseEvent)
|
|
529
|
+
const startX = startPoint.clientX
|
|
530
|
+
const startY = startPoint.clientY
|
|
531
|
+
const rect = container.getBoundingClientRect()
|
|
532
|
+
const gutter = 40 // 20px padding on each side
|
|
533
|
+
const maxW = rect.width - gutter
|
|
534
|
+
const maxH = rect.height - gutter
|
|
535
|
+
const startW = iframeWidth.value ?? maxW
|
|
536
|
+
const startH = iframeHeight.value ?? maxH
|
|
537
|
+
|
|
538
|
+
const isHorizontal = edge === 'left' || edge === 'right'
|
|
539
|
+
const sign = (edge === 'left' || edge === 'top') ? -1 : 1
|
|
540
|
+
|
|
541
|
+
document.documentElement.style.cursor = isHorizontal ? 'ew-resize' : 'ns-resize'
|
|
542
|
+
|
|
543
|
+
const onMove = (ev: MouseEvent | TouchEvent) => {
|
|
544
|
+
const point = ev.type === 'touchmove' ? (ev as TouchEvent).touches[0] : (ev as MouseEvent)
|
|
545
|
+
if (isHorizontal) {
|
|
546
|
+
// Symmetric: each side moves by the delta, so total change is 2x
|
|
547
|
+
const delta = (point.clientX - startX) * sign
|
|
548
|
+
iframeWidth.value = Math.max(200, Math.min(maxW, startW + delta * 2))
|
|
549
|
+
} else {
|
|
550
|
+
const delta = (point.clientY - startY) * sign
|
|
551
|
+
iframeHeight.value = Math.max(100, Math.min(maxH, startH + delta * 2))
|
|
552
|
+
}
|
|
553
|
+
}
|
|
247
554
|
|
|
248
|
-
const
|
|
249
|
-
|
|
555
|
+
const onUp = () => {
|
|
556
|
+
isDragging.value = false
|
|
557
|
+
document.documentElement.style.cursor = ''
|
|
558
|
+
updateFullSize()
|
|
559
|
+
document.removeEventListener('mousemove', onMove)
|
|
560
|
+
document.removeEventListener('mouseup', onUp)
|
|
561
|
+
document.removeEventListener('touchmove', onMove)
|
|
562
|
+
document.removeEventListener('touchend', onUp)
|
|
563
|
+
}
|
|
250
564
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
565
|
+
document.addEventListener('mousemove', onMove)
|
|
566
|
+
document.addEventListener('mouseup', onUp)
|
|
567
|
+
document.addEventListener('touchmove', onMove, { passive: false })
|
|
568
|
+
document.addEventListener('touchend', onUp)
|
|
255
569
|
}
|
|
256
570
|
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const side = Math.max(top, bottom)
|
|
265
|
-
if (top < side) topPanel.value?.resize(side)
|
|
266
|
-
if (bottom < side) bottomPanel.value?.resize(side)
|
|
571
|
+
function updateFullSize() {
|
|
572
|
+
const container = containerEl.value
|
|
573
|
+
if (!container) return
|
|
574
|
+
const rect = container.getBoundingClientRect()
|
|
575
|
+
const gutter = 40
|
|
576
|
+
isFullSize.value = (iframeWidth.value === null || iframeWidth.value >= rect.width - gutter - 2)
|
|
577
|
+
&& (iframeHeight.value === null || iframeHeight.value >= rect.height - gutter - 2)
|
|
267
578
|
}
|
|
268
579
|
|
|
269
|
-
function applyDeviceSize(device: Device | null | undefined) {
|
|
270
|
-
const el = containerEl.value
|
|
271
|
-
if (!el) return
|
|
272
580
|
|
|
581
|
+
function applyDeviceSize(device: Device | null | undefined) {
|
|
273
582
|
if (!device) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
topPanel.value?.resize(0)
|
|
278
|
-
bottomPanel.value?.resize(0)
|
|
279
|
-
}
|
|
583
|
+
iframeWidth.value = null
|
|
584
|
+
iframeHeight.value = null
|
|
585
|
+
updateFullSize()
|
|
280
586
|
return
|
|
281
587
|
}
|
|
282
588
|
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
const hPanelSpace = rect.width - handleSize * 2
|
|
288
|
-
const vPanelSpace = rect.height - handleSize * 2
|
|
589
|
+
const container = containerEl.value
|
|
590
|
+
if (!container) return
|
|
591
|
+
const rect = container.getBoundingClientRect()
|
|
592
|
+
const gutter = 40
|
|
289
593
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
leftPanel.value?.resize(hSide)
|
|
294
|
-
rightPanel.value?.resize(hSide)
|
|
295
|
-
topPanel.value?.resize(vSide)
|
|
296
|
-
bottomPanel.value?.resize(vSide)
|
|
594
|
+
iframeWidth.value = Math.min(device.width, rect.width - gutter)
|
|
595
|
+
iframeHeight.value = Math.min(device.height, rect.height - gutter)
|
|
596
|
+
updateFullSize()
|
|
297
597
|
}
|
|
298
598
|
|
|
299
599
|
watch(() => props.device, (device) => {
|
|
300
600
|
if (viewMode.value === 'source') return
|
|
301
|
-
|
|
601
|
+
// Only apply when a device is selected, not when cleared (drag start clears device)
|
|
602
|
+
if (device) applyDeviceSize(device)
|
|
302
603
|
})
|
|
303
604
|
|
|
304
605
|
watch(() => props.resetKey, () => {
|
|
@@ -313,6 +614,7 @@ watch(viewMode, async (mode) => {
|
|
|
313
614
|
})
|
|
314
615
|
|
|
315
616
|
let observer: ResizeObserver | null = null
|
|
617
|
+
let containerObserver: ResizeObserver | null = null
|
|
316
618
|
|
|
317
619
|
function forwardIframeKeys(iframe: HTMLIFrameElement) {
|
|
318
620
|
try {
|
|
@@ -327,15 +629,16 @@ function forwardIframeKeys(iframe: HTMLIFrameElement) {
|
|
|
327
629
|
metaKey: e.metaKey,
|
|
328
630
|
shiftKey: e.shiftKey,
|
|
329
631
|
altKey: e.altKey,
|
|
632
|
+
bubbles: true,
|
|
330
633
|
}))
|
|
331
634
|
})
|
|
332
635
|
} catch {}
|
|
333
636
|
}
|
|
334
637
|
|
|
335
638
|
onMounted(() => {
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
const rect =
|
|
639
|
+
const wrapper = wrapperEl.value
|
|
640
|
+
if (wrapper) {
|
|
641
|
+
const rect = wrapper.getBoundingClientRect()
|
|
339
642
|
panelWidth.value = Math.round(rect.width)
|
|
340
643
|
panelHeight.value = Math.round(rect.height)
|
|
341
644
|
observer = new ResizeObserver((entries) => {
|
|
@@ -343,25 +646,48 @@ onMounted(() => {
|
|
|
343
646
|
panelWidth.value = Math.round(entry.contentRect.width)
|
|
344
647
|
panelHeight.value = Math.round(entry.contentRect.height)
|
|
345
648
|
}
|
|
649
|
+
updateIframeContentHeight()
|
|
346
650
|
})
|
|
347
|
-
observer.observe(
|
|
651
|
+
observer.observe(wrapper)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const container = containerEl.value
|
|
655
|
+
if (container) {
|
|
656
|
+
const gutter = 40
|
|
657
|
+
const rect = container.getBoundingClientRect()
|
|
658
|
+
maxIframeWidth.value = Math.max(0, Math.round(rect.width - gutter))
|
|
659
|
+
maxIframeHeight.value = Math.max(0, Math.round(rect.height - gutter))
|
|
660
|
+
containerObserver = new ResizeObserver((entries) => {
|
|
661
|
+
for (const entry of entries) {
|
|
662
|
+
maxIframeWidth.value = Math.max(0, Math.round(entry.contentRect.width - gutter))
|
|
663
|
+
maxIframeHeight.value = Math.max(0, Math.round(entry.contentRect.height - gutter))
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
containerObserver.observe(container)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const el = iframeEl.value
|
|
670
|
+
if (el) {
|
|
348
671
|
el.addEventListener('load', () => forwardIframeKeys(el))
|
|
349
672
|
}
|
|
350
673
|
})
|
|
351
674
|
|
|
352
675
|
onUnmounted(() => {
|
|
353
676
|
observer?.disconnect()
|
|
677
|
+
containerObserver?.disconnect()
|
|
354
678
|
})
|
|
355
679
|
|
|
356
680
|
const bottomPanelOpen = ref(false)
|
|
357
681
|
const tabsPanelHeight = ref(40)
|
|
358
682
|
const activeTab = ref<string | undefined>(undefined)
|
|
359
683
|
|
|
684
|
+
const defaultTab = () => compatibilityDisabled.value ? 'stats' : 'compatibility'
|
|
685
|
+
|
|
360
686
|
function toggleBottomPanel() {
|
|
361
687
|
bottomPanelOpen.value = !bottomPanelOpen.value
|
|
362
688
|
if (bottomPanelOpen.value) {
|
|
363
|
-
tabsPanelHeight.value =
|
|
364
|
-
if (!activeTab.value) activeTab.value =
|
|
689
|
+
tabsPanelHeight.value = 300
|
|
690
|
+
if (!activeTab.value) activeTab.value = defaultTab()
|
|
365
691
|
} else {
|
|
366
692
|
tabsPanelHeight.value = 40
|
|
367
693
|
activeTab.value = undefined
|
|
@@ -378,38 +704,45 @@ function onTabClick(tab: string) {
|
|
|
378
704
|
activeTab.value = tab
|
|
379
705
|
if (!bottomPanelOpen.value) {
|
|
380
706
|
bottomPanelOpen.value = true
|
|
381
|
-
tabsPanelHeight.value =
|
|
707
|
+
tabsPanelHeight.value = 300
|
|
382
708
|
}
|
|
383
709
|
}
|
|
384
710
|
|
|
385
711
|
const tabsDragging = ref(false)
|
|
386
712
|
|
|
387
|
-
function onTabsDragStart(e: MouseEvent) {
|
|
713
|
+
function onTabsDragStart(e: MouseEvent | TouchEvent) {
|
|
388
714
|
e.preventDefault()
|
|
389
715
|
tabsDragging.value = true
|
|
390
|
-
const
|
|
716
|
+
const isTouch = e.type === 'touchstart'
|
|
717
|
+
const startY = isTouch ? (e as TouchEvent).touches[0].clientY : (e as MouseEvent).clientY
|
|
391
718
|
const startHeight = tabsPanelHeight.value
|
|
392
719
|
|
|
393
|
-
const
|
|
394
|
-
|
|
720
|
+
const rootEl = containerEl.value?.closest('.relative.h-full') as HTMLElement | null
|
|
721
|
+
const maxHeight = rootEl ? rootEl.getBoundingClientRect().height : Infinity
|
|
722
|
+
|
|
723
|
+
const onMove = (e: MouseEvent | TouchEvent) => {
|
|
724
|
+
const clientY = e.type === 'touchmove' ? (e as TouchEvent).touches[0].clientY : (e as MouseEvent).clientY
|
|
725
|
+
const newHeight = Math.max(40, Math.min(maxHeight, startHeight + startY - clientY))
|
|
395
726
|
tabsPanelHeight.value = newHeight
|
|
396
727
|
bottomPanelOpen.value = newHeight > 40
|
|
397
728
|
|
|
398
729
|
if (!bottomPanelOpen.value) {
|
|
399
730
|
activeTab.value = undefined
|
|
400
731
|
} else if (!activeTab.value) {
|
|
401
|
-
activeTab.value =
|
|
732
|
+
activeTab.value = defaultTab()
|
|
402
733
|
}
|
|
403
734
|
}
|
|
404
735
|
|
|
405
|
-
const
|
|
736
|
+
const onEnd = () => {
|
|
406
737
|
tabsDragging.value = false
|
|
407
|
-
document.removeEventListener('mousemove',
|
|
408
|
-
document.removeEventListener('mouseup',
|
|
738
|
+
document.removeEventListener('mousemove', onMove)
|
|
739
|
+
document.removeEventListener('mouseup', onEnd)
|
|
740
|
+
document.removeEventListener('touchmove', onMove)
|
|
741
|
+
document.removeEventListener('touchend', onEnd)
|
|
409
742
|
}
|
|
410
743
|
|
|
411
|
-
document.addEventListener('mousemove',
|
|
412
|
-
document.addEventListener('mouseup',
|
|
744
|
+
document.addEventListener(isTouch ? 'touchmove' : 'mousemove', onMove)
|
|
745
|
+
document.addEventListener(isTouch ? 'touchend' : 'mouseup', onEnd)
|
|
413
746
|
}
|
|
414
747
|
|
|
415
748
|
const stripeBg = {
|
|
@@ -420,187 +753,294 @@ const stripeBg = {
|
|
|
420
753
|
</script>
|
|
421
754
|
|
|
422
755
|
<template>
|
|
423
|
-
<div class="
|
|
424
|
-
<div class="
|
|
756
|
+
<div class="relative h-full">
|
|
757
|
+
<div class="absolute inset-0 bottom-10 overflow-hidden">
|
|
425
758
|
<!-- Source code view -->
|
|
426
759
|
<div v-show="viewMode === 'source'" class="absolute inset-0 min-w-0 overflow-hidden">
|
|
427
760
|
<div class="absolute top-3 left-6 z-10">
|
|
428
761
|
<DropdownMenu :modal="false">
|
|
429
|
-
<DropdownMenuTrigger class="inline-flex items-center gap-1 rounded-md bg-white/10 px-2.5 h-7 text-xs font-medium text-gray-300 hover:bg-
|
|
762
|
+
<DropdownMenuTrigger class="inline-flex items-center gap-1 rounded-md bg-[#27212e]/80 dark:bg-gray-950/80 backdrop-blur-md border border-white/10 px-2.5 h-7 text-xs font-medium text-gray-300 hover:bg-[#27212e] dark:hover:bg-gray-950 transition-colors">
|
|
430
763
|
{{ sourceView === 'compiled' ? 'HTML' : sourceView === 'vue' ? 'Source' : 'Plaintext' }}
|
|
431
764
|
<ChevronDown class="size-3 opacity-50" />
|
|
432
765
|
</DropdownMenuTrigger>
|
|
433
|
-
<DropdownMenuContent align="start" class="min-w-
|
|
434
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
435
|
-
<Check v-if="sourceView === 'vue'" class="size-3
|
|
436
|
-
<span :class="sourceView === 'vue' ? '' : 'pl-5
|
|
766
|
+
<DropdownMenuContent align="start" class="min-w-32 bg-[#27212e]/80 dark:bg-gray-950/80 backdrop-blur-md border-white/10">
|
|
767
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'vue'">
|
|
768
|
+
<Check v-if="sourceView === 'vue'" class="size-3 text-gray-200" />
|
|
769
|
+
<span :class="[sourceView === 'vue' ? 'text-gray-200' : 'pl-5']">Source</span>
|
|
437
770
|
</DropdownMenuItem>
|
|
438
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
439
|
-
<Check v-if="sourceView === 'compiled'" class="size-3
|
|
440
|
-
<span :class="sourceView === 'compiled' ? '' : 'pl-5
|
|
771
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'compiled'">
|
|
772
|
+
<Check v-if="sourceView === 'compiled'" class="size-3 text-gray-200" />
|
|
773
|
+
<span :class="[sourceView === 'compiled' ? 'text-gray-200' : 'pl-5']">HTML</span>
|
|
441
774
|
</DropdownMenuItem>
|
|
442
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
443
|
-
<Check v-if="sourceView === 'plaintext'" class="size-3
|
|
444
|
-
<span :class="sourceView === 'plaintext' ? '' : 'pl-5
|
|
775
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'plaintext'">
|
|
776
|
+
<Check v-if="sourceView === 'plaintext'" class="size-3 text-gray-200" />
|
|
777
|
+
<span :class="[sourceView === 'plaintext' ? 'text-gray-200' : 'pl-5']">Plaintext</span>
|
|
445
778
|
</DropdownMenuItem>
|
|
446
779
|
</DropdownMenuContent>
|
|
447
780
|
</DropdownMenu>
|
|
448
781
|
</div>
|
|
449
782
|
<button
|
|
450
|
-
class="absolute top-3 right-
|
|
783
|
+
class="absolute top-3 right-[26px] z-10 inline-flex items-center justify-center rounded-md size-7 bg-[#27212e]/80 dark:bg-gray-950/80 backdrop-blur-md border border-white/10 hover:bg-[#27212e] dark:hover:bg-gray-950 group disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
451
784
|
:disabled="copied"
|
|
452
785
|
@click="copySource"
|
|
453
786
|
>
|
|
454
|
-
<svg v-if="!copied" class="size-5 text-gray-400 group-hover:text-gray-300" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
455
|
-
<svg v-else class="size-5 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
|
787
|
+
<svg v-if="!copied" class="size-3.5 text-gray-400 group-hover:text-gray-300" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
788
|
+
<svg v-else class="size-3.5 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
|
456
789
|
</button>
|
|
457
|
-
<div
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
790
|
+
<ScrollArea v-show="sourceView === 'compiled'" class="h-full [&_[data-slot=scroll-area-viewport]>div]:flex [&_[data-slot=scroll-area-viewport]>div]:flex-col [&_[data-slot=scroll-area-viewport]>div]:min-h-full">
|
|
791
|
+
<div
|
|
792
|
+
ref="compiledSourceEl"
|
|
793
|
+
class="flex-1 bg-[#27212e] dark:bg-gray-950 shiki-line-numbers [&_pre]:p-6 [&_pre]:pt-14 [&_pre]:text-base [&_pre]:leading-6 [&_pre]:min-h-full dark:[&_pre]:bg-gray-950!"
|
|
794
|
+
v-html="sourceHtml"
|
|
795
|
+
/>
|
|
796
|
+
<ScrollBar orientation="horizontal" />
|
|
797
|
+
</ScrollArea>
|
|
798
|
+
<ScrollArea v-show="sourceView === 'vue'" class="h-full [&_[data-slot=scroll-area-viewport]>div]:flex [&_[data-slot=scroll-area-viewport]>div]:flex-col [&_[data-slot=scroll-area-viewport]>div]:min-h-full">
|
|
799
|
+
<div
|
|
800
|
+
ref="vueSourceEl"
|
|
801
|
+
class="flex-1 bg-[#27212e] dark:bg-gray-950 shiki-line-numbers [&_pre]:p-6 [&_pre]:pt-14 [&_pre]:text-base [&_pre]:leading-6 [&_pre]:min-h-full dark:[&_pre]:bg-gray-950!"
|
|
802
|
+
v-html="vueSourceHtml"
|
|
803
|
+
/>
|
|
804
|
+
<ScrollBar orientation="horizontal" />
|
|
805
|
+
</ScrollArea>
|
|
806
|
+
<ScrollArea v-show="sourceView === 'plaintext'" class="h-full [&_[data-slot=scroll-area-viewport]>div]:flex [&_[data-slot=scroll-area-viewport]>div]:flex-col [&_[data-slot=scroll-area-viewport]>div]:min-h-full">
|
|
807
|
+
<pre
|
|
808
|
+
ref="plaintextEl"
|
|
809
|
+
class="p-6 pt-14 text-sm leading-6 flex-1 text-gray-300 bg-[#27212e] dark:bg-gray-950 whitespace-pre-wrap break-words"
|
|
810
|
+
>{{ plaintextContent }}</pre>
|
|
811
|
+
</ScrollArea>
|
|
472
812
|
</div>
|
|
473
813
|
|
|
814
|
+
<!-- Blocks iframe from stealing pointer events while dragging tabs -->
|
|
815
|
+
<div v-if="tabsDragging" class="fixed inset-0 z-50" />
|
|
816
|
+
|
|
474
817
|
<!-- Preview view -->
|
|
475
818
|
<div v-show="viewMode !== 'source'" class="absolute inset-0">
|
|
476
819
|
<div class="relative h-full opacity-5" :style="stripeBg" />
|
|
477
820
|
</div>
|
|
478
821
|
|
|
479
|
-
<div v-show="viewMode !== 'source'" ref="containerEl" class="absolute inset-0 z-10 flex
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
<
|
|
501
|
-
</
|
|
822
|
+
<div v-show="viewMode !== 'source'" ref="containerEl" class="absolute inset-0 z-10 flex items-center justify-center">
|
|
823
|
+
<!-- Blocks iframe from stealing pointer events while dragging -->
|
|
824
|
+
<div v-if="isDragging" class="absolute inset-0 z-20" />
|
|
825
|
+
<div
|
|
826
|
+
class="relative"
|
|
827
|
+
:style="{
|
|
828
|
+
width: iframeWidth != null ? `${iframeWidth + 40}px` : '100%',
|
|
829
|
+
height: iframeHeight != null ? `${iframeHeight + 40}px` : '100%',
|
|
830
|
+
transition: isDragging ? 'none' : 'width 0.2s ease, height 0.2s ease',
|
|
831
|
+
}"
|
|
832
|
+
>
|
|
833
|
+
<!-- Top handle -->
|
|
834
|
+
<div class="group hidden min-[430px]:flex absolute top-0 left-5 right-5 h-5 items-center justify-center cursor-ns-resize" @mousedown="onEdgeDrag($event, 'top')" @touchstart.prevent="onEdgeDrag($event, 'top')">
|
|
835
|
+
<div class="h-1 w-12 rounded-full bg-gray-300 dark:bg-gray-600 group-hover:bg-gray-400 group-active:bg-gray-500 dark:group-hover:bg-gray-500 dark:group-active:bg-gray-400 transition-colors" />
|
|
836
|
+
</div>
|
|
837
|
+
<!-- Bottom handle -->
|
|
838
|
+
<div class="group hidden min-[430px]:flex absolute bottom-0 left-5 right-5 h-5 items-center justify-center cursor-ns-resize" @mousedown="onEdgeDrag($event, 'bottom')" @touchstart.prevent="onEdgeDrag($event, 'bottom')">
|
|
839
|
+
<div class="h-1 w-12 rounded-full bg-gray-300 dark:bg-gray-600 group-hover:bg-gray-400 group-active:bg-gray-500 dark:group-hover:bg-gray-500 dark:group-active:bg-gray-400 transition-colors" />
|
|
840
|
+
</div>
|
|
841
|
+
<!-- Left handle -->
|
|
842
|
+
<div class="group hidden min-[430px]:flex absolute left-0 top-5 bottom-5 w-5 items-center justify-center cursor-ew-resize" @mousedown="onEdgeDrag($event, 'left')" @touchstart.prevent="onEdgeDrag($event, 'left')">
|
|
843
|
+
<div class="w-1 h-12 rounded-full bg-gray-300 dark:bg-gray-600 group-hover:bg-gray-400 group-active:bg-gray-500 dark:group-hover:bg-gray-500 dark:group-active:bg-gray-400 transition-colors" />
|
|
844
|
+
</div>
|
|
845
|
+
<!-- Right handle -->
|
|
846
|
+
<div class="group hidden min-[430px]:flex absolute right-0 top-5 bottom-5 w-5 items-center justify-center cursor-ew-resize" @mousedown="onEdgeDrag($event, 'right')" @touchstart.prevent="onEdgeDrag($event, 'right')">
|
|
847
|
+
<div class="w-1 h-12 rounded-full bg-gray-300 dark:bg-gray-600 group-hover:bg-gray-400 group-active:bg-gray-500 dark:group-hover:bg-gray-500 dark:group-active:bg-gray-400 transition-colors" />
|
|
848
|
+
</div>
|
|
849
|
+
<!-- Iframe -->
|
|
850
|
+
<div ref="wrapperEl" class="absolute inset-0 min-[430px]:inset-5 border border-gray-200 dark:border-gray-800">
|
|
851
|
+
<ScrollArea class="h-full w-full bg-white dark:bg-gray-950">
|
|
852
|
+
<iframe
|
|
853
|
+
ref="iframeEl"
|
|
854
|
+
:srcdoc="srcdoc"
|
|
855
|
+
@load="onIframeLoad"
|
|
856
|
+
class="w-full border-0 bg-white dark:bg-gray-950"
|
|
857
|
+
:style="{ height: iframeContentHeight ? `${iframeContentHeight}px` : '100%' }"
|
|
858
|
+
/>
|
|
859
|
+
</ScrollArea>
|
|
860
|
+
</div>
|
|
502
861
|
</div>
|
|
503
862
|
</div>
|
|
504
863
|
</div>
|
|
505
864
|
|
|
506
|
-
<!-- Tabs panel (
|
|
865
|
+
<!-- Tabs panel (overlay) -->
|
|
507
866
|
<div
|
|
508
|
-
class="
|
|
509
|
-
:class="
|
|
867
|
+
class="absolute bottom-0 left-0 right-0 z-20 overflow-hidden border-t border-gray-200 dark:border-gray-800/50"
|
|
868
|
+
:class="[
|
|
869
|
+
!tabsDragging ? 'transition-[height] duration-200 ease-in-out' : '',
|
|
870
|
+
'bg-white dark:bg-gray-950',
|
|
871
|
+
]"
|
|
510
872
|
:style="{ height: `${tabsPanelHeight}px` }"
|
|
511
873
|
>
|
|
512
874
|
<div
|
|
513
|
-
class="relative h-
|
|
875
|
+
class="relative h-0 cursor-row-resize before:absolute before:top-0 before:left-0 before:right-0 before:h-3.25 before:content-['']"
|
|
514
876
|
@mousedown="onTabsDragStart"
|
|
877
|
+
@touchstart.prevent="onTabsDragStart"
|
|
515
878
|
/>
|
|
516
879
|
<Tabs :model-value="activeTab" class="flex flex-col min-h-0 h-full">
|
|
517
|
-
<div class="flex items-center justify-between min-h-10
|
|
880
|
+
<div class="flex items-center justify-between min-h-10 pl-2 pr-3 shrink-0" :class="bottomPanelOpen ? 'border-b' : ''">
|
|
518
881
|
<TabsList class="h-full bg-transparent! rounded-none! p-0 gap-1">
|
|
519
|
-
<TabsTrigger value="compatibility" class="text-xs px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('compatibility')">
|
|
520
|
-
|
|
882
|
+
<TabsTrigger v-if="!compatibilityDisabled" value="compatibility" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('compatibility')">
|
|
883
|
+
Checks
|
|
521
884
|
</TabsTrigger>
|
|
522
|
-
<TabsTrigger value="
|
|
523
|
-
Linter
|
|
524
|
-
</TabsTrigger>
|
|
525
|
-
<TabsTrigger value="stats" class="text-xs px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent" @click="onTabClick('stats')">
|
|
885
|
+
<TabsTrigger value="stats" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('stats')">
|
|
526
886
|
Stats
|
|
527
887
|
</TabsTrigger>
|
|
888
|
+
<TabsTrigger value="test" class="text-xs font-normal px-3 h-full rounded-none! border-0! shadow-none! border-b! border-transparent select-none data-[state=active]:border-gray-400 data-[state=active]:dark:border-gray-600 data-[state=active]:bg-transparent data-[state=inactive]:bg-transparent dark:bg-transparent! dark:hover:bg-transparent!" @click="onTabClick('test')">
|
|
889
|
+
Test
|
|
890
|
+
</TabsTrigger>
|
|
528
891
|
</TabsList>
|
|
529
892
|
<Button variant="ghost" size="icon" class="h-7 w-7 hover:bg-transparent!" @click="toggleBottomPanel">
|
|
530
|
-
<ChevronUp v-if="!bottomPanelOpen" class="size-4" />
|
|
531
|
-
<ChevronDown v-else class="size-4" />
|
|
893
|
+
<ChevronUp v-if="!bottomPanelOpen" class="size-4 dark:text-gray-400" :stroke-width="1" />
|
|
894
|
+
<ChevronDown v-else class="size-4 dark:text-gray-400" :stroke-width="1" />
|
|
532
895
|
</Button>
|
|
533
896
|
</div>
|
|
534
|
-
<div class="flex-1
|
|
535
|
-
<TabsContent value="compatibility" class="mt-0">
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
897
|
+
<div class="flex-1 min-h-0">
|
|
898
|
+
<TabsContent value="compatibility" class="mt-0 h-full flex flex-col"><div v-if="!compatibilityLoading && !compatibilityError && compatibilityIssues.length > 0" class="flex gap-1 pl-3 pr-4 py-2 border-b border-gray-200 dark:border-white/10 shrink-0">
|
|
899
|
+
<button
|
|
900
|
+
v-for="cat in activeCompatibilityCategories"
|
|
901
|
+
:key="cat"
|
|
902
|
+
class="px-2 py-0.5 text-[11px] rounded-full cursor-default transition-colors"
|
|
903
|
+
:class="compatibilityCategory === cat
|
|
904
|
+
? 'bg-gray-900 text-white dark:bg-gray-600 dark:text-gray-100'
|
|
905
|
+
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10'"
|
|
906
|
+
@click="compatibilityCategory = cat"
|
|
543
907
|
>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
908
|
+
{{ cat === 'css' ? 'CSS' : cat === 'html' ? 'HTML' : cat.charAt(0).toUpperCase() + cat.slice(1) }}
|
|
909
|
+
<span class="ml-0.5 tabular-nums">{{ compatibilityIssues.filter(i => i.category === cat).length }}</span>
|
|
910
|
+
</button>
|
|
911
|
+
</div>
|
|
912
|
+
<ScrollArea class="h-full flex-1 min-h-0 pl-5">
|
|
913
|
+
<p v-if="compatibilityLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Running checks...</p>
|
|
914
|
+
<p v-else-if="compatibilityError" class="pr-4 py-3 text-xs text-red-500 dark:text-red-400">{{ compatibilityError }}</p>
|
|
915
|
+
<p v-else-if="compatibilityIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No issues found.</p>
|
|
916
|
+
<ul v-else class="text-xs divide-y">
|
|
917
|
+
<li
|
|
918
|
+
v-for="(issue, i) in filteredCompatibilityIssues"
|
|
919
|
+
:key="i"
|
|
920
|
+
class="pr-4 py-2"
|
|
921
|
+
>
|
|
922
|
+
<div class="flex items-center justify-between gap-4">
|
|
923
|
+
<div>
|
|
924
|
+
<a v-if="issue.url" :href="issue.url" target="_blank" rel="noopener" class="font-medium hover:underline" :class="issueColorClass(issue)">
|
|
925
|
+
{{ issue.title }}
|
|
926
|
+
</a>
|
|
927
|
+
<span v-else class="font-medium" :class="issueColorClass(issue)">
|
|
928
|
+
{{ issue.title }}
|
|
929
|
+
</span>
|
|
930
|
+
<div class="text-gray-500 dark:text-gray-400 mt-0.5">
|
|
931
|
+
<template v-if="issue.kind === 'lint'">
|
|
932
|
+
<template v-for="(seg, j) in messageSegments(issue.message)" :key="j">
|
|
933
|
+
<code v-if="seg.code" class="px-1 py-0.5 rounded bg-gray-100 dark:bg-white/10 font-mono text-[11px]">{{ seg.text }}</code>
|
|
934
|
+
<template v-else>{{ seg.text }}</template>
|
|
935
|
+
</template>
|
|
936
|
+
</template>
|
|
937
|
+
<template v-else>
|
|
938
|
+
{{ supportPrefix(issue) }}
|
|
939
|
+
<template v-if="(issue.affectedClients?.length ?? 0) <= 4 || expandedIssueKeys.has(issueKey(issue, i))">
|
|
940
|
+
{{ (issue.affectedClients ?? []).join(', ') }}
|
|
941
|
+
</template>
|
|
942
|
+
<template v-else>
|
|
943
|
+
{{ issue.affectedClients!.slice(0, 4).join(', ') }}
|
|
944
|
+
<button class="underline cursor-pointer hover:text-gray-700 dark:hover:text-gray-200" @click="expandedIssueKeys.add(issueKey(issue, i)); expandedIssueKeys = new Set(expandedIssueKeys)">
|
|
945
|
+
+ {{ issue.affectedClients!.length - 4 }} others
|
|
946
|
+
</button>
|
|
947
|
+
</template>
|
|
948
|
+
</template>
|
|
555
949
|
</div>
|
|
556
950
|
</div>
|
|
951
|
+
<button v-if="issue.line" class="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer tabular-nums shrink-0" @click="openInEditor(issue.file, issue.line!)">{{ isCurrentFile(issue) ? `L${issue.line}` : `${componentName(issue.file)}:${issue.line}` }}</button>
|
|
557
952
|
</div>
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
</ul>
|
|
953
|
+
</li>
|
|
954
|
+
</ul>
|
|
955
|
+
</ScrollArea>
|
|
562
956
|
</TabsContent>
|
|
563
|
-
<TabsContent value="
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
<
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
<span class="font-medium" :class="
|
|
575
|
-
{{ issue.title }}
|
|
576
|
-
</span>
|
|
577
|
-
<div class="text-gray-500 dark:text-gray-400 mt-0.5">{{ issue.message }}</div>
|
|
957
|
+
<TabsContent value="stats" class="mt-0 h-full">
|
|
958
|
+
<ScrollArea class="h-full pl-5">
|
|
959
|
+
<p v-if="statsLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Loading stats...</p>
|
|
960
|
+
<p v-else-if="!stats" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No stats available.</p>
|
|
961
|
+
<ul v-else class="text-xs divide-y divide-gray-200 dark:divide-white/10">
|
|
962
|
+
<li class="pr-4 py-2">
|
|
963
|
+
<div class="flex items-center justify-between gap-4">
|
|
964
|
+
<div>
|
|
965
|
+
<span class="font-medium" :class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'">Size</span>
|
|
966
|
+
<div class="text-gray-500 dark:text-gray-400 mt-0.5">Compiled HTML size. Gmail clips emails larger than ~100KB.</div>
|
|
967
|
+
</div>
|
|
968
|
+
<span class="font-medium tabular-nums shrink-0" :class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'">{{ stats.size.formatted }}</span>
|
|
578
969
|
</div>
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
970
|
+
</li>
|
|
971
|
+
<li class="pr-4 py-2">
|
|
972
|
+
<div class="flex items-center justify-between gap-4">
|
|
973
|
+
<div>
|
|
974
|
+
<span class="font-medium text-gray-900 dark:text-gray-300">Images</span>
|
|
975
|
+
<div class="text-gray-500 dark:text-gray-400 mt-0.5">Total from <img> tags and CSS background images.</div>
|
|
976
|
+
</div>
|
|
977
|
+
<span class="font-medium tabular-nums shrink-0">{{ stats.images }}</span>
|
|
978
|
+
</div>
|
|
979
|
+
</li>
|
|
980
|
+
<li class="pr-4 py-2">
|
|
981
|
+
<div class="flex items-center justify-between gap-4">
|
|
982
|
+
<div>
|
|
983
|
+
<span class="font-medium text-gray-900 dark:text-gray-300">Links</span>
|
|
984
|
+
<div class="text-gray-500 dark:text-gray-400 mt-0.5">Total <a> tags with an href attribute.</div>
|
|
985
|
+
</div>
|
|
986
|
+
<span class="font-medium tabular-nums shrink-0">{{ stats.links }}</span>
|
|
987
|
+
</div>
|
|
988
|
+
</li>
|
|
989
|
+
</ul>
|
|
990
|
+
</ScrollArea>
|
|
583
991
|
</TabsContent>
|
|
584
|
-
<TabsContent value="
|
|
585
|
-
<
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
992
|
+
<TabsContent value="test" class="mt-0 h-full">
|
|
993
|
+
<ScrollArea class="h-full pl-5">
|
|
994
|
+
<div class="pr-4 py-3 max-w-md">
|
|
995
|
+
<div class="space-y-2">
|
|
996
|
+
<div class="flex items-center gap-2">
|
|
997
|
+
<label for="email-to" class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0 cursor-pointer">To</label>
|
|
998
|
+
<TagsInput v-model="emailTo" delimiter=" " add-on-paste add-on-blur class="flex-1 min-h-7 gap-1 px-2 py-1">
|
|
999
|
+
<TagsInputItem v-for="item in emailTo" :key="item" :value="item" class="h-5 text-xs rounded">
|
|
1000
|
+
<TagsInputItemText class="px-1.5 py-0 text-xs" />
|
|
1001
|
+
<TagsInputItemDelete class="size-3.5" />
|
|
1002
|
+
</TagsInputItem>
|
|
1003
|
+
<TagsInputInput id="email-to" class="text-xs min-h-5 px-0.5" placeholder="Add emails..." />
|
|
1004
|
+
</TagsInput>
|
|
1005
|
+
</div>
|
|
1006
|
+
<div class="flex items-center gap-2">
|
|
1007
|
+
<label for="email-subject" class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0 cursor-pointer">Subject</label>
|
|
1008
|
+
<div class="flex-1 flex items-center gap-3">
|
|
1009
|
+
<Input id="email-subject" v-model="emailSubject" :placeholder="String(route.params.template)" class="flex-1 h-7 text-xs! px-2" />
|
|
1010
|
+
<label class="flex items-center gap-1.5 cursor-pointer select-none shrink-0">
|
|
1011
|
+
<Checkbox v-model="emailPreventThreading" :default-checked="true" class="size-3.5" />
|
|
1012
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">Prevent threading</span>
|
|
1013
|
+
</label>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div class="flex items-center gap-3 mt-3">
|
|
1018
|
+
<Button
|
|
1019
|
+
size="sm"
|
|
1020
|
+
class="h-7 text-xs px-3"
|
|
1021
|
+
:disabled="!emailTo.length || emailSending"
|
|
1022
|
+
@click="sendTestEmail"
|
|
1023
|
+
>
|
|
1024
|
+
<svg v-if="emailSending" class="size-3.5 animate-spin [animation-duration:0.6s]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /></svg>
|
|
1025
|
+
{{ emailSending ? 'Sending' : 'Send' }}
|
|
1026
|
+
</Button>
|
|
1027
|
+
</div>
|
|
1028
|
+
<div v-if="emailResult" class="mt-2">
|
|
1029
|
+
<p class="text-xs" :class="emailResult.success ? 'text-gray-950 dark:text-white' : 'text-red-600'">
|
|
1030
|
+
{{ emailResult.message }}
|
|
1031
|
+
<a
|
|
1032
|
+
v-if="emailResult.previewUrl"
|
|
1033
|
+
:href="emailResult.previewUrl"
|
|
1034
|
+
target="_blank"
|
|
1035
|
+
rel="noopener"
|
|
1036
|
+
class="text-gray-500 dark:text-gray-400 hover:underline"
|
|
1037
|
+
>
|
|
1038
|
+
(view)
|
|
1039
|
+
</a>
|
|
1040
|
+
</p>
|
|
1041
|
+
</div>
|
|
602
1042
|
</div>
|
|
603
|
-
</
|
|
1043
|
+
</ScrollArea>
|
|
604
1044
|
</TabsContent>
|
|
605
1045
|
</div>
|
|
606
1046
|
</Tabs>
|