(() => {
return isPackageSelected(props.result.package.name)
})
-function formatScore(value?: number): string {
- if (value === undefined || value === 0) return '-'
- return Math.round(value * 100).toString()
-}
-
function isColumnVisible(id: string): boolean {
return props.columns.find(c => c.id === id)?.visible ?? false
}
@@ -163,38 +157,6 @@ const compactNumberFormatter = useCompactNumberFormatter()
-
-
- |
- {{ formatScore(score?.detail?.quality) }}
- |
-
-
-
- {{ formatScore(score?.detail?.popularity) }}
- |
-
-
-
- {{ formatScore(score?.detail?.maintenance) }}
- |
-
-
-
- {{ formatScore(score?.final) }}
- |
-
diff --git a/app/components/ScrollToTop.client.vue b/app/components/ScrollToTop.client.vue
index dda52dc90b..e011ff8d50 100644
--- a/app/components/ScrollToTop.client.vue
+++ b/app/components/ScrollToTop.client.vue
@@ -2,7 +2,7 @@
const route = useRoute()
// Pages where scroll-to-top should NOT be shown
-const excludedRoutes = new Set(['index', 'code'])
+const excludedRoutes = new Set(['index', 'docs', 'code'])
const isPackagePage = computed(() => route.name === 'package' || route.name === 'package-version')
const isActive = computed(() => !excludedRoutes.has(route.name as string) && !isPackagePage.value)
diff --git a/app/composables/npm/search-utils.ts b/app/composables/npm/search-utils.ts
index c9b4816012..290feaea46 100644
--- a/app/composables/npm/search-utils.ts
+++ b/app/composables/npm/search-utils.ts
@@ -11,7 +11,6 @@ export function metaToSearchResult(meta: PackageMetaResponse): NpmSearchResult {
author: meta.author,
maintainers: meta.maintainers,
},
- score: { final: 0, detail: { quality: 0, popularity: 0, maintenance: 0 } },
searchScore: 0,
downloads: meta.weeklyDownloads !== undefined ? { weekly: meta.weeklyDownloads } : undefined,
updated: meta.date,
diff --git a/app/composables/npm/useAlgoliaSearch.ts b/app/composables/npm/useAlgoliaSearch.ts
index 5f8d08ab1a..a6ba645e18 100644
--- a/app/composables/npm/useAlgoliaSearch.ts
+++ b/app/composables/npm/useAlgoliaSearch.ts
@@ -91,14 +91,6 @@ function hitToSearchResult(hit: AlgoliaHit): NpmSearchResult {
}))
: [],
},
- score: {
- final: 0,
- detail: {
- quality: hit.popular ? 1 : 0,
- popularity: hit.downloadsRatio,
- maintenance: 0,
- },
- },
searchScore: 0,
downloads: {
weekly: Math.round(hit.downloadsLast30Days / 4.3),
diff --git a/app/composables/npm/usePackage.ts b/app/composables/npm/usePackage.ts
index 50a40e6c52..a71d8eb566 100644
--- a/app/composables/npm/usePackage.ts
+++ b/app/composables/npm/usePackage.ts
@@ -37,7 +37,7 @@ export function transformPackument(
const timeA = pkg.time[a]
const timeB = pkg.time[b]
if (!timeA || !timeB) return 0
- return new Date(timeB).getTime() - new Date(timeA).getTime()
+ return Date.parse(timeB) - Date.parse(timeA)
})
.slice(0, RECENT_VERSIONS_COUNT)
diff --git a/app/composables/useGlobalSearch.ts b/app/composables/useGlobalSearch.ts
index 77367a049a..45bb478487 100644
--- a/app/composables/useGlobalSearch.ts
+++ b/app/composables/useGlobalSearch.ts
@@ -4,6 +4,8 @@ import { debounce } from 'perfect-debounce'
// Pages that have their own local filter using ?q
const pagesWithLocalFilter = new Set(['~username', 'org'])
+const SEARCH_DEBOUNCE_MS = 100
+
export function useGlobalSearch(place: 'header' | 'content' = 'content') {
const { settings } = useSettings()
const { searchProvider } = useSearchProvider()
@@ -27,10 +29,14 @@ export function useGlobalSearch(place: 'header' | 'content' = 'content') {
// Syncs instantly when instantSearch is on, but only on Enter press when off
const committedSearchQuery = useState('committed-search-query', () => searchQuery.value)
+ const commitSearchQuery = debounce((val: string) => {
+ committedSearchQuery.value = val
+ }, SEARCH_DEBOUNCE_MS)
+
// This is basically doing instant search as user types
watch(searchQuery, val => {
if (settings.value.instantSearch) {
- committedSearchQuery.value = val
+ commitSearchQuery(val)
}
})
@@ -71,10 +77,11 @@ export function useGlobalSearch(place: 'header' | 'content' = 'content') {
})
}
- const updateUrlQuery = debounce(updateUrlQueryImpl, 250)
+ const updateUrlQuery = debounce(updateUrlQueryImpl, SEARCH_DEBOUNCE_MS)
function flushUpdateUrlQuery() {
// Commit the current query when explicitly submitted (Enter pressed)
+ commitSearchQuery.cancel()
committedSearchQuery.value = searchQuery.value
// When instant search is off the debounce queue is empty, so call directly
if (!settings.value.instantSearch) {
diff --git a/app/composables/useStructuredFilters.ts b/app/composables/useStructuredFilters.ts
index 7b0af8cc01..f229384d6a 100644
--- a/app/composables/useStructuredFilters.ts
+++ b/app/composables/useStructuredFilters.ts
@@ -327,23 +327,11 @@ export function useStructuredFilters(options: UseStructuredFiltersOptions) {
diff = (a.downloads?.weekly ?? 0) - (b.downloads?.weekly ?? 0)
break
case 'updated':
- diff = new Date(a.package.date).getTime() - new Date(b.package.date).getTime()
+ diff = Date.parse(a.package.date) - Date.parse(b.package.date)
break
case 'name':
diff = a.package.name.localeCompare(b.package.name)
break
- case 'quality':
- diff = (a.score?.detail?.quality ?? 0) - (b.score?.detail?.quality ?? 0)
- break
- case 'popularity':
- diff = (a.score?.detail?.popularity ?? 0) - (b.score?.detail?.popularity ?? 0)
- break
- case 'maintenance':
- diff = (a.score?.detail?.maintenance ?? 0) - (b.score?.detail?.maintenance ?? 0)
- break
- case 'score':
- diff = (a.score?.final ?? 0) - (b.score?.final ?? 0)
- break
case 'relevance':
// Relevance preserves server order (already sorted by search relevance)
diff = 0
diff --git a/app/pages/search.vue b/app/pages/search.vue
index b306359ca7..b635065aa0 100644
--- a/app/pages/search.vue
+++ b/app/pages/search.vue
@@ -134,10 +134,6 @@ const ALL_SORT_KEYS: SortKey[] = [
'downloads-year',
'updated',
'name',
- 'quality',
- 'popularity',
- 'maintenance',
- 'score',
]
// Disable sort keys the current provider can't meaningfully sort by
@@ -238,7 +234,7 @@ const displayResults = computed(() => {
diff = (a.downloads?.weekly ?? 0) - (b.downloads?.weekly ?? 0)
break
case 'updated':
- diff = new Date(a.package.date).getTime() - new Date(b.package.date).getTime()
+ diff = Date.parse(a.package.date) - Date.parse(b.package.date)
break
case 'name':
diff = a.package.name.localeCompare(b.package.name)
@@ -354,13 +350,19 @@ const canPublishToScope = computed(() => {
// Show claim prompt when valid name, available, either not connected or connected and has permission
const showClaimPrompt = computed(() => {
- return (
- isValidPackageName.value &&
- packageAvailability.value?.available === true &&
- packageAvailability.value.name === query.value.trim() &&
- (!isConnected.value || (isConnected.value && canPublishToScope.value)) &&
- status.value !== 'pending'
- )
+ if (!isValidPackageName.value) return false
+ if (isConnected.value && !canPublishToScope.value) return false
+
+ const avail = packageAvailability.value
+
+ // Confirmed: availability result matches current committed query
+ if (avail?.available === true && avail.name === committedQuery.value.trim()) return true
+
+ // Pending: a new fetch is in flight — keep the claim visible if the last known
+ // result was "available" so it doesn't flicker until new data arrives
+ if (status.value === 'pending' && avail?.available === true) return true
+
+ return false
})
const claimPackageModalRef = useTemplateRef('claimPackageModalRef')
@@ -711,22 +713,28 @@ onBeforeUnmount(() => {
status === 'success'
"
>
-
-
-
+
+
+
+
{
|