nuxtseo-shared 0.1.4 → 0.1.6
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/content.d.mts +8 -20
- package/dist/content.mjs +1 -3
- package/dist/layer-devtools/assets/css/global.css +216 -0
- package/dist/layer-devtools/components/DevtoolsDocs.vue +11 -0
- package/dist/layer-devtools/components/DevtoolsLayout.vue +213 -0
- package/dist/layer-devtools/composables/init.ts +15 -0
- package/package.json +2 -2
package/dist/content.d.mts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
1
|
//#region src/content.d.ts
|
|
4
|
-
type ZodInstance = typeof z;
|
|
5
|
-
type ZodTypeAny = z.ZodTypeAny;
|
|
6
2
|
interface ContentSchemaOptions {
|
|
7
3
|
/**
|
|
8
4
|
* Pass the `z` instance from `@nuxt/content` to ensure `.editor()` works
|
|
9
5
|
* across Zod versions. When omitted, the module's bundled `z` is used.
|
|
10
6
|
*/
|
|
11
|
-
z?:
|
|
7
|
+
z?: any;
|
|
12
8
|
}
|
|
13
9
|
interface ContentEditorConfig {
|
|
14
10
|
hidden?: boolean;
|
|
@@ -19,13 +15,12 @@ interface ContentEditorConfig {
|
|
|
19
15
|
* Apply Nuxt Content `.editor()` metadata to a zod schema field.
|
|
20
16
|
* No-ops gracefully when `.editor()` is not patched onto ZodType (outside Nuxt Content).
|
|
21
17
|
*/
|
|
22
|
-
declare function withEditor<T
|
|
18
|
+
declare function withEditor<T>(schema: T, config: ContentEditorConfig): T;
|
|
23
19
|
/**
|
|
24
20
|
* Hide a zod schema field from the Nuxt Content / Studio editor.
|
|
25
|
-
* Only use for fields that genuinely don't work in a form (freeform JSON, deeply nested arrays).
|
|
26
21
|
*/
|
|
27
|
-
declare function withEditorHidden<T
|
|
28
|
-
interface DefineContentSchemaConfig<TSchema
|
|
22
|
+
declare function withEditorHidden<T>(schema: T): T;
|
|
23
|
+
interface DefineContentSchemaConfig<TSchema = any, TDefineOptions extends ContentSchemaOptions = ContentSchemaOptions> {
|
|
29
24
|
/**
|
|
30
25
|
* The field name used in frontmatter (e.g. 'robots', 'sitemap', 'ogImage').
|
|
31
26
|
*/
|
|
@@ -34,7 +29,7 @@ interface DefineContentSchemaConfig<TSchema extends ZodTypeAny, TDefineOptions e
|
|
|
34
29
|
* Build the zod schema for this field. Receives the zod instance
|
|
35
30
|
* (either the user's `@nuxt/content` patched version or the module's bundled one).
|
|
36
31
|
*/
|
|
37
|
-
buildSchema: (z:
|
|
32
|
+
buildSchema: (z: any) => TSchema;
|
|
38
33
|
/**
|
|
39
34
|
* Module label for deprecation warnings (e.g. 'robots', 'sitemap').
|
|
40
35
|
*/
|
|
@@ -58,7 +53,6 @@ interface DefineContentSchemaConfig<TSchema extends ZodTypeAny, TDefineOptions e
|
|
|
58
53
|
* - Deprecated `asXxxCollection()` wrapper with migration warning
|
|
59
54
|
*
|
|
60
55
|
* @example
|
|
61
|
-
* // In @nuxtjs/robots/content.ts
|
|
62
56
|
* import { z } from 'zod'
|
|
63
57
|
* import { createContentSchemaFactory } from 'nuxtseo-shared/content'
|
|
64
58
|
*
|
|
@@ -66,21 +60,15 @@ interface DefineContentSchemaConfig<TSchema extends ZodTypeAny, TDefineOptions e
|
|
|
66
60
|
* fieldName: 'robots',
|
|
67
61
|
* label: 'robots',
|
|
68
62
|
* docsUrl: 'https://nuxtseo.com/robots/guides/content',
|
|
69
|
-
* buildSchema: (z) => z.
|
|
63
|
+
* buildSchema: (z) => z.union([z.string(), z.boolean()]).optional(),
|
|
70
64
|
* }, z)
|
|
71
65
|
*
|
|
72
66
|
* export { defineSchema as defineRobotsSchema, asCollection as asRobotsCollection, schema }
|
|
73
67
|
*/
|
|
74
|
-
declare function createContentSchemaFactory<TSchema
|
|
68
|
+
declare function createContentSchemaFactory<TSchema, TDefineOptions extends ContentSchemaOptions = ContentSchemaOptions>(config: DefineContentSchemaConfig<TSchema, TDefineOptions>, defaultZ: any): {
|
|
75
69
|
defineSchema: (options?: TDefineOptions) => TSchema;
|
|
76
70
|
asCollection: <T>(collection: any) => T;
|
|
77
|
-
schema:
|
|
78
|
-
[x: string]: TSchema;
|
|
79
|
-
}, "strip", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<{
|
|
80
|
-
[x: string]: TSchema;
|
|
81
|
-
}>, any> extends infer T ? { [k in keyof T]: T[k] } : never, z.baseObjectInputType<{
|
|
82
|
-
[x: string]: TSchema;
|
|
83
|
-
}> extends infer T_1 ? { [k_1 in keyof T_1]: T_1[k_1] } : never>;
|
|
71
|
+
schema: any;
|
|
84
72
|
fieldSchema: TSchema;
|
|
85
73
|
};
|
|
86
74
|
//#endregion
|
package/dist/content.mjs
CHANGED
|
@@ -9,7 +9,6 @@ function withEditor(schema, config) {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Hide a zod schema field from the Nuxt Content / Studio editor.
|
|
12
|
-
* Only use for fields that genuinely don't work in a form (freeform JSON, deeply nested arrays).
|
|
13
12
|
*/
|
|
14
13
|
function withEditorHidden(schema) {
|
|
15
14
|
return withEditor(schema, { hidden: true });
|
|
@@ -23,7 +22,6 @@ function withEditorHidden(schema) {
|
|
|
23
22
|
* - Deprecated `asXxxCollection()` wrapper with migration warning
|
|
24
23
|
*
|
|
25
24
|
* @example
|
|
26
|
-
* // In @nuxtjs/robots/content.ts
|
|
27
25
|
* import { z } from 'zod'
|
|
28
26
|
* import { createContentSchemaFactory } from 'nuxtseo-shared/content'
|
|
29
27
|
*
|
|
@@ -31,7 +29,7 @@ function withEditorHidden(schema) {
|
|
|
31
29
|
* fieldName: 'robots',
|
|
32
30
|
* label: 'robots',
|
|
33
31
|
* docsUrl: 'https://nuxtseo.com/robots/guides/content',
|
|
34
|
-
* buildSchema: (z) => z.
|
|
32
|
+
* buildSchema: (z) => z.union([z.string(), z.boolean()]).optional(),
|
|
35
33
|
* }, z)
|
|
36
34
|
*
|
|
37
35
|
* export { defineSchema as defineRobotsSchema, asCollection as asRobotsCollection, schema }
|
|
@@ -375,3 +375,219 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
375
375
|
.dark ::selection {
|
|
376
376
|
background: oklch(65% 0.2 145 / 0.35);
|
|
377
377
|
}
|
|
378
|
+
|
|
379
|
+
/* ---- DevtoolsLayout ---- */
|
|
380
|
+
|
|
381
|
+
/* Base HTML for devtools */
|
|
382
|
+
html {
|
|
383
|
+
font-family: var(--font-sans);
|
|
384
|
+
overflow-y: scroll;
|
|
385
|
+
overscroll-behavior: none;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
body {
|
|
389
|
+
min-height: 100vh;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
html.dark {
|
|
393
|
+
color-scheme: dark;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Header */
|
|
397
|
+
.devtools-header {
|
|
398
|
+
border-bottom: 1px solid var(--color-border);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.devtools-header-content {
|
|
402
|
+
display: flex;
|
|
403
|
+
justify-content: space-between;
|
|
404
|
+
align-items: center;
|
|
405
|
+
padding: 0.625rem 1rem;
|
|
406
|
+
max-width: 80rem;
|
|
407
|
+
margin: 0 auto;
|
|
408
|
+
width: 100%;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@media (min-width: 640px) {
|
|
412
|
+
.devtools-header-content {
|
|
413
|
+
padding: 0.75rem 1.25rem;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.devtools-divider {
|
|
418
|
+
width: 1px;
|
|
419
|
+
height: 1.25rem;
|
|
420
|
+
background: var(--color-border);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.devtools-brand-icon {
|
|
424
|
+
display: flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
justify-content: center;
|
|
427
|
+
width: 1.75rem;
|
|
428
|
+
height: 1.75rem;
|
|
429
|
+
border-radius: var(--radius-sm);
|
|
430
|
+
background: oklch(65% 0.2 145 / 0.12);
|
|
431
|
+
color: var(--seo-green);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* Navigation tabs */
|
|
435
|
+
.devtools-nav-tabs {
|
|
436
|
+
display: flex;
|
|
437
|
+
align-items: center;
|
|
438
|
+
gap: 0.125rem;
|
|
439
|
+
padding: 0.25rem;
|
|
440
|
+
border-radius: var(--radius-md);
|
|
441
|
+
background: var(--color-surface-sunken);
|
|
442
|
+
border: 1px solid var(--color-border-subtle);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.devtools-nav-tab {
|
|
446
|
+
position: relative;
|
|
447
|
+
border-radius: var(--radius-sm);
|
|
448
|
+
transition: background 150ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 150ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.devtools-nav-tab-inner {
|
|
452
|
+
display: flex;
|
|
453
|
+
align-items: center;
|
|
454
|
+
gap: 0.375rem;
|
|
455
|
+
padding: 0.375rem 0.5rem;
|
|
456
|
+
color: var(--color-text-muted);
|
|
457
|
+
font-size: 0.8125rem;
|
|
458
|
+
font-weight: 500;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@media (min-width: 640px) {
|
|
462
|
+
.devtools-nav-tab-inner {
|
|
463
|
+
padding: 0.375rem 0.75rem;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.devtools-nav-tab:hover .devtools-nav-tab-inner {
|
|
468
|
+
color: var(--color-text);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.devtools-nav-tab.active {
|
|
472
|
+
background: var(--color-surface-elevated);
|
|
473
|
+
box-shadow: 0 1px 3px oklch(0% 0 0 / 0.08);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.dark .devtools-nav-tab.active {
|
|
477
|
+
box-shadow: 0 1px 3px oklch(0% 0 0 / 0.3);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.devtools-nav-tab.active .devtools-nav-tab-inner {
|
|
481
|
+
color: var(--color-text);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.devtools-nav-label {
|
|
485
|
+
display: none;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
@media (min-width: 640px) {
|
|
489
|
+
.devtools-nav-label {
|
|
490
|
+
display: inline;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.devtools-nav-action {
|
|
495
|
+
color: var(--color-text-muted) !important;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.devtools-nav-action:hover {
|
|
499
|
+
color: var(--color-text) !important;
|
|
500
|
+
background: var(--color-surface-sunken) !important;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* Preview source toggle */
|
|
504
|
+
.devtools-preview-toggle {
|
|
505
|
+
display: flex;
|
|
506
|
+
gap: 1px;
|
|
507
|
+
background: var(--color-border);
|
|
508
|
+
border-radius: 6px;
|
|
509
|
+
overflow: hidden;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.devtools-preview-btn {
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
gap: 0.25rem;
|
|
516
|
+
padding: 0.25rem 0.5rem;
|
|
517
|
+
font-size: 0.6875rem;
|
|
518
|
+
font-weight: 500;
|
|
519
|
+
color: var(--color-text-muted);
|
|
520
|
+
background: var(--color-surface-sunken);
|
|
521
|
+
border: none;
|
|
522
|
+
cursor: pointer;
|
|
523
|
+
transition: color 150ms, background 150ms;
|
|
524
|
+
white-space: nowrap;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.devtools-preview-btn:hover {
|
|
528
|
+
color: var(--color-text);
|
|
529
|
+
background: var(--color-surface-elevated);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.devtools-preview-btn.active {
|
|
533
|
+
color: var(--color-text);
|
|
534
|
+
background: var(--color-surface-elevated);
|
|
535
|
+
box-shadow: 0 1px 2px oklch(0% 0 0 / 0.06);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.dark .devtools-preview-btn.active {
|
|
539
|
+
box-shadow: 0 1px 2px oklch(0% 0 0 / 0.2);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* Production URL badge */
|
|
543
|
+
.devtools-production-badge {
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
gap: 0.375rem;
|
|
547
|
+
padding: 0.125rem 0.5rem;
|
|
548
|
+
border-radius: 9999px;
|
|
549
|
+
background: oklch(85% 0.12 145 / 0.12);
|
|
550
|
+
color: oklch(45% 0.15 145);
|
|
551
|
+
font-weight: 500;
|
|
552
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.dark .devtools-production-badge {
|
|
556
|
+
background: oklch(35% 0.08 145 / 0.2);
|
|
557
|
+
color: oklch(75% 0.12 145);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.devtools-production-dot {
|
|
561
|
+
width: 6px;
|
|
562
|
+
height: 6px;
|
|
563
|
+
border-radius: 50%;
|
|
564
|
+
background: oklch(65% 0.2 145);
|
|
565
|
+
animation: pulse-dot 2s ease-in-out infinite;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
@keyframes pulse-dot {
|
|
569
|
+
0%, 100% { opacity: 1; }
|
|
570
|
+
50% { opacity: 0.4; }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* Main content wrapper */
|
|
574
|
+
.devtools-main {
|
|
575
|
+
flex: 1;
|
|
576
|
+
display: flex;
|
|
577
|
+
flex-direction: column;
|
|
578
|
+
padding: 0.75rem;
|
|
579
|
+
min-height: calc(100vh - 60px);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
@media (min-width: 640px) {
|
|
583
|
+
.devtools-main {
|
|
584
|
+
padding: 1rem;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
@media (max-height: 600px) {
|
|
589
|
+
.devtools-main {
|
|
590
|
+
padding: 0;
|
|
591
|
+
min-height: 0;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { url } = defineProps<{
|
|
3
|
+
url: string
|
|
4
|
+
}>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="h-full max-h-full overflow-hidden">
|
|
9
|
+
<iframe :src="url" class="w-full h-full border-none" style="min-height: calc(100vh - 100px);" />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { colorMode } from '../composables/rpc'
|
|
4
|
+
import { hasProductionUrl, isProductionMode, previewSource, productionUrl } from '../composables/state'
|
|
5
|
+
|
|
6
|
+
export interface DevtoolsNavItem {
|
|
7
|
+
value: string
|
|
8
|
+
to?: string
|
|
9
|
+
icon: string
|
|
10
|
+
label: string
|
|
11
|
+
devOnly?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
title,
|
|
16
|
+
icon,
|
|
17
|
+
version,
|
|
18
|
+
navItems,
|
|
19
|
+
githubUrl,
|
|
20
|
+
loading = false,
|
|
21
|
+
} = defineProps<{
|
|
22
|
+
title: string
|
|
23
|
+
icon: string
|
|
24
|
+
version?: string
|
|
25
|
+
navItems: DevtoolsNavItem[]
|
|
26
|
+
githubUrl: string
|
|
27
|
+
loading?: boolean
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
refresh: []
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const activeTab = defineModel<string>('activeTab')
|
|
35
|
+
|
|
36
|
+
const isDark = computed(() => colorMode.value === 'dark')
|
|
37
|
+
|
|
38
|
+
useHead({
|
|
39
|
+
title: `Nuxt ${title}`,
|
|
40
|
+
htmlAttrs: {
|
|
41
|
+
class: () => isDark.value ? 'dark' : '',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const filteredNavItems = computed(() =>
|
|
46
|
+
isProductionMode.value
|
|
47
|
+
? navItems.filter(item => !item.devOnly)
|
|
48
|
+
: navItems,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const productionHostname = computed(() => {
|
|
52
|
+
try {
|
|
53
|
+
return new URL(productionUrl.value).hostname
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return productionUrl.value
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const isRouteNav = computed(() => navItems.some(item => item.to))
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<UApp>
|
|
65
|
+
<div class="relative bg-base flex flex-col min-h-screen">
|
|
66
|
+
<div class="gradient-bg" />
|
|
67
|
+
|
|
68
|
+
<header class="devtools-header glass sticky top-0 z-50">
|
|
69
|
+
<div class="devtools-header-content">
|
|
70
|
+
<!-- Logo & Brand -->
|
|
71
|
+
<div class="flex items-center gap-3 sm:gap-4">
|
|
72
|
+
<a
|
|
73
|
+
href="https://nuxtseo.com"
|
|
74
|
+
target="_blank"
|
|
75
|
+
class="flex items-center opacity-90 hover:opacity-100 transition-opacity"
|
|
76
|
+
>
|
|
77
|
+
<NuxtSeoLogo class="h-6 sm:h-7" />
|
|
78
|
+
</a>
|
|
79
|
+
|
|
80
|
+
<div class="devtools-divider" />
|
|
81
|
+
|
|
82
|
+
<div class="flex items-center gap-2">
|
|
83
|
+
<div class="devtools-brand-icon">
|
|
84
|
+
<UIcon :name="icon" class="text-base sm:text-lg" />
|
|
85
|
+
</div>
|
|
86
|
+
<h1 class="text-sm sm:text-base font-semibold tracking-tight text-[var(--color-text)]">
|
|
87
|
+
{{ title }}
|
|
88
|
+
</h1>
|
|
89
|
+
<UBadge
|
|
90
|
+
v-if="version"
|
|
91
|
+
class="font-mono text-[10px] sm:text-xs hidden sm:inline-flex"
|
|
92
|
+
>
|
|
93
|
+
{{ version }}
|
|
94
|
+
</UBadge>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Navigation -->
|
|
99
|
+
<nav class="flex items-center gap-1 sm:gap-2">
|
|
100
|
+
<!-- Nav Tabs -->
|
|
101
|
+
<div class="devtools-nav-tabs">
|
|
102
|
+
<template v-if="isRouteNav">
|
|
103
|
+
<NuxtLink
|
|
104
|
+
v-for="item of filteredNavItems"
|
|
105
|
+
:key="item.value"
|
|
106
|
+
:to="item.to"
|
|
107
|
+
class="devtools-nav-tab"
|
|
108
|
+
:class="[
|
|
109
|
+
activeTab === item.value ? 'active' : '',
|
|
110
|
+
loading ? 'opacity-50 pointer-events-none' : '',
|
|
111
|
+
]"
|
|
112
|
+
>
|
|
113
|
+
<UTooltip :text="item.label">
|
|
114
|
+
<div class="devtools-nav-tab-inner">
|
|
115
|
+
<UIcon
|
|
116
|
+
:name="item.icon"
|
|
117
|
+
class="text-base sm:text-lg"
|
|
118
|
+
:class="activeTab === item.value ? 'text-[var(--seo-green)]' : ''"
|
|
119
|
+
/>
|
|
120
|
+
<span class="devtools-nav-label">{{ item.label }}</span>
|
|
121
|
+
</div>
|
|
122
|
+
</UTooltip>
|
|
123
|
+
</NuxtLink>
|
|
124
|
+
</template>
|
|
125
|
+
<template v-else>
|
|
126
|
+
<button
|
|
127
|
+
v-for="item of filteredNavItems"
|
|
128
|
+
:key="item.value"
|
|
129
|
+
type="button"
|
|
130
|
+
class="devtools-nav-tab"
|
|
131
|
+
:class="[
|
|
132
|
+
activeTab === item.value ? 'active' : '',
|
|
133
|
+
loading ? 'opacity-50 pointer-events-none' : '',
|
|
134
|
+
]"
|
|
135
|
+
@click="activeTab = item.value"
|
|
136
|
+
>
|
|
137
|
+
<UTooltip :text="item.label">
|
|
138
|
+
<div class="devtools-nav-tab-inner">
|
|
139
|
+
<UIcon
|
|
140
|
+
:name="item.icon"
|
|
141
|
+
class="text-base sm:text-lg"
|
|
142
|
+
:class="activeTab === item.value ? 'text-[var(--seo-green)]' : ''"
|
|
143
|
+
/>
|
|
144
|
+
<span class="devtools-nav-label">{{ item.label }}</span>
|
|
145
|
+
</div>
|
|
146
|
+
</UTooltip>
|
|
147
|
+
</button>
|
|
148
|
+
</template>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Preview source toggle -->
|
|
152
|
+
<div v-if="hasProductionUrl" class="devtools-preview-toggle">
|
|
153
|
+
<button
|
|
154
|
+
class="devtools-preview-btn"
|
|
155
|
+
:class="{ active: previewSource === 'local' }"
|
|
156
|
+
@click="previewSource = 'local'"
|
|
157
|
+
>
|
|
158
|
+
<UIcon name="carbon:laptop" class="w-3.5 h-3.5" />
|
|
159
|
+
<span class="hidden sm:inline">Local</span>
|
|
160
|
+
</button>
|
|
161
|
+
<button
|
|
162
|
+
class="devtools-preview-btn"
|
|
163
|
+
:class="{ active: previewSource === 'production' }"
|
|
164
|
+
@click="previewSource = 'production'"
|
|
165
|
+
>
|
|
166
|
+
<UIcon name="carbon:cloud" class="w-3.5 h-3.5" />
|
|
167
|
+
<span class="hidden sm:inline">Production</span>
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<!-- Production URL indicator -->
|
|
172
|
+
<UTooltip v-if="isProductionMode" :text="productionUrl">
|
|
173
|
+
<span class="devtools-production-badge">
|
|
174
|
+
<span class="devtools-production-dot" />
|
|
175
|
+
<span class="hidden sm:inline text-xs">{{ productionHostname }}</span>
|
|
176
|
+
</span>
|
|
177
|
+
</UTooltip>
|
|
178
|
+
|
|
179
|
+
<!-- Actions -->
|
|
180
|
+
<div class="flex items-center gap-1">
|
|
181
|
+
<slot name="actions" />
|
|
182
|
+
|
|
183
|
+
<UTooltip text="Refresh">
|
|
184
|
+
<UButton
|
|
185
|
+
icon="carbon:reset"
|
|
186
|
+
class="devtools-nav-action"
|
|
187
|
+
@click="emit('refresh')"
|
|
188
|
+
/>
|
|
189
|
+
</UTooltip>
|
|
190
|
+
|
|
191
|
+
<UTooltip text="GitHub">
|
|
192
|
+
<UButton
|
|
193
|
+
icon="simple-icons:github"
|
|
194
|
+
:to="githubUrl"
|
|
195
|
+
target="_blank"
|
|
196
|
+
class="devtools-nav-action hidden sm:flex"
|
|
197
|
+
/>
|
|
198
|
+
</UTooltip>
|
|
199
|
+
</div>
|
|
200
|
+
</nav>
|
|
201
|
+
</div>
|
|
202
|
+
</header>
|
|
203
|
+
|
|
204
|
+
<!-- Main Content -->
|
|
205
|
+
<div class="devtools-main">
|
|
206
|
+
<main class="mx-auto flex flex-col w-full max-w-7xl">
|
|
207
|
+
<DevtoolsLoading v-if="loading" />
|
|
208
|
+
<slot v-else />
|
|
209
|
+
</main>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</UApp>
|
|
213
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import { colorMode } from './rpc'
|
|
3
|
+
|
|
4
|
+
export function useDevtoolsInit(title: string) {
|
|
5
|
+
const isDark = computed(() => colorMode.value === 'dark')
|
|
6
|
+
|
|
7
|
+
useHead({
|
|
8
|
+
title: `Nuxt ${title}`,
|
|
9
|
+
htmlAttrs: {
|
|
10
|
+
class: () => isDark.value ? 'dark' : '',
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return { isDark }
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxtseo-shared",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"description": "Shared utilities for Nuxt SEO modules.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"nuxt": "^3.16.0 || ^4.0.0",
|
|
51
51
|
"nuxt-site-config": "^3.2.0",
|
|
52
52
|
"vue": "^3.5.0",
|
|
53
|
-
"zod": "^3.23.0"
|
|
53
|
+
"zod": "^3.23.0 || ^4.0.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"nuxt-site-config": {
|