nuxtseo-shared 0.1.5 → 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.
|
@@ -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
|
+
}
|