@maizzle/framework 6.0.0-rc.5 → 6.0.0-rc.7
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 +42 -0
- package/dist/components/CodeBlock.vue +12 -19
- package/dist/components/Column.vue +61 -0
- package/dist/components/Container.vue +40 -0
- package/dist/components/Head.vue +8 -0
- package/dist/components/Html.vue +53 -0
- package/dist/components/Image.vue +70 -0
- package/dist/components/Markdown.vue +70 -0
- package/dist/components/Overlap.vue +60 -0
- package/dist/components/Row.vue +80 -0
- package/dist/components/Spacer.vue +50 -7
- package/dist/plugins/postcss/tailwindCleanup.mjs +22 -13
- package/dist/plugins/postcss/tailwindCleanup.mjs.map +1 -1
- package/dist/render/createRenderer.d.mts +2 -3
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +55 -4
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +83 -3
- 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 +15 -15
- 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 +40 -0
- package/dist/server/email.mjs.map +1 -0
- package/dist/server/ui/App.vue +204 -68
- 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 +369 -150
- package/dist/transformers/inlineCSS.mjs +9 -0
- 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/tailwindcss.mjs +3 -7
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/types/config.d.mts +38 -4
- package/dist/types/config.d.mts.map +1 -1
- package/dist/types/index.d.mts +2 -2
- package/package.json +7 -3
- package/dist/server/ui/components/ui/resizable/ResizableHandle.vue +0 -30
- package/dist/server/ui/components/ui/resizable/ResizablePanel.vue +0 -21
- package/dist/server/ui/components/ui/resizable/ResizablePanelGroup.vue +0 -25
- package/dist/server/ui/components/ui/resizable/index.ts +0 -3
- /package/dist/components/{Preview.vue → Preheader.vue} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode } from 'vue'
|
|
3
|
+
import type { PropType } from 'vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'Body',
|
|
7
|
+
inheritAttrs: false,
|
|
8
|
+
props: {
|
|
9
|
+
xmlLang: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: 'en'
|
|
12
|
+
},
|
|
13
|
+
dir: {
|
|
14
|
+
type: String as PropType<'ltr' | 'rtl'>,
|
|
15
|
+
default: 'ltr'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
setup(props, { slots, attrs }) {
|
|
19
|
+
return () => {
|
|
20
|
+
const extraAttrs = Object.entries(attrs)
|
|
21
|
+
.map(([key, value]) => value === true ? key : `${key}="${value}"`)
|
|
22
|
+
.join(' ')
|
|
23
|
+
|
|
24
|
+
const parts = [
|
|
25
|
+
`xml:lang="${props.xmlLang}"`,
|
|
26
|
+
`dir="${props.dir}"`,
|
|
27
|
+
'style="margin: 0; padding: 0; width: 100%; word-break: break-word;"',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
if (extraAttrs) {
|
|
31
|
+
parts.push(extraAttrs)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
createStaticVNode(`<body ${parts.join(' ')}>`, 1),
|
|
36
|
+
slots.default?.(),
|
|
37
|
+
createStaticVNode('</body>', 1),
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { createStaticVNode } from 'vue'
|
|
3
|
-
import { codeToHtml, getSingletonHighlighter } from 'shiki'
|
|
2
|
+
import { createStaticVNode, type PropType } from 'vue'
|
|
3
|
+
import { codeToHtml, getSingletonHighlighter, type BundledLanguage, type BundledTheme } from 'shiki'
|
|
4
4
|
|
|
5
5
|
export default {
|
|
6
6
|
props: {
|
|
@@ -9,33 +9,26 @@ export default {
|
|
|
9
9
|
type: String,
|
|
10
10
|
default: ''
|
|
11
11
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type: String
|
|
15
|
-
default: ''
|
|
16
|
-
},
|
|
17
|
-
/** The language for syntax highlighting. @default 'html' */
|
|
18
|
-
lang: {
|
|
19
|
-
type: String,
|
|
12
|
+
/** The language for syntax highlighting. @default 'html' */
|
|
13
|
+
language: {
|
|
14
|
+
type: String as PropType<BundledLanguage>,
|
|
20
15
|
default: 'html'
|
|
21
16
|
},
|
|
22
17
|
/** The shiki theme to use. @default 'github-light' */
|
|
23
18
|
theme: {
|
|
24
|
-
type: String
|
|
19
|
+
type: String as PropType<BundledTheme>,
|
|
25
20
|
default: 'github-light'
|
|
26
21
|
},
|
|
27
|
-
/** CSS class for the wrapping table cell. @default 'max-w-0 mso-padding-alt-
|
|
22
|
+
/** CSS class for the wrapping table cell. @default 'max-w-0 mso-padding-alt-4' */
|
|
28
23
|
tdClass: {
|
|
29
24
|
type: String,
|
|
30
|
-
default: 'max-w-0 mso-padding-alt-
|
|
25
|
+
default: 'max-w-0 mso-padding-alt-4'
|
|
31
26
|
}
|
|
32
27
|
},
|
|
33
28
|
inheritAttrs: false,
|
|
34
29
|
async setup(props, { slots, attrs }) {
|
|
35
|
-
// Prefer
|
|
36
|
-
let source = props.
|
|
37
|
-
? Buffer.from(props.encodedCode, 'base64').toString('utf-8')
|
|
38
|
-
: props.code
|
|
30
|
+
// Prefer code prop → slot text
|
|
31
|
+
let source = props.code
|
|
39
32
|
|
|
40
33
|
if (!source) {
|
|
41
34
|
const slotContent = slots.default?.()
|
|
@@ -51,7 +44,7 @@ export default {
|
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
const highlighted = await codeToHtml(source, {
|
|
54
|
-
lang: props.
|
|
47
|
+
lang: props.language,
|
|
55
48
|
theme: props.theme,
|
|
56
49
|
})
|
|
57
50
|
|
|
@@ -64,7 +57,7 @@ export default {
|
|
|
64
57
|
.replace(/<\/code><\/pre>$/, '')
|
|
65
58
|
|
|
66
59
|
const classes = ['font-mono', attrs.class].filter(Boolean).join(' ')
|
|
67
|
-
const baseStyles = `background-color:${bg};padding:
|
|
60
|
+
const baseStyles = `background-color:${bg};padding:16px;overflow:auto;white-space:pre;word-wrap:normal;word-break:normal;word-spacing:normal`
|
|
68
61
|
const styles = [baseStyles, attrs.style].filter(Boolean).join(';')
|
|
69
62
|
|
|
70
63
|
const html = `<table class="w-full"><tr><td class="${props.tdClass}"><pre class="${classes}" style="${styles}"><code>${codeContent}</code></pre></td></tr></table>`
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, createStaticVNode, inject, useAttrs } from 'vue'
|
|
3
|
+
import type { ComputedRef } from 'vue'
|
|
4
|
+
import { normalizeToPixels } from './utils.ts'
|
|
5
|
+
|
|
6
|
+
defineOptions({ inheritAttrs: false })
|
|
7
|
+
|
|
8
|
+
const attrs = useAttrs()
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
/** Override the auto-computed min-width. */
|
|
12
|
+
width: {
|
|
13
|
+
type: [String, Number],
|
|
14
|
+
default: null
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const injectedMinWidth = inject<ComputedRef<string> | null>('columnMinWidth', null)
|
|
19
|
+
const containerWidth = inject<ComputedRef<string | number> | null>('containerWidth', null)
|
|
20
|
+
const injectedMsoWidth = inject<ComputedRef<string> | null>('columnMsoWidth', null)
|
|
21
|
+
|
|
22
|
+
const minWidth = computed(() => {
|
|
23
|
+
if (props.width) return normalizeToPixels(props.width)
|
|
24
|
+
if (injectedMinWidth?.value) return injectedMinWidth.value
|
|
25
|
+
|
|
26
|
+
// Fallback: divide container width by 2 if available
|
|
27
|
+
if (containerWidth?.value) {
|
|
28
|
+
const val = containerWidth.value
|
|
29
|
+
if (typeof val === 'number') return `${val / 2}px`
|
|
30
|
+
const num = Number.parseFloat(val)
|
|
31
|
+
const unit = val.replace(String(num), '') || 'px'
|
|
32
|
+
return `${num / 2}${unit}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return '18.75em'
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const msoWidth = computed(() => injectedMsoWidth?.value ?? '50%')
|
|
39
|
+
|
|
40
|
+
const styles = computed(() => {
|
|
41
|
+
return `display: inline-block; min-width: ${minWidth.value}; font-size: 16px; vertical-align: top;`
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const MsoBefore = () => createStaticVNode(
|
|
45
|
+
`<!--[if mso]><td width="${msoWidth.value}" style="vertical-align:top"><![endif]-->`,
|
|
46
|
+
1
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const MsoAfter = () => createStaticVNode(
|
|
50
|
+
'<!--[if mso]></td><![endif]-->',
|
|
51
|
+
1
|
|
52
|
+
)
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<MsoBefore />
|
|
57
|
+
<div v-bind="attrs" :style="styles">
|
|
58
|
+
<slot />
|
|
59
|
+
</div>
|
|
60
|
+
<MsoAfter />
|
|
61
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, provide, createStaticVNode, useAttrs } from 'vue'
|
|
3
|
+
import { normalizeToPixels } from './utils.ts'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
const attrs = useAttrs()
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
/** Max width of the container. */
|
|
11
|
+
width: {
|
|
12
|
+
type: [String, Number],
|
|
13
|
+
default: '37.5em'
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
provide('containerWidth', computed(() => props.width))
|
|
18
|
+
|
|
19
|
+
const styles = computed(() => {
|
|
20
|
+
return `max-width: ${normalizeToPixels(props.width)}; margin: 0 auto;`
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const MsoBefore = () => createStaticVNode(
|
|
24
|
+
`<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" style="width:${normalizeToPixels(props.width)}" align="center"><tr><td><![endif]-->`,
|
|
25
|
+
1
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const MsoAfter = () => createStaticVNode(
|
|
29
|
+
'<!--[if mso]></td></tr></table><![endif]-->',
|
|
30
|
+
1
|
|
31
|
+
)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<MsoBefore />
|
|
36
|
+
<div v-bind="attrs" :style="styles">
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
<MsoAfter />
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode } from 'vue'
|
|
3
|
+
import type { PropType } from 'vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: 'Html',
|
|
7
|
+
inheritAttrs: false,
|
|
8
|
+
props: {
|
|
9
|
+
lang: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: 'en'
|
|
12
|
+
},
|
|
13
|
+
dir: {
|
|
14
|
+
type: String as PropType<'ltr' | 'rtl'>,
|
|
15
|
+
default: 'ltr'
|
|
16
|
+
},
|
|
17
|
+
xmlns: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: null
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
setup(props, { slots, attrs }) {
|
|
23
|
+
return () => {
|
|
24
|
+
const extraAttrs = Object.entries(attrs)
|
|
25
|
+
.map(([key, value]) => value === true ? key : `${key}="${value}"`)
|
|
26
|
+
.join(' ')
|
|
27
|
+
|
|
28
|
+
const parts = [
|
|
29
|
+
`lang="${props.lang}"`,
|
|
30
|
+
`dir="${props.dir}"`,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
if (props.xmlns) {
|
|
34
|
+
parts.push(
|
|
35
|
+
`xmlns="${props.xmlns}"`,
|
|
36
|
+
'xmlns:v="urn:schemas-microsoft-com:vml"',
|
|
37
|
+
'xmlns:o="urn:schemas-microsoft-com:office:office"',
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (extraAttrs) {
|
|
42
|
+
parts.push(extraAttrs)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
createStaticVNode(`<html ${parts.join(' ')}>`, 1),
|
|
47
|
+
slots.default?.(),
|
|
48
|
+
createStaticVNode('</html>', 1),
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineOptions({ inheritAttrs: false })
|
|
5
|
+
|
|
6
|
+
const attrs = useAttrs()
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
/** The image source URL. When reducedMotionSrc is used, this becomes the static fallback. */
|
|
10
|
+
src: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: true
|
|
13
|
+
},
|
|
14
|
+
/** Alt text for the image. */
|
|
15
|
+
alt: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: ''
|
|
18
|
+
},
|
|
19
|
+
/** Image source for dark mode. */
|
|
20
|
+
darkSrc: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: null
|
|
23
|
+
},
|
|
24
|
+
/** The width of the image, rendered without units. */
|
|
25
|
+
width: {
|
|
26
|
+
type: [String, Number],
|
|
27
|
+
required: true
|
|
28
|
+
},
|
|
29
|
+
/** Animated image source, shown when user has no reduced motion preference. */
|
|
30
|
+
reducedMotionSrc: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: null
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
function mimeFromExtension(src: string): string {
|
|
37
|
+
const ext = src.split('.').pop()?.toLowerCase() ?? ''
|
|
38
|
+
|
|
39
|
+
const types: Record<string, string> = {
|
|
40
|
+
apng: 'image/apng',
|
|
41
|
+
avif: 'image/avif',
|
|
42
|
+
gif: 'image/gif',
|
|
43
|
+
jpg: 'image/jpeg',
|
|
44
|
+
jpeg: 'image/jpeg',
|
|
45
|
+
jfif: 'image/jpeg',
|
|
46
|
+
png: 'image/png',
|
|
47
|
+
svg: 'image/svg+xml',
|
|
48
|
+
webp: 'image/webp',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return types[ext] ?? ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const reducedMotionType = computed(() => mimeFromExtension(props.reducedMotionSrc ?? ''))
|
|
55
|
+
|
|
56
|
+
const imgWidth = computed(() => Number.parseInt(String(props.width), 10))
|
|
57
|
+
|
|
58
|
+
const usePicture = computed(() => props.darkSrc || props.reducedMotionSrc)
|
|
59
|
+
|
|
60
|
+
const imgStyle = 'max-width: 100%; vertical-align: middle;'
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<picture v-if="usePicture">
|
|
65
|
+
<source v-if="darkSrc" :srcset="darkSrc" media="(prefers-color-scheme: dark)">
|
|
66
|
+
<source v-if="reducedMotionSrc" :srcset="reducedMotionSrc" :type="reducedMotionType || undefined" media="(prefers-reduced-motion: no-preference)">
|
|
67
|
+
<img v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :style="imgStyle">
|
|
68
|
+
</picture>
|
|
69
|
+
<img v-else v-bind="attrs" :src="src" :alt="alt" :width="imgWidth" :style="imgStyle">
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createStaticVNode, type PropType } from 'vue'
|
|
3
|
+
import { createMarkdownExit } from 'markdown-exit'
|
|
4
|
+
import { codeToHtml, type BundledTheme } from 'shiki'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
props: {
|
|
8
|
+
/** Markdown content string. Overrides slot content. */
|
|
9
|
+
content: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: ''
|
|
12
|
+
},
|
|
13
|
+
/** Path to a markdown file to render. Resolved at build time. */
|
|
14
|
+
src: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ''
|
|
17
|
+
},
|
|
18
|
+
/** Shiki theme for fenced code blocks. @default 'github-dark-high-contrast' */
|
|
19
|
+
shikiTheme: {
|
|
20
|
+
type: String as PropType<BundledTheme>,
|
|
21
|
+
default: 'github-dark-high-contrast'
|
|
22
|
+
},
|
|
23
|
+
/** Wrap output in a div element. @default true */
|
|
24
|
+
wrapper: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: true
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
inheritAttrs: false,
|
|
30
|
+
async setup(props, { slots, attrs }) {
|
|
31
|
+
let source = props.content
|
|
32
|
+
|
|
33
|
+
if (!source) {
|
|
34
|
+
const slotContent = slots.default?.()
|
|
35
|
+
source = slotContent
|
|
36
|
+
?.map((vnode: any) => (typeof vnode.children === 'string' ? vnode.children : ''))
|
|
37
|
+
.join('') ?? ''
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
source = source.trim()
|
|
41
|
+
|
|
42
|
+
if (!source) {
|
|
43
|
+
return () => createStaticVNode('', 0)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const md = createMarkdownExit({
|
|
47
|
+
html: true,
|
|
48
|
+
linkify: true,
|
|
49
|
+
typographer: true,
|
|
50
|
+
highlight: async (code, lang) => {
|
|
51
|
+
try {
|
|
52
|
+
return await codeToHtml(code, { lang, theme: props.shikiTheme })
|
|
53
|
+
} catch {
|
|
54
|
+
return ''
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
let html = await md.renderAsync(source)
|
|
60
|
+
|
|
61
|
+
if (props.wrapper) {
|
|
62
|
+
const classes = attrs.class ? ` class="${attrs.class}"` : ''
|
|
63
|
+
const style = attrs.style ? ` style="${attrs.style}"` : ''
|
|
64
|
+
html = `<div${classes}${style}>${html}</div>`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return () => createStaticVNode(html, 1)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs, createStaticVNode } from 'vue'
|
|
3
|
+
import { normalizeToPixels } from './utils.ts'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
const attrs = useAttrs()
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
/** Max height of the overlapped (background) content. */
|
|
11
|
+
height: {
|
|
12
|
+
type: [String, Number],
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
/** Width of the overlay table and VML rect. */
|
|
16
|
+
width: {
|
|
17
|
+
type: [String, Number],
|
|
18
|
+
required: true
|
|
19
|
+
},
|
|
20
|
+
/** Height of the VML rect for Outlook. Defaults to height. */
|
|
21
|
+
msoHeight: {
|
|
22
|
+
type: [String, Number],
|
|
23
|
+
default: null
|
|
24
|
+
},
|
|
25
|
+
/** VML textbox inset value for Outlook positioning. */
|
|
26
|
+
msoInset: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: '0,-60px,0,0'
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const backgroundStyles = computed(() => {
|
|
33
|
+
return `max-height: ${normalizeToPixels(props.height)}; margin: 0 auto; text-align: center;`
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const vmlOpen = computed(() => {
|
|
37
|
+
const w = normalizeToPixels(props.width)
|
|
38
|
+
const h = normalizeToPixels(props.msoHeight ?? props.height)
|
|
39
|
+
|
|
40
|
+
return `<!--[if mso]><v:rect xmlns:v="urn:schemas-microsoft-com:vml" stroked="f" filled="f" style="width:${w};height:${h};"><v:textbox inset="${props.msoInset}"><![endif]-->`
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const VmlBefore = () => createStaticVNode(vmlOpen.value, 1)
|
|
44
|
+
const VmlAfter = () => createStaticVNode('<!--[if mso]></v:textbox></v:rect><![endif]-->', 1)
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div v-bind="attrs" :style="backgroundStyles">
|
|
49
|
+
<slot />
|
|
50
|
+
</div>
|
|
51
|
+
<table style="max-height: 0; position: relative; opacity: 0.999;">
|
|
52
|
+
<tr>
|
|
53
|
+
<td :style="`width: ${normalizeToPixels(props.width)}; max-width: 100%; vertical-align: top;`">
|
|
54
|
+
<VmlBefore />
|
|
55
|
+
<slot name="overlay" />
|
|
56
|
+
<VmlAfter />
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
</table>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Comment, computed, createStaticVNode, inject, provide, useAttrs, useSlots, Fragment } from 'vue'
|
|
3
|
+
import type { ComputedRef, VNode } from 'vue'
|
|
4
|
+
|
|
5
|
+
defineOptions({ inheritAttrs: false })
|
|
6
|
+
|
|
7
|
+
const attrs = useAttrs()
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
/** Override the inherited container width. */
|
|
11
|
+
width: {
|
|
12
|
+
type: [String, Number],
|
|
13
|
+
default: null
|
|
14
|
+
},
|
|
15
|
+
/** Override the auto-detected column count. */
|
|
16
|
+
cols: {
|
|
17
|
+
type: Number,
|
|
18
|
+
default: null
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const slots = useSlots()
|
|
23
|
+
|
|
24
|
+
function countChildren(vnodes: VNode[]): number {
|
|
25
|
+
let count = 0
|
|
26
|
+
|
|
27
|
+
for (const vnode of vnodes) {
|
|
28
|
+
if (vnode.type === Fragment && Array.isArray(vnode.children)) {
|
|
29
|
+
count += countChildren(vnode.children as VNode[])
|
|
30
|
+
} else if (vnode.type !== Comment && typeof vnode.type !== 'symbol') {
|
|
31
|
+
count++
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return count
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const columnCount = computed(() => {
|
|
39
|
+
if (props.cols) return props.cols
|
|
40
|
+
|
|
41
|
+
const children = slots.default?.() ?? []
|
|
42
|
+
return countChildren(children) || 1
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const containerWidth = inject<ComputedRef<string | number> | null>('containerWidth', null)
|
|
46
|
+
|
|
47
|
+
const rowWidth = computed(() => props.width ?? containerWidth?.value ?? '37.5em')
|
|
48
|
+
|
|
49
|
+
function divideValue(value: string | number, divisor: number): string {
|
|
50
|
+
if (typeof value === 'number') {
|
|
51
|
+
return `${value / divisor}px`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const num = Number.parseFloat(value)
|
|
55
|
+
const unit = value.replace(String(num), '') || 'px'
|
|
56
|
+
|
|
57
|
+
return `${num / divisor}${unit}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
provide('columnMinWidth', computed(() => divideValue(rowWidth.value, columnCount.value)))
|
|
61
|
+
provide('columnMsoWidth', computed(() => `${Math.round(100 / columnCount.value)}%`))
|
|
62
|
+
|
|
63
|
+
const MsoBefore = () => createStaticVNode(
|
|
64
|
+
'<!--[if mso]><table role="none" cellpadding="0" cellspacing="0" width="100%"><tr><![endif]-->',
|
|
65
|
+
1
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const MsoAfter = () => createStaticVNode(
|
|
69
|
+
'<!--[if mso]></tr></table><![endif]-->',
|
|
70
|
+
1
|
|
71
|
+
)
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<MsoBefore />
|
|
76
|
+
<div v-bind="attrs" style="width: 100%; font-size: 0;">
|
|
77
|
+
<slot />
|
|
78
|
+
</div>
|
|
79
|
+
<MsoAfter />
|
|
80
|
+
</template>
|
|
@@ -3,11 +3,21 @@ import { computed } from 'vue'
|
|
|
3
3
|
import { normalizeToPixels } from './utils.ts'
|
|
4
4
|
|
|
5
5
|
const props = defineProps({
|
|
6
|
-
/** The
|
|
7
|
-
|
|
6
|
+
/** The type of spacer. */
|
|
7
|
+
type: {
|
|
8
|
+
type: String as () => 'vertical' | 'horizontal',
|
|
9
|
+
default: 'vertical'
|
|
10
|
+
},
|
|
11
|
+
/** The height of the spacer (vertical). */
|
|
12
|
+
height: {
|
|
8
13
|
type: [String, Number],
|
|
9
14
|
default: null
|
|
10
15
|
},
|
|
16
|
+
/** The width of the spacer (horizontal). */
|
|
17
|
+
width: {
|
|
18
|
+
type: [String, Number],
|
|
19
|
+
default: 16
|
|
20
|
+
},
|
|
11
21
|
/** The alternative height to use in Outlook. */
|
|
12
22
|
msoHeight: {
|
|
13
23
|
type: [String, Number],
|
|
@@ -15,11 +25,16 @@ const props = defineProps({
|
|
|
15
25
|
}
|
|
16
26
|
})
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
function parsePixelValue(value: string | number): number {
|
|
29
|
+
if (typeof value === 'number') return value
|
|
30
|
+
return Number.parseFloat(value) || 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const verticalStyles = computed(() => {
|
|
19
34
|
const s = []
|
|
20
35
|
|
|
21
|
-
if (props.
|
|
22
|
-
s.push(`line-height: ${normalizeToPixels(props.
|
|
36
|
+
if (props.height) {
|
|
37
|
+
s.push(`line-height: ${normalizeToPixels(props.height)};`)
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
if (props.msoHeight) {
|
|
@@ -28,9 +43,37 @@ const styles = computed(() => {
|
|
|
28
43
|
|
|
29
44
|
return s.join('')
|
|
30
45
|
})
|
|
46
|
+
|
|
47
|
+
const horizontalStyles = computed(() => {
|
|
48
|
+
return `display:inline-block; width: ${normalizeToPixels(props.width)}; font-size: 16px;${msoFontWidth.value}`
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const msoFontWidth = computed(() => {
|
|
52
|
+
const widthPx = parsePixelValue(props.width)
|
|
53
|
+
const emspBase = 16
|
|
54
|
+
const maxPercent = 500
|
|
55
|
+
const maxPerEmsp = emspBase * (maxPercent / 100)
|
|
56
|
+
const numEmsps = Math.ceil(widthPx / maxPerEmsp)
|
|
57
|
+
const percent = Math.round((widthPx / (numEmsps * emspBase)) * 100)
|
|
58
|
+
|
|
59
|
+
return ` mso-font-width:${percent}%;`
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const emspCount = computed(() => {
|
|
63
|
+
const widthPx = parsePixelValue(props.width)
|
|
64
|
+
const maxPerEmsp = 16 * 5
|
|
65
|
+
return Math.ceil(widthPx / maxPerEmsp)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const emsps = computed(() => '\u2003'.repeat(emspCount.value))
|
|
31
69
|
</script>
|
|
32
70
|
|
|
33
71
|
<template>
|
|
34
|
-
<
|
|
35
|
-
|
|
72
|
+
<template v-if="type === 'horizontal'">
|
|
73
|
+
<i :style="horizontalStyles">{{ emsps }}</i>
|
|
74
|
+
</template>
|
|
75
|
+
<template v-else>
|
|
76
|
+
<div v-if="height" role="separator" :style="verticalStyles">‍</div>
|
|
77
|
+
<div v-else role="separator">‍</div>
|
|
78
|
+
</template>
|
|
36
79
|
</template>
|
|
@@ -14,20 +14,29 @@ const DEFAULT_AT_RULES = ["layer", "property"];
|
|
|
14
14
|
function tailwindCleanup(config) {
|
|
15
15
|
const selectors = config.postcss?.removeSelectors ?? DEFAULT_SELECTORS;
|
|
16
16
|
const atRules = config.postcss?.removeAtRules ?? DEFAULT_AT_RULES;
|
|
17
|
-
return [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
postcssPlugin: "tailwind-cleanup-selectors",
|
|
20
|
+
Rule(rule) {
|
|
21
|
+
const parts = rule.selector.split(",").map((s) => s.trim());
|
|
22
|
+
const kept = parts.filter((p) => !selectors.some((s) => p === s || p.startsWith(`${s}(`)));
|
|
23
|
+
if (kept.length === 0) rule.remove();
|
|
24
|
+
else if (kept.length < parts.length) rule.selector = kept.join(", ");
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
postcssPlugin: "tailwind-cleanup-at-rules",
|
|
29
|
+
AtRule(rule) {
|
|
30
|
+
if (atRules.includes(rule.name)) rule.remove();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
postcssPlugin: "tailwind-cleanup-text-decoration",
|
|
35
|
+
Declaration(decl) {
|
|
36
|
+
if (decl.prop === "text-decoration-line") decl.prop = "text-decoration";
|
|
37
|
+
}
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
postcssPlugin: "tailwind-cleanup-at-rules",
|
|
27
|
-
AtRule(rule) {
|
|
28
|
-
if (atRules.includes(rule.name)) rule.remove();
|
|
29
|
-
}
|
|
30
|
-
}];
|
|
39
|
+
];
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
//#endregion
|