diff --git a/app/app.vue b/app/app.vue index 0c05b133b..9694cd532 100644 --- a/app/app.vue +++ b/app/app.vue @@ -3,7 +3,6 @@ const colorMode = useColorMode() const route = useRoute() const isChatRoute = computed(() => route.path === '/chat' || route.path.startsWith('/chat/')) const { version } = useDocsVersion() -const { searchGroups, searchLinks, searchTerm, searchFuse } = useNavigation() const { fetchList: fetchModules } = useModules() const { fetchList: fetchHosting } = useHostingProviders() const { track } = useAnalytics() @@ -16,10 +15,7 @@ watch(() => colorMode.preference, (newMode, oldMode) => { } }) -const [{ data: navigation }, { data: files }] = await Promise.all([ - useFetch('/api/navigation.json'), - useFetch('/api/search.json', { server: false }) -]) +const { data: navigation } = await useFetch('/api/navigation.json') onNuxtReady(() => { fetchModules() @@ -64,9 +60,6 @@ if (import.meta.server) { } const versionNavigation = computed(() => navigation.value?.filter(item => item.path === version.value.path || item.path === '/blog') ?? []) -const versionFiles = computed(() => files.value?.filter((file) => { - return file.id.startsWith(version.value.path + '/') || file.id.startsWith('/blog/') -}) ?? []) provide('navigation', versionNavigation) @@ -80,26 +73,16 @@ provide('navigation', versionNavigation) - - - - - - + + + - + diff --git a/app/components/Search.vue b/app/components/Search.vue new file mode 100644 index 000000000..631cb309b --- /dev/null +++ b/app/components/Search.vue @@ -0,0 +1,44 @@ + + + diff --git a/app/composables/useNavigation.ts b/app/composables/useNavigation.ts index 16401b44a..5b52cc1c4 100644 --- a/app/composables/useNavigation.ts +++ b/app/composables/useNavigation.ts @@ -4,7 +4,7 @@ import { createSharedComposable } from '@vueuse/core' // Stable reference so the deep watcher inside `useFuse` doesn't rebuild // the entire Fuse index on every reactive flush. const searchFuse = { - resultLimit: 42, + resultLimit: 25, fuseOptions: { threshold: 0 } @@ -118,6 +118,7 @@ function _useHeaderLinks() { }, { label: 'Updates', icon: 'i-lucide-newspaper', + search: false, to: '/blog', children: [{ label: 'Blog', @@ -229,6 +230,7 @@ const _useNavigation = () => { id: `module-${module.name}`, label: module.npm, suffix: module.description, + downloads: module.stats?.downloads ?? 0, avatar: { src: moduleImage(module.icon), ui: { @@ -255,6 +257,20 @@ const _useNavigation = () => { }))) const searchGroups = computed(() => [{ + id: 'modules-search', + label: 'Modules', + items: modulesItems.value, + postFilter: (searchTerm: string, items: any[]) => { + if (!searchTerm) { + return [...items].sort((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0)) + } + return items + } + }, { + id: 'hosting-search', + label: 'Hosting', + items: hostingItems.value + }, { id: 'ask-ai-search', label: 'AI', ignoreFilter: true, @@ -273,14 +289,6 @@ const _useNavigation = () => { openAgent(searchTerm.value) } }] - }, { - id: 'modules-search', - label: 'Modules', - items: modulesItems.value - }, { - id: 'hosting-search', - label: 'Hosting', - items: hostingItems.value }]) watchDebounced(searchTerm, (term) => { diff --git a/app/error.vue b/app/error.vue index f8e36eb02..975c391b8 100644 --- a/app/error.vue +++ b/app/error.vue @@ -10,14 +10,10 @@ defineProps<{ error: NuxtError }>() const route = useRoute() const { version } = useDocsVersion() -const { searchGroups, searchLinks, searchTerm, searchFuse } = useNavigation() const { fetchList: fetchModules } = useModules() const { fetchList: fetchHosting } = useHostingProviders() -const [{ data: navigation }, { data: files }] = await Promise.all([ - useFetch('/api/navigation.json'), - useFetch('/api/search.json', { server: false }) -]) +const { data: navigation } = await useFetch('/api/navigation.json') onNuxtReady(() => { fetchModules() @@ -25,9 +21,6 @@ onNuxtReady(() => { }) const versionNavigation = computed(() => navigation.value?.filter(item => item.path === version.value.path || item.path === '/blog') ?? []) -const versionFiles = computed(() => files.value?.filter((file) => { - return file.id.startsWith(version.value.path + '/') || file.id.startsWith('/blog/') -}) ?? []) provide('navigation', versionNavigation) @@ -42,14 +35,7 @@ provide('navigation', versionNavigation) - + diff --git a/content/blog/.navigation.yml b/content/blog/.navigation.yml new file mode 100644 index 000000000..eb9121c4a --- /dev/null +++ b/content/blog/.navigation.yml @@ -0,0 +1 @@ +icon: i-lucide-newspaper diff --git a/nuxt.config.ts b/nuxt.config.ts index c6dccca47..de3d9cb47 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -169,7 +169,6 @@ export default defineNuxtConfig({ '/admin/**': { ssr: false }, // Main navigation '/api/navigation.json': { prerender: true }, - '/api/search.json': { prerender: true }, // Redirects '/docs': { redirect: '/docs/getting-started/introduction', prerender: false }, '/docs/3.x': { redirect: '/docs/3.x/getting-started/introduction', prerender: false }, diff --git a/package.json b/package.json index ee1df56e1..302ec0a3a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@nuxt/hints": "^1.1.2", "@nuxt/image": "^2.0.0", "@nuxt/scripts": "^1.1.0", - "@nuxt/ui": "^4.7.1", + "@nuxt/ui": "^4.8.0", "@nuxthub/core": "^0.10.7", "@nuxtjs/html-validator": "^2.1.0", "@nuxtjs/mcp-toolkit": "^0.17.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 540d843ed..206beda82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: ^1.1.0 version: 1.1.0(@types/youtube@0.2.0)(@unhead/vue@2.1.15(vue@3.5.34(typescript@6.0.3)))(@vercel/functions@3.6.0)(db0@0.3.4(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)))(ioredis@5.10.1)(magicast@0.5.3)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@6.0.3)(vite@7.3.3(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3)) '@nuxt/ui': - specifier: ^4.7.1 - version: 4.7.1(2ea66ffd91c5bb6b00b990c832f16d94) + specifier: ^4.8.0 + version: 4.8.0(2ea66ffd91c5bb6b00b990c832f16d94) '@nuxthub/core': specifier: ^0.10.7 version: 0.10.7(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@vercel/functions@3.6.0)(db0@0.3.4(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(better-sqlite3@12.10.0)))(ioredis@5.10.1)(magicast@0.5.3)(typescript@6.0.3)(vue-tsc@3.3.0(typescript@6.0.3)) @@ -1894,8 +1894,8 @@ packages: vitest: optional: true - '@nuxt/ui@4.7.1': - resolution: {integrity: sha512-s3Ix89RkJTeNDlLg7EflckkFxQgzm2W9bt4CBsudi7wNdmhbb3nzYG6rcns2R2Wos0gZlYkSfDKaX1o3zMC+Aw==} + '@nuxt/ui@4.8.0': + resolution: {integrity: sha512-ymnGxvMCYtPzuMGIK/fnd41/Arh9hxfmz9m5auaf51jg/zhSpAkIesTVSv0at+bGKXxwtQZoPS9GXs0+SzWfjg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8484,8 +8484,8 @@ packages: rehype-sort-attributes@5.0.1: resolution: {integrity: sha512-Bxo+AKUIELcnnAZwJDt5zUDDRpt4uzhfz9d0PVGhcxYWsbFj5Cv35xuWxu5r1LeYNFNhgGqsr9Q2QiIOM/Qctg==} - reka-ui@2.9.6: - resolution: {integrity: sha512-K6bL457owpvWONc7hsjFxo3HDC9s6IzhRqShW0w9JSKelPGfRbkHD558UQTn/NH1cvrXVHygKyC7fExFmRketg==} + reka-ui@2.9.7: + resolution: {integrity: sha512-aX7foYYR20v4+majO58OJJdBNfLMm0eJb448l9N4JVy8JB7GXOr4H/S4a+J1pkcoxZH8Cb7YHpJ855+miAm7sA==} peerDependencies: vue: '>= 3.4.0' @@ -9722,8 +9722,8 @@ packages: typescript: optional: true - vue-component-type-helpers@3.2.8: - resolution: {integrity: sha512-9689efAXhN/EV86plgkL/XFiJSXhGtWPG6JDboZ+QnjlUWUUQrQ0ILKQtw4iQsuwIwu5k6Aw+JnehDe7161e7A==} + vue-component-type-helpers@3.3.1: + resolution: {integrity: sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==} vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} @@ -11883,7 +11883,7 @@ snapshots: - typescript - vite - '@nuxt/ui@4.7.1(2ea66ffd91c5bb6b00b990c832f16d94)': + '@nuxt/ui@4.8.0(2ea66ffd91c5bb6b00b990c832f16d94)': dependencies: '@floating-ui/dom': 1.7.6 '@iconify/vue': 5.0.1(vue@3.5.34(typescript@6.0.3)) @@ -11936,7 +11936,7 @@ snapshots: motion-v: 2.2.1(@vueuse/core@14.3.0(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) ohash: 2.0.11 pathe: 2.0.3 - reka-ui: 2.9.6(vue@3.5.34(typescript@6.0.3)) + reka-ui: 2.9.7(vue@3.5.34(typescript@6.0.3)) scule: 1.3.0 tailwind-merge: 3.6.0 tailwind-variants: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.0) @@ -11947,8 +11947,8 @@ snapshots: unplugin: 3.0.0 unplugin-auto-import: 21.0.0(@nuxt/kit@4.4.6(magicast@0.5.3))(@vueuse/core@14.3.0(vue@3.5.34(typescript@6.0.3))) unplugin-vue-components: 32.0.0(@nuxt/kit@4.4.6(magicast@0.5.3))(vue@3.5.34(typescript@6.0.3)) - vaul-vue: 0.4.1(reka-ui@2.9.6(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) - vue-component-type-helpers: 3.2.8 + vaul-vue: 0.4.1(reka-ui@2.9.7(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)) + vue-component-type-helpers: 3.3.1 optionalDependencies: '@internationalized/date': 3.12.1 '@internationalized/number': 3.6.6 @@ -14304,7 +14304,7 @@ snapshots: '@vue/compiler-dom': 3.5.34 js-beautify: 1.15.4 vue: 3.5.34(typescript@6.0.3) - vue-component-type-helpers: 3.2.8 + vue-component-type-helpers: 3.3.1 optionalDependencies: '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3)) @@ -18412,7 +18412,7 @@ snapshots: dependencies: '@nuxt/devtools-kit': 4.0.0-alpha.3(magicast@0.5.3)(vite@7.3.3(@types/node@25.6.2)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) '@nuxt/kit': 4.4.6(magicast@0.5.3) - '@nuxt/ui': 4.7.1(2ea66ffd91c5bb6b00b990c832f16d94) + '@nuxt/ui': 4.8.0(2ea66ffd91c5bb6b00b990c832f16d94) '@shikijs/langs': 4.1.0 '@shikijs/themes': 4.1.0 '@vueuse/nuxt': 14.3.0(magicast@0.5.3)(nuxt@4.4.6(211797a271aa9149e05160704ba2ff9e))(vue@3.5.34(typescript@6.0.3)) @@ -19483,7 +19483,7 @@ snapshots: '@types/hast': 3.0.4 unist-util-visit: 5.1.0 - reka-ui@2.9.6(vue@3.5.34(typescript@6.0.3)): + reka-ui@2.9.7(vue@3.5.34(typescript@6.0.3)): dependencies: '@floating-ui/dom': 1.7.6 '@floating-ui/vue': 1.1.11(vue@3.5.34(typescript@6.0.3)) @@ -20757,10 +20757,10 @@ snapshots: vary@1.1.2: {} - vaul-vue@0.4.1(reka-ui@2.9.6(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)): + vaul-vue@0.4.1(reka-ui@2.9.7(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)): dependencies: '@vueuse/core': 10.11.1(vue@3.5.34(typescript@6.0.3)) - reka-ui: 2.9.6(vue@3.5.34(typescript@6.0.3)) + reka-ui: 2.9.7(vue@3.5.34(typescript@6.0.3)) vue: 3.5.34(typescript@6.0.3) transitivePeerDependencies: - '@vue/composition-api' @@ -20958,7 +20958,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - vue-component-type-helpers@3.2.8: {} + vue-component-type-helpers@3.3.1: {} vue-demi@0.14.10(vue@3.5.34(typescript@6.0.3)): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 953be7278..aa0c4b4c9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,3 +11,6 @@ allowBuilds: puppeteer: false unrs-resolver: false vue-demi: false + +minimumReleaseAgeExclude: + - '@nuxt/ui@4.8.0' diff --git a/server/api/search.json.get.ts b/server/api/search.json.get.ts deleted file mode 100644 index 7dfe14f38..000000000 --- a/server/api/search.json.get.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This route will be pre-rendered as /api/navigation.json -import { queryCollectionSearchSections } from '@nuxt/content/server' - -export default defineEventHandler(async (event) => { - return Promise.all([ - queryCollectionSearchSections(event, 'docsv3', { ignoredTags: ['style'] }), - queryCollectionSearchSections(event, 'docsv4', { ignoredTags: ['style'] }), - queryCollectionSearchSections(event, 'docsv5', { ignoredTags: ['style'] }), - queryCollectionSearchSections(event, 'blog') - ]).then(data => data.flat()) -})