nuxt-og-image 6.5.2 → 6.6.0
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/chunks/tw4.cjs +1 -1
- package/dist/chunks/tw4.mjs +1 -1
- package/dist/chunks/uno.cjs +1 -1
- package/dist/chunks/uno.mjs +1 -1
- package/dist/devtools/components/og-image/AddComponentDialog.vue +85 -0
- package/dist/devtools/components/og-image/BlueskyCardRenderer.vue +134 -0
- package/dist/devtools/components/og-image/CreateOgImageDialog.vue +56 -0
- package/dist/devtools/components/og-image/DiscordCardRenderer.vue +125 -0
- package/dist/devtools/components/og-image/FacebookCardRenderer.vue +128 -0
- package/dist/devtools/components/og-image/IFrameLoader.vue +93 -0
- package/dist/devtools/components/og-image/ImageLoader.vue +197 -0
- package/dist/devtools/components/og-image/LinkedInCardRenderer.vue +100 -0
- package/dist/devtools/components/og-image/RendererSelectModal.vue +356 -0
- package/dist/devtools/components/og-image/SlackCardRenderer.vue +140 -0
- package/dist/devtools/components/og-image/TemplateComponentPreview.vue +186 -0
- package/dist/devtools/components/og-image/TwitterCardRenderer.vue +170 -0
- package/dist/devtools/components/og-image/WhatsAppRenderer.vue +294 -0
- package/dist/devtools/lib/og-image/keys.ts +8 -0
- package/dist/devtools/lib/og-image/og-image.ts +536 -0
- package/dist/devtools/lib/og-image/renderer-select.ts +3 -0
- package/dist/devtools/lib/og-image/rpc-types.ts +2 -0
- package/dist/devtools/lib/og-image/rpc.ts +73 -0
- package/dist/devtools/lib/og-image/runtime-types.ts +10 -0
- package/dist/devtools/lib/og-image/shared/urlEncoding.ts +502 -0
- package/dist/devtools/lib/og-image/shared.ts +30 -0
- package/dist/devtools/lib/og-image/templates.ts +9 -0
- package/dist/devtools/lib/og-image/types.ts +38 -0
- package/dist/devtools/lib/og-image/util/logic.ts +21 -0
- package/dist/devtools/nuxt.config.ts +6 -0
- package/dist/devtools/pages/og-image/debug.vue +32 -0
- package/dist/devtools/pages/og-image/docs.vue +3 -0
- package/dist/devtools/pages/og-image/index.vue +1682 -0
- package/dist/devtools/pages/og-image/templates.vue +150 -0
- package/dist/devtools/pages/og-image.vue +184 -0
- package/dist/module.cjs +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/og-image/core/plugins/imageSrc.js +2 -2
- package/dist/runtime/server/og-image/core/style-attr.d.ts +8 -0
- package/dist/runtime/server/og-image/core/style-attr.js +34 -0
- package/dist/runtime/server/og-image/core/vnodes.d.ts +2 -1
- package/dist/runtime/server/og-image/core/vnodes.js +3 -27
- package/dist/shared/{nuxt-og-image.CgPzmzQY.cjs → nuxt-og-image.CfTPCtaS.cjs} +38 -24
- package/dist/shared/{nuxt-og-image.DGAMxBol.mjs → nuxt-og-image.DdbTs-xp.mjs} +37 -23
- package/package.json +17 -18
- package/dist/devtools/200.html +0 -1
- package/dist/devtools/404.html +0 -1
- package/dist/devtools/_fonts/4ppnHhMi-pBsWSPo7mY0avYxlDoAg1N3PTzCwXLZ5rA-d9oibkGnTd1JL3tc_xnaVgBLYmOB8kjrK2cvZaqwj9s.woff2 +0 -0
- package/dist/devtools/_fonts/PV2hrQG6wq5BlIPDjdL1IcOflycaghyt5MHzlBqZtlo-lb_WexLz3VZqfTN0oi554iBH5tT2j2UFEV-XErCAS3E.woff2 +0 -0
- package/dist/devtools/_fonts/VE4cDVCv5MxbFM7ZLoLCGbIpNd71zhp7MDI9lmN5Y7I-xZyDYCUVrd6LV8eVGF3Um3UZjBFuUtDGtvdyTBBRYBo.woff2 +0 -0
- package/dist/devtools/_fonts/fVoGbnMbBFd5L9BBp9fUPavUSkZ_EmsQNSyadkT-108-U4T0khaeLQSIhtt9eVvaCEKJjtWJ4ioRJOf8hvqkWY0.woff2 +0 -0
- package/dist/devtools/_fonts/lQAxeCEs1R0Lw-H9XRU1RlOARQN8J6npRsPjyEDMe5s-_DUSLEkO3tKTuun_gSnDLoQPVEnpOnyqZMOw0ByZ6PA.woff2 +0 -0
- package/dist/devtools/_fonts/lntlqNHKLV2n82yTwMde70QqOjcfLE2XJ5oKZ3vRPWc-z6TxpIZQdWXztWLr9_OFWqt_WJJoeGtuK_-XQMZGQwE.woff2 +0 -0
- package/dist/devtools/_nuxt/B9jrmesR.js +0 -1
- package/dist/devtools/_nuxt/BA-4cUNc.js +0 -1
- package/dist/devtools/_nuxt/BOEXnX7x.js +0 -3
- package/dist/devtools/_nuxt/BWKJ0Uxb.js +0 -30
- package/dist/devtools/_nuxt/Bdtz4ZK9.js +0 -4
- package/dist/devtools/_nuxt/C5797Ieg.js +0 -1
- package/dist/devtools/_nuxt/C5jzcy9i.js +0 -1
- package/dist/devtools/_nuxt/CCTv7mmB.js +0 -1
- package/dist/devtools/_nuxt/CP0tQR2M.js +0 -1
- package/dist/devtools/_nuxt/C_JnDlx-.js +0 -1
- package/dist/devtools/_nuxt/CdmciVPJ.js +0 -1
- package/dist/devtools/_nuxt/CqjbkMN9.js +0 -3
- package/dist/devtools/_nuxt/CuJezOxb.js +0 -1
- package/dist/devtools/_nuxt/D4EkL0XJ.js +0 -6
- package/dist/devtools/_nuxt/DKaW7clv.js +0 -1
- package/dist/devtools/_nuxt/DSl3mlLY.js +0 -2
- package/dist/devtools/_nuxt/DYta7Mdi.js +0 -3
- package/dist/devtools/_nuxt/DevtoolsSection.CcOMr_vO.css +0 -1
- package/dist/devtools/_nuxt/DevtoolsSnippet.BhrTdbXn.css +0 -1
- package/dist/devtools/_nuxt/E8AZ6HoH.js +0 -1
- package/dist/devtools/_nuxt/IFrameLoader.k_861Nnq.css +0 -1
- package/dist/devtools/_nuxt/O5eLyffU.js +0 -1
- package/dist/devtools/_nuxt/builds/latest.json +0 -1
- package/dist/devtools/_nuxt/builds/meta/a36bc212-7179-4aa2-a534-6366229bd7b1.json +0 -1
- package/dist/devtools/_nuxt/entry.CcrXpo2y.css +0 -2
- package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
- package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
- package/dist/devtools/_nuxt/pages.Ci_xvfJ8.css +0 -1
- package/dist/devtools/_nuxt/renderer-select.COGJ4ZQe.css +0 -1
- package/dist/devtools/_nuxt/templates.BByln3BG.css +0 -1
- package/dist/devtools/_nuxt/wMBdlVF-.js +0 -152
- package/dist/devtools/debug/index.html +0 -1
- package/dist/devtools/docs/index.html +0 -1
- package/dist/devtools/index.html +0 -1
- package/dist/devtools/templates/index.html +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { withQuery } from 'ufo'
|
|
3
|
+
import { navigateTo } from '#imports'
|
|
4
|
+
import { useOgImage } from '../../lib/og-image/og-image'
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
appComponents,
|
|
8
|
+
communityComponents,
|
|
9
|
+
activeComponentName,
|
|
10
|
+
src,
|
|
11
|
+
aspectRatio,
|
|
12
|
+
isLoading,
|
|
13
|
+
patchOptions,
|
|
14
|
+
} = useOgImage()
|
|
15
|
+
|
|
16
|
+
async function selectTemplate(componentName: string) {
|
|
17
|
+
patchOptions({ component: componentName })
|
|
18
|
+
await navigateTo('/')
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div class="templates-page animate-fade-up">
|
|
24
|
+
<!-- Loading state -->
|
|
25
|
+
<div v-if="isLoading" class="loading-container">
|
|
26
|
+
<div class="loading-spinner" />
|
|
27
|
+
<p class="loading-text">
|
|
28
|
+
Loading templates…
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Content -->
|
|
33
|
+
<div v-else class="templates-content stagger-children">
|
|
34
|
+
<!-- Your Templates -->
|
|
35
|
+
<DevtoolsSection v-if="appComponents.length" icon="carbon:app" text="Your Templates">
|
|
36
|
+
<template #description>
|
|
37
|
+
Custom OG Image components in your project
|
|
38
|
+
</template>
|
|
39
|
+
<div class="template-grid">
|
|
40
|
+
<button
|
|
41
|
+
v-for="name in appComponents"
|
|
42
|
+
:key="name.pascalName"
|
|
43
|
+
class="template-item"
|
|
44
|
+
@click="selectTemplate(name.pascalName)"
|
|
45
|
+
>
|
|
46
|
+
<TemplateComponentPreview
|
|
47
|
+
:component="name"
|
|
48
|
+
:src="withQuery(src, { component: name.pascalName })"
|
|
49
|
+
:aspect-ratio="aspectRatio"
|
|
50
|
+
:active="name.pascalName === activeComponentName"
|
|
51
|
+
/>
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</DevtoolsSection>
|
|
55
|
+
|
|
56
|
+
<!-- Community Templates -->
|
|
57
|
+
<DevtoolsSection icon="carbon:user-multiple" text="Community Templates">
|
|
58
|
+
<template #description>
|
|
59
|
+
Pre-built templates you can use or eject
|
|
60
|
+
</template>
|
|
61
|
+
<div class="template-grid">
|
|
62
|
+
<button
|
|
63
|
+
v-for="name in communityComponents"
|
|
64
|
+
:key="name.pascalName"
|
|
65
|
+
class="template-item"
|
|
66
|
+
@click="selectTemplate(name.pascalName)"
|
|
67
|
+
>
|
|
68
|
+
<TemplateComponentPreview
|
|
69
|
+
:component="name"
|
|
70
|
+
:src="withQuery(src, { component: name.pascalName })"
|
|
71
|
+
:aspect-ratio="aspectRatio"
|
|
72
|
+
:active="name.pascalName === activeComponentName"
|
|
73
|
+
/>
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</DevtoolsSection>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style scoped>
|
|
82
|
+
.templates-page {
|
|
83
|
+
padding-bottom: 2rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.loading-container {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
min-height: 300px;
|
|
92
|
+
gap: 1rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.loading-spinner {
|
|
96
|
+
width: 2.5rem;
|
|
97
|
+
height: 2.5rem;
|
|
98
|
+
border: 2px solid var(--color-border);
|
|
99
|
+
border-top-color: var(--seo-green);
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
animation: spin 0.8s linear infinite;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@keyframes spin {
|
|
105
|
+
to {
|
|
106
|
+
transform: rotate(360deg);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.loading-text {
|
|
111
|
+
font-size: 0.875rem;
|
|
112
|
+
color: var(--color-text-muted);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.templates-content {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
gap: 1.5rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.template-grid {
|
|
122
|
+
display: grid;
|
|
123
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
124
|
+
gap: 1rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@media (min-width: 640px) {
|
|
128
|
+
.template-grid {
|
|
129
|
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
130
|
+
gap: 1.25rem;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.template-item {
|
|
135
|
+
padding: 0;
|
|
136
|
+
background: none;
|
|
137
|
+
border: none;
|
|
138
|
+
text-align: left;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
transition: transform 150ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.template-item:hover {
|
|
144
|
+
transform: translateY(-2px);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.template-item:active {
|
|
148
|
+
transform: translateY(0);
|
|
149
|
+
}
|
|
150
|
+
</style>
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { OgImageRuntimeConfig } from '../lib/og-image/runtime-types'
|
|
3
|
+
import type { GlobalDebugResponse, PathDebugResponse } from '../lib/og-image/types'
|
|
4
|
+
import defu from 'defu'
|
|
5
|
+
import { appFetch } from 'nuxtseo-layer-devtools/composables/rpc'
|
|
6
|
+
import { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'
|
|
7
|
+
import { path, productionUrl, query, refreshTime } from 'nuxtseo-layer-devtools/composables/state'
|
|
8
|
+
import { computed, provide, useAsyncData, useNuxtApp, useRoute, watch } from '#imports'
|
|
9
|
+
import AddComponentDialog from '../components/og-image/AddComponentDialog.vue'
|
|
10
|
+
import CreateOgImageDialog from '../components/og-image/CreateOgImageDialog.vue'
|
|
11
|
+
import RendererSelectModal from '../components/og-image/RendererSelectModal.vue'
|
|
12
|
+
import { GlobalDebugKey, PathDebugKey, PathDebugStatusKey, RefetchPathDebugKey } from '../lib/og-image/keys'
|
|
13
|
+
import { useOgImage } from '../lib/og-image/og-image'
|
|
14
|
+
import { encodeOgImageParams } from '../lib/og-image/shared/urlEncoding'
|
|
15
|
+
import { ogImageKey, options, optionsOverrides } from '../lib/og-image/util/logic'
|
|
16
|
+
|
|
17
|
+
const RE_IMAGE_EXT = /\.(png|jpeg|jpg|webp)$/
|
|
18
|
+
|
|
19
|
+
await loadShiki()
|
|
20
|
+
|
|
21
|
+
const nuxtApp = useNuxtApp()
|
|
22
|
+
nuxtApp.payload.data = nuxtApp.payload.data || {}
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error untyped
|
|
25
|
+
const { data: globalDebug } = useAsyncData<GlobalDebugResponse>('og-image-global-debug', () => {
|
|
26
|
+
if (!appFetch.value)
|
|
27
|
+
return { runtimeConfig: {} as OgImageRuntimeConfig, componentNames: [] }
|
|
28
|
+
return appFetch.value('/_og/debug.json')
|
|
29
|
+
}, {
|
|
30
|
+
watch: [appFetch, refreshTime],
|
|
31
|
+
default: () => ({ runtimeConfig: {} as OgImageRuntimeConfig, componentNames: [] }),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Read og:image URL from host document — already has correct component/params from defineOgImage
|
|
35
|
+
function getHostOgImageDebugUrl(): string | undefined {
|
|
36
|
+
try {
|
|
37
|
+
const doc = window.parent?.document
|
|
38
|
+
if (!doc)
|
|
39
|
+
return
|
|
40
|
+
const meta = doc.querySelector('meta[property="og:image"]') || doc.querySelector('meta[name="twitter:image"]')
|
|
41
|
+
const content = meta?.getAttribute('content')
|
|
42
|
+
if (!content?.includes('/_og/'))
|
|
43
|
+
return
|
|
44
|
+
return new URL(content).pathname.replace(RE_IMAGE_EXT, '.json')
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Cross-origin parent documents cannot be inspected; fall back to route options.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { data: pathDebug, refresh: refreshPathDebug, status: pathDebugStatus } = useAsyncData<PathDebugResponse>('og-image-path-debug', async () => {
|
|
52
|
+
if (!appFetch.value)
|
|
53
|
+
return { extract: { options: [], socialPreview: { root: {}, images: [] } } }
|
|
54
|
+
let url = getHostOgImageDebugUrl()
|
|
55
|
+
if (!url) {
|
|
56
|
+
const params = defu(
|
|
57
|
+
{ key: ogImageKey.value || 'og', _path: path.value, _query: query.value },
|
|
58
|
+
optionsOverrides.value,
|
|
59
|
+
options.value,
|
|
60
|
+
)
|
|
61
|
+
const encoded = encodeOgImageParams(params)
|
|
62
|
+
url = `/_og/d/${encoded || 'default'}.json`
|
|
63
|
+
}
|
|
64
|
+
return (appFetch.value(url) as Promise<PathDebugResponse>).catch((err: any): PathDebugResponse => ({
|
|
65
|
+
extract: { options: [], socialPreview: { root: {}, images: [] } },
|
|
66
|
+
fetchError: {
|
|
67
|
+
statusCode: err?.data?.statusCode || err?.statusCode,
|
|
68
|
+
message: err?.data?.stack?.[0] || err?.data?.message || err?.message || 'Unknown error',
|
|
69
|
+
stack: err?.data?.stack,
|
|
70
|
+
},
|
|
71
|
+
}))
|
|
72
|
+
}, {
|
|
73
|
+
watch: [appFetch, path, refreshTime, ogImageKey],
|
|
74
|
+
default: () => ({ extract: { options: [], socialPreview: { root: {}, images: [] } } }),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Sync production URL from site config for production preview toggle
|
|
78
|
+
watch(globalDebug, (val) => {
|
|
79
|
+
// @ts-expect-error globalDebug type doesn't include siteConfigUrl
|
|
80
|
+
if (val?.siteConfigUrl)
|
|
81
|
+
// @ts-expect-error globalDebug type doesn't include siteConfigUrl
|
|
82
|
+
productionUrl.value = val.siteConfigUrl
|
|
83
|
+
}, { immediate: true })
|
|
84
|
+
|
|
85
|
+
// @ts-expect-error untyped
|
|
86
|
+
provide(GlobalDebugKey, globalDebug)
|
|
87
|
+
provide(PathDebugKey, pathDebug)
|
|
88
|
+
provide(PathDebugStatusKey, pathDebugStatus)
|
|
89
|
+
provide(RefetchPathDebugKey, refreshPathDebug)
|
|
90
|
+
|
|
91
|
+
const {
|
|
92
|
+
isDebugLoading,
|
|
93
|
+
error,
|
|
94
|
+
refreshSources,
|
|
95
|
+
resetProps,
|
|
96
|
+
} = useOgImage()
|
|
97
|
+
|
|
98
|
+
await resetProps(false)
|
|
99
|
+
|
|
100
|
+
const route = useRoute()
|
|
101
|
+
const currentTab = computed(() => {
|
|
102
|
+
const p = route.path
|
|
103
|
+
if (p === '/og-image/templates')
|
|
104
|
+
return 'templates'
|
|
105
|
+
if (p === '/og-image/debug')
|
|
106
|
+
return 'debug'
|
|
107
|
+
if (p === '/og-image/docs')
|
|
108
|
+
return 'docs'
|
|
109
|
+
return 'design'
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const navItems = [
|
|
113
|
+
{ value: 'design', to: '/og-image', icon: 'carbon:brush-freehand', label: 'Design', devOnly: false },
|
|
114
|
+
{ value: 'templates', to: '/og-image/templates', icon: 'carbon:image', label: 'Templates', devOnly: false },
|
|
115
|
+
{ value: 'debug', to: '/og-image/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },
|
|
116
|
+
{ value: 'docs', to: '/og-image/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
const runtimeVersion = computed(() => {
|
|
120
|
+
// @ts-expect-error untyped
|
|
121
|
+
return globalDebug.value?.runtimeConfig?.version || 'unknown'
|
|
122
|
+
})
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<DevtoolsLayout
|
|
127
|
+
module-name="nuxt-og-image"
|
|
128
|
+
title="OG Image"
|
|
129
|
+
icon="carbon:image-search"
|
|
130
|
+
:version="runtimeVersion"
|
|
131
|
+
:nav-items="navItems"
|
|
132
|
+
github-url="https://github.com/nuxt-modules/og-image"
|
|
133
|
+
:loading="isDebugLoading || !!error"
|
|
134
|
+
:active-tab="currentTab"
|
|
135
|
+
@refresh="refreshSources"
|
|
136
|
+
>
|
|
137
|
+
<AddComponentDialog />
|
|
138
|
+
<CreateOgImageDialog />
|
|
139
|
+
<RendererSelectModal />
|
|
140
|
+
<NuxtPage />
|
|
141
|
+
</DevtoolsLayout>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<style>
|
|
145
|
+
/* Textarea */
|
|
146
|
+
textarea {
|
|
147
|
+
background: var(--color-surface-sunken);
|
|
148
|
+
border: 1px solid var(--color-border);
|
|
149
|
+
border-radius: var(--radius-md);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
textarea:focus-visible {
|
|
153
|
+
border-color: var(--seo-green);
|
|
154
|
+
outline: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* JSON Editor theme */
|
|
158
|
+
:root {
|
|
159
|
+
--jse-theme-color: var(--color-surface-elevated) !important;
|
|
160
|
+
--jse-text-color-inverse: var(--color-text-muted) !important;
|
|
161
|
+
--jse-theme-color-highlight: var(--color-surface-sunken) !important;
|
|
162
|
+
--jse-panel-background: var(--color-surface-elevated) !important;
|
|
163
|
+
--jse-background-color: var(--jse-panel-background) !important;
|
|
164
|
+
--jse-error-color: oklch(65% 0.2 25 / 0.3) !important;
|
|
165
|
+
--jse-main-border: none !important;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.dark,
|
|
169
|
+
.jse-theme-dark {
|
|
170
|
+
--jse-panel-background: var(--color-neutral-900) !important;
|
|
171
|
+
--jse-theme-color: var(--color-neutral-900) !important;
|
|
172
|
+
--jse-text-color-inverse: var(--color-neutral-300) !important;
|
|
173
|
+
--jse-main-border: none !important;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.jse-main {
|
|
177
|
+
min-height: 1em !important;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.jse-contents {
|
|
181
|
+
border-width: 0 !important;
|
|
182
|
+
border-radius: var(--radius-md) !important;
|
|
183
|
+
}
|
|
184
|
+
</style>
|
package/dist/module.cjs
CHANGED
|
@@ -10,7 +10,7 @@ require('ohash');
|
|
|
10
10
|
require('pathe');
|
|
11
11
|
require('pkg-types');
|
|
12
12
|
require('std-env');
|
|
13
|
-
const module$1 = require('./shared/nuxt-og-image.
|
|
13
|
+
const module$1 = require('./shared/nuxt-og-image.CfTPCtaS.cjs');
|
|
14
14
|
require('nuxtseo-shared/kit');
|
|
15
15
|
require('../dist/runtime/logger.js');
|
|
16
16
|
require('node:crypto');
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import 'ohash';
|
|
|
8
8
|
import 'pathe';
|
|
9
9
|
import 'pkg-types';
|
|
10
10
|
import 'std-env';
|
|
11
|
-
export { m as default } from './shared/nuxt-og-image.
|
|
11
|
+
export { m as default } from './shared/nuxt-og-image.DdbTs-xp.mjs';
|
|
12
12
|
import 'nuxtseo-shared/kit';
|
|
13
13
|
import '../dist/runtime/logger.js';
|
|
14
14
|
import 'node:crypto';
|
|
@@ -120,8 +120,8 @@ export default defineTransformer([
|
|
|
120
120
|
let src = node.props.src;
|
|
121
121
|
if (src.startsWith("data:"))
|
|
122
122
|
return;
|
|
123
|
-
if (src.endsWith(".webp")) {
|
|
124
|
-
logger.warn("Using WebP images with Satori is not supported. Please consider switching image format or use the
|
|
123
|
+
if (ctx.renderer.name === "satori" && src.endsWith(".webp")) {
|
|
124
|
+
logger.warn("Using WebP images with Satori is not supported. Please consider switching image format or use the takumi or browser renderer.", src);
|
|
125
125
|
}
|
|
126
126
|
const isRelative = src.startsWith("/");
|
|
127
127
|
if (!isRelative)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an inline `style="..."` attribute value into a camelCase property map
|
|
3
|
+
* suitable for handing to Satori / Takumi.
|
|
4
|
+
*
|
|
5
|
+
* Kept in a leaf module (no nitro / h3 imports) so it can be unit-tested
|
|
6
|
+
* directly without pulling the full runtime bundle.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseStyleAttr(style: string | null | undefined): Record<string, any> | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { splitCssDeclarations } from "../utils/css.js";
|
|
2
|
+
const RE_KEBAB_SEGMENT = /-([a-z])/g;
|
|
3
|
+
const RE_CSS_QUOTES = /^['"](.+)['"]$/;
|
|
4
|
+
const RE_IMPORTANT = /\s*!important\s*$/;
|
|
5
|
+
const RE_HTML_ENTITY = /&(?:amp|quot|#39|#x27);/g;
|
|
6
|
+
const HTML_ENTITY_MAP = {
|
|
7
|
+
"&": "&",
|
|
8
|
+
""": '"',
|
|
9
|
+
"'": "'",
|
|
10
|
+
"'": "'"
|
|
11
|
+
};
|
|
12
|
+
function camelCase(str) {
|
|
13
|
+
return str.replace(RE_KEBAB_SEGMENT, (_, c) => c.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
function decodeHtmlEntities(value) {
|
|
16
|
+
return value.replace(RE_HTML_ENTITY, (m) => HTML_ENTITY_MAP[m] ?? m);
|
|
17
|
+
}
|
|
18
|
+
export function parseStyleAttr(style) {
|
|
19
|
+
if (!style)
|
|
20
|
+
return void 0;
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const decl of splitCssDeclarations(decodeHtmlEntities(style))) {
|
|
23
|
+
const colonIdx = decl.indexOf(":");
|
|
24
|
+
if (colonIdx === -1)
|
|
25
|
+
continue;
|
|
26
|
+
const prop = decl.slice(0, colonIdx).trim();
|
|
27
|
+
const val = decl.slice(colonIdx + 1).trim();
|
|
28
|
+
if (prop && val) {
|
|
29
|
+
const cleanVal = val.replace(RE_IMPORTANT, "");
|
|
30
|
+
result[camelCase(prop)] = prop === "font-family" ? cleanVal.replace(RE_CSS_QUOTES, "$1") : cleanVal;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.keys(result).length ? result : void 0;
|
|
34
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ElementNode } from 'ultrahtml';
|
|
2
2
|
import type { OgImageRenderEventContext, VNode } from '../../../types.js';
|
|
3
|
+
import { parseStyleAttr } from './style-attr.js';
|
|
4
|
+
export { parseStyleAttr };
|
|
3
5
|
export declare const SVG_CAMEL_ATTR_VALUES: Set<string>;
|
|
4
6
|
/**
|
|
5
7
|
* Resolve an SVG element's width or height from multiple sources:
|
|
@@ -11,7 +13,6 @@ export declare const SVG_CAMEL_ATTR_VALUES: Set<string>;
|
|
|
11
13
|
* can fall back to ancestor or renderer-specific resolution.
|
|
12
14
|
*/
|
|
13
15
|
export declare function resolveSvgDimension(props: Record<string, any>, style: Record<string, any> | undefined, key: 'width' | 'height'): number | undefined;
|
|
14
|
-
export declare function parseStyleAttr(style: string | null | undefined): Record<string, any> | undefined;
|
|
15
16
|
export declare function elementToVNode(el: ElementNode): VNode;
|
|
16
17
|
export declare function htmlToVNode(html: string): VNode;
|
|
17
18
|
export declare function warnUnsupportedSvgElements(vnode: VNode, component?: string): void;
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { ELEMENT_NODE, parse, TEXT_NODE } from "ultrahtml";
|
|
2
2
|
import { querySelector } from "ultrahtml/selector";
|
|
3
|
-
import { decodeHtml
|
|
3
|
+
import { decodeHtml } from "../../util/encoding.js";
|
|
4
4
|
import { fetchIsland } from "../../util/kit.js";
|
|
5
5
|
import { logger } from "../../util/logger.js";
|
|
6
|
-
import { splitCssDeclarations } from "../utils/css.js";
|
|
7
6
|
import { walkTree } from "./plugins.js";
|
|
8
7
|
import encoding from "./plugins/encoding.js";
|
|
9
8
|
import imageSrc from "./plugins/imageSrc.js";
|
|
10
9
|
import styleDirectives from "./plugins/styleDirectives.js";
|
|
10
|
+
import { parseStyleAttr } from "./style-attr.js";
|
|
11
11
|
import { applyEmojis } from "./transforms/emojis/index.js";
|
|
12
|
-
|
|
13
|
-
const RE_AMP_ENTITY = /&/g;
|
|
14
|
-
const RE_CSS_QUOTES = /^['"](.+)['"]$/;
|
|
15
|
-
const RE_IMPORTANT = /\s*!important\s*$/;
|
|
12
|
+
export { parseStyleAttr };
|
|
16
13
|
const RE_BODY_CONTENT = /<body>([\s\S]*)<\/body>/;
|
|
17
|
-
function camelCase(str) {
|
|
18
|
-
return str.replace(RE_KEBAB_SEGMENT, (_, c) => c.toUpperCase());
|
|
19
|
-
}
|
|
20
14
|
const SVG_CAMEL_ATTRS = {
|
|
21
15
|
viewbox: "viewBox",
|
|
22
16
|
preserveaspectratio: "preserveAspectRatio",
|
|
@@ -94,23 +88,6 @@ export function resolveSvgDimension(props, style, key) {
|
|
|
94
88
|
}
|
|
95
89
|
}
|
|
96
90
|
}
|
|
97
|
-
export function parseStyleAttr(style) {
|
|
98
|
-
if (!style)
|
|
99
|
-
return void 0;
|
|
100
|
-
const result = {};
|
|
101
|
-
for (const decl of splitCssDeclarations(style.replace(RE_AMP_ENTITY, "&"))) {
|
|
102
|
-
const colonIdx = decl.indexOf(":");
|
|
103
|
-
if (colonIdx === -1)
|
|
104
|
-
continue;
|
|
105
|
-
const prop = decl.slice(0, colonIdx).trim();
|
|
106
|
-
const val = decl.slice(colonIdx + 1).trim();
|
|
107
|
-
if (prop && val) {
|
|
108
|
-
const cleanVal = val.replace(RE_IMPORTANT, "");
|
|
109
|
-
result[camelCase(prop)] = prop === "font-family" ? cleanVal.replace(RE_CSS_QUOTES, "$1") : cleanVal;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return Object.keys(result).length ? result : void 0;
|
|
113
|
-
}
|
|
114
91
|
export function elementToVNode(el) {
|
|
115
92
|
const props = {};
|
|
116
93
|
const { style, ...attrs } = el.attributes;
|
|
@@ -180,7 +157,6 @@ export async function createVNodes(ctx, options) {
|
|
|
180
157
|
if (!html) {
|
|
181
158
|
const islandTimeout = ctx.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
182
159
|
const island = await ctx.timings.measure("island-fetch", () => fetchIsland(ctx.e, ctx.options.component, typeof ctx.options.props !== "undefined" ? ctx.options.props : ctx.options, islandTimeout));
|
|
183
|
-
island.html = htmlDecodeQuotes(island.html);
|
|
184
160
|
await applyEmojis(ctx, island);
|
|
185
161
|
html = island.html;
|
|
186
162
|
if (html?.includes("<body>")) {
|
|
@@ -3125,7 +3125,6 @@ const RE_SVG_ID_ATTR = /\bid="([^"]+)"/g;
|
|
|
3125
3125
|
const RE_SVG_VIEWBOX = /viewBox="([^"]*)"/;
|
|
3126
3126
|
const RE_SVG_BODY = /<svg[^>]*>([\s\S]*)<\/svg>/;
|
|
3127
3127
|
const RE_OG_IMAGE_QUERY$1 = /\?og-image(?:-depth=\d+)?$/;
|
|
3128
|
-
const RE_TEMPLATE_CONTENT = /<template>([\s\S]*?)<\/template>/;
|
|
3129
3128
|
const RE_TEXT_BETWEEN_TAGS = />([^<]*)</g;
|
|
3130
3129
|
const RE_SPECIAL_REGEX_CHARS = /[.*+?^${}()|[\]\\]/g;
|
|
3131
3130
|
const RE_WRAPPER_DIV_START = /^<div>/;
|
|
@@ -3137,7 +3136,6 @@ const RE_DATA_ATTR = /\bdata-([\w-]+)="([^"]*)"/g;
|
|
|
3137
3136
|
const RE_DYNAMIC_DATA_THEME = /\s:data-theme="[^"]+"/;
|
|
3138
3137
|
const RE_COLOR_ATTR_NAME = /^(?:color|fill|stroke|flood-color|lighting-color|stop-color)$/;
|
|
3139
3138
|
const RE_NON_STYLE_VAR_ATTR = /\b(?!style|class)([a-zA-Z-]+)="([^"]*var\(--[^"]+)"/g;
|
|
3140
|
-
const RE_QUOTED_ATTR = /^([a-z-]+)="(.*)"$/i;
|
|
3141
3139
|
const RE_COLOR_PROP_NAME = /color|fill|stroke|background|border|outline|shadow|accent|caret/;
|
|
3142
3140
|
const RE_STYLE_VAR_ATTR = /\bstyle="([^"]*var\(--[^"]+)"/g;
|
|
3143
3141
|
const RE_INLINE_STYLE = /\bstyle="([^"]*)"/g;
|
|
@@ -3187,11 +3185,11 @@ function buildIconSvg(iconData, defaultWidth, defaultHeight, attrs) {
|
|
|
3187
3185
|
existingStyle = value;
|
|
3188
3186
|
continue;
|
|
3189
3187
|
}
|
|
3190
|
-
filteredAttrs.push(`${key}="${value}"`);
|
|
3188
|
+
filteredAttrs.push(`${key}="${escapeAttrValue(value)}"`);
|
|
3191
3189
|
}
|
|
3192
3190
|
const attrsStr = filteredAttrs.length > 0 ? ` ${filteredAttrs.join(" ")}` : "";
|
|
3193
3191
|
const mergedStyle = existingStyle ? `display:flex; ${existingStyle}` : "display:flex";
|
|
3194
|
-
let svg = `<span${attrsStr} style="${mergedStyle}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="100%" height="100%" fill="currentColor">${body}</svg></span>`;
|
|
3192
|
+
let svg = `<span${attrsStr} style="${escapeAttrValue(mergedStyle)}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="100%" height="100%" fill="currentColor">${body}</svg></span>`;
|
|
3195
3193
|
svg = makeIdsUnique(svg);
|
|
3196
3194
|
return svg;
|
|
3197
3195
|
}
|
|
@@ -3343,6 +3341,21 @@ function mergeThemePairs(classes, light, dark) {
|
|
|
3343
3341
|
}
|
|
3344
3342
|
return result;
|
|
3345
3343
|
}
|
|
3344
|
+
function getOuterTemplateBounds(code) {
|
|
3345
|
+
let descriptor;
|
|
3346
|
+
try {
|
|
3347
|
+
descriptor = compilerSfc.parse(code).descriptor;
|
|
3348
|
+
} catch {
|
|
3349
|
+
return void 0;
|
|
3350
|
+
}
|
|
3351
|
+
const template = descriptor.template;
|
|
3352
|
+
if (!template)
|
|
3353
|
+
return void 0;
|
|
3354
|
+
return {
|
|
3355
|
+
templateStart: template.loc.start.offset,
|
|
3356
|
+
templateEnd: template.loc.end.offset
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3346
3359
|
const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
3347
3360
|
let emojiIcons = null;
|
|
3348
3361
|
return {
|
|
@@ -3359,12 +3372,11 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3359
3372
|
const id = rawId.replace(RE_OG_IMAGE_QUERY$1, "");
|
|
3360
3373
|
if (rawId.includes("?og-image"))
|
|
3361
3374
|
options.onNestedTransform?.(id);
|
|
3362
|
-
const
|
|
3363
|
-
if (!
|
|
3375
|
+
const bounds = getOuterTemplateBounds(code);
|
|
3376
|
+
if (!bounds)
|
|
3364
3377
|
return;
|
|
3365
3378
|
const s = new MagicString__default(code);
|
|
3366
|
-
const templateStart
|
|
3367
|
-
const templateEnd = code.indexOf("</template>");
|
|
3379
|
+
const { templateStart, templateEnd } = bounds;
|
|
3368
3380
|
let template = code.slice(templateStart, templateEnd);
|
|
3369
3381
|
let hasChanges = false;
|
|
3370
3382
|
if (options.emojiSet && emojiUtils_js.RE_MATCH_EMOJIS.test(template)) {
|
|
@@ -3551,10 +3563,11 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3551
3563
|
}
|
|
3552
3564
|
});
|
|
3553
3565
|
if (result) {
|
|
3554
|
-
const
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3566
|
+
const newBounds = getOuterTemplateBounds(result.code);
|
|
3567
|
+
if (newBounds) {
|
|
3568
|
+
template = result.code.slice(newBounds.templateStart, newBounds.templateEnd);
|
|
3569
|
+
hasChanges = true;
|
|
3570
|
+
}
|
|
3558
3571
|
}
|
|
3559
3572
|
} catch (err) {
|
|
3560
3573
|
const componentName = id.split("/").pop();
|
|
@@ -3578,16 +3591,16 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3578
3591
|
template.replace(RE_NON_STYLE_VAR_ATTR, (match, attr, value) => {
|
|
3579
3592
|
const resolved = resolveCssVars(value, vars);
|
|
3580
3593
|
if (resolved !== value) {
|
|
3581
|
-
replacements.push({ from: match, to:
|
|
3594
|
+
replacements.push({ from: match, to: "", attr, value: resolved });
|
|
3582
3595
|
}
|
|
3583
3596
|
return match;
|
|
3584
3597
|
});
|
|
3585
3598
|
for (const r of replacements) {
|
|
3586
|
-
|
|
3587
|
-
if (
|
|
3588
|
-
|
|
3589
|
-
r.to = `${attrMatch[1]}="${downleveled}"`;
|
|
3599
|
+
let value = r.value;
|
|
3600
|
+
if (RE_COLOR_ATTR_NAME.test(r.attr)) {
|
|
3601
|
+
value = await downlevelColor(r.attr, value);
|
|
3590
3602
|
}
|
|
3603
|
+
r.to = `${r.attr}="${escapeAttrValue(value)}"`;
|
|
3591
3604
|
template = template.replace(r.from, r.to);
|
|
3592
3605
|
hasChanges = true;
|
|
3593
3606
|
}
|
|
@@ -3596,12 +3609,11 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3596
3609
|
template.replace(RE_STYLE_VAR_ATTR, (match, styleValue) => {
|
|
3597
3610
|
const resolved = resolveCssVars(styleValue, vars);
|
|
3598
3611
|
if (resolved !== styleValue)
|
|
3599
|
-
styleReplacements.push({ from: match,
|
|
3612
|
+
styleReplacements.push({ from: match, content: resolved });
|
|
3600
3613
|
return match;
|
|
3601
3614
|
});
|
|
3602
3615
|
for (const r of styleReplacements) {
|
|
3603
|
-
const
|
|
3604
|
-
const declarations = styleContent.split(";").filter(Boolean);
|
|
3616
|
+
const declarations = r.content.split(";").filter(Boolean);
|
|
3605
3617
|
const downleveled = [];
|
|
3606
3618
|
for (const decl of declarations) {
|
|
3607
3619
|
const colonIdx = decl.indexOf(":");
|
|
@@ -3618,8 +3630,8 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3618
3630
|
}
|
|
3619
3631
|
downleveled.push(`${prop}: ${value}`);
|
|
3620
3632
|
}
|
|
3621
|
-
|
|
3622
|
-
template = template.replace(r.from,
|
|
3633
|
+
const to = `style="${escapeAttrValue(downleveled.join("; "))}"`;
|
|
3634
|
+
template = template.replace(r.from, to);
|
|
3623
3635
|
hasChanges = true;
|
|
3624
3636
|
}
|
|
3625
3637
|
RE_DYNAMIC_STYLE_VAR.lastIndex = 0;
|
|
@@ -3719,7 +3731,9 @@ const AssetTransformPlugin = unplugin.createUnplugin((options) => {
|
|
|
3719
3731
|
existingProps[prop.trim()] = value;
|
|
3720
3732
|
}
|
|
3721
3733
|
const merged = { ...matchedStyles, ...existingProps };
|
|
3722
|
-
el.attributes.style =
|
|
3734
|
+
el.attributes.style = escapeAttrValue(
|
|
3735
|
+
Object.entries(merged).map(([p, v]) => `${p}:${v}`).join(";")
|
|
3736
|
+
);
|
|
3723
3737
|
const remaining = elClasses.filter((c) => !consumedClasses.has(c));
|
|
3724
3738
|
if (remaining.length > 0)
|
|
3725
3739
|
el.attributes.class = remaining.join(" ");
|
|
@@ -4699,7 +4713,7 @@ const module$1 = kit.defineNuxtModule({
|
|
|
4699
4713
|
await onUpgrade(nuxt, options, previousVersion);
|
|
4700
4714
|
},
|
|
4701
4715
|
async setup(config, nuxt) {
|
|
4702
|
-
const _resolver = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/nuxt-og-image.
|
|
4716
|
+
const _resolver = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/nuxt-og-image.CfTPCtaS.cjs', document.baseURI).href)));
|
|
4703
4717
|
const fixSharedPath = (p) => {
|
|
4704
4718
|
return p.replace(/\/shared\/runtime(\/|$)/, "/runtime$1").replace(/\/shared\/client(\/|$)/, "/client$1").replace(/\/shared\/devtools(\/|$)/, "/devtools$1");
|
|
4705
4719
|
};
|