@maizzle/framework 6.0.0-rc.14 → 6.0.0-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/components/Body.vue +9 -2
- package/dist/components/Button.vue +13 -8
- package/dist/components/Column.vue +22 -8
- package/dist/components/Container.vue +9 -5
- package/dist/components/Head.vue +1 -1
- package/dist/components/Html.vue +7 -2
- package/dist/components/Layout.vue +45 -15
- package/dist/components/Outlook.vue +38 -11
- package/dist/components/Overlap.vue +8 -3
- package/dist/components/Raw.vue +28 -0
- package/dist/components/Row.vue +72 -11
- package/dist/components/Section.vue +9 -5
- package/dist/components/Spacer.vue +9 -4
- package/dist/components/utils.d.mts +11 -1
- package/dist/components/utils.d.mts.map +1 -1
- package/dist/components/utils.mjs +11 -1
- package/dist/components/utils.mjs.map +1 -1
- package/dist/components/utils.ts +12 -0
- package/dist/composables/useOutlookFallback.d.mts +21 -0
- package/dist/composables/useOutlookFallback.d.mts.map +1 -0
- package/dist/composables/useOutlookFallback.mjs +30 -0
- package/dist/composables/useOutlookFallback.mjs.map +1 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/prepare.d.mts +17 -0
- package/dist/prepare.d.mts.map +1 -0
- package/dist/prepare.mjs +44 -0
- package/dist/prepare.mjs.map +1 -0
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +9 -75
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/render/plugins/codeBlockExtract.d.mts +14 -0
- package/dist/render/plugins/codeBlockExtract.d.mts.map +1 -0
- package/dist/render/plugins/codeBlockExtract.mjs +34 -0
- package/dist/render/plugins/codeBlockExtract.mjs.map +1 -0
- package/dist/render/plugins/markdownExtract.d.mts +12 -0
- package/dist/render/plugins/markdownExtract.d.mts.map +1 -0
- package/dist/render/plugins/markdownExtract.mjs +50 -0
- package/dist/render/plugins/markdownExtract.mjs.map +1 -0
- package/dist/render/plugins/rawExtract.d.mts +14 -0
- package/dist/render/plugins/rawExtract.d.mts.map +1 -0
- package/dist/render/plugins/rawExtract.mjs +34 -0
- package/dist/render/plugins/rawExtract.mjs.map +1 -0
- package/dist/render/plugins/rowSourceLocation.d.mts +18 -0
- package/dist/render/plugins/rowSourceLocation.d.mts.map +1 -0
- package/dist/render/plugins/rowSourceLocation.mjs +45 -0
- package/dist/render/plugins/rowSourceLocation.mjs.map +1 -0
- package/dist/server/ui/App.vue +47 -2
- package/dist/server/ui/pages/Preview.vue +32 -3
- package/dist/transformers/columnWidth.d.mts +9 -9
- package/dist/transformers/columnWidth.d.mts.map +1 -1
- package/dist/transformers/columnWidth.mjs +422 -41
- package/dist/transformers/columnWidth.mjs.map +1 -1
- package/dist/transformers/filters/index.mjs +1 -1
- package/dist/transformers/filters/index.mjs.map +1 -1
- package/dist/transformers/index.mjs +1 -1
- package/dist/transformers/index.mjs.map +1 -1
- package/node_modules/maizzle/dist/commands/new.mjs +3 -3
- package/node_modules/maizzle/dist/index.d.mts +1 -0
- package/node_modules/maizzle/dist/index.mjs +3 -0
- package/node_modules/maizzle/package.json +3 -2
- package/node_modules/nypm/dist/cli.mjs +28 -5
- package/node_modules/nypm/dist/index.d.mts +0 -8
- package/node_modules/nypm/dist/index.mjs +27 -4
- package/node_modules/nypm/package.json +12 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</picture>
|
|
8
8
|
</a>
|
|
9
9
|
</p>
|
|
10
|
-
<p>
|
|
10
|
+
<p>The modern email development framework</p>
|
|
11
11
|
<div>
|
|
12
12
|
|
|
13
13
|
[![Version][npm-version-shield]][npm]
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
## About
|
|
22
22
|
|
|
23
|
-
> **Note:** This repository contains the core code of the Maizzle framework. If you want to
|
|
23
|
+
> **Note:** This repository contains the core code of the Maizzle framework. If you want to start building HTML emails using Maizzle, visit the [Starter repository](https://github.com/maizzle/maizzle).
|
|
24
24
|
|
|
25
|
-
Maizzle is a framework
|
|
25
|
+
Maizzle is a Vite-powered framework for building HTML emails with Vue and Tailwind CSS.
|
|
26
26
|
|
|
27
27
|
## Documentation
|
|
28
28
|
|
package/dist/components/Body.vue
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { createStaticVNode, inject, useAttrs, useSlots } from 'vue'
|
|
3
3
|
import type { PropType } from 'vue'
|
|
4
|
+
import { outlookFallbackProp } from './utils.ts'
|
|
5
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
4
6
|
|
|
5
7
|
defineOptions({ inheritAttrs: false })
|
|
6
8
|
|
|
@@ -65,9 +67,12 @@ const props = defineProps({
|
|
|
65
67
|
ariaLabel: {
|
|
66
68
|
type: String,
|
|
67
69
|
default: undefined
|
|
68
|
-
}
|
|
70
|
+
},
|
|
71
|
+
outlookFallback: outlookFallbackProp,
|
|
69
72
|
})
|
|
70
73
|
|
|
74
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
75
|
+
|
|
71
76
|
const htmlLang = inject<string>('htmlLang', 'en')
|
|
72
77
|
|
|
73
78
|
const render = () => {
|
|
@@ -78,10 +83,12 @@ const render = () => {
|
|
|
78
83
|
const lang = props.xmlLang ?? htmlLang
|
|
79
84
|
|
|
80
85
|
const parts = [
|
|
81
|
-
`xml:lang="${lang}"`,
|
|
82
86
|
`dir="${props.dir}"`,
|
|
83
87
|
'style="margin: 0; padding: 0; width: 100%; word-break: break-word;"',
|
|
84
88
|
]
|
|
89
|
+
if (outlookFallback) {
|
|
90
|
+
parts.unshift(`xml:lang="${lang}"`)
|
|
91
|
+
}
|
|
85
92
|
|
|
86
93
|
if (extraAttrs) {
|
|
87
94
|
parts.push(extraAttrs)
|
|
@@ -3,6 +3,8 @@ import { computed, useAttrs } from 'vue'
|
|
|
3
3
|
import type { PropType } from 'vue'
|
|
4
4
|
import { twMerge } from 'tailwind-merge'
|
|
5
5
|
import Outlook from './Outlook.vue'
|
|
6
|
+
import { outlookFallbackProp } from './utils.ts'
|
|
7
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
6
8
|
|
|
7
9
|
defineOptions({ inheritAttrs: false })
|
|
8
10
|
|
|
@@ -103,9 +105,12 @@ const props = defineProps({
|
|
|
103
105
|
iconClass: {
|
|
104
106
|
type: String,
|
|
105
107
|
default: ''
|
|
106
|
-
}
|
|
108
|
+
},
|
|
109
|
+
outlookFallback: outlookFallbackProp,
|
|
107
110
|
})
|
|
108
111
|
|
|
112
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
113
|
+
|
|
109
114
|
const parsedIconWidth = computed(() => parseInt(String(props.iconWidth), 10))
|
|
110
115
|
|
|
111
116
|
const alignClass = computed(() => props.align ? ({
|
|
@@ -171,21 +176,21 @@ const mergedClass = computed(() => twMerge(defaultClasses.value, attrs.class as
|
|
|
171
176
|
:class="mergedClass"
|
|
172
177
|
>
|
|
173
178
|
<template v-if="!isLink">
|
|
174
|
-
<Outlook><i class="mso-font-width-[150%]" :style="`mso-text-raise: ${msoPb};`" hidden> </i></Outlook>
|
|
179
|
+
<Outlook v-if="outlookFallback"><i class="mso-font-width-[150%]" :style="`mso-text-raise: ${msoPb};`" hidden> </i></Outlook>
|
|
175
180
|
<template v-if="icon && iconPosition === 'left'">
|
|
176
|
-
<span :style="`mso-text-raise: ${msoPt}`">
|
|
181
|
+
<span :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined">
|
|
177
182
|
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
|
|
178
183
|
</span>
|
|
179
|
-
<Outlook><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
184
|
+
<Outlook v-if="outlookFallback"><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
180
185
|
</template>
|
|
181
|
-
<span :class="icon ? (iconPosition === 'right' ? 'mr-2' : 'ml-2') : ''" :style="`mso-text-raise: ${msoPt}`"><slot /></span>
|
|
186
|
+
<span :class="icon ? (iconPosition === 'right' ? 'mr-2' : 'ml-2') : ''" :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined"><slot /></span>
|
|
182
187
|
<template v-if="icon && iconPosition === 'right'">
|
|
183
|
-
<Outlook><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
184
|
-
<span :style="`mso-text-raise: ${msoPt}`">
|
|
188
|
+
<Outlook v-if="outlookFallback"><i class="mso-font-width-[30%]" hidden> ​</i></Outlook>
|
|
189
|
+
<span :style="outlookFallback ? `mso-text-raise: ${msoPt}` : undefined">
|
|
185
190
|
<img :src="icon" :width="parsedIconWidth" :class="`align-baseline max-w-full ${iconClass}`" alt="">
|
|
186
191
|
</span>
|
|
187
192
|
</template>
|
|
188
|
-
<Outlook><i class="mso-font-width-[150%]" hidden> ​</i></Outlook>
|
|
193
|
+
<Outlook v-if="outlookFallback"><i class="mso-font-width-[150%]" hidden> ​</i></Outlook>
|
|
189
194
|
</template>
|
|
190
195
|
<template v-else>
|
|
191
196
|
<slot />
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, createStaticVNode, inject, useAttrs } from 'vue'
|
|
3
3
|
import type { ComputedRef } from 'vue'
|
|
4
|
-
import {
|
|
4
|
+
import { twMerge } from 'tailwind-merge'
|
|
5
|
+
import { nextId, normalizeToPixels, outlookFallbackProp } from './utils.ts'
|
|
6
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
5
7
|
|
|
6
8
|
defineOptions({ inheritAttrs: false })
|
|
7
9
|
|
|
@@ -29,9 +31,12 @@ const props = defineProps({
|
|
|
29
31
|
msoStyle: {
|
|
30
32
|
type: String,
|
|
31
33
|
default: undefined
|
|
32
|
-
}
|
|
34
|
+
},
|
|
35
|
+
outlookFallback: outlookFallbackProp,
|
|
33
36
|
})
|
|
34
37
|
|
|
38
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
39
|
+
|
|
35
40
|
const columnCount = inject<ComputedRef<number> | null>('columnCount', null)
|
|
36
41
|
|
|
37
42
|
const count = computed(() => columnCount?.value ?? 2)
|
|
@@ -49,9 +54,17 @@ const msoWidth = computed(() => {
|
|
|
49
54
|
return `__MAIZZLE_COLW_${colId}__`
|
|
50
55
|
})
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Baseline display/typography lives in classes — not inline `:style` —
|
|
59
|
+
* so the user can override any of them via tailwind utilities. Inline
|
|
60
|
+
* `display: inline-block` would silently shadow a class like
|
|
61
|
+
* `inline-table` during CSS inlining; routing both through twMerge lets
|
|
62
|
+
* the user's utility cleanly replace ours instead of being dropped.
|
|
63
|
+
*/
|
|
64
|
+
const baseClass = 'inline-block align-top text-base'
|
|
65
|
+
const mergedClass = computed(() => twMerge(baseClass, (attrs.class as string) ?? ''))
|
|
66
|
+
|
|
67
|
+
const styles = computed(() => `min-width: ${minWidth.value};`)
|
|
55
68
|
|
|
56
69
|
const tdStyle = computed(() => {
|
|
57
70
|
const parts = [`width: ${msoWidth.value}`, 'vertical-align: top']
|
|
@@ -71,14 +84,15 @@ const MsoAfter = () => createStaticVNode(
|
|
|
71
84
|
</script>
|
|
72
85
|
|
|
73
86
|
<template>
|
|
74
|
-
<MsoBefore />
|
|
87
|
+
<MsoBefore v-if="outlookFallback" />
|
|
75
88
|
<div
|
|
76
|
-
v-bind="attrs"
|
|
89
|
+
v-bind="{ ...attrs, class: undefined }"
|
|
90
|
+
:class="mergedClass"
|
|
77
91
|
:style="styles"
|
|
78
92
|
:data-maizzle-cw-id="colId"
|
|
79
93
|
:data-maizzle-cw-count="useMarker ? count : null"
|
|
80
94
|
>
|
|
81
95
|
<slot />
|
|
82
96
|
</div>
|
|
83
|
-
<MsoAfter />
|
|
97
|
+
<MsoAfter v-if="outlookFallback" />
|
|
84
98
|
</template>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, provide, createStaticVNode, useAttrs } from 'vue'
|
|
3
3
|
import { twMerge } from 'tailwind-merge'
|
|
4
|
-
import { hasWidthUtility, nextId, normalizeToPixels } from './utils.ts'
|
|
4
|
+
import { hasWidthUtility, nextId, normalizeToPixels, outlookFallbackProp } from './utils.ts'
|
|
5
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
5
6
|
|
|
6
7
|
defineOptions({ inheritAttrs: false })
|
|
7
8
|
|
|
@@ -33,12 +34,15 @@ const props = defineProps({
|
|
|
33
34
|
msoWidth: {
|
|
34
35
|
type: [String, Number],
|
|
35
36
|
default: null
|
|
36
|
-
}
|
|
37
|
+
},
|
|
38
|
+
outlookFallback: outlookFallbackProp,
|
|
37
39
|
})
|
|
38
40
|
|
|
41
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
42
|
+
|
|
39
43
|
provide('containerWidth', computed(() => props.width))
|
|
40
44
|
|
|
41
|
-
const useMarker = props.width == null && props.msoWidth == null
|
|
45
|
+
const useMarker = outlookFallback && props.width == null && props.msoWidth == null
|
|
42
46
|
const msoId = useMarker ? nextId('c') : null
|
|
43
47
|
|
|
44
48
|
const styles = computed(() => {
|
|
@@ -77,7 +81,7 @@ const MsoAfter = () => createStaticVNode(
|
|
|
77
81
|
</script>
|
|
78
82
|
|
|
79
83
|
<template>
|
|
80
|
-
<MsoBefore />
|
|
84
|
+
<MsoBefore v-if="outlookFallback" />
|
|
81
85
|
<div
|
|
82
86
|
v-bind="{ ...attrs, class: undefined }"
|
|
83
87
|
:class="mergedClass"
|
|
@@ -87,5 +91,5 @@ const MsoAfter = () => createStaticVNode(
|
|
|
87
91
|
>
|
|
88
92
|
<slot />
|
|
89
93
|
</div>
|
|
90
|
-
<MsoAfter />
|
|
94
|
+
<MsoAfter v-if="outlookFallback" />
|
|
91
95
|
</template>
|
package/dist/components/Head.vue
CHANGED
|
@@ -6,7 +6,7 @@ const props = defineProps({
|
|
|
6
6
|
* Render an empty `<head>` before the main head element.
|
|
7
7
|
*
|
|
8
8
|
* This is a workaround for Yahoo! Mail on Android, which
|
|
9
|
-
* strips
|
|
9
|
+
* strips the first `<head>` element it finds.
|
|
10
10
|
*
|
|
11
11
|
* @default false
|
|
12
12
|
*/
|
package/dist/components/Html.vue
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { createStaticVNode, provide, useAttrs, useSlots } from 'vue'
|
|
3
3
|
import type { PropType } from 'vue'
|
|
4
|
+
import { outlookFallbackProp } from './utils.ts'
|
|
5
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
4
6
|
|
|
5
7
|
defineOptions({ inheritAttrs: false })
|
|
6
8
|
|
|
@@ -65,9 +67,12 @@ const props = defineProps({
|
|
|
65
67
|
xmlns: {
|
|
66
68
|
type: [Boolean, String],
|
|
67
69
|
default: true
|
|
68
|
-
}
|
|
70
|
+
},
|
|
71
|
+
outlookFallback: outlookFallbackProp,
|
|
69
72
|
})
|
|
70
73
|
|
|
74
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
75
|
+
|
|
71
76
|
provide('htmlLang', props.lang)
|
|
72
77
|
|
|
73
78
|
const render = () => {
|
|
@@ -80,7 +85,7 @@ const render = () => {
|
|
|
80
85
|
`dir="${props.dir}"`,
|
|
81
86
|
]
|
|
82
87
|
|
|
83
|
-
if (props.xmlns !== false && props.xmlns !== 'false') {
|
|
88
|
+
if (outlookFallback && props.xmlns !== false && props.xmlns !== 'false') {
|
|
84
89
|
parts.push(
|
|
85
90
|
'xmlns:v="urn:schemas-microsoft-com:vml"',
|
|
86
91
|
'xmlns:o="urn:schemas-microsoft-com:office:office"',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, useAttrs, type PropType } from 'vue'
|
|
2
|
+
import { computed, useAttrs, createStaticVNode, type PropType } from 'vue'
|
|
3
3
|
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import { outlookFallbackProp } from './utils.ts'
|
|
5
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
4
6
|
|
|
5
7
|
defineOptions({ inheritAttrs: false })
|
|
6
8
|
|
|
@@ -30,6 +32,18 @@ const props = defineProps({
|
|
|
30
32
|
type: String as PropType<'ltr' | 'rtl'>,
|
|
31
33
|
default: 'ltr'
|
|
32
34
|
},
|
|
35
|
+
/**
|
|
36
|
+
* Render an empty `<head>` before the main head element.
|
|
37
|
+
*
|
|
38
|
+
* This is a workaround for Yahoo! Mail on Android, which
|
|
39
|
+
* strips the first `<head>` element it finds.
|
|
40
|
+
*
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
doubleHead: {
|
|
44
|
+
type: [Boolean, String],
|
|
45
|
+
default: false
|
|
46
|
+
},
|
|
33
47
|
/**
|
|
34
48
|
* Accessible label for the email article wrapper.
|
|
35
49
|
*
|
|
@@ -40,24 +54,20 @@ const props = defineProps({
|
|
|
40
54
|
ariaLabel: {
|
|
41
55
|
type: String,
|
|
42
56
|
default: undefined
|
|
43
|
-
}
|
|
57
|
+
},
|
|
58
|
+
outlookFallback: outlookFallbackProp,
|
|
44
59
|
})
|
|
45
60
|
|
|
61
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
62
|
+
|
|
46
63
|
const attrs = useAttrs()
|
|
47
64
|
const bodyMergedClass = computed(() => twMerge('m-0 p-0 size-full [word-break:break-word]', props.bodyClass))
|
|
48
65
|
const articleMergedClass = computed(() => twMerge('[font-size:max(16px,1rem)] font-inter', attrs.class as string))
|
|
49
|
-
</script>
|
|
50
66
|
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<meta name="x-apple-disable-message-reformatting">
|
|
56
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
57
|
-
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
|
|
58
|
-
<meta name="color-scheme" content="light dark">
|
|
59
|
-
<meta name="supported-color-schemes" content="light dark">
|
|
60
|
-
<!--[if mso]>
|
|
67
|
+
const EmptyHead = () => createStaticVNode('<head></head>', 1)
|
|
68
|
+
|
|
69
|
+
const MsoHead = () => createStaticVNode(
|
|
70
|
+
`<!--[if mso]>
|
|
61
71
|
<noscript>
|
|
62
72
|
<xml>
|
|
63
73
|
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
@@ -69,7 +79,27 @@ const articleMergedClass = computed(() => twMerge('[font-size:max(16px,1rem)] fo
|
|
|
69
79
|
td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
|
|
70
80
|
.mso-break-all {word-break: break-all;}
|
|
71
81
|
</style>
|
|
72
|
-
<![endif]
|
|
82
|
+
<![endif]-->`,
|
|
83
|
+
1
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const htmlXmlns = computed(() => outlookFallback ? {
|
|
87
|
+
'xmlns:v': 'urn:schemas-microsoft-com:vml',
|
|
88
|
+
'xmlns:o': 'urn:schemas-microsoft-com:office:office',
|
|
89
|
+
} : {})
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<html :lang="lang" :dir="dir" v-bind="htmlXmlns">
|
|
94
|
+
<EmptyHead v-if="props.doubleHead === true || props.doubleHead === 'true'" />
|
|
95
|
+
<head>
|
|
96
|
+
<meta charset="utf-8">
|
|
97
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
98
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
99
|
+
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
|
|
100
|
+
<meta name="color-scheme" content="light dark">
|
|
101
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
102
|
+
<MsoHead v-if="outlookFallback" />
|
|
73
103
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
74
104
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
|
|
75
105
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
|
|
@@ -81,7 +111,7 @@ const articleMergedClass = computed(() => twMerge('[font-size:max(16px,1rem)] fo
|
|
|
81
111
|
}
|
|
82
112
|
</style>
|
|
83
113
|
</head>
|
|
84
|
-
<body :xml:lang="lang" :class="bodyMergedClass">
|
|
114
|
+
<body :xml:lang="outlookFallback ? lang : null" :class="bodyMergedClass">
|
|
85
115
|
<div
|
|
86
116
|
role="article"
|
|
87
117
|
aria-roledescription="email"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { computed, createStaticVNode } from 'vue'
|
|
2
|
+
import { computed, createStaticVNode, type PropType } from 'vue'
|
|
3
3
|
|
|
4
4
|
const VERSION_MAP = {
|
|
5
5
|
2003: 11,
|
|
@@ -10,7 +10,10 @@ const VERSION_MAP = {
|
|
|
10
10
|
2019: 16
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
type Year = `${keyof typeof VERSION_MAP}`
|
|
14
|
+
type YearList = Year | (string & {})
|
|
15
|
+
|
|
16
|
+
const toMso = (v: string) => VERSION_MAP[v as unknown as keyof typeof VERSION_MAP]
|
|
14
17
|
|
|
15
18
|
const parseList = (value: string) =>
|
|
16
19
|
value
|
|
@@ -30,7 +33,7 @@ export default {
|
|
|
30
33
|
* @example '2013'
|
|
31
34
|
* @example '2013,2016'
|
|
32
35
|
*/
|
|
33
|
-
only: String
|
|
36
|
+
only: String as PropType<YearList>,
|
|
34
37
|
/**
|
|
35
38
|
* Render content in all Outlook versions except the specified one(s).
|
|
36
39
|
*
|
|
@@ -39,31 +42,55 @@ export default {
|
|
|
39
42
|
* @example '2007'
|
|
40
43
|
* @example '2007,2010'
|
|
41
44
|
*/
|
|
42
|
-
not: String
|
|
45
|
+
not: String as PropType<YearList>,
|
|
43
46
|
/**
|
|
44
47
|
* Render content in Outlook versions lower than the specified year.
|
|
45
48
|
*
|
|
46
49
|
* @example '2013'
|
|
47
50
|
*/
|
|
48
|
-
lt: String
|
|
51
|
+
lt: String as PropType<Year>,
|
|
49
52
|
/**
|
|
50
53
|
* Render content in Outlook versions lower than or equal to the specified year.
|
|
51
54
|
*
|
|
52
55
|
* @example '2013'
|
|
53
56
|
*/
|
|
54
|
-
lte: String
|
|
57
|
+
lte: String as PropType<Year>,
|
|
55
58
|
/**
|
|
56
59
|
* Render content in Outlook versions greater than the specified year.
|
|
57
60
|
*
|
|
58
61
|
* @example '2010'
|
|
59
62
|
*/
|
|
60
|
-
gt: String
|
|
63
|
+
gt: String as PropType<Year>,
|
|
61
64
|
/**
|
|
62
65
|
* Render content in Outlook versions greater than or equal to the specified year.
|
|
63
66
|
*
|
|
64
67
|
* @example '2010'
|
|
65
68
|
*/
|
|
66
|
-
gte: String
|
|
69
|
+
gte: String as PropType<Year>,
|
|
70
|
+
/**
|
|
71
|
+
* Raw HTML inserted at the start of the conditional comment, before the slot.
|
|
72
|
+
*
|
|
73
|
+
* Bypasses Vue's template parser, so unbalanced tags are preserved — useful
|
|
74
|
+
* for MSO ghost tables where the opening `<table><tr><td>` must live inside
|
|
75
|
+
* the conditional comment.
|
|
76
|
+
*
|
|
77
|
+
* @example '<table align="center" width="600"><tr><td>'
|
|
78
|
+
*/
|
|
79
|
+
open: {
|
|
80
|
+
type: String,
|
|
81
|
+
default: ''
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Raw HTML inserted at the end of the conditional comment, after the slot.
|
|
85
|
+
*
|
|
86
|
+
* Pair with `open` to close ghost-table tags inside the conditional.
|
|
87
|
+
*
|
|
88
|
+
* @example '</td></tr></table>'
|
|
89
|
+
*/
|
|
90
|
+
close: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: ''
|
|
93
|
+
}
|
|
67
94
|
},
|
|
68
95
|
setup(props, { slots }) {
|
|
69
96
|
const condition = computed(() => {
|
|
@@ -97,13 +124,13 @@ export default {
|
|
|
97
124
|
return 'mso'
|
|
98
125
|
})
|
|
99
126
|
|
|
100
|
-
const start = computed(() => `<!--[if ${condition.value}]
|
|
101
|
-
const end =
|
|
127
|
+
const start = computed(() => `<!--[if ${condition.value}]>${props.open}`)
|
|
128
|
+
const end = computed(() => `${props.close}<![endif]-->`)
|
|
102
129
|
|
|
103
130
|
return () => [
|
|
104
131
|
createStaticVNode(start.value, 1),
|
|
105
132
|
slots.default?.(),
|
|
106
|
-
createStaticVNode(end, 1),
|
|
133
|
+
createStaticVNode(end.value, 1),
|
|
107
134
|
]
|
|
108
135
|
}
|
|
109
136
|
}
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
hasWidthInStyle,
|
|
7
7
|
hasWidthUtility,
|
|
8
8
|
nextId,
|
|
9
|
-
normalizeToPixels
|
|
9
|
+
normalizeToPixels,
|
|
10
|
+
outlookFallbackProp
|
|
10
11
|
} from './utils.ts'
|
|
12
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
11
13
|
|
|
12
14
|
defineOptions({ inheritAttrs: false })
|
|
13
15
|
|
|
@@ -60,8 +62,11 @@ const props = defineProps({
|
|
|
60
62
|
type: String,
|
|
61
63
|
default: '0,-60px,0,0'
|
|
62
64
|
},
|
|
65
|
+
outlookFallback: outlookFallbackProp,
|
|
63
66
|
})
|
|
64
67
|
|
|
68
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
69
|
+
|
|
65
70
|
const userStyle = computed(() => {
|
|
66
71
|
const s = attrs.style
|
|
67
72
|
if (!s) return ''
|
|
@@ -132,9 +137,9 @@ const VmlAfter = () => createStaticVNode('<!--[if mso]></v:textbox></v:rect><![e
|
|
|
132
137
|
<table style="max-height: 0; position: relative; opacity: 0.999;">
|
|
133
138
|
<tr>
|
|
134
139
|
<td :style="tdStyle">
|
|
135
|
-
<VmlBefore />
|
|
140
|
+
<VmlBefore v-if="outlookFallback" />
|
|
136
141
|
<slot name="overlay" />
|
|
137
|
-
<VmlAfter />
|
|
142
|
+
<VmlAfter v-if="outlookFallback" />
|
|
138
143
|
</td>
|
|
139
144
|
</tr>
|
|
140
145
|
</table>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode } from 'vue'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
props: {
|
|
7
|
+
/**
|
|
8
|
+
* Raw content to emit verbatim.
|
|
9
|
+
*
|
|
10
|
+
* Auto-populated from slot content by
|
|
11
|
+
* the `maizzle:raw-extract` Vite plugin
|
|
12
|
+
* before Vue compiles the template,
|
|
13
|
+
* so `{{ }}` and other Vue/ESP
|
|
14
|
+
* syntax pass through untouched.
|
|
15
|
+
*/
|
|
16
|
+
content: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: '',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
setup(props) {
|
|
22
|
+
if (!props.content) {
|
|
23
|
+
return () => createStaticVNode('', 0)
|
|
24
|
+
}
|
|
25
|
+
return () => createStaticVNode(props.content, 1)
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
</script>
|
package/dist/components/Row.vue
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
const warnedLocations = new Set<string>()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<script setup lang="ts">
|
|
2
|
-
import { Comment, computed, createStaticVNode, provide, useAttrs, useSlots, Fragment } from 'vue'
|
|
6
|
+
import { Comment, Text, computed, createStaticVNode, provide, useAttrs, useSlots, Fragment } from 'vue'
|
|
3
7
|
import type { VNode } from 'vue'
|
|
4
|
-
import {
|
|
8
|
+
import { twMerge } from 'tailwind-merge'
|
|
9
|
+
import Column from './Column.vue'
|
|
10
|
+
import { hasWidthInStyle, hasWidthUtility, normalizeToPixels, outlookFallbackProp } from './utils.ts'
|
|
11
|
+
import { useOutlookFallback } from '../composables/useOutlookFallback'
|
|
5
12
|
|
|
6
13
|
defineOptions({ inheritAttrs: false })
|
|
7
14
|
|
|
@@ -30,9 +37,12 @@ const props = defineProps({
|
|
|
30
37
|
cols: {
|
|
31
38
|
type: Number,
|
|
32
39
|
default: null
|
|
33
|
-
}
|
|
40
|
+
},
|
|
41
|
+
outlookFallback: outlookFallbackProp,
|
|
34
42
|
})
|
|
35
43
|
|
|
44
|
+
const outlookFallback = useOutlookFallback(props.outlookFallback)
|
|
45
|
+
|
|
36
46
|
const slots = useSlots()
|
|
37
47
|
|
|
38
48
|
function countChildren(vnodes: VNode[]): number {
|
|
@@ -49,6 +59,41 @@ function countChildren(vnodes: VNode[]): number {
|
|
|
49
59
|
return count
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
function hasColumnChild(vnodes: VNode[]): boolean {
|
|
63
|
+
for (const vnode of vnodes) {
|
|
64
|
+
if (vnode.type === Fragment && Array.isArray(vnode.children)) {
|
|
65
|
+
if (hasColumnChild(vnode.children as VNode[])) return true
|
|
66
|
+
} else if (vnode.type === Column) {
|
|
67
|
+
return true
|
|
68
|
+
} else if (
|
|
69
|
+
typeof vnode.type === 'object'
|
|
70
|
+
&& vnode.type !== null
|
|
71
|
+
&& '__name' in vnode.type
|
|
72
|
+
&& (vnode.type as { __name?: string }).__name === 'Column'
|
|
73
|
+
) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasMeaningfulContent(vnodes: VNode[]): boolean {
|
|
81
|
+
for (const vnode of vnodes) {
|
|
82
|
+
if (vnode.type === Comment) continue
|
|
83
|
+
if (vnode.type === Fragment && Array.isArray(vnode.children)) {
|
|
84
|
+
if (hasMeaningfulContent(vnode.children as VNode[])) return true
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
if (vnode.type === Text) {
|
|
88
|
+
if (typeof vnode.children === 'string' && vnode.children.trim()) return true
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
if (typeof vnode.type === 'symbol') continue
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
52
97
|
const columnCount = computed(() => {
|
|
53
98
|
if (props.cols) return props.cols
|
|
54
99
|
|
|
@@ -78,15 +123,20 @@ const colWidthSource = computed(() => {
|
|
|
78
123
|
})
|
|
79
124
|
|
|
80
125
|
const restAttrs = computed(() => {
|
|
81
|
-
const { style: _, ...rest } = attrs
|
|
126
|
+
const { style: _, class: __, 'data-maizzle-loc': ___, ...rest } = attrs
|
|
82
127
|
return rest
|
|
83
128
|
})
|
|
84
129
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
/**
|
|
131
|
+
* `font-size: 0;` removes the whitespace gap between inline-block
|
|
132
|
+
* children. Lives in a class so users can override (e.g. via a custom
|
|
133
|
+
* `text-*`) and twMerge resolves the conflict cleanly instead of the
|
|
134
|
+
* inline declaration silently shadowing the user's class.
|
|
135
|
+
*/
|
|
136
|
+
const baseClass = 'text-0'
|
|
137
|
+
const mergedClass = computed(() => twMerge(baseClass, (attrs.class as string) ?? ''))
|
|
138
|
+
|
|
139
|
+
const divStyle = computed(() => userStyle.value || undefined)
|
|
90
140
|
|
|
91
141
|
const MsoBefore = () => createStaticVNode(
|
|
92
142
|
'<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width: 100%"><tr><![endif]-->',
|
|
@@ -97,16 +147,27 @@ const MsoAfter = () => createStaticVNode(
|
|
|
97
147
|
'<!--[if mso]></tr></table><![endif]-->',
|
|
98
148
|
1
|
|
99
149
|
)
|
|
150
|
+
|
|
151
|
+
const initialChildren = slots.default?.() ?? []
|
|
152
|
+
if (outlookFallback && hasMeaningfulContent(initialChildren) && !hasColumnChild(initialChildren)) {
|
|
153
|
+
const loc = (attrs['data-maizzle-loc'] as string | undefined) ?? '<unknown location>'
|
|
154
|
+
if (!warnedLocations.has(loc)) {
|
|
155
|
+
warnedLocations.add(loc)
|
|
156
|
+
const display = loc.split('/').pop() ?? loc
|
|
157
|
+
console.warn(`[maizzle] <Row> in ${display} has no <Column> inside it. Layout will break in Outlook.`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
100
160
|
</script>
|
|
101
161
|
|
|
102
162
|
<template>
|
|
103
|
-
<MsoBefore />
|
|
163
|
+
<MsoBefore v-if="outlookFallback" />
|
|
104
164
|
<div
|
|
105
165
|
v-bind="restAttrs"
|
|
166
|
+
:class="mergedClass"
|
|
106
167
|
:style="divStyle"
|
|
107
168
|
:data-maizzle-cw="colWidthSource"
|
|
108
169
|
>
|
|
109
170
|
<slot />
|
|
110
171
|
</div>
|
|
111
|
-
<MsoAfter />
|
|
172
|
+
<MsoAfter v-if="outlookFallback" />
|
|
112
173
|
</template>
|