nuxtseo-layer-devtools 0.4.5 → 0.5.1

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.
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ const { requiredPending = 0, recommendedPending = 0 } = defineProps<{
3
+ requiredPending?: number
4
+ recommendedPending?: number
5
+ }>()
6
+ </script>
7
+
8
+ <template>
9
+ <span
10
+ v-if="requiredPending > 0 || recommendedPending > 0"
11
+ class="checklist-badge"
12
+ :class="requiredPending > 0 ? 'checklist-badge--required' : 'checklist-badge--recommended'"
13
+ >
14
+ {{ requiredPending + recommendedPending }}
15
+ </span>
16
+ <span v-else class="checklist-badge checklist-badge--complete">
17
+ <UIcon name="carbon:checkmark" class="w-2.5 h-2.5" />
18
+ </span>
19
+ </template>
20
+
21
+ <style scoped>
22
+ .checklist-badge {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ min-width: 1rem;
27
+ height: 1rem;
28
+ padding: 0 0.25rem;
29
+ border-radius: var(--radius-full, 9999px);
30
+ font-size: 0.5625rem;
31
+ font-weight: 700;
32
+ line-height: 1;
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ .checklist-badge--required {
37
+ background: oklch(65% 0.18 25 / 0.15);
38
+ color: oklch(55% 0.18 25);
39
+ }
40
+
41
+ .dark .checklist-badge--required {
42
+ background: oklch(45% 0.14 25 / 0.2);
43
+ color: oklch(72% 0.14 25);
44
+ }
45
+
46
+ .checklist-badge--recommended {
47
+ background: oklch(80% 0.12 85 / 0.15);
48
+ color: oklch(50% 0.15 85);
49
+ }
50
+
51
+ .dark .checklist-badge--recommended {
52
+ background: oklch(50% 0.12 85 / 0.2);
53
+ color: oklch(75% 0.12 85);
54
+ }
55
+
56
+ .checklist-badge--complete {
57
+ background: oklch(75% 0.15 145 / 0.12);
58
+ color: oklch(50% 0.15 145);
59
+ }
60
+
61
+ .dark .checklist-badge--complete {
62
+ background: oklch(50% 0.15 145 / 0.15);
63
+ color: oklch(75% 0.18 145);
64
+ }
65
+ </style>
@@ -0,0 +1,171 @@
1
+ <script setup lang="ts">
2
+ import type { ChecklistItemResult } from '../composables/checklist'
3
+
4
+ const { item } = defineProps<{
5
+ item: ChecklistItemResult
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <div class="checklist-item" :class="item.passed ? 'is-passed' : `is-pending-${item.level}`">
11
+ <div class="checklist-item-status">
12
+ <UIcon
13
+ :name="item.passed ? 'carbon:checkmark-filled' : item.level === 'required' ? 'carbon:warning-alt-filled' : 'carbon:circle-dash'"
14
+ class="checklist-item-icon"
15
+ />
16
+ </div>
17
+ <div class="checklist-item-content">
18
+ <div class="checklist-item-header">
19
+ <span class="checklist-item-label">{{ item.label }}</span>
20
+ <UBadge
21
+ v-if="!item.passed"
22
+ size="xs"
23
+ :color="item.level === 'required' ? 'error' : 'warning'"
24
+ variant="subtle"
25
+ class="checklist-item-level"
26
+ >
27
+ {{ item.level === 'required' ? 'Required' : 'Tip' }}
28
+ </UBadge>
29
+ </div>
30
+ <div class="checklist-item-description">
31
+ {{ item.description }}
32
+ </div>
33
+ <div v-if="item.detail" class="checklist-item-detail">
34
+ {{ item.detail }}
35
+ </div>
36
+ </div>
37
+ <a
38
+ :href="item.docsUrl"
39
+ target="_blank"
40
+ rel="noopener"
41
+ class="checklist-item-docs"
42
+ :class="item.passed ? 'is-subtle' : ''"
43
+ >
44
+ <UIcon name="carbon:arrow-up-right" class="w-3 h-3" />
45
+ </a>
46
+ </div>
47
+ </template>
48
+
49
+ <style scoped>
50
+ .checklist-item {
51
+ display: flex;
52
+ align-items: flex-start;
53
+ gap: 0.5rem;
54
+ padding: 0.4375rem 0.5rem;
55
+ border-radius: var(--radius-md);
56
+ transition: background 100ms;
57
+ }
58
+
59
+ .checklist-item:hover {
60
+ background: var(--color-surface-elevated);
61
+ }
62
+
63
+ .checklist-item.is-passed {
64
+ opacity: 0.55;
65
+ }
66
+
67
+ .checklist-item.is-passed:hover {
68
+ opacity: 0.8;
69
+ }
70
+
71
+ .checklist-item-status {
72
+ flex-shrink: 0;
73
+ padding-top: 0.125rem;
74
+ }
75
+
76
+ .checklist-item-icon {
77
+ font-size: 0.8125rem;
78
+ }
79
+
80
+ .checklist-item.is-passed .checklist-item-icon {
81
+ color: oklch(50% 0.15 145);
82
+ }
83
+
84
+ .dark .checklist-item.is-passed .checklist-item-icon {
85
+ color: oklch(70% 0.18 145);
86
+ }
87
+
88
+ .checklist-item.is-pending-required .checklist-item-icon {
89
+ color: oklch(55% 0.2 25);
90
+ }
91
+
92
+ .dark .checklist-item.is-pending-required .checklist-item-icon {
93
+ color: oklch(70% 0.16 25);
94
+ }
95
+
96
+ .checklist-item.is-pending-recommended .checklist-item-icon {
97
+ color: oklch(52% 0.15 85);
98
+ }
99
+
100
+ .dark .checklist-item.is-pending-recommended .checklist-item-icon {
101
+ color: oklch(72% 0.12 85);
102
+ }
103
+
104
+ .checklist-item-content {
105
+ flex: 1;
106
+ min-width: 0;
107
+ }
108
+
109
+ .checklist-item-header {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.375rem;
113
+ }
114
+
115
+ .checklist-item-label {
116
+ font-size: 0.6875rem;
117
+ font-weight: 600;
118
+ color: var(--color-text);
119
+ }
120
+
121
+ .checklist-item-level {
122
+ font-size: 0.5rem !important;
123
+ padding: 0 0.25rem !important;
124
+ line-height: 1.4 !important;
125
+ }
126
+
127
+ .checklist-item-description {
128
+ font-size: 0.625rem;
129
+ color: var(--color-text-muted);
130
+ margin-top: 0.0625rem;
131
+ line-height: 1.35;
132
+ }
133
+
134
+ .checklist-item-detail {
135
+ display: inline-block;
136
+ font-size: 0.5625rem;
137
+ font-family: var(--font-mono, monospace);
138
+ color: var(--color-text-subtle);
139
+ margin-top: 0.1875rem;
140
+ padding: 0.0625rem 0.3125rem;
141
+ background: var(--color-surface-sunken);
142
+ border-radius: var(--radius-sm);
143
+ }
144
+
145
+ .checklist-item-docs {
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ flex-shrink: 0;
150
+ width: 1.25rem;
151
+ height: 1.25rem;
152
+ margin-top: 0.0625rem;
153
+ border-radius: var(--radius-sm);
154
+ color: var(--color-text-muted);
155
+ text-decoration: none;
156
+ transition: color 100ms, background 100ms;
157
+ }
158
+
159
+ .checklist-item-docs:hover {
160
+ color: var(--seo-green);
161
+ background: oklch(from var(--seo-green) l c h / 0.08);
162
+ }
163
+
164
+ .checklist-item-docs.is-subtle {
165
+ opacity: 0;
166
+ }
167
+
168
+ .checklist-item:hover .checklist-item-docs.is-subtle {
169
+ opacity: 1;
170
+ }
171
+ </style>
@@ -1,9 +1,11 @@
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
+ import { useSetupChecklist } from '../composables/checklist'
5
+ import { fetchInstalledModules, findModuleByName, showModuleSplash } from '../composables/modules'
5
6
  import { colorMode } from '../composables/rpc'
6
7
  import { hasProductionUrl, isConnected, isProductionMode, isStandalone, path, previewSource, productionUrl, standaloneUrl } from '../composables/state'
8
+ import { useModuleUpdate } from '../composables/update-check'
7
9
 
8
10
  export interface DevtoolsNavItem {
9
11
  value: string
@@ -35,6 +37,10 @@ const emit = defineEmits<{
35
37
  refresh: []
36
38
  }>()
37
39
 
40
+ const moduleInfo = computed(() => moduleName ? findModuleByName(moduleName) : undefined)
41
+ const npmPackage = computed(() => moduleInfo.value?.npm)
42
+ const { hasUpdate, latestVersion } = useModuleUpdate(npmPackage.value, version)
43
+
38
44
  // Fetch installed modules for the splash screen
39
45
  fetchInstalledModules()
40
46
 
@@ -88,6 +94,13 @@ const standaloneHostname = computed(() => {
88
94
 
89
95
  const showStandaloneSetup = computed(() => !isConnected.value && !isStandalone.value)
90
96
 
97
+ const { evaluated, getModuleResultByName } = useSetupChecklist()
98
+ const moduleChecklistResult = computed(() => {
99
+ if (!evaluated.value || !moduleName)
100
+ return undefined
101
+ return getModuleResultByName(moduleName)
102
+ })
103
+
91
104
  function disconnectStandalone() {
92
105
  standaloneUrl.value = ''
93
106
  }
@@ -125,14 +138,28 @@ function disconnectStandalone() {
125
138
  <span class="text-sm sm:text-base font-semibold tracking-tight text-[var(--color-text)]">
126
139
  {{ title }}
127
140
  </span>
141
+ <DevtoolsChecklistBadge
142
+ v-if="moduleChecklistResult?.totalPending"
143
+ :required-pending="moduleChecklistResult.requiredPending"
144
+ :recommended-pending="moduleChecklistResult.recommendedPending"
145
+ />
128
146
  <UIcon name="carbon:chevron-down" class="w-3 h-3 opacity-50 transition-transform" :class="showModuleSplash ? 'rotate-180' : ''" />
129
147
  </button>
130
- <UBadge
131
- v-if="version"
132
- class="font-mono text-[10px] sm:text-xs hidden sm:inline-flex"
133
- >
134
- v{{ version }}
135
- </UBadge>
148
+ <UTooltip v-if="version" :text="hasUpdate ? `Update available: v${latestVersion}` : `v${version}`">
149
+ <a
150
+ :href="hasUpdate && npmPackage ? `https://npmjs.com/package/${npmPackage}` : undefined"
151
+ :target="hasUpdate ? '_blank' : undefined"
152
+ rel="noopener"
153
+ class="version-badge-wrapper"
154
+ >
155
+ <UBadge
156
+ class="font-mono text-[10px] sm:text-xs hidden sm:inline-flex"
157
+ >
158
+ v{{ version }}
159
+ </UBadge>
160
+ <span v-if="hasUpdate" class="update-dot" />
161
+ </a>
162
+ </UTooltip>
136
163
  <!-- Mode dropdown: embedded with production URL -->
137
164
  <div v-if="hasProductionUrl && !isStandalone" ref="modeDropdownRef" class="mode-dropdown-wrapper">
138
165
  <button type="button" class="devtools-mode-btn" @click="modeDropdownOpen = !modeDropdownOpen">
@@ -272,6 +299,20 @@ function disconnectStandalone() {
272
299
  </div>
273
300
  </div>
274
301
 
302
+ <!-- Setup checklist alert -->
303
+ <DevtoolsAlert
304
+ v-if="moduleChecklistResult?.requiredPending"
305
+ variant="warning"
306
+ >
307
+ {{ moduleChecklistResult.requiredPending }} required setup {{ moduleChecklistResult.requiredPending === 1 ? 'step' : 'steps' }} remaining
308
+ <template #action>
309
+ <button type="button" class="checklist-alert-action" @click="showModuleSplash = true">
310
+ View setup
311
+ <UIcon name="carbon:arrow-right" class="w-3 h-3" />
312
+ </button>
313
+ </template>
314
+ </DevtoolsAlert>
315
+
275
316
  <!-- Main Content -->
276
317
  <div class="devtools-main">
277
318
  <main class="devtools-main-content">
@@ -279,6 +320,9 @@ function disconnectStandalone() {
279
320
  <DevtoolsLoading v-show="!showStandaloneSetup && loading" />
280
321
  <div v-show="!showStandaloneSetup && !loading">
281
322
  <slot />
323
+ <div v-if="activeTab === 'debug' && moduleName" class="devtools-troubleshooting-section">
324
+ <DevtoolsTroubleshooting :module-name="moduleName" :version="version" />
325
+ </div>
282
326
  </div>
283
327
  </main>
284
328
  </div>
@@ -289,6 +333,25 @@ function disconnectStandalone() {
289
333
  </template>
290
334
 
291
335
  <style scoped>
336
+ .version-badge-wrapper {
337
+ position: relative;
338
+ display: inline-flex;
339
+ align-items: center;
340
+ text-decoration: none;
341
+ }
342
+
343
+ .update-dot {
344
+ position: absolute;
345
+ top: -2px;
346
+ right: -2px;
347
+ width: 7px;
348
+ height: 7px;
349
+ border-radius: 50%;
350
+ background: var(--seo-green);
351
+ border: 1.5px solid var(--color-surface);
352
+ pointer-events: none;
353
+ }
354
+
292
355
  .devtools-module-switcher {
293
356
  display: flex;
294
357
  align-items: center;
@@ -418,4 +481,25 @@ function disconnectStandalone() {
418
481
  .standalone-path-input:focus {
419
482
  border-color: var(--seo-green);
420
483
  }
484
+
485
+ .devtools-troubleshooting-section {
486
+ padding: 1.5rem 1rem 1rem;
487
+ max-width: 48rem;
488
+ }
489
+
490
+ .checklist-alert-action {
491
+ display: flex;
492
+ align-items: center;
493
+ gap: 0.25rem;
494
+ font-size: 0.75rem;
495
+ font-weight: 600;
496
+ color: inherit;
497
+ opacity: 0.8;
498
+ cursor: pointer;
499
+ transition: opacity 100ms;
500
+ }
501
+
502
+ .checklist-alert-action:hover {
503
+ opacity: 1;
504
+ }
421
505
  </style>