nuxtseo-layer-devtools 5.0.1 → 5.1.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.
@@ -15,8 +15,7 @@ onClickOutside(panelRef, () => {
15
15
  showModuleSplash.value = false
16
16
  })
17
17
 
18
- const coreModules = computed(() => moduleCatalog.value.filter(m => !m.pro))
19
- const proModules = computed(() => moduleCatalog.value.filter(m => m.pro))
18
+ const allModules = computed(() => moduleCatalog.value)
20
19
 
21
20
  const selectedForInstall = ref(new Set<string>())
22
21
 
@@ -56,9 +55,6 @@ const tabs = computed<TabsItem[]>(() => {
56
55
  const items: TabsItem[] = [
57
56
  { label: 'Modules', value: 'modules', icon: 'i-carbon-grid' },
58
57
  ]
59
- if (proModules.value.length) {
60
- items.push({ label: 'Pro', value: 'pro', icon: 'i-carbon-locked' })
61
- }
62
58
  if (evaluated.value && summary.value.total > 0) {
63
59
  items.push({
64
60
  label: 'Setup',
@@ -116,7 +112,7 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
116
112
  <div v-show="activeTab === 'modules'" class="splash-tab-content">
117
113
  <div class="splash-grid">
118
114
  <button
119
- v-for="mod of coreModules"
115
+ v-for="mod of allModules"
120
116
  :key="mod.name"
121
117
  type="button"
122
118
  class="splash-module"
@@ -157,43 +153,6 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
157
153
  </Transition>
158
154
  </div>
159
155
 
160
- <!-- Pro tab -->
161
- <div v-show="activeTab === 'pro'" class="splash-tab-content">
162
- <div class="splash-grid">
163
- <button
164
- v-for="mod of proModules"
165
- :key="mod.name"
166
- type="button"
167
- class="splash-module"
168
- :class="{
169
- 'is-current': mod.name === currentModule,
170
- 'is-unavailable': !mod.installed,
171
- 'is-switchable': mod.installed && mod.name !== currentModule && isConnected,
172
- }"
173
- :disabled="!mod.installed || mod.name === currentModule"
174
- @click="handleModuleClick(mod)"
175
- >
176
- <div class="splash-module-icon">
177
- <UIcon :name="mod.icon" class="text-base" />
178
- </div>
179
- <span class="splash-module-title">{{ mod.title }}</span>
180
- <UBadge size="xs" color="neutral" variant="outline" class="splash-pro-badge">
181
- PRO
182
- </UBadge>
183
- </button>
184
- </div>
185
- <div class="splash-pro-status">
186
- <div class="splash-pro-status-inner">
187
- <UIcon name="carbon:locked" class="w-3.5 h-3.5 opacity-50" />
188
- <span>Unlock Pro modules with a license key</span>
189
- </div>
190
- <a href="https://nuxtseo.com/pro" target="_blank" rel="noopener" class="splash-pro-cta">
191
- Learn more
192
- <UIcon name="carbon:arrow-right" class="w-3 h-3" />
193
- </a>
194
- </div>
195
- </div>
196
-
197
156
  <!-- Setup tab -->
198
157
  <div v-show="activeTab === 'setup'" class="splash-tab-content">
199
158
  <div v-if="evaluated && summary.total > 0" class="splash-health-header">
@@ -209,6 +168,21 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
209
168
  <DevtoolsSetupChecklist :current-module="currentModule" />
210
169
  </div>
211
170
 
171
+ <!-- Pro ad -->
172
+ <a href="https://nuxtseo.com/pro" target="_blank" rel="noopener" class="splash-pro-ad">
173
+ <div class="splash-pro-ad-content">
174
+ <UIcon name="i-carbon-chart-line-data" class="w-4 h-4 text-violet-500" />
175
+ <div>
176
+ <span class="splash-pro-ad-title">Nuxt SEO Pro</span>
177
+ <span class="splash-pro-ad-desc">GSC analytics, indexing diagnostics, competitor tracking &amp; MCP server</span>
178
+ </div>
179
+ </div>
180
+ <span class="splash-pro-ad-cta">
181
+ Learn more
182
+ <UIcon name="carbon:arrow-right" class="w-3 h-3" />
183
+ </span>
184
+ </a>
185
+
212
186
  <!-- Footer -->
213
187
  <div class="splash-footer">
214
188
  <a href="https://nuxtseo.com" target="_blank" rel="noopener" class="splash-link">
@@ -392,48 +366,6 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
392
366
  flex-shrink: 0;
393
367
  }
394
368
 
395
- .splash-pro-badge {
396
- font-size: 9px !important;
397
- flex-shrink: 0;
398
- color: oklch(65% 0.25 290) !important;
399
- border-color: oklch(65% 0.25 290 / 0.3) !important;
400
- }
401
-
402
- /* Pro status / CTA */
403
- .splash-pro-status {
404
- display: flex;
405
- align-items: center;
406
- justify-content: space-between;
407
- margin: 0.375rem 0 0;
408
- padding: 0.4rem 0.625rem;
409
- border-radius: var(--radius-md);
410
- background: oklch(65% 0.25 290 / 0.06);
411
- border: 1px solid oklch(65% 0.25 290 / 0.12);
412
- font-size: 0.6875rem;
413
- color: var(--color-text-muted);
414
- }
415
-
416
- .splash-pro-status-inner {
417
- display: flex;
418
- align-items: center;
419
- gap: 0.375rem;
420
- }
421
-
422
- .splash-pro-cta {
423
- display: flex;
424
- align-items: center;
425
- gap: 0.25rem;
426
- font-size: 0.6875rem;
427
- font-weight: 500;
428
- color: oklch(65% 0.25 290);
429
- text-decoration: none;
430
- transition: opacity 100ms;
431
- }
432
-
433
- .splash-pro-cta:hover {
434
- opacity: 0.8;
435
- }
436
-
437
369
  /* Setup Health header */
438
370
  .splash-health-header {
439
371
  display: flex;
@@ -543,6 +475,58 @@ function handleModuleClick(mod: typeof moduleCatalog.value[0]) {
543
475
  max-height: 4rem;
544
476
  }
545
477
 
478
+ /* Pro ad */
479
+ .splash-pro-ad {
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: space-between;
483
+ gap: 0.5rem;
484
+ margin: 0.375rem 0.75rem 0;
485
+ padding: 0.5rem 0.625rem;
486
+ border-radius: var(--radius-md);
487
+ background: oklch(65% 0.25 290 / 0.06);
488
+ border: 1px solid oklch(65% 0.25 290 / 0.12);
489
+ text-decoration: none;
490
+ transition: border-color 100ms, background 100ms;
491
+ }
492
+
493
+ .splash-pro-ad:hover {
494
+ background: oklch(65% 0.25 290 / 0.1);
495
+ border-color: oklch(65% 0.25 290 / 0.2);
496
+ }
497
+
498
+ .splash-pro-ad-content {
499
+ display: flex;
500
+ align-items: flex-start;
501
+ gap: 0.5rem;
502
+ min-width: 0;
503
+ }
504
+
505
+ .splash-pro-ad-title {
506
+ display: block;
507
+ font-size: 0.6875rem;
508
+ font-weight: 600;
509
+ color: var(--color-text);
510
+ }
511
+
512
+ .splash-pro-ad-desc {
513
+ display: block;
514
+ font-size: 0.625rem;
515
+ color: var(--color-text-muted);
516
+ line-height: 1.4;
517
+ }
518
+
519
+ .splash-pro-ad-cta {
520
+ display: flex;
521
+ align-items: center;
522
+ gap: 0.25rem;
523
+ flex-shrink: 0;
524
+ font-size: 0.625rem;
525
+ font-weight: 500;
526
+ color: oklch(65% 0.25 290);
527
+ white-space: nowrap;
528
+ }
529
+
546
530
  /* Footer */
547
531
  .splash-footer {
548
532
  display: flex;
@@ -21,7 +21,6 @@ export interface SeoModuleCatalogEntry {
21
21
  route?: string
22
22
  npm: string
23
23
  repo: string
24
- pro?: boolean
25
24
  playgrounds?: Record<string, string>
26
25
  }
27
26
 
@@ -55,7 +54,6 @@ function moduleToCatalogEntry(mod: NuxtSEOModule): Omit<SeoModuleCatalogEntry, '
55
54
  icon: toIconify(mod.icon),
56
55
  npm: mod.npm,
57
56
  repo: mod.repo,
58
- pro: mod.pro,
59
57
  playgrounds: mod.playgrounds,
60
58
  }
61
59
  }
@@ -7,6 +7,22 @@ export interface ModuleUpdateInfo {
7
7
  hasUpdate: boolean
8
8
  }
9
9
 
10
+ function parseSemver(version: string): [number, number, number] {
11
+ // eslint-disable-next-line e18e/prefer-static-regex
12
+ const parts = version.replace(/^v/, '').split('.').map(Number)
13
+ return [parts[0] || 0, parts[1] || 0, parts[2] || 0]
14
+ }
15
+
16
+ function isNewerVersion(latest: string, current: string): boolean {
17
+ const [lMajor, lMinor, lPatch] = parseSemver(latest)
18
+ const [cMajor, cMinor, cPatch] = parseSemver(current)
19
+ if (lMajor !== cMajor)
20
+ return lMajor > cMajor
21
+ if (lMinor !== cMinor)
22
+ return lMinor > cMinor
23
+ return lPatch > cPatch
24
+ }
25
+
10
26
  const updateCache = ref<Record<string, ModuleUpdateInfo>>({})
11
27
 
12
28
  export function useModuleUpdate(npmPackage: string | undefined, currentVersion: string | undefined): { hasUpdate: ComputedRef<boolean>, latestVersion: ComputedRef<string | undefined>, info: ComputedRef<ModuleUpdateInfo | undefined> } {
@@ -29,7 +45,7 @@ export function useModuleUpdate(npmPackage: string | undefined, currentVersion:
29
45
  updateCache.value[npmPackage] = {
30
46
  currentVersion,
31
47
  latestVersion: latest,
32
- hasUpdate: latest !== currentVersion,
48
+ hasUpdate: isNewerVersion(latest, currentVersion),
33
49
  }
34
50
  })
35
51
  .catch(() => {})
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxtseo-layer-devtools",
3
3
  "type": "module",
4
- "version": "5.0.1",
4
+ "version": "5.1.0",
5
5
  "description": "Shared Nuxt layer for Nuxt SEO devtools clients.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -47,7 +47,7 @@
47
47
  "ofetch": "^1.5.1",
48
48
  "shiki": "^4.0.2",
49
49
  "ufo": "^1.6.3",
50
- "nuxtseo-shared": "5.0.1"
50
+ "nuxtseo-shared": "5.1.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "nuxt": "^4.4.2",