@maizzle/framework 6.0.0-rc.0 → 6.0.0-rc.10
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/bin/maizzle.mjs +1 -1
- package/dist/_virtual/_rolldown/runtime.mjs +32 -0
- package/dist/build.mjs +29 -9
- package/dist/build.mjs.map +1 -1
- package/dist/components/Body.vue +111 -0
- package/dist/components/Button.vue +68 -14
- package/dist/components/CodeBlock.vue +68 -0
- package/dist/components/CodeInline.vue +49 -0
- package/dist/components/Column.vue +86 -0
- package/dist/components/Container.vue +48 -0
- package/dist/components/Divider.vue +28 -0
- package/dist/components/Head.vue +30 -0
- package/dist/components/Heading.vue +28 -0
- package/dist/components/Html.vue +104 -0
- package/dist/components/Image.vue +70 -0
- package/dist/components/Layout.vue +93 -0
- package/dist/components/Link.vue +26 -0
- package/dist/components/Markdown.vue +89 -0
- package/dist/components/Outlook.vue +36 -0
- package/dist/components/Overlap.vue +80 -0
- package/dist/components/Preheader.vue +20 -0
- package/dist/components/Row.vue +91 -0
- package/dist/components/Section.vue +83 -0
- package/dist/components/Spacer.vue +50 -7
- package/dist/components/Text.vue +29 -0
- package/dist/components/Vml.vue +165 -13
- package/dist/composables/renderContext.d.mts +5 -0
- package/dist/composables/renderContext.d.mts.map +1 -1
- package/dist/composables/renderContext.mjs.map +1 -1
- package/dist/composables/usePreviewText.d.mts +24 -0
- package/dist/composables/usePreviewText.d.mts.map +1 -0
- package/dist/composables/usePreviewText.mjs +29 -0
- package/dist/composables/usePreviewText.mjs.map +1 -0
- package/dist/config/index.mjs +25 -1
- package/dist/config/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +3 -1
- package/dist/node_modules/picomatch/index.mjs +13 -0
- package/dist/node_modules/picomatch/index.mjs.map +1 -0
- package/dist/node_modules/picomatch/lib/constants.mjs +174 -0
- package/dist/node_modules/picomatch/lib/constants.mjs.map +1 -0
- package/dist/node_modules/picomatch/lib/parse.mjs +1067 -0
- package/dist/node_modules/picomatch/lib/parse.mjs.map +1 -0
- package/dist/node_modules/picomatch/lib/picomatch.mjs +304 -0
- package/dist/node_modules/picomatch/lib/picomatch.mjs.map +1 -0
- package/dist/node_modules/picomatch/lib/scan.mjs +296 -0
- package/dist/node_modules/picomatch/lib/scan.mjs.map +1 -0
- package/dist/node_modules/picomatch/lib/utils.mjs +53 -0
- package/dist/node_modules/picomatch/lib/utils.mjs.map +1 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +24 -7
- package/dist/plugin.mjs.map +1 -1
- package/dist/plugins/postcss/tailwindCleanup.d.mts.map +1 -1
- package/dist/plugins/postcss/tailwindCleanup.mjs +44 -13
- package/dist/plugins/postcss/tailwindCleanup.mjs.map +1 -1
- package/dist/render/createRenderer.d.mts +10 -3
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +147 -10
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/render/index.mjs +9 -3
- package/dist/render/index.mjs.map +1 -1
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +159 -63
- package/dist/serve.mjs.map +1 -1
- package/dist/server/compatibility.d.mts +1 -2
- package/dist/server/compatibility.d.mts.map +1 -1
- package/dist/server/compatibility.mjs +30 -16
- package/dist/server/compatibility.mjs.map +1 -1
- package/dist/server/email.d.mts +17 -0
- package/dist/server/email.d.mts.map +1 -0
- package/dist/server/email.mjs +41 -0
- package/dist/server/email.mjs.map +1 -0
- package/dist/server/linter.d.mts +1 -2
- package/dist/server/linter.d.mts.map +1 -1
- package/dist/server/linter.mjs +60 -71
- package/dist/server/linter.mjs.map +1 -1
- package/dist/server/ui/App.vue +205 -69
- 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/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/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/main.css +20 -20
- package/dist/server/ui/pages/Home.vue +12 -5
- package/dist/server/ui/pages/Preview.vue +506 -216
- package/dist/transformers/entities.d.mts.map +1 -1
- package/dist/transformers/entities.mjs +3 -0
- package/dist/transformers/entities.mjs.map +1 -1
- package/dist/transformers/filters/defaults.d.mts +6 -0
- package/dist/transformers/filters/defaults.d.mts.map +1 -0
- package/dist/transformers/filters/defaults.mjs +78 -0
- package/dist/transformers/filters/defaults.mjs.map +1 -0
- package/dist/transformers/filters/index.d.mts +22 -0
- package/dist/transformers/filters/index.d.mts.map +1 -0
- package/dist/transformers/filters/index.mjs +67 -0
- package/dist/transformers/filters/index.mjs.map +1 -0
- package/dist/transformers/index.d.mts +11 -9
- package/dist/transformers/index.d.mts.map +1 -1
- package/dist/transformers/index.mjs +19 -11
- package/dist/transformers/index.mjs.map +1 -1
- package/dist/transformers/inlineCSS.d.mts +1 -14
- package/dist/transformers/inlineCSS.d.mts.map +1 -1
- package/dist/transformers/inlineCSS.mjs +25 -34
- package/dist/transformers/inlineCSS.mjs.map +1 -1
- package/dist/transformers/purgeCSS.d.mts.map +1 -1
- package/dist/transformers/purgeCSS.mjs +67 -1
- package/dist/transformers/purgeCSS.mjs.map +1 -1
- package/dist/transformers/sixHex.d.mts +16 -0
- package/dist/transformers/sixHex.d.mts.map +1 -0
- package/dist/transformers/sixHex.mjs +30 -0
- package/dist/transformers/sixHex.mjs.map +1 -0
- package/dist/transformers/tailwindcss.d.mts +6 -2
- package/dist/transformers/tailwindcss.d.mts.map +1 -1
- package/dist/transformers/tailwindcss.mjs +52 -28
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/types/config.d.mts +459 -17
- package/dist/types/config.d.mts.map +1 -1
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/ast/serializer.d.mts +3 -2
- package/dist/utils/ast/serializer.d.mts.map +1 -1
- package/dist/utils/ast/serializer.mjs +24 -0
- package/dist/utils/ast/serializer.mjs.map +1 -1
- package/dist/utils/detect.d.mts +5 -0
- package/dist/utils/detect.d.mts.map +1 -0
- package/dist/utils/detect.mjs +11 -0
- package/dist/utils/detect.mjs.map +1 -0
- package/node_modules/@clack/core/CHANGELOG.md +87 -4
- package/node_modules/@clack/core/README.md +1 -1
- package/node_modules/@clack/core/dist/index.d.mts +186 -48
- package/node_modules/@clack/core/dist/index.mjs +10 -14
- package/node_modules/@clack/core/dist/index.mjs.map +1 -1
- package/node_modules/@clack/core/package.json +7 -9
- package/node_modules/@clack/prompts/CHANGELOG.md +171 -7
- package/node_modules/@clack/prompts/README.md +66 -3
- package/node_modules/@clack/prompts/dist/index.d.mts +302 -76
- package/node_modules/@clack/prompts/dist/index.mjs +134 -84
- package/node_modules/@clack/prompts/dist/index.mjs.map +1 -1
- package/node_modules/@clack/prompts/package.json +14 -10
- package/node_modules/citty/LICENSE +0 -15
- package/node_modules/citty/README.md +166 -69
- package/node_modules/citty/dist/index.d.mts +88 -56
- package/node_modules/citty/dist/index.mjs +399 -437
- package/node_modules/citty/package.json +28 -35
- package/node_modules/giget/README.md +59 -11
- package/node_modules/giget/dist/THIRD-PARTY-LICENSES.md +205 -0
- package/node_modules/giget/dist/_chunks/giget.mjs +508 -0
- package/node_modules/giget/dist/_chunks/libs/citty.mjs +269 -0
- package/node_modules/giget/dist/_chunks/libs/nypm.d.mts +1 -0
- package/node_modules/giget/dist/_chunks/libs/nypm.mjs +669 -0
- package/node_modules/giget/dist/_chunks/libs/tar.mjs +2931 -0
- package/node_modules/giget/dist/_chunks/rolldown-runtime.mjs +14 -0
- package/node_modules/giget/dist/cli.d.mts +1 -0
- package/node_modules/giget/dist/cli.mjs +89 -111
- package/node_modules/giget/dist/index.d.mts +46 -35
- package/node_modules/giget/dist/index.mjs +2 -22
- package/node_modules/giget/package.json +32 -45
- package/node_modules/maizzle/README.md +140 -0
- package/node_modules/maizzle/bin/maizzle.mjs +5 -0
- package/node_modules/maizzle/dist/commands/new.d.mts +7 -0
- package/node_modules/maizzle/dist/commands/new.mjs +278 -0
- package/node_modules/{@maizzle/cli → maizzle}/dist/index.d.mts +1 -1
- package/node_modules/maizzle/dist/index.mjs +44 -0
- package/node_modules/{commander → maizzle/node_modules/commander}/Readme.md +94 -67
- package/node_modules/{commander → maizzle/node_modules/commander}/lib/argument.js +5 -4
- package/node_modules/{commander → maizzle/node_modules/commander}/lib/command.js +154 -39
- package/node_modules/{commander → maizzle/node_modules/commander}/lib/help.js +77 -39
- package/node_modules/{commander → maizzle/node_modules/commander}/lib/option.js +16 -3
- package/node_modules/{commander → maizzle/node_modules/commander}/package-support.json +4 -1
- package/node_modules/{commander → maizzle/node_modules/commander}/package.json +8 -8
- package/node_modules/{commander → maizzle/node_modules/commander}/typings/index.d.ts +71 -3
- package/node_modules/{@maizzle/cli → maizzle}/package.json +13 -11
- package/node_modules/tinyexec/README.md +49 -3
- package/node_modules/tinyexec/dist/main.d.mts +25 -14
- package/node_modules/tinyexec/dist/main.mjs +148 -100
- package/node_modules/tinyexec/package.json +9 -8
- package/package.json +9 -4
- 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/node_modules/@clack/core/dist/index.cjs +0 -15
- package/node_modules/@clack/core/dist/index.cjs.map +0 -1
- package/node_modules/@clack/core/dist/index.d.cts +0 -211
- package/node_modules/@clack/core/dist/index.d.ts +0 -211
- package/node_modules/@clack/prompts/dist/index.cjs +0 -87
- package/node_modules/@clack/prompts/dist/index.cjs.map +0 -1
- package/node_modules/@clack/prompts/dist/index.d.cts +0 -165
- package/node_modules/@clack/prompts/dist/index.d.ts +0 -165
- package/node_modules/@maizzle/cli/README.md +0 -58
- package/node_modules/@maizzle/cli/dist/index.mjs +0 -42
- package/node_modules/citty/dist/index.cjs +0 -475
- package/node_modules/citty/dist/index.d.cts +0 -80
- package/node_modules/citty/dist/index.d.ts +0 -80
- package/node_modules/consola/LICENSE +0 -47
- package/node_modules/consola/README.md +0 -352
- package/node_modules/consola/basic.d.ts +0 -1
- package/node_modules/consola/browser.d.ts +0 -1
- package/node_modules/consola/core.d.ts +0 -1
- package/node_modules/consola/dist/basic.cjs +0 -32
- package/node_modules/consola/dist/basic.d.cts +0 -23
- package/node_modules/consola/dist/basic.d.mts +0 -21
- package/node_modules/consola/dist/basic.d.ts +0 -23
- package/node_modules/consola/dist/basic.mjs +0 -24
- package/node_modules/consola/dist/browser.cjs +0 -84
- package/node_modules/consola/dist/browser.d.cts +0 -23
- package/node_modules/consola/dist/browser.d.mts +0 -21
- package/node_modules/consola/dist/browser.d.ts +0 -23
- package/node_modules/consola/dist/browser.mjs +0 -76
- package/node_modules/consola/dist/chunks/prompt.cjs +0 -288
- package/node_modules/consola/dist/chunks/prompt.mjs +0 -280
- package/node_modules/consola/dist/core.cjs +0 -517
- package/node_modules/consola/dist/core.d.cts +0 -459
- package/node_modules/consola/dist/core.d.mts +0 -459
- package/node_modules/consola/dist/core.d.ts +0 -459
- package/node_modules/consola/dist/core.mjs +0 -512
- package/node_modules/consola/dist/index.cjs +0 -663
- package/node_modules/consola/dist/index.d.cts +0 -24
- package/node_modules/consola/dist/index.d.mts +0 -22
- package/node_modules/consola/dist/index.d.ts +0 -24
- package/node_modules/consola/dist/index.mjs +0 -651
- package/node_modules/consola/dist/shared/consola.DCGIlDNP.cjs +0 -75
- package/node_modules/consola/dist/shared/consola.DRwqZj3T.mjs +0 -72
- package/node_modules/consola/dist/shared/consola.DXBYu-KD.mjs +0 -288
- package/node_modules/consola/dist/shared/consola.DwRq1yyg.cjs +0 -312
- package/node_modules/consola/dist/utils.cjs +0 -64
- package/node_modules/consola/dist/utils.d.cts +0 -286
- package/node_modules/consola/dist/utils.d.mts +0 -286
- package/node_modules/consola/dist/utils.d.ts +0 -286
- package/node_modules/consola/dist/utils.mjs +0 -54
- package/node_modules/consola/lib/index.cjs +0 -10
- package/node_modules/consola/package.json +0 -136
- package/node_modules/consola/utils.d.ts +0 -1
- package/node_modules/create-maizzle/README.md +0 -86
- package/node_modules/create-maizzle/bin/create-maizzle.mjs +0 -4
- package/node_modules/create-maizzle/node_modules/@clack/core/CHANGELOG.md +0 -340
- package/node_modules/create-maizzle/node_modules/@clack/core/LICENSE +0 -9
- package/node_modules/create-maizzle/node_modules/@clack/core/README.md +0 -22
- package/node_modules/create-maizzle/node_modules/@clack/core/dist/index.d.mts +0 -349
- package/node_modules/create-maizzle/node_modules/@clack/core/dist/index.mjs +0 -11
- package/node_modules/create-maizzle/node_modules/@clack/core/dist/index.mjs.map +0 -1
- package/node_modules/create-maizzle/node_modules/@clack/core/package.json +0 -60
- package/node_modules/create-maizzle/node_modules/@clack/prompts/CHANGELOG.md +0 -576
- package/node_modules/create-maizzle/node_modules/@clack/prompts/LICENSE +0 -9
- package/node_modules/create-maizzle/node_modules/@clack/prompts/README.md +0 -270
- package/node_modules/create-maizzle/node_modules/@clack/prompts/dist/index.d.mts +0 -391
- package/node_modules/create-maizzle/node_modules/@clack/prompts/dist/index.mjs +0 -137
- package/node_modules/create-maizzle/node_modules/@clack/prompts/dist/index.mjs.map +0 -1
- package/node_modules/create-maizzle/node_modules/@clack/prompts/package.json +0 -65
- package/node_modules/create-maizzle/package.json +0 -47
- package/node_modules/create-maizzle/src/index.js +0 -242
- package/node_modules/defu/LICENSE +0 -21
- package/node_modules/defu/README.md +0 -171
- package/node_modules/defu/dist/defu.cjs +0 -77
- package/node_modules/defu/dist/defu.d.cts +0 -31
- package/node_modules/defu/dist/defu.d.mts +0 -29
- package/node_modules/defu/dist/defu.d.ts +0 -31
- package/node_modules/defu/dist/defu.mjs +0 -69
- package/node_modules/defu/lib/defu.cjs +0 -10
- package/node_modules/defu/lib/defu.d.cts +0 -12
- package/node_modules/defu/package.json +0 -48
- package/node_modules/giget/dist/shared/giget.OCaTp9b-.mjs +0 -468
- package/node_modules/node-fetch-native/LICENSE +0 -114
- package/node_modules/node-fetch-native/README.md +0 -225
- package/node_modules/node-fetch-native/dist/chunks/multipart-parser.cjs +0 -2
- package/node_modules/node-fetch-native/dist/chunks/multipart-parser.mjs +0 -2
- package/node_modules/node-fetch-native/dist/index.cjs +0 -1
- package/node_modules/node-fetch-native/dist/index.mjs +0 -1
- package/node_modules/node-fetch-native/dist/native.cjs +0 -1
- package/node_modules/node-fetch-native/dist/native.mjs +0 -1
- package/node_modules/node-fetch-native/dist/node.cjs +0 -19
- package/node_modules/node-fetch-native/dist/node.mjs +0 -19
- package/node_modules/node-fetch-native/dist/polyfill.cjs +0 -1
- package/node_modules/node-fetch-native/dist/polyfill.mjs +0 -1
- package/node_modules/node-fetch-native/dist/proxy-stub.cjs +0 -1
- package/node_modules/node-fetch-native/dist/proxy-stub.mjs +0 -1
- package/node_modules/node-fetch-native/dist/proxy.cjs +0 -58
- package/node_modules/node-fetch-native/dist/shared/node-fetch-native.DfbY2q-x.mjs +0 -1
- package/node_modules/node-fetch-native/dist/shared/node-fetch-native.DhEqb06g.cjs +0 -1
- package/node_modules/node-fetch-native/index.d.ts +0 -1
- package/node_modules/node-fetch-native/lib/empty.cjs +0 -0
- package/node_modules/node-fetch-native/lib/empty.mjs +0 -0
- package/node_modules/node-fetch-native/lib/index.cjs +0 -11
- package/node_modules/node-fetch-native/lib/index.d.cts +0 -10
- package/node_modules/node-fetch-native/lib/index.d.mts +0 -10
- package/node_modules/node-fetch-native/lib/index.d.ts +0 -10
- package/node_modules/node-fetch-native/lib/native.cjs +0 -11
- package/node_modules/node-fetch-native/lib/polyfill.d.cts +0 -1
- package/node_modules/node-fetch-native/lib/polyfill.d.mts +0 -1
- package/node_modules/node-fetch-native/lib/polyfill.d.ts +0 -1
- package/node_modules/node-fetch-native/lib/proxy.d.ts +0 -32
- package/node_modules/node-fetch-native/node.d.ts +0 -1
- package/node_modules/node-fetch-native/package.json +0 -138
- package/node_modules/node-fetch-native/polyfill.d.ts +0 -1
- package/node_modules/node-fetch-native/proxy.d.ts +0 -1
- package/node_modules/nypm/node_modules/citty/LICENSE +0 -21
- package/node_modules/nypm/node_modules/citty/README.md +0 -231
- package/node_modules/nypm/node_modules/citty/dist/index.d.mts +0 -112
- package/node_modules/nypm/node_modules/citty/dist/index.mjs +0 -425
- package/node_modules/nypm/node_modules/citty/package.json +0 -42
- /package/node_modules/{nypm/node_modules/citty → citty}/dist/THIRD-PARTY-LICENSES.md +0 -0
- /package/node_modules/{nypm/node_modules/citty → citty}/dist/_chunks/libs/scule.mjs +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/LICENSE +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/component.d.mts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/component.mjs +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/config.d.mts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/config.mjs +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/layout.d.mts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/layout.mjs +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/scaffold.d.mts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/scaffold.mjs +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/stubs/component.vue +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/stubs/config.ts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/stubs/layout.vue +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/stubs/template.vue +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/template.d.mts +0 -0
- /package/node_modules/{@maizzle/cli → maizzle}/dist/commands/make/template.mjs +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/LICENSE +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/esm.mjs +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/index.js +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/lib/error.js +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/lib/suggestSimilar.js +0 -0
- /package/node_modules/{commander → maizzle/node_modules/commander}/typings/esm.d.mts +0 -0
|
@@ -1,20 +1,26 @@
|
|
|
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
|
-
import { ChevronUp, ChevronDown, Check } from 'lucide-vue-next'
|
|
4
|
+
import { ChevronUp, ChevronDown, Check, Info } from 'lucide-vue-next'
|
|
5
5
|
import {
|
|
6
6
|
DropdownMenu,
|
|
7
7
|
DropdownMenuContent,
|
|
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'
|
|
23
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
18
24
|
|
|
19
25
|
import stripesUrl from '../stripes.svg'
|
|
20
26
|
|
|
@@ -24,9 +30,16 @@ 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' })
|
|
@@ -40,45 +53,45 @@ const sourceView = ref<'compiled' | 'vue' | 'plaintext'>('compiled')
|
|
|
40
53
|
const copied = ref(false)
|
|
41
54
|
|
|
42
55
|
const iframeEl = ref<HTMLIFrameElement>()
|
|
56
|
+
const compiledSourceEl = ref<HTMLElement>()
|
|
43
57
|
const vueSourceEl = ref<HTMLElement>()
|
|
44
58
|
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>>()
|
|
59
|
+
const wrapperEl = ref<HTMLElement>()
|
|
50
60
|
|
|
51
61
|
const panelWidth = defineModel<number>('panelWidth', { default: 0 })
|
|
52
62
|
const panelHeight = defineModel<number>('panelHeight', { default: 0 })
|
|
53
63
|
const isDragging = defineModel<boolean>('isDragging', { default: false })
|
|
54
64
|
const isFullSize = defineModel<boolean>('isFullSize', { default: true })
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
// Custom resizable: width/height of the iframe wrapper (null = fill container)
|
|
67
|
+
const iframeWidth = ref<number | null>(null)
|
|
68
|
+
const iframeHeight = ref<number | null>(null)
|
|
69
|
+
const iframeContentHeight = ref<number | null>(null)
|
|
57
70
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
&& sideSizes.value.right < 0.5
|
|
61
|
-
&& sideSizes.value.top < 0.5
|
|
62
|
-
&& sideSizes.value.bottom < 0.5
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function copySource() {
|
|
71
|
+
function copySource() {
|
|
72
|
+
let text: string
|
|
66
73
|
if (sourceView.value === 'compiled') {
|
|
67
|
-
|
|
74
|
+
text = srcdoc.value
|
|
68
75
|
} else if (sourceView.value === 'plaintext') {
|
|
69
|
-
|
|
76
|
+
text = plaintextContent.value
|
|
70
77
|
} else {
|
|
71
78
|
const el = document.createElement('div')
|
|
72
79
|
el.innerHTML = vueSourceHtml.value
|
|
73
|
-
|
|
80
|
+
text = el.textContent || ''
|
|
74
81
|
}
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
|
|
83
|
+
const blob = new Blob([text], { type: 'text/plain' })
|
|
84
|
+
const item = new ClipboardItem({ 'text/plain': blob })
|
|
85
|
+
navigator.clipboard.write([item]).then(() => {
|
|
86
|
+
copied.value = true
|
|
87
|
+
setTimeout(() => { copied.value = false }, 2000)
|
|
88
|
+
})
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
interface CompatibilityIssue {
|
|
80
92
|
type: 'error' | 'warning'
|
|
81
93
|
title: string
|
|
94
|
+
category: string
|
|
82
95
|
clients: Array<{ name: string, notes: string[] }>
|
|
83
96
|
url?: string
|
|
84
97
|
line?: number
|
|
@@ -99,14 +112,111 @@ interface TemplateStats {
|
|
|
99
112
|
|
|
100
113
|
const compatibilityIssues = ref<CompatibilityIssue[]>([])
|
|
101
114
|
const compatibilityLoading = ref(false)
|
|
115
|
+
const compatibilityError = ref('')
|
|
116
|
+
const compatibilityCategory = ref('')
|
|
117
|
+
const compatibilityCategories = ['css', 'html', 'image', 'others'] as const
|
|
118
|
+
const activeCompatibilityCategories = computed(() =>
|
|
119
|
+
compatibilityCategories.filter(cat => compatibilityIssues.value.some(i => i.category === cat))
|
|
120
|
+
)
|
|
121
|
+
const filteredCompatibilityIssues = computed(() => {
|
|
122
|
+
if (!compatibilityCategory.value) return compatibilityIssues.value
|
|
123
|
+
return compatibilityIssues.value.filter(i => i.category === compatibilityCategory.value)
|
|
124
|
+
})
|
|
102
125
|
const lintIssues = ref<LintIssue[]>([])
|
|
103
126
|
const lintLoading = ref(false)
|
|
104
127
|
const stats = ref<TemplateStats | null>(null)
|
|
105
128
|
const statsLoading = ref(false)
|
|
106
129
|
|
|
130
|
+
// Email test state
|
|
131
|
+
const emailTo = ref<string[]>([])
|
|
132
|
+
const emailSubject = ref('')
|
|
133
|
+
const emailSending = ref(false)
|
|
134
|
+
const emailPreventThreading = ref(true)
|
|
135
|
+
const emailResult = ref<{ success: boolean; message: string; previewUrl?: string } | null>(null)
|
|
136
|
+
|
|
137
|
+
async function fetchEmailConfig() {
|
|
138
|
+
try {
|
|
139
|
+
const res = await fetch('/__maizzle/email-config')
|
|
140
|
+
const data = await res.json()
|
|
141
|
+
if (data.to?.length && !emailTo.value.length) emailTo.value = data.to
|
|
142
|
+
if (data.subject && !emailSubject.value) emailSubject.value = data.subject
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function sendTestEmail() {
|
|
147
|
+
if (!emailTo.value.length) return
|
|
148
|
+
emailSending.value = true
|
|
149
|
+
emailResult.value = null
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(`/__maizzle/email/${route.params.template}`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
to: emailTo.value,
|
|
157
|
+
subject: (() => {
|
|
158
|
+
let subj = emailSubject.value || String(route.params.template)
|
|
159
|
+
if (emailPreventThreading.value) {
|
|
160
|
+
subj += ` | ${new Date().toISOString().slice(0, 19)}`
|
|
161
|
+
}
|
|
162
|
+
return subj
|
|
163
|
+
})(),
|
|
164
|
+
}),
|
|
165
|
+
})
|
|
166
|
+
emailResult.value = await res.json()
|
|
167
|
+
} catch (error: any) {
|
|
168
|
+
emailResult.value = { success: false, message: error.message }
|
|
169
|
+
} finally {
|
|
170
|
+
emailSending.value = false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let renderedHtml = ''
|
|
175
|
+
|
|
176
|
+
function updateIframeContentHeight() {
|
|
177
|
+
const iframe = iframeEl.value
|
|
178
|
+
const doc = iframe?.contentDocument
|
|
179
|
+
if (!iframe || !doc?.documentElement) return
|
|
180
|
+
|
|
181
|
+
// Hide iframe body overflow — scrolling is handled by the outer ScrollArea
|
|
182
|
+
if (doc.body) doc.body.style.overflow = 'hidden'
|
|
183
|
+
|
|
184
|
+
// Save scroll position of the ScrollArea viewport
|
|
185
|
+
const viewport = wrapperEl.value?.querySelector('[data-slot="scroll-area-viewport"]')
|
|
186
|
+
const scrollTop = viewport?.scrollTop ?? 0
|
|
187
|
+
|
|
188
|
+
// Temporarily collapse to measure true content height
|
|
189
|
+
iframe.style.height = '0'
|
|
190
|
+
iframeContentHeight.value = doc.documentElement.scrollHeight
|
|
191
|
+
iframe.style.height = `${iframeContentHeight.value}px`
|
|
192
|
+
|
|
193
|
+
// Restore scroll position
|
|
194
|
+
if (viewport) {
|
|
195
|
+
viewport.scrollTop = scrollTop
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
107
199
|
async function fetchTemplate() {
|
|
108
200
|
const res = await fetch(`/__maizzle/render/${route.params.template}`)
|
|
109
|
-
|
|
201
|
+
renderedHtml = await res.text()
|
|
202
|
+
|
|
203
|
+
const iframe = iframeEl.value
|
|
204
|
+
const doc = iframe?.contentDocument
|
|
205
|
+
|
|
206
|
+
// Write directly into the iframe document to avoid a full reload,
|
|
207
|
+
// which preserves scroll position natively.
|
|
208
|
+
if (doc) {
|
|
209
|
+
doc.open()
|
|
210
|
+
doc.write(renderedHtml)
|
|
211
|
+
doc.close()
|
|
212
|
+
// Hide iframe body overflow — scrolling is handled by the outer ScrollArea
|
|
213
|
+
if (doc.body) doc.body.style.overflow = 'hidden'
|
|
214
|
+
await nextTick()
|
|
215
|
+
updateIframeContentHeight()
|
|
216
|
+
} else {
|
|
217
|
+
// Fallback for initial load
|
|
218
|
+
srcdoc.value = renderedHtml
|
|
219
|
+
}
|
|
110
220
|
}
|
|
111
221
|
|
|
112
222
|
async function fetchSource() {
|
|
@@ -138,9 +248,22 @@ async function fetchStats() {
|
|
|
138
248
|
|
|
139
249
|
async function fetchCompatibility() {
|
|
140
250
|
compatibilityLoading.value = true
|
|
251
|
+
compatibilityError.value = ''
|
|
141
252
|
try {
|
|
142
|
-
const res = await fetch(
|
|
143
|
-
|
|
253
|
+
const res = await fetch('/__maizzle/compatibility', {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
body: renderedHtml,
|
|
256
|
+
})
|
|
257
|
+
const data = await res.json()
|
|
258
|
+
if (data?.error) {
|
|
259
|
+
compatibilityError.value = data.error
|
|
260
|
+
compatibilityIssues.value = []
|
|
261
|
+
} else {
|
|
262
|
+
compatibilityIssues.value = data
|
|
263
|
+
// Default to first category that has issues
|
|
264
|
+
const firstCat = compatibilityCategories.find(cat => data.some((i: CompatibilityIssue) => i.category === cat))
|
|
265
|
+
compatibilityCategory.value = firstCat || ''
|
|
266
|
+
}
|
|
144
267
|
} catch {
|
|
145
268
|
compatibilityIssues.value = []
|
|
146
269
|
} finally {
|
|
@@ -151,8 +274,11 @@ async function fetchCompatibility() {
|
|
|
151
274
|
async function fetchLint() {
|
|
152
275
|
lintLoading.value = true
|
|
153
276
|
try {
|
|
154
|
-
const
|
|
155
|
-
|
|
277
|
+
const template = props.templates?.find(t => t.href === '/' + route.params.template)
|
|
278
|
+
const filePath = template?.path ?? route.params.template
|
|
279
|
+
const res = await fetch(`/__maizzle/lint/${filePath}`)
|
|
280
|
+
const data = await res.json()
|
|
281
|
+
lintIssues.value = Array.isArray(data) ? data.filter((i: LintIssue) => i.title) : []
|
|
156
282
|
} catch {
|
|
157
283
|
lintIssues.value = []
|
|
158
284
|
} finally {
|
|
@@ -165,18 +291,24 @@ watch(() => route.params.template, () => {
|
|
|
165
291
|
vueSourceHtml.value = ''
|
|
166
292
|
plaintextContent.value = ''
|
|
167
293
|
compatibilityIssues.value = []
|
|
294
|
+
compatibilityError.value = ''
|
|
168
295
|
lintIssues.value = []
|
|
169
296
|
stats.value = null
|
|
297
|
+
emailResult.value = null
|
|
170
298
|
sourceView.value = 'compiled'
|
|
171
|
-
fetchTemplate()
|
|
172
|
-
fetchCompatibility()
|
|
299
|
+
fetchTemplate().then(fetchCompatibility)
|
|
173
300
|
fetchLint()
|
|
174
301
|
fetchStats()
|
|
302
|
+
fetchEmailConfig()
|
|
175
303
|
if (viewMode.value === 'source') fetchSource()
|
|
176
304
|
}, { immediate: true })
|
|
177
305
|
|
|
178
306
|
watch(viewMode, (mode) => {
|
|
179
|
-
if (mode === 'source'
|
|
307
|
+
if (mode === 'source') {
|
|
308
|
+
if (sourceView.value === 'compiled' && !sourceHtml.value) fetchSource()
|
|
309
|
+
if (sourceView.value === 'vue' && !vueSourceHtml.value) fetchVueSource()
|
|
310
|
+
if (sourceView.value === 'plaintext' && !plaintextContent.value) fetchPlaintext()
|
|
311
|
+
}
|
|
180
312
|
})
|
|
181
313
|
|
|
182
314
|
watch(sourceView, (view) => {
|
|
@@ -187,15 +319,16 @@ watch(sourceView, (view) => {
|
|
|
187
319
|
|
|
188
320
|
if ((import.meta as any).hot) {
|
|
189
321
|
;(import.meta as any).hot.on('maizzle:template-updated', () => {
|
|
190
|
-
fetchTemplate()
|
|
191
|
-
fetchCompatibility()
|
|
322
|
+
fetchTemplate().then(fetchCompatibility)
|
|
192
323
|
fetchLint()
|
|
193
324
|
fetchStats()
|
|
194
|
-
// Clear non-active source views so they re-fetch when switched to
|
|
195
|
-
if (sourceView.value !== 'compiled') sourceHtml.value = ''
|
|
196
|
-
if (sourceView.value !== 'vue') vueSourceHtml.value = ''
|
|
197
|
-
if (sourceView.value !== 'plaintext') plaintextContent.value = ''
|
|
198
325
|
|
|
326
|
+
// Always clear all source views so they re-fetch when switched to
|
|
327
|
+
sourceHtml.value = ''
|
|
328
|
+
vueSourceHtml.value = ''
|
|
329
|
+
plaintextContent.value = ''
|
|
330
|
+
|
|
331
|
+
// Re-fetch the active source view immediately if currently visible
|
|
199
332
|
if (viewMode.value === 'source') {
|
|
200
333
|
if (sourceView.value === 'compiled') fetchSource()
|
|
201
334
|
if (sourceView.value === 'vue') fetchVueSource()
|
|
@@ -231,74 +364,112 @@ async function goToLine(line: number) {
|
|
|
231
364
|
}
|
|
232
365
|
}
|
|
233
366
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
367
|
+
async function goToCompiledLine(line: number) {
|
|
368
|
+
viewMode.value = 'source'
|
|
369
|
+
sourceView.value = 'compiled'
|
|
237
370
|
|
|
238
|
-
|
|
371
|
+
if (!sourceHtml.value) {
|
|
372
|
+
await fetchSource()
|
|
373
|
+
}
|
|
239
374
|
|
|
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 }
|
|
375
|
+
await nextTick()
|
|
244
376
|
|
|
245
|
-
|
|
246
|
-
if (!
|
|
377
|
+
const el = compiledSourceEl.value
|
|
378
|
+
if (!el) return
|
|
247
379
|
|
|
248
|
-
|
|
249
|
-
if (Math.abs(left - right) < 0.5) return
|
|
380
|
+
el.querySelectorAll('.shiki-highlight-line').forEach(l => l.classList.remove('shiki-highlight-line'))
|
|
250
381
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
382
|
+
const lineEl = el.querySelector(`[data-line="${line}"]`)
|
|
383
|
+
if (lineEl) {
|
|
384
|
+
lineEl.classList.add('shiki-highlight-line')
|
|
385
|
+
lineEl.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
386
|
+
}
|
|
255
387
|
}
|
|
256
388
|
|
|
257
|
-
|
|
258
|
-
|
|
389
|
+
const emit = defineEmits<{ 'clear-device': [] }>()
|
|
390
|
+
|
|
391
|
+
type Edge = 'left' | 'right' | 'top' | 'bottom'
|
|
259
392
|
|
|
260
|
-
|
|
261
|
-
|
|
393
|
+
function onEdgeDrag(e: MouseEvent | TouchEvent, edge: Edge) {
|
|
394
|
+
e.preventDefault()
|
|
395
|
+
isDragging.value = true
|
|
396
|
+
emit('clear-device')
|
|
397
|
+
|
|
398
|
+
const container = containerEl.value
|
|
399
|
+
if (!container) return
|
|
400
|
+
|
|
401
|
+
const isTouch = e.type === 'touchstart'
|
|
402
|
+
const startPoint = isTouch ? (e as TouchEvent).touches[0] : (e as MouseEvent)
|
|
403
|
+
const startX = startPoint.clientX
|
|
404
|
+
const startY = startPoint.clientY
|
|
405
|
+
const rect = container.getBoundingClientRect()
|
|
406
|
+
const gutter = 40 // 20px padding on each side
|
|
407
|
+
const maxW = rect.width - gutter
|
|
408
|
+
const maxH = rect.height - gutter
|
|
409
|
+
const startW = iframeWidth.value ?? maxW
|
|
410
|
+
const startH = iframeHeight.value ?? maxH
|
|
411
|
+
|
|
412
|
+
const isHorizontal = edge === 'left' || edge === 'right'
|
|
413
|
+
const sign = (edge === 'left' || edge === 'top') ? -1 : 1
|
|
414
|
+
|
|
415
|
+
const onMove = (ev: MouseEvent | TouchEvent) => {
|
|
416
|
+
const point = ev.type === 'touchmove' ? (ev as TouchEvent).touches[0] : (ev as MouseEvent)
|
|
417
|
+
if (isHorizontal) {
|
|
418
|
+
// Symmetric: each side moves by the delta, so total change is 2x
|
|
419
|
+
const delta = (point.clientX - startX) * sign
|
|
420
|
+
iframeWidth.value = Math.max(200, Math.min(maxW, startW + delta * 2))
|
|
421
|
+
} else {
|
|
422
|
+
const delta = (point.clientY - startY) * sign
|
|
423
|
+
iframeHeight.value = Math.max(100, Math.min(maxH, startH + delta * 2))
|
|
424
|
+
}
|
|
425
|
+
}
|
|
262
426
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
427
|
+
const onUp = () => {
|
|
428
|
+
isDragging.value = false
|
|
429
|
+
updateFullSize()
|
|
430
|
+
document.removeEventListener('mousemove', onMove)
|
|
431
|
+
document.removeEventListener('mouseup', onUp)
|
|
432
|
+
document.removeEventListener('touchmove', onMove)
|
|
433
|
+
document.removeEventListener('touchend', onUp)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
document.addEventListener('mousemove', onMove)
|
|
437
|
+
document.addEventListener('mouseup', onUp)
|
|
438
|
+
document.addEventListener('touchmove', onMove, { passive: false })
|
|
439
|
+
document.addEventListener('touchend', onUp)
|
|
267
440
|
}
|
|
268
441
|
|
|
269
|
-
function
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
442
|
+
function updateFullSize() {
|
|
443
|
+
const container = containerEl.value
|
|
444
|
+
if (!container) return
|
|
445
|
+
const rect = container.getBoundingClientRect()
|
|
446
|
+
const gutter = 40
|
|
447
|
+
isFullSize.value = (iframeWidth.value === null || iframeWidth.value >= rect.width - gutter - 2)
|
|
448
|
+
&& (iframeHeight.value === null || iframeHeight.value >= rect.height - gutter - 2)
|
|
449
|
+
}
|
|
272
450
|
|
|
451
|
+
function applyDeviceSize(device: Device | null | undefined) {
|
|
273
452
|
if (!device) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
topPanel.value?.resize(0)
|
|
278
|
-
bottomPanel.value?.resize(0)
|
|
279
|
-
}
|
|
453
|
+
iframeWidth.value = null
|
|
454
|
+
iframeHeight.value = null
|
|
455
|
+
updateFullSize()
|
|
280
456
|
return
|
|
281
457
|
}
|
|
282
458
|
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
const hPanelSpace = rect.width - handleSize * 2
|
|
288
|
-
const vPanelSpace = rect.height - handleSize * 2
|
|
289
|
-
|
|
290
|
-
const hSide = Math.max(0, ((hPanelSpace - device.width) / 2) / hPanelSpace * 100)
|
|
291
|
-
const vSide = Math.max(0, ((vPanelSpace - device.height) / 2) / vPanelSpace * 100)
|
|
459
|
+
const container = containerEl.value
|
|
460
|
+
if (!container) return
|
|
461
|
+
const rect = container.getBoundingClientRect()
|
|
462
|
+
const gutter = 40
|
|
292
463
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
bottomPanel.value?.resize(vSide)
|
|
464
|
+
iframeWidth.value = Math.min(device.width, rect.width - gutter)
|
|
465
|
+
iframeHeight.value = Math.min(device.height, rect.height - gutter)
|
|
466
|
+
updateFullSize()
|
|
297
467
|
}
|
|
298
468
|
|
|
299
469
|
watch(() => props.device, (device) => {
|
|
300
470
|
if (viewMode.value === 'source') return
|
|
301
|
-
|
|
471
|
+
// Only apply when a device is selected, not when cleared (drag start clears device)
|
|
472
|
+
if (device) applyDeviceSize(device)
|
|
302
473
|
})
|
|
303
474
|
|
|
304
475
|
watch(() => props.resetKey, () => {
|
|
@@ -333,9 +504,9 @@ function forwardIframeKeys(iframe: HTMLIFrameElement) {
|
|
|
333
504
|
}
|
|
334
505
|
|
|
335
506
|
onMounted(() => {
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
const rect =
|
|
507
|
+
const wrapper = wrapperEl.value
|
|
508
|
+
if (wrapper) {
|
|
509
|
+
const rect = wrapper.getBoundingClientRect()
|
|
339
510
|
panelWidth.value = Math.round(rect.width)
|
|
340
511
|
panelHeight.value = Math.round(rect.height)
|
|
341
512
|
observer = new ResizeObserver((entries) => {
|
|
@@ -343,8 +514,13 @@ onMounted(() => {
|
|
|
343
514
|
panelWidth.value = Math.round(entry.contentRect.width)
|
|
344
515
|
panelHeight.value = Math.round(entry.contentRect.height)
|
|
345
516
|
}
|
|
517
|
+
updateIframeContentHeight()
|
|
346
518
|
})
|
|
347
|
-
observer.observe(
|
|
519
|
+
observer.observe(wrapper)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const el = iframeEl.value
|
|
523
|
+
if (el) {
|
|
348
524
|
el.addEventListener('load', () => forwardIframeKeys(el))
|
|
349
525
|
}
|
|
350
526
|
})
|
|
@@ -360,7 +536,7 @@ const activeTab = ref<string | undefined>(undefined)
|
|
|
360
536
|
function toggleBottomPanel() {
|
|
361
537
|
bottomPanelOpen.value = !bottomPanelOpen.value
|
|
362
538
|
if (bottomPanelOpen.value) {
|
|
363
|
-
tabsPanelHeight.value =
|
|
539
|
+
tabsPanelHeight.value = 300
|
|
364
540
|
if (!activeTab.value) activeTab.value = 'compatibility'
|
|
365
541
|
} else {
|
|
366
542
|
tabsPanelHeight.value = 40
|
|
@@ -378,7 +554,7 @@ function onTabClick(tab: string) {
|
|
|
378
554
|
activeTab.value = tab
|
|
379
555
|
if (!bottomPanelOpen.value) {
|
|
380
556
|
bottomPanelOpen.value = true
|
|
381
|
-
tabsPanelHeight.value =
|
|
557
|
+
tabsPanelHeight.value = 300
|
|
382
558
|
}
|
|
383
559
|
}
|
|
384
560
|
|
|
@@ -390,8 +566,11 @@ function onTabsDragStart(e: MouseEvent) {
|
|
|
390
566
|
const startY = e.clientY
|
|
391
567
|
const startHeight = tabsPanelHeight.value
|
|
392
568
|
|
|
569
|
+
const rootEl = containerEl.value?.closest('.relative.h-full') as HTMLElement | null
|
|
570
|
+
const maxHeight = rootEl ? rootEl.getBoundingClientRect().height : Infinity
|
|
571
|
+
|
|
393
572
|
const onMouseMove = (e: MouseEvent) => {
|
|
394
|
-
const newHeight = Math.max(40, startHeight + startY - e.clientY)
|
|
573
|
+
const newHeight = Math.max(40, Math.min(maxHeight, startHeight + startY - e.clientY))
|
|
395
574
|
tabsPanelHeight.value = newHeight
|
|
396
575
|
bottomPanelOpen.value = newHeight > 40
|
|
397
576
|
|
|
@@ -420,187 +599,298 @@ const stripeBg = {
|
|
|
420
599
|
</script>
|
|
421
600
|
|
|
422
601
|
<template>
|
|
423
|
-
<div class="
|
|
424
|
-
<div class="
|
|
602
|
+
<div class="relative h-full">
|
|
603
|
+
<div class="absolute inset-0 bottom-10 overflow-hidden">
|
|
425
604
|
<!-- Source code view -->
|
|
426
605
|
<div v-show="viewMode === 'source'" class="absolute inset-0 min-w-0 overflow-hidden">
|
|
427
606
|
<div class="absolute top-3 left-6 z-10">
|
|
428
607
|
<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-
|
|
608
|
+
<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
609
|
{{ sourceView === 'compiled' ? 'HTML' : sourceView === 'vue' ? 'Source' : 'Plaintext' }}
|
|
431
610
|
<ChevronDown class="size-3 opacity-50" />
|
|
432
611
|
</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
|
|
612
|
+
<DropdownMenuContent align="start" class="min-w-32 bg-[#27212e]/80 dark:bg-gray-950/80 backdrop-blur-md border-white/10">
|
|
613
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'vue'">
|
|
614
|
+
<Check v-if="sourceView === 'vue'" class="size-3 text-gray-200" />
|
|
615
|
+
<span :class="[sourceView === 'vue' ? 'text-gray-200' : 'pl-5']">Source</span>
|
|
437
616
|
</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
|
|
617
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'compiled'">
|
|
618
|
+
<Check v-if="sourceView === 'compiled'" class="size-3 text-gray-200" />
|
|
619
|
+
<span :class="[sourceView === 'compiled' ? 'text-gray-200' : 'pl-5']">HTML</span>
|
|
441
620
|
</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
|
|
621
|
+
<DropdownMenuItem class="text-xs font-medium text-gray-400 focus:text-gray-200 focus:bg-white/10" @click="sourceView = 'plaintext'">
|
|
622
|
+
<Check v-if="sourceView === 'plaintext'" class="size-3 text-gray-200" />
|
|
623
|
+
<span :class="[sourceView === 'plaintext' ? 'text-gray-200' : 'pl-5']">Plaintext</span>
|
|
445
624
|
</DropdownMenuItem>
|
|
446
625
|
</DropdownMenuContent>
|
|
447
626
|
</DropdownMenu>
|
|
448
627
|
</div>
|
|
449
628
|
<button
|
|
450
|
-
class="absolute top-3 right-
|
|
629
|
+
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
630
|
:disabled="copied"
|
|
452
631
|
@click="copySource"
|
|
453
632
|
>
|
|
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>
|
|
633
|
+
<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>
|
|
634
|
+
<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
635
|
</button>
|
|
457
|
-
<div
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
636
|
+
<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">
|
|
637
|
+
<div
|
|
638
|
+
ref="compiledSourceEl"
|
|
639
|
+
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!"
|
|
640
|
+
v-html="sourceHtml"
|
|
641
|
+
/>
|
|
642
|
+
<ScrollBar orientation="horizontal" />
|
|
643
|
+
</ScrollArea>
|
|
644
|
+
<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">
|
|
645
|
+
<div
|
|
646
|
+
ref="vueSourceEl"
|
|
647
|
+
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!"
|
|
648
|
+
v-html="vueSourceHtml"
|
|
649
|
+
/>
|
|
650
|
+
<ScrollBar orientation="horizontal" />
|
|
651
|
+
</ScrollArea>
|
|
652
|
+
<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">
|
|
653
|
+
<pre
|
|
654
|
+
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"
|
|
655
|
+
>{{ plaintextContent }}</pre>
|
|
656
|
+
</ScrollArea>
|
|
472
657
|
</div>
|
|
473
658
|
|
|
659
|
+
<!-- Blocks iframe from stealing pointer events while dragging tabs -->
|
|
660
|
+
<div v-if="tabsDragging" class="fixed inset-0 z-50" />
|
|
661
|
+
|
|
474
662
|
<!-- Preview view -->
|
|
475
663
|
<div v-show="viewMode !== 'source'" class="absolute inset-0">
|
|
476
664
|
<div class="relative h-full opacity-5" :style="stripeBg" />
|
|
477
665
|
</div>
|
|
478
666
|
|
|
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
|
-
</
|
|
667
|
+
<div v-show="viewMode !== 'source'" ref="containerEl" class="absolute inset-0 z-10 flex items-center justify-center">
|
|
668
|
+
<!-- Blocks iframe from stealing pointer events while dragging -->
|
|
669
|
+
<div v-if="isDragging" class="absolute inset-0 z-20" />
|
|
670
|
+
<div
|
|
671
|
+
class="relative"
|
|
672
|
+
:style="{
|
|
673
|
+
width: iframeWidth != null ? `${iframeWidth + 40}px` : '100%',
|
|
674
|
+
height: iframeHeight != null ? `${iframeHeight + 40}px` : '100%',
|
|
675
|
+
transition: isDragging ? 'none' : 'width 0.2s ease, height 0.2s ease',
|
|
676
|
+
}"
|
|
677
|
+
>
|
|
678
|
+
<!-- Top handle -->
|
|
679
|
+
<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')">
|
|
680
|
+
<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" />
|
|
681
|
+
</div>
|
|
682
|
+
<!-- Bottom handle -->
|
|
683
|
+
<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')">
|
|
684
|
+
<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" />
|
|
685
|
+
</div>
|
|
686
|
+
<!-- Left handle -->
|
|
687
|
+
<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')">
|
|
688
|
+
<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" />
|
|
689
|
+
</div>
|
|
690
|
+
<!-- Right handle -->
|
|
691
|
+
<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')">
|
|
692
|
+
<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" />
|
|
693
|
+
</div>
|
|
694
|
+
<!-- Iframe -->
|
|
695
|
+
<div ref="wrapperEl" class="absolute inset-0 min-[430px]:inset-5 border border-gray-200 dark:border-gray-800">
|
|
696
|
+
<ScrollArea class="h-full w-full bg-white dark:bg-gray-950">
|
|
697
|
+
<iframe
|
|
698
|
+
ref="iframeEl"
|
|
699
|
+
:srcdoc="srcdoc"
|
|
700
|
+
@load="updateIframeContentHeight"
|
|
701
|
+
class="w-full border-0 bg-white dark:bg-gray-950"
|
|
702
|
+
:style="{ height: iframeContentHeight ? `${iframeContentHeight}px` : '100%' }"
|
|
703
|
+
/>
|
|
704
|
+
</ScrollArea>
|
|
705
|
+
</div>
|
|
502
706
|
</div>
|
|
503
707
|
</div>
|
|
504
708
|
</div>
|
|
505
709
|
|
|
506
|
-
<!-- Tabs panel (
|
|
710
|
+
<!-- Tabs panel (overlay) -->
|
|
507
711
|
<div
|
|
508
|
-
class="
|
|
509
|
-
:class="
|
|
712
|
+
class="absolute bottom-0 left-0 right-0 z-20 overflow-hidden border-t border-gray-200 dark:border-gray-800/50"
|
|
713
|
+
:class="[
|
|
714
|
+
!tabsDragging ? 'transition-[height] duration-200 ease-in-out' : '',
|
|
715
|
+
'bg-white dark:bg-gray-950',
|
|
716
|
+
]"
|
|
510
717
|
:style="{ height: `${tabsPanelHeight}px` }"
|
|
511
718
|
>
|
|
512
719
|
<div
|
|
513
|
-
class="relative h-
|
|
720
|
+
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
721
|
@mousedown="onTabsDragStart"
|
|
515
722
|
/>
|
|
516
723
|
<Tabs :model-value="activeTab" class="flex flex-col min-h-0 h-full">
|
|
517
|
-
<div class="flex items-center justify-between min-h-10
|
|
724
|
+
<div class="flex items-center justify-between min-h-10 pl-2 pr-3 shrink-0" :class="bottomPanelOpen ? 'border-b' : ''">
|
|
518
725
|
<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')">
|
|
726
|
+
<TabsTrigger 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" @click="onTabClick('compatibility')">
|
|
520
727
|
Compatibility
|
|
521
728
|
</TabsTrigger>
|
|
522
|
-
<TabsTrigger value="lint" 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('lint')">
|
|
729
|
+
<TabsTrigger value="lint" 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" @click="onTabClick('lint')">
|
|
523
730
|
Linter
|
|
524
731
|
</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')">
|
|
732
|
+
<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" @click="onTabClick('stats')">
|
|
526
733
|
Stats
|
|
527
734
|
</TabsTrigger>
|
|
735
|
+
<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" @click="onTabClick('test')">
|
|
736
|
+
Test
|
|
737
|
+
</TabsTrigger>
|
|
528
738
|
</TabsList>
|
|
529
739
|
<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" />
|
|
740
|
+
<ChevronUp v-if="!bottomPanelOpen" class="size-4 dark:text-gray-400" :stroke-width="1" />
|
|
741
|
+
<ChevronDown v-else class="size-4 dark:text-gray-400" :stroke-width="1" />
|
|
532
742
|
</Button>
|
|
533
743
|
</div>
|
|
534
|
-
<div class="flex-1
|
|
535
|
-
<TabsContent value="compatibility" class="mt-0">
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
744
|
+
<div class="flex-1 min-h-0">
|
|
745
|
+
<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">
|
|
746
|
+
<button
|
|
747
|
+
v-for="cat in activeCompatibilityCategories"
|
|
748
|
+
:key="cat"
|
|
749
|
+
class="px-2 py-0.5 text-[11px] rounded-full cursor-pointer transition-colors"
|
|
750
|
+
:class="compatibilityCategory === cat
|
|
751
|
+
? 'bg-gray-900 text-white dark:bg-gray-600 dark:text-gray-100'
|
|
752
|
+
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10'"
|
|
753
|
+
@click="compatibilityCategory = cat"
|
|
543
754
|
>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
755
|
+
{{ cat === 'css' ? 'CSS' : cat === 'html' ? 'HTML' : cat.charAt(0).toUpperCase() + cat.slice(1) }}
|
|
756
|
+
<span class="ml-0.5 tabular-nums">{{ compatibilityIssues.filter(i => i.category === cat).length }}</span>
|
|
757
|
+
</button>
|
|
758
|
+
</div>
|
|
759
|
+
<ScrollArea class="h-full flex-1 min-h-0 pl-5">
|
|
760
|
+
<p v-if="compatibilityLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Checking compatibility...</p>
|
|
761
|
+
<p v-else-if="compatibilityError" class="pr-4 py-3 text-xs text-red-500 dark:text-red-400">{{ compatibilityError }}</p>
|
|
762
|
+
<p v-else-if="compatibilityIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No compatibility issues found.</p>
|
|
763
|
+
<ul v-else class="text-xs divide-y">
|
|
764
|
+
<li
|
|
765
|
+
v-for="(issue, i) in filteredCompatibilityIssues"
|
|
766
|
+
:key="i"
|
|
767
|
+
class="pr-4 py-1.5 hover:bg-gray-50 dark:hover:bg-white/5"
|
|
768
|
+
>
|
|
769
|
+
<div class="flex items-center gap-2">
|
|
770
|
+
<a v-if="issue.url" :href="issue.url" target="_blank" rel="noopener" class="font-medium hover:underline shrink-0" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
|
|
547
771
|
{{ issue.title }}
|
|
548
772
|
</a>
|
|
549
|
-
<span v-else class="font-medium" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
|
|
773
|
+
<span v-else class="font-medium shrink-0" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
|
|
550
774
|
{{ issue.title }}
|
|
551
775
|
</span>
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
776
|
+
<span class="text-gray-400 dark:text-gray-600 shrink-0">·</span>
|
|
777
|
+
<span class="text-gray-500 dark:text-gray-400 truncate">{{ issue.type === 'error' ? 'Not supported' : 'Partial support' }} in {{ issue.clients.map((c: any) => c.name).join(', ') }}</span>
|
|
778
|
+
<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 ml-auto" @click="goToCompiledLine(issue.line!)">L{{ issue.line }}</button>
|
|
779
|
+
</div>
|
|
780
|
+
</li>
|
|
781
|
+
</ul>
|
|
782
|
+
</ScrollArea>
|
|
783
|
+
</TabsContent>
|
|
784
|
+
<TabsContent value="lint" class="mt-0 h-full">
|
|
785
|
+
<ScrollArea class="h-full pl-5">
|
|
786
|
+
<p v-if="lintLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Linting...</p>
|
|
787
|
+
<p v-else-if="lintIssues.length === 0" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No issues found.</p>
|
|
788
|
+
<ul v-else class="text-xs divide-y">
|
|
789
|
+
<li
|
|
790
|
+
v-for="(issue, i) in lintIssues"
|
|
791
|
+
:key="i"
|
|
792
|
+
class="pr-4 py-2 hover:bg-gray-50 dark:hover:bg-white/5"
|
|
793
|
+
>
|
|
794
|
+
<div class="flex items-start justify-between gap-4">
|
|
795
|
+
<div>
|
|
796
|
+
<span class="font-medium" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
|
|
797
|
+
{{ issue.title }}
|
|
798
|
+
</span>
|
|
799
|
+
<div class="text-gray-500 dark:text-gray-400 mt-0.5">{{ issue.message }}</div>
|
|
556
800
|
</div>
|
|
801
|
+
<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="goToLine(issue.line!)">L{{ issue.line }}</button>
|
|
557
802
|
</div>
|
|
558
|
-
|
|
803
|
+
</li>
|
|
804
|
+
</ul>
|
|
805
|
+
</ScrollArea>
|
|
806
|
+
</TabsContent>
|
|
807
|
+
<TabsContent value="stats" class="mt-0 h-full">
|
|
808
|
+
<ScrollArea class="h-full pl-5">
|
|
809
|
+
<p v-if="statsLoading" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">Loading stats...</p>
|
|
810
|
+
<p v-else-if="!stats" class="pr-4 py-3 text-xs text-gray-500 dark:text-gray-400">No stats available.</p>
|
|
811
|
+
<div v-else class="pr-4 py-3 flex items-center gap-6 text-xs">
|
|
812
|
+
<div class="flex items-center gap-1.5">
|
|
813
|
+
<span class="text-gray-500 dark:text-gray-400">Size</span>
|
|
814
|
+
<span
|
|
815
|
+
class="font-medium tabular-nums"
|
|
816
|
+
:class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'"
|
|
817
|
+
>{{ stats.size.formatted }}</span>
|
|
818
|
+
<TooltipProvider :delay-duration="0">
|
|
819
|
+
<Tooltip>
|
|
820
|
+
<TooltipTrigger as-child>
|
|
821
|
+
<button type="button">
|
|
822
|
+
<Info class="size-3 text-gray-400 dark:text-gray-500" />
|
|
823
|
+
</button>
|
|
824
|
+
</TooltipTrigger>
|
|
825
|
+
<TooltipContent class="max-w-60">
|
|
826
|
+
Compiled HTML size, excludes image files. Gmail clips content at ~100KB.
|
|
827
|
+
</TooltipContent>
|
|
828
|
+
</Tooltip>
|
|
829
|
+
</TooltipProvider>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="flex items-center gap-1.5">
|
|
832
|
+
<span class="text-gray-500 dark:text-gray-400">Images</span>
|
|
833
|
+
<span class="font-medium tabular-nums">{{ stats.images }}</span>
|
|
834
|
+
</div>
|
|
835
|
+
<div class="flex items-center gap-1.5">
|
|
836
|
+
<span class="text-gray-500 dark:text-gray-400">Links</span>
|
|
837
|
+
<span class="font-medium tabular-nums">{{ stats.links }}</span>
|
|
559
838
|
</div>
|
|
560
|
-
</
|
|
561
|
-
</
|
|
839
|
+
</div>
|
|
840
|
+
</ScrollArea>
|
|
562
841
|
</TabsContent>
|
|
563
|
-
<TabsContent value="
|
|
564
|
-
<
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
842
|
+
<TabsContent value="test" class="mt-0 h-full">
|
|
843
|
+
<ScrollArea class="h-full pl-5">
|
|
844
|
+
<div class="pr-4 py-3 max-w-md">
|
|
845
|
+
<div class="space-y-2">
|
|
846
|
+
<div class="flex items-center gap-2">
|
|
847
|
+
<label class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0">To</label>
|
|
848
|
+
<TagsInput v-model="emailTo" delimiter=" " add-on-paste add-on-blur class="flex-1 min-h-7 gap-1 px-2 py-1">
|
|
849
|
+
<TagsInputItem v-for="item in emailTo" :key="item" :value="item" class="h-5 text-xs rounded">
|
|
850
|
+
<TagsInputItemText class="px-1.5 py-0 text-xs" />
|
|
851
|
+
<TagsInputItemDelete class="size-3.5" />
|
|
852
|
+
</TagsInputItem>
|
|
853
|
+
<TagsInputInput class="text-xs min-h-5 px-0.5" placeholder="Add emails..." />
|
|
854
|
+
</TagsInput>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="flex items-center gap-2">
|
|
857
|
+
<label class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0">Subject</label>
|
|
858
|
+
<div class="flex-1 flex items-center gap-3">
|
|
859
|
+
<Input v-model="emailSubject" :placeholder="String(route.params.template)" class="flex-1 h-7 text-xs! px-2" />
|
|
860
|
+
<label class="flex items-center gap-1.5 cursor-pointer select-none shrink-0">
|
|
861
|
+
<Checkbox v-model="emailPreventThreading" :default-checked="true" class="size-3.5" />
|
|
862
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">Prevent threading</span>
|
|
863
|
+
</label>
|
|
864
|
+
</div>
|
|
578
865
|
</div>
|
|
579
|
-
<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="goToLine(issue.line!)">L{{ issue.line }}</button>
|
|
580
866
|
</div>
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
867
|
+
<div class="flex items-center gap-3 mt-3">
|
|
868
|
+
<Button
|
|
869
|
+
size="sm"
|
|
870
|
+
class="h-7 text-xs px-3"
|
|
871
|
+
:disabled="!emailTo.length || emailSending"
|
|
872
|
+
@click="sendTestEmail"
|
|
873
|
+
>
|
|
874
|
+
<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>
|
|
875
|
+
{{ emailSending ? 'Sending' : 'Send' }}
|
|
876
|
+
</Button>
|
|
877
|
+
</div>
|
|
878
|
+
<div v-if="emailResult" class="mt-2">
|
|
879
|
+
<p class="text-xs" :class="emailResult.success ? 'text-gray-950 dark:text-white' : 'text-red-600'">
|
|
880
|
+
{{ emailResult.message }}
|
|
881
|
+
<a
|
|
882
|
+
v-if="emailResult.previewUrl"
|
|
883
|
+
:href="emailResult.previewUrl"
|
|
884
|
+
target="_blank"
|
|
885
|
+
rel="noopener"
|
|
886
|
+
class="text-gray-500 dark:text-gray-400 hover:underline"
|
|
887
|
+
>
|
|
888
|
+
(view)
|
|
889
|
+
</a>
|
|
890
|
+
</p>
|
|
891
|
+
</div>
|
|
602
892
|
</div>
|
|
603
|
-
</
|
|
893
|
+
</ScrollArea>
|
|
604
894
|
</TabsContent>
|
|
605
895
|
</div>
|
|
606
896
|
</Tabs>
|