nuxtseo-layer-devtools 0.3.0 → 0.3.4
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { onClickOutside } from '@vueuse/core'
|
|
3
3
|
import { computed, ref } from 'vue'
|
|
4
|
+
import { fetchInstalledModules, showModuleSplash } from '../composables/modules'
|
|
4
5
|
import { colorMode } from '../composables/rpc'
|
|
5
6
|
import { hasProductionUrl, isConnected, isProductionMode, isStandalone, path, previewSource, productionUrl, standaloneUrl } from '../composables/state'
|
|
6
7
|
|
|
@@ -16,6 +17,7 @@ const {
|
|
|
16
17
|
title,
|
|
17
18
|
icon,
|
|
18
19
|
version,
|
|
20
|
+
moduleName,
|
|
19
21
|
navItems,
|
|
20
22
|
githubUrl,
|
|
21
23
|
loading = false,
|
|
@@ -23,6 +25,7 @@ const {
|
|
|
23
25
|
title: string
|
|
24
26
|
icon: string
|
|
25
27
|
version?: string
|
|
28
|
+
moduleName?: string
|
|
26
29
|
navItems: DevtoolsNavItem[]
|
|
27
30
|
githubUrl: string
|
|
28
31
|
loading?: boolean
|
|
@@ -32,6 +35,9 @@ const emit = defineEmits<{
|
|
|
32
35
|
refresh: []
|
|
33
36
|
}>()
|
|
34
37
|
|
|
38
|
+
// Fetch installed modules for the splash screen
|
|
39
|
+
fetchInstalledModules()
|
|
40
|
+
|
|
35
41
|
const activeTab = defineModel<string>('activeTab')
|
|
36
42
|
|
|
37
43
|
const isDark = computed(() => colorMode.value === 'dark')
|
|
@@ -96,15 +102,14 @@ function disconnectStandalone() {
|
|
|
96
102
|
<div class="devtools-header-content">
|
|
97
103
|
<!-- Logo & Brand -->
|
|
98
104
|
<div class="flex items-center gap-3 sm:gap-4">
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class="flex items-center opacity-90 hover:opacity-100 transition-opacity"
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
aria-label="Nuxt SEO Modules"
|
|
108
|
+
class="flex items-center opacity-90 hover:opacity-100 transition-opacity cursor-pointer"
|
|
109
|
+
@click="showModuleSplash = true"
|
|
105
110
|
>
|
|
106
111
|
<NuxtSeoLogo class="h-6 sm:h-7" />
|
|
107
|
-
</
|
|
112
|
+
</button>
|
|
108
113
|
|
|
109
114
|
<div class="devtools-divider" />
|
|
110
115
|
|
|
@@ -271,6 +276,8 @@ function disconnectStandalone() {
|
|
|
271
276
|
</main>
|
|
272
277
|
</div>
|
|
273
278
|
</div>
|
|
279
|
+
|
|
280
|
+
<DevtoolsModuleSplash :current-module="moduleName" />
|
|
274
281
|
</UApp>
|
|
275
282
|
</template>
|
|
276
283
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onClickOutside } from '@vueuse/core'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import { moduleCatalog, showModuleSplash, switchToModule } from '../composables/modules'
|
|
5
|
+
import { isConnected } from '../composables/state'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
currentModule?: string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const panelRef = ref<HTMLElement>()
|
|
12
|
+
onClickOutside(panelRef, () => {
|
|
13
|
+
showModuleSplash.value = false
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
|
|
17
|
+
if (!mod.installed || mod.name === props.currentModule)
|
|
18
|
+
return
|
|
19
|
+
switchToModule(mod.name)
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<Teleport to="body">
|
|
25
|
+
<Transition name="splash">
|
|
26
|
+
<div v-if="showModuleSplash" class="splash-overlay">
|
|
27
|
+
<div ref="panelRef" class="splash-panel">
|
|
28
|
+
<div class="splash-header">
|
|
29
|
+
<NuxtSeoLogo class="h-7" />
|
|
30
|
+
<p class="splash-subtitle">
|
|
31
|
+
All the SEO modules you need for Nuxt, in one place.
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="splash-grid">
|
|
36
|
+
<button
|
|
37
|
+
v-for="mod of moduleCatalog"
|
|
38
|
+
:key="mod.name"
|
|
39
|
+
type="button"
|
|
40
|
+
class="splash-module"
|
|
41
|
+
:class="{
|
|
42
|
+
'is-installed': mod.installed,
|
|
43
|
+
'is-current': mod.name === currentModule,
|
|
44
|
+
'is-unavailable': !mod.installed,
|
|
45
|
+
'is-switchable': mod.installed && mod.name !== currentModule && isConnected,
|
|
46
|
+
}"
|
|
47
|
+
:disabled="!mod.installed || mod.name === currentModule"
|
|
48
|
+
@click="handleModuleClick(mod)"
|
|
49
|
+
>
|
|
50
|
+
<div class="splash-module-icon">
|
|
51
|
+
<UIcon :name="mod.icon" class="text-xl" />
|
|
52
|
+
</div>
|
|
53
|
+
<div class="splash-module-info">
|
|
54
|
+
<div class="splash-module-title">
|
|
55
|
+
{{ mod.title }}
|
|
56
|
+
<UBadge v-if="mod.pro" size="xs" color="neutral" variant="outline" class="ml-1 text-[9px]">
|
|
57
|
+
PRO
|
|
58
|
+
</UBadge>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="splash-module-desc">
|
|
61
|
+
{{ mod.description }}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="splash-module-status">
|
|
65
|
+
<span v-if="mod.name === currentModule" class="splash-current-badge">Current</span>
|
|
66
|
+
<UIcon v-else-if="mod.installed && isConnected" name="carbon:arrow-right" class="text-sm opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
67
|
+
<span v-else-if="!mod.installed" class="splash-not-installed">Not installed</span>
|
|
68
|
+
</div>
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="splash-footer">
|
|
73
|
+
<a href="https://nuxtseo.com" target="_blank" rel="noopener" class="splash-link">
|
|
74
|
+
<UIcon name="carbon:earth" class="w-3.5 h-3.5" />
|
|
75
|
+
nuxtseo.com
|
|
76
|
+
</a>
|
|
77
|
+
<button type="button" class="splash-close" @click="showModuleSplash = false">
|
|
78
|
+
Close
|
|
79
|
+
<UIcon name="carbon:close" class="w-3.5 h-3.5" />
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</Transition>
|
|
85
|
+
</Teleport>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.splash-overlay {
|
|
90
|
+
position: fixed;
|
|
91
|
+
inset: 0;
|
|
92
|
+
z-index: 200;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
background: oklch(0% 0 0 / 0.5);
|
|
97
|
+
backdrop-filter: blur(4px);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.splash-panel {
|
|
101
|
+
width: min(580px, calc(100vw - 2rem));
|
|
102
|
+
max-height: calc(100vh - 2rem);
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
border-radius: var(--radius-lg);
|
|
105
|
+
border: 1px solid var(--color-border);
|
|
106
|
+
background: var(--color-surface);
|
|
107
|
+
box-shadow: 0 24px 48px oklch(0% 0 0 / 0.2);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.dark .splash-panel {
|
|
111
|
+
box-shadow: 0 24px 48px oklch(0% 0 0 / 0.5);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.splash-header {
|
|
115
|
+
padding: 1.5rem 1.5rem 0;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.splash-subtitle {
|
|
120
|
+
margin-top: 0.5rem;
|
|
121
|
+
font-size: 0.8125rem;
|
|
122
|
+
color: var(--color-text-muted);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.splash-grid {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
gap: 2px;
|
|
129
|
+
padding: 1rem 0.75rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.splash-module {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
gap: 0.75rem;
|
|
136
|
+
padding: 0.625rem 0.75rem;
|
|
137
|
+
border-radius: var(--radius-md);
|
|
138
|
+
text-align: left;
|
|
139
|
+
cursor: default;
|
|
140
|
+
transition: background 100ms, opacity 100ms;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.splash-module.is-switchable {
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.splash-module.is-switchable:hover {
|
|
148
|
+
background: var(--color-surface-elevated);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.splash-module.is-switchable:hover .splash-module-icon {
|
|
152
|
+
color: var(--seo-green);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.splash-module.is-current {
|
|
156
|
+
background: oklch(from var(--seo-green) l c h / 0.08);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.splash-module.is-unavailable {
|
|
160
|
+
opacity: 0.45;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.splash-module-icon {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
justify-content: center;
|
|
167
|
+
width: 2.25rem;
|
|
168
|
+
height: 2.25rem;
|
|
169
|
+
flex-shrink: 0;
|
|
170
|
+
border-radius: var(--radius-sm);
|
|
171
|
+
background: var(--color-surface-sunken);
|
|
172
|
+
color: var(--color-text-muted);
|
|
173
|
+
transition: color 100ms;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.splash-module.is-current .splash-module-icon {
|
|
177
|
+
background: oklch(from var(--seo-green) l c h / 0.15);
|
|
178
|
+
color: var(--seo-green);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.splash-module-info {
|
|
182
|
+
flex: 1;
|
|
183
|
+
min-width: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.splash-module-title {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
font-size: 0.8125rem;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
color: var(--color-text);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.splash-module-desc {
|
|
195
|
+
font-size: 0.6875rem;
|
|
196
|
+
color: var(--color-text-muted);
|
|
197
|
+
margin-top: 1px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.splash-module-status {
|
|
201
|
+
flex-shrink: 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.splash-current-badge {
|
|
205
|
+
font-size: 0.625rem;
|
|
206
|
+
font-weight: 600;
|
|
207
|
+
text-transform: uppercase;
|
|
208
|
+
letter-spacing: 0.05em;
|
|
209
|
+
color: var(--seo-green);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.splash-not-installed {
|
|
213
|
+
font-size: 0.6875rem;
|
|
214
|
+
color: var(--color-text-subtle);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.splash-footer {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: space-between;
|
|
221
|
+
padding: 0.75rem 1.25rem;
|
|
222
|
+
border-top: 1px solid var(--color-border);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.splash-link {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: 0.375rem;
|
|
229
|
+
font-size: 0.75rem;
|
|
230
|
+
color: var(--color-text-muted);
|
|
231
|
+
text-decoration: none;
|
|
232
|
+
transition: color 100ms;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.splash-link:hover {
|
|
236
|
+
color: var(--seo-green);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.splash-close {
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
gap: 0.375rem;
|
|
243
|
+
font-size: 0.75rem;
|
|
244
|
+
font-weight: 500;
|
|
245
|
+
color: var(--color-text-muted);
|
|
246
|
+
padding: 0.25rem 0.625rem;
|
|
247
|
+
border-radius: var(--radius-sm);
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
transition: background 100ms, color 100ms;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.splash-close:hover {
|
|
253
|
+
background: var(--color-surface-sunken);
|
|
254
|
+
color: var(--color-text);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Transitions */
|
|
258
|
+
.splash-enter-active {
|
|
259
|
+
transition: opacity 200ms ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.splash-enter-active .splash-panel {
|
|
263
|
+
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 200ms ease;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.splash-leave-active {
|
|
267
|
+
transition: opacity 150ms ease;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.splash-leave-active .splash-panel {
|
|
271
|
+
transition: transform 150ms ease, opacity 150ms ease;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.splash-enter-from {
|
|
275
|
+
opacity: 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.splash-enter-from .splash-panel {
|
|
279
|
+
transform: scale(0.95);
|
|
280
|
+
opacity: 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.splash-leave-to {
|
|
284
|
+
opacity: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.splash-leave-to .splash-panel {
|
|
288
|
+
transform: scale(0.97);
|
|
289
|
+
opacity: 0;
|
|
290
|
+
}
|
|
291
|
+
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import { standaloneUrl } from '../composables/state'
|
|
4
4
|
|
|
5
|
+
const trailingSlashes = /\/+$/
|
|
5
6
|
const urlInput = ref(standaloneUrl.value || 'http://localhost:3000')
|
|
6
7
|
const error = ref('')
|
|
7
8
|
const connecting = ref(false)
|
|
@@ -9,7 +10,7 @@ const connecting = ref(false)
|
|
|
9
10
|
async function connect() {
|
|
10
11
|
error.value = ''
|
|
11
12
|
connecting.value = true
|
|
12
|
-
const url = urlInput.value.replace(
|
|
13
|
+
const url = urlInput.value.replace(trailingSlashes, '')
|
|
13
14
|
try {
|
|
14
15
|
// Verify the dev server is reachable
|
|
15
16
|
await fetch(url, { mode: 'no-cors', signal: AbortSignal.timeout(5000) })
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { NuxtDevtoolsIframeClient } from '@nuxt/devtools-kit/types'
|
|
2
|
+
import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
import { isConnected } from './state'
|
|
5
|
+
|
|
6
|
+
export interface SeoModuleInfo {
|
|
7
|
+
name: string
|
|
8
|
+
title: string
|
|
9
|
+
icon: string
|
|
10
|
+
route: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SeoModuleCatalogEntry {
|
|
14
|
+
name: string
|
|
15
|
+
title: string
|
|
16
|
+
description: string
|
|
17
|
+
icon: string
|
|
18
|
+
installed: boolean
|
|
19
|
+
route?: string
|
|
20
|
+
npmUrl: string
|
|
21
|
+
pro?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Full catalog of all Nuxt SEO modules for the splash screen
|
|
25
|
+
const MODULE_CATALOG: Omit<SeoModuleCatalogEntry, 'installed' | 'route'>[] = [
|
|
26
|
+
{ name: 'nuxt-robots', title: 'Robots', description: 'Manage robots.txt and meta robots', icon: 'carbon:bot', npmUrl: 'https://npmjs.com/package/@nuxtjs/robots' },
|
|
27
|
+
{ name: 'sitemap', title: 'Sitemap', description: 'Generate XML sitemaps', icon: 'carbon:load-balancer-application', npmUrl: 'https://npmjs.com/package/@nuxtjs/sitemap' },
|
|
28
|
+
{ name: 'nuxt-og-image', title: 'OG Image', description: 'Generate dynamic Open Graph images', icon: 'carbon:image-search', npmUrl: 'https://npmjs.com/package/nuxt-og-image' },
|
|
29
|
+
{ name: 'nuxt-schema-org', title: 'Schema.org', description: 'Add structured data with Schema.org', icon: 'carbon:chart-relationship', npmUrl: 'https://npmjs.com/package/nuxt-schema-org' },
|
|
30
|
+
{ name: 'nuxt-seo-utils', title: 'SEO Utils', description: 'Core SEO utilities and meta tags', icon: 'carbon:search-locate', npmUrl: 'https://npmjs.com/package/nuxt-seo-utils' },
|
|
31
|
+
{ name: 'nuxt-link-checker', title: 'Link Checker', description: 'Find and fix broken links', icon: 'carbon:cloud-satellite-link', npmUrl: 'https://npmjs.com/package/nuxt-link-checker' },
|
|
32
|
+
{ name: 'nuxt-site-config', title: 'Site Config', description: 'Shared site configuration', icon: 'carbon:settings', npmUrl: 'https://npmjs.com/package/nuxt-site-config' },
|
|
33
|
+
{ name: 'nuxt-ai-ready', title: 'AI Ready', description: 'Optimize for AI search engines', icon: 'carbon:machine-learning-model', npmUrl: 'https://npmjs.com/package/nuxt-ai-ready', pro: true },
|
|
34
|
+
{ name: 'nuxt-skew-protection', title: 'Skew Protection', description: 'Protect against deployment skew', icon: 'carbon:shield-check', npmUrl: 'https://npmjs.com/package/nuxt-skew-protection', pro: true },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const installedModules = ref<SeoModuleInfo[]>([])
|
|
38
|
+
export const showModuleSplash = ref(false)
|
|
39
|
+
|
|
40
|
+
export const moduleCatalog = computed<SeoModuleCatalogEntry[]>(() => {
|
|
41
|
+
return MODULE_CATALOG.map((entry) => {
|
|
42
|
+
const installed = installedModules.value.find(m => m.name === entry.name)
|
|
43
|
+
return {
|
|
44
|
+
...entry,
|
|
45
|
+
installed: !!installed,
|
|
46
|
+
route: installed?.route,
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export function fetchInstalledModules(): void {
|
|
52
|
+
const inIframe = window.parent !== window
|
|
53
|
+
if (!inIframe)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
onDevtoolsClientConnected(async (client) => {
|
|
57
|
+
const rpc = client.devtools.extendClientRpc('nuxt-seo-modules', {})
|
|
58
|
+
try {
|
|
59
|
+
const modules = await (rpc as any).getInstalledSeoModules()
|
|
60
|
+
if (Array.isArray(modules))
|
|
61
|
+
installedModules.value = modules
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// RPC not available (module might not have the shared registration)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveDevtoolsIframe(): NuxtDevtoolsIframeClient | undefined {
|
|
70
|
+
// We're inside the devtools iframe, so we need to go up to the parent devtools frame
|
|
71
|
+
try {
|
|
72
|
+
const devtoolsWindow = window.parent
|
|
73
|
+
if (!devtoolsWindow)
|
|
74
|
+
return
|
|
75
|
+
return (devtoolsWindow as any).__NUXT_DEVTOOLS__ as NuxtDevtoolsIframeClient
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function switchToModule(moduleName: string): void {
|
|
83
|
+
if (!isConnected.value)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
const devtoolsClient = resolveDevtoolsIframe()
|
|
87
|
+
if (!devtoolsClient)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
const iframe = devtoolsClient.host?.getIframe?.()
|
|
91
|
+
if (iframe) {
|
|
92
|
+
iframe.src = `/__nuxt_devtools__/client/modules/custom-${moduleName}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
showModuleSplash.value = false
|
|
96
|
+
}
|
package/composables/rpc.ts
CHANGED
|
@@ -33,7 +33,9 @@ export function useDevtoolsConnection(options: DevtoolsConnectionOptions = {}):
|
|
|
33
33
|
// @ts-expect-error untyped
|
|
34
34
|
appFetch.value = client.host.app.$fetch
|
|
35
35
|
watchEffect(() => {
|
|
36
|
-
colorMode.value = client.host.app.colorMode
|
|
36
|
+
colorMode.value = client.host.app.colorMode?.value ?? (
|
|
37
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
38
|
+
)
|
|
37
39
|
})
|
|
38
40
|
devtools.value = client.devtools
|
|
39
41
|
options.onConnected?.(client)
|
package/composables/state.ts
CHANGED
|
@@ -9,6 +9,11 @@ export const path = ref('/')
|
|
|
9
9
|
export const query = ref()
|
|
10
10
|
export const base = ref('/')
|
|
11
11
|
|
|
12
|
+
// Standalone mode state
|
|
13
|
+
export const standaloneUrl = useLocalStorage<string>('nuxt-seo:standalone-url', '')
|
|
14
|
+
export const isConnected = ref(false)
|
|
15
|
+
export const isStandalone = computed(() => !isConnected.value && !!standaloneUrl.value)
|
|
16
|
+
|
|
12
17
|
export const host = computed(() => {
|
|
13
18
|
if (isStandalone.value)
|
|
14
19
|
return standaloneUrl.value
|
|
@@ -35,8 +40,3 @@ export const hasProductionUrl = computed(() => {
|
|
|
35
40
|
})
|
|
36
41
|
|
|
37
42
|
export const isProductionMode = computed(() => previewSource.value === 'production' && hasProductionUrl.value)
|
|
38
|
-
|
|
39
|
-
// Standalone mode state
|
|
40
|
-
export const standaloneUrl = useLocalStorage<string>('nuxt-seo:standalone-url', '')
|
|
41
|
-
export const isConnected = ref(false)
|
|
42
|
-
export const isStandalone = computed(() => !isConnected.value && !!standaloneUrl.value)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxtseo-layer-devtools",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"description": "Shared Nuxt layer for Nuxt SEO devtools clients.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"nuxt": "^4.4.2",
|
|
41
|
-
"vue": "^3.5.
|
|
41
|
+
"vue": "^3.5.31"
|
|
42
42
|
}
|
|
43
43
|
}
|