@maizzle/framework 6.0.0-rc.7 → 6.0.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Body.vue +105 -36
- package/dist/components/Button.vue +4 -1
- package/dist/components/CodeBlock.vue +1 -1
- package/dist/components/CodeInline.vue +6 -1
- package/dist/components/Column.vue +30 -5
- package/dist/components/Container.vue +10 -2
- package/dist/components/Divider.vue +28 -0
- package/dist/components/Head.vue +22 -0
- package/dist/components/Heading.vue +28 -0
- package/dist/components/Html.vue +98 -47
- package/dist/components/Layout.vue +93 -0
- package/dist/components/Link.vue +26 -0
- package/dist/components/Markdown.vue +15 -2
- package/dist/components/Outlook.vue +36 -0
- package/dist/components/Overlap.vue +25 -5
- package/dist/components/Preheader.vue +1 -1
- package/dist/components/Row.vue +16 -5
- package/dist/components/Section.vue +83 -0
- package/dist/components/Text.vue +29 -0
- package/dist/components/Vml.vue +165 -13
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +13 -1
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/serve.mjs +1 -1
- package/dist/serve.mjs.map +1 -1
- package/dist/server/compatibility.mjs +15 -1
- package/dist/server/compatibility.mjs.map +1 -1
- package/dist/server/email.mjs +2 -1
- package/dist/server/email.mjs.map +1 -1
- 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 +9 -9
- package/dist/server/ui/pages/Preview.vue +215 -150
- package/dist/transformers/inlineCSS.d.mts +1 -14
- package/dist/transformers/inlineCSS.d.mts.map +1 -1
- package/dist/transformers/inlineCSS.mjs +16 -34
- package/dist/transformers/inlineCSS.mjs.map +1 -1
- package/dist/types/config.d.mts +11 -27
- package/dist/types/config.d.mts.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
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,
|
|
4
|
+
import { ChevronUp, ChevronDown, Check, Info } from 'lucide-vue-next'
|
|
5
5
|
import {
|
|
6
6
|
DropdownMenu,
|
|
7
7
|
DropdownMenuContent,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
TagsInputItemDelete,
|
|
21
21
|
TagsInputItemText,
|
|
22
22
|
} from '@/components/ui/tags-input'
|
|
23
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
23
24
|
|
|
24
25
|
import stripesUrl from '../stripes.svg'
|
|
25
26
|
|
|
@@ -29,9 +30,16 @@ interface Device {
|
|
|
29
30
|
height: number
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
interface Template {
|
|
34
|
+
name: string
|
|
35
|
+
path: string
|
|
36
|
+
href: string
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
const props = defineProps<{
|
|
33
40
|
device?: Device | null
|
|
34
41
|
resetKey?: number
|
|
42
|
+
templates?: Template[]
|
|
35
43
|
}>()
|
|
36
44
|
|
|
37
45
|
const viewMode = defineModel<'preview' | 'source'>('viewMode', { default: 'preview' })
|
|
@@ -60,23 +68,30 @@ const iframeWidth = ref<number | null>(null)
|
|
|
60
68
|
const iframeHeight = ref<number | null>(null)
|
|
61
69
|
const iframeContentHeight = ref<number | null>(null)
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
function copySource() {
|
|
72
|
+
let text: string
|
|
64
73
|
if (sourceView.value === 'compiled') {
|
|
65
|
-
|
|
74
|
+
text = srcdoc.value
|
|
66
75
|
} else if (sourceView.value === 'plaintext') {
|
|
67
|
-
|
|
76
|
+
text = plaintextContent.value
|
|
68
77
|
} else {
|
|
69
78
|
const el = document.createElement('div')
|
|
70
79
|
el.innerHTML = vueSourceHtml.value
|
|
71
|
-
|
|
80
|
+
text = el.textContent || ''
|
|
72
81
|
}
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
})
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
interface CompatibilityIssue {
|
|
78
92
|
type: 'error' | 'warning'
|
|
79
93
|
title: string
|
|
94
|
+
category: string
|
|
80
95
|
clients: Array<{ name: string, notes: string[] }>
|
|
81
96
|
url?: string
|
|
82
97
|
line?: number
|
|
@@ -98,6 +113,15 @@ interface TemplateStats {
|
|
|
98
113
|
const compatibilityIssues = ref<CompatibilityIssue[]>([])
|
|
99
114
|
const compatibilityLoading = ref(false)
|
|
100
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
|
+
})
|
|
101
125
|
const lintIssues = ref<LintIssue[]>([])
|
|
102
126
|
const lintLoading = ref(false)
|
|
103
127
|
const stats = ref<TemplateStats | null>(null)
|
|
@@ -236,6 +260,9 @@ async function fetchCompatibility() {
|
|
|
236
260
|
compatibilityIssues.value = []
|
|
237
261
|
} else {
|
|
238
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 || ''
|
|
239
266
|
}
|
|
240
267
|
} catch {
|
|
241
268
|
compatibilityIssues.value = []
|
|
@@ -247,8 +274,11 @@ async function fetchCompatibility() {
|
|
|
247
274
|
async function fetchLint() {
|
|
248
275
|
lintLoading.value = true
|
|
249
276
|
try {
|
|
250
|
-
const
|
|
251
|
-
|
|
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) : []
|
|
252
282
|
} catch {
|
|
253
283
|
lintIssues.value = []
|
|
254
284
|
} finally {
|
|
@@ -360,7 +390,7 @@ const emit = defineEmits<{ 'clear-device': [] }>()
|
|
|
360
390
|
|
|
361
391
|
type Edge = 'left' | 'right' | 'top' | 'bottom'
|
|
362
392
|
|
|
363
|
-
function onEdgeDrag(e: MouseEvent, edge: Edge) {
|
|
393
|
+
function onEdgeDrag(e: MouseEvent | TouchEvent, edge: Edge) {
|
|
364
394
|
e.preventDefault()
|
|
365
395
|
isDragging.value = true
|
|
366
396
|
emit('clear-device')
|
|
@@ -368,8 +398,10 @@ function onEdgeDrag(e: MouseEvent, edge: Edge) {
|
|
|
368
398
|
const container = containerEl.value
|
|
369
399
|
if (!container) return
|
|
370
400
|
|
|
371
|
-
const
|
|
372
|
-
const
|
|
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
|
|
373
405
|
const rect = container.getBoundingClientRect()
|
|
374
406
|
const gutter = 40 // 20px padding on each side
|
|
375
407
|
const maxW = rect.width - gutter
|
|
@@ -380,13 +412,14 @@ function onEdgeDrag(e: MouseEvent, edge: Edge) {
|
|
|
380
412
|
const isHorizontal = edge === 'left' || edge === 'right'
|
|
381
413
|
const sign = (edge === 'left' || edge === 'top') ? -1 : 1
|
|
382
414
|
|
|
383
|
-
const onMove = (ev: MouseEvent) => {
|
|
415
|
+
const onMove = (ev: MouseEvent | TouchEvent) => {
|
|
416
|
+
const point = ev.type === 'touchmove' ? (ev as TouchEvent).touches[0] : (ev as MouseEvent)
|
|
384
417
|
if (isHorizontal) {
|
|
385
418
|
// Symmetric: each side moves by the delta, so total change is 2x
|
|
386
|
-
const delta = (
|
|
419
|
+
const delta = (point.clientX - startX) * sign
|
|
387
420
|
iframeWidth.value = Math.max(200, Math.min(maxW, startW + delta * 2))
|
|
388
421
|
} else {
|
|
389
|
-
const delta = (
|
|
422
|
+
const delta = (point.clientY - startY) * sign
|
|
390
423
|
iframeHeight.value = Math.max(100, Math.min(maxH, startH + delta * 2))
|
|
391
424
|
}
|
|
392
425
|
}
|
|
@@ -396,10 +429,14 @@ function onEdgeDrag(e: MouseEvent, edge: Edge) {
|
|
|
396
429
|
updateFullSize()
|
|
397
430
|
document.removeEventListener('mousemove', onMove)
|
|
398
431
|
document.removeEventListener('mouseup', onUp)
|
|
432
|
+
document.removeEventListener('touchmove', onMove)
|
|
433
|
+
document.removeEventListener('touchend', onUp)
|
|
399
434
|
}
|
|
400
435
|
|
|
401
436
|
document.addEventListener('mousemove', onMove)
|
|
402
437
|
document.addEventListener('mouseup', onUp)
|
|
438
|
+
document.addEventListener('touchmove', onMove, { passive: false })
|
|
439
|
+
document.addEventListener('touchend', onUp)
|
|
403
440
|
}
|
|
404
441
|
|
|
405
442
|
function updateFullSize() {
|
|
@@ -568,53 +605,53 @@ const stripeBg = {
|
|
|
568
605
|
<div v-show="viewMode === 'source'" class="absolute inset-0 min-w-0 overflow-hidden">
|
|
569
606
|
<div class="absolute top-3 left-6 z-10">
|
|
570
607
|
<DropdownMenu :modal="false">
|
|
571
|
-
<DropdownMenuTrigger class="inline-flex items-center gap-1 rounded-md 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">
|
|
572
609
|
{{ sourceView === 'compiled' ? 'HTML' : sourceView === 'vue' ? 'Source' : 'Plaintext' }}
|
|
573
610
|
<ChevronDown class="size-3 opacity-50" />
|
|
574
611
|
</DropdownMenuTrigger>
|
|
575
|
-
<DropdownMenuContent align="start" class="min-w-32 bg-
|
|
576
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
577
|
-
<Check v-if="sourceView === 'vue'" class="size-3 text-gray-
|
|
578
|
-
<span :class="[sourceView === 'vue' ? 'text-gray-
|
|
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>
|
|
579
616
|
</DropdownMenuItem>
|
|
580
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
581
|
-
<Check v-if="sourceView === 'compiled'" class="size-3 text-gray-
|
|
582
|
-
<span :class="[sourceView === 'compiled' ? 'text-gray-
|
|
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>
|
|
583
620
|
</DropdownMenuItem>
|
|
584
|
-
<DropdownMenuItem class="text-xs font-medium text-gray-
|
|
585
|
-
<Check v-if="sourceView === 'plaintext'" class="size-3 text-gray-
|
|
586
|
-
<span :class="[sourceView === 'plaintext' ? 'text-gray-
|
|
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>
|
|
587
624
|
</DropdownMenuItem>
|
|
588
625
|
</DropdownMenuContent>
|
|
589
626
|
</DropdownMenu>
|
|
590
627
|
</div>
|
|
591
628
|
<button
|
|
592
|
-
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"
|
|
593
630
|
:disabled="copied"
|
|
594
631
|
@click="copySource"
|
|
595
632
|
>
|
|
596
|
-
<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>
|
|
597
|
-
<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>
|
|
598
635
|
</button>
|
|
599
|
-
<ScrollArea v-show="sourceView === 'compiled'" class="h-full">
|
|
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">
|
|
600
637
|
<div
|
|
601
638
|
ref="compiledSourceEl"
|
|
602
|
-
class="shiki-line-numbers [&_pre]:p-6 [&_pre]:pt-14 [&_pre]:text-base [&_pre]:leading-6 [&_pre]:min-h-full dark:[&_pre]:bg-gray-950!"
|
|
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!"
|
|
603
640
|
v-html="sourceHtml"
|
|
604
641
|
/>
|
|
605
642
|
<ScrollBar orientation="horizontal" />
|
|
606
643
|
</ScrollArea>
|
|
607
|
-
<ScrollArea v-show="sourceView === 'vue'" class="h-full">
|
|
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">
|
|
608
645
|
<div
|
|
609
646
|
ref="vueSourceEl"
|
|
610
|
-
class="shiki-line-numbers [&_pre]:p-6 [&_pre]:pt-14 [&_pre]:text-base [&_pre]:leading-6 [&_pre]:min-h-full dark:[&_pre]:bg-gray-950!"
|
|
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!"
|
|
611
648
|
v-html="vueSourceHtml"
|
|
612
649
|
/>
|
|
613
650
|
<ScrollBar orientation="horizontal" />
|
|
614
651
|
</ScrollArea>
|
|
615
|
-
<ScrollArea v-show="sourceView === 'plaintext'" class="h-full">
|
|
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">
|
|
616
653
|
<pre
|
|
617
|
-
class="p-6 pt-14 text-sm leading-6
|
|
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"
|
|
618
655
|
>{{ plaintextContent }}</pre>
|
|
619
656
|
</ScrollArea>
|
|
620
657
|
</div>
|
|
@@ -639,23 +676,23 @@ const stripeBg = {
|
|
|
639
676
|
}"
|
|
640
677
|
>
|
|
641
678
|
<!-- Top handle -->
|
|
642
|
-
<div class="group absolute top-0 left-5 right-5 h-5
|
|
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')">
|
|
643
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" />
|
|
644
681
|
</div>
|
|
645
682
|
<!-- Bottom handle -->
|
|
646
|
-
<div class="group absolute bottom-0 left-5 right-5 h-5
|
|
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')">
|
|
647
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" />
|
|
648
685
|
</div>
|
|
649
686
|
<!-- Left handle -->
|
|
650
|
-
<div class="group absolute left-0 top-5 bottom-5 w-5
|
|
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')">
|
|
651
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" />
|
|
652
689
|
</div>
|
|
653
690
|
<!-- Right handle -->
|
|
654
|
-
<div class="group absolute right-0 top-5 bottom-5 w-5
|
|
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')">
|
|
655
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" />
|
|
656
693
|
</div>
|
|
657
694
|
<!-- Iframe -->
|
|
658
|
-
<div ref="wrapperEl" class="absolute inset-5 border border-gray-200 dark:border-gray-800">
|
|
695
|
+
<div ref="wrapperEl" class="absolute inset-0 min-[430px]:inset-5 border border-gray-200 dark:border-gray-800">
|
|
659
696
|
<ScrollArea class="h-full w-full bg-white dark:bg-gray-950">
|
|
660
697
|
<iframe
|
|
661
698
|
ref="iframeEl"
|
|
@@ -684,7 +721,7 @@ const stripeBg = {
|
|
|
684
721
|
@mousedown="onTabsDragStart"
|
|
685
722
|
/>
|
|
686
723
|
<Tabs :model-value="activeTab" class="flex flex-col min-h-0 h-full">
|
|
687
|
-
<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' : ''">
|
|
688
725
|
<TabsList class="h-full bg-transparent! rounded-none! p-0 gap-1">
|
|
689
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')">
|
|
690
727
|
Compatibility
|
|
@@ -705,128 +742,156 @@ const stripeBg = {
|
|
|
705
742
|
</Button>
|
|
706
743
|
</div>
|
|
707
744
|
<div class="flex-1 min-h-0">
|
|
708
|
-
<TabsContent value="compatibility" class="mt-0 h-full"><
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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"
|
|
717
754
|
>
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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'">
|
|
721
771
|
{{ issue.title }}
|
|
722
772
|
</a>
|
|
723
|
-
<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'">
|
|
724
774
|
{{ issue.title }}
|
|
725
775
|
</span>
|
|
726
|
-
<
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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>
|
|
730
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>
|
|
731
802
|
</div>
|
|
732
|
-
|
|
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>
|
|
733
830
|
</div>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
<TabsContent value="lint" class="mt-0 h-full"><ScrollArea class="h-full">
|
|
738
|
-
<p v-if="lintLoading" class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">Linting...</p>
|
|
739
|
-
<p v-else-if="lintIssues.length === 0" class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">No issues found.</p>
|
|
740
|
-
<ul v-else class="text-xs divide-y">
|
|
741
|
-
<li
|
|
742
|
-
v-for="(issue, i) in lintIssues"
|
|
743
|
-
:key="i"
|
|
744
|
-
class="px-4 py-2 hover:bg-gray-50 dark:hover:bg-white/5"
|
|
745
|
-
>
|
|
746
|
-
<div class="flex items-start justify-between gap-4">
|
|
747
|
-
<div>
|
|
748
|
-
<span class="font-medium" :class="issue.type === 'error' ? 'text-red-600' : 'text-amber-600'">
|
|
749
|
-
{{ issue.title }}
|
|
750
|
-
</span>
|
|
751
|
-
<div class="text-gray-500 dark:text-gray-400 mt-0.5">{{ issue.message }}</div>
|
|
752
|
-
</div>
|
|
753
|
-
<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>
|
|
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>
|
|
754
834
|
</div>
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
<TabsContent value="stats" class="mt-0 h-full"><ScrollArea class="h-full">
|
|
759
|
-
<p v-if="statsLoading" class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">Loading stats...</p>
|
|
760
|
-
<p v-else-if="!stats" class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">No stats available.</p>
|
|
761
|
-
<div v-else class="px-4 py-3 flex items-center gap-6 text-xs">
|
|
762
|
-
<div class="flex items-center gap-1.5">
|
|
763
|
-
<span class="text-gray-500 dark:text-gray-400">Size</span>
|
|
764
|
-
<span
|
|
765
|
-
class="font-medium tabular-nums"
|
|
766
|
-
:class="stats.size.bytes > 102400 ? 'text-red-600' : stats.size.bytes > 51200 ? 'text-amber-600' : 'text-gray-900 dark:text-gray-300'"
|
|
767
|
-
>{{ stats.size.formatted }}</span>
|
|
768
|
-
</div>
|
|
769
|
-
<div class="flex items-center gap-1.5">
|
|
770
|
-
<span class="text-gray-500 dark:text-gray-400">Images</span>
|
|
771
|
-
<span class="font-medium tabular-nums">{{ stats.images }}</span>
|
|
772
|
-
</div>
|
|
773
|
-
<div class="flex items-center gap-1.5">
|
|
774
|
-
<span class="text-gray-500 dark:text-gray-400">Links</span>
|
|
775
|
-
<span class="font-medium tabular-nums">{{ stats.links }}</span>
|
|
776
|
-
</div>
|
|
777
|
-
</div>
|
|
778
|
-
</ScrollArea></TabsContent>
|
|
779
|
-
<TabsContent value="test" class="mt-0 h-full"><ScrollArea class="h-full">
|
|
780
|
-
<div class="px-4 py-3 max-w-md">
|
|
781
|
-
<div class="space-y-2">
|
|
782
|
-
<div class="flex items-center gap-2">
|
|
783
|
-
<label class="text-xs text-gray-500 dark:text-gray-400 w-12 shrink-0">To</label>
|
|
784
|
-
<TagsInput v-model="emailTo" delimiter=" " add-on-paste class="flex-1 min-h-7 gap-1 px-2 py-1">
|
|
785
|
-
<TagsInputItem v-for="item in emailTo" :key="item" :value="item" class="h-5 text-xs rounded">
|
|
786
|
-
<TagsInputItemText class="px-1.5 py-0 text-xs" />
|
|
787
|
-
<TagsInputItemDelete class="size-3.5" />
|
|
788
|
-
</TagsInputItem>
|
|
789
|
-
<TagsInputInput class="text-xs min-h-5 px-0.5" placeholder="Add emails..." />
|
|
790
|
-
</TagsInput>
|
|
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>
|
|
791
838
|
</div>
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
839
|
+
</div>
|
|
840
|
+
</ScrollArea>
|
|
841
|
+
</TabsContent>
|
|
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>
|
|
800
865
|
</div>
|
|
801
866
|
</div>
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
@click="sendTestEmail"
|
|
809
|
-
>
|
|
810
|
-
<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>
|
|
811
|
-
{{ emailSending ? 'Sending' : 'Send' }}
|
|
812
|
-
</Button>
|
|
813
|
-
</div>
|
|
814
|
-
<div v-if="emailResult" class="mt-2">
|
|
815
|
-
<p class="text-xs" :class="emailResult.success ? 'text-gray-950 dark:text-white' : 'text-red-600'">
|
|
816
|
-
{{ emailResult.message }}
|
|
817
|
-
<a
|
|
818
|
-
v-if="emailResult.previewUrl"
|
|
819
|
-
:href="emailResult.previewUrl"
|
|
820
|
-
target="_blank"
|
|
821
|
-
rel="noopener"
|
|
822
|
-
class="inline-flex items-center gap-0.5 text-gray-500 dark:text-gray-400 hover:underline ml-1"
|
|
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"
|
|
823
873
|
>
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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>
|
|
827
892
|
</div>
|
|
828
|
-
</
|
|
829
|
-
</
|
|
893
|
+
</ScrollArea>
|
|
894
|
+
</TabsContent>
|
|
830
895
|
</div>
|
|
831
896
|
</Tabs>
|
|
832
897
|
</div>
|
|
@@ -9,20 +9,7 @@ import { ChildNode } from "domhandler";
|
|
|
9
9
|
* This is important for email client compatibility (especially Outlook on Windows).
|
|
10
10
|
*
|
|
11
11
|
* Enabled when `css.inline` is set to `true` or an object with options.
|
|
12
|
-
*
|
|
13
|
-
* Options:
|
|
14
|
-
* - removeStyleTags: Remove style tags after inlining (default: false)
|
|
15
|
-
* - removeInlinedSelectors: Remove classes after they've been inlined (default: true)
|
|
16
|
-
* - preferUnitlessValues: Convert 0px, 0em, etc. to 0 (default: true)
|
|
17
|
-
* - safelist: Selectors that should not be removed after inlining
|
|
18
|
-
* - styleToAttribute: Map CSS properties to HTML attributes (e.g., background-color -> bgcolor)
|
|
19
|
-
* - applyWidthAttributes: Add width attributes based on inline CSS (default: true)
|
|
20
|
-
* - applyHeightAttributes: Add height attributes based on inline CSS (default: true)
|
|
21
|
-
* - widthElements: Elements that can receive width attributes (default: ['img', 'video'])
|
|
22
|
-
* - heightElements: Elements that can receive height attributes (default: ['img', 'video'])
|
|
23
|
-
* - excludedProperties: CSS properties to exclude from inlining
|
|
24
|
-
* - codeBlocks: Fenced code blocks to ignore (default: { EJS: { start: '<%', end: '%>' }, HBS: { start: '{{', end: '}}' } })
|
|
25
|
-
* - customCSS: Additional CSS to inline
|
|
12
|
+
* All Juice options are supported and passed through directly.
|
|
26
13
|
*/
|
|
27
14
|
declare function inlineCSS(dom: ChildNode[], config?: CssConfig): ChildNode[];
|
|
28
15
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inlineCSS.d.mts","names":[],"sources":["../../src/transformers/inlineCSS.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"inlineCSS.d.mts","names":[],"sources":["../../src/transformers/inlineCSS.ts"],"mappings":";;;;;;AAeA;;;;;;;iBAAgB,SAAA,CAAU,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,SAAA,GAAiB,SAAA"}
|