Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 31 additions & 26 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const {
committedModel: committedQuery,
provider: searchProvider,
} = useGlobalSearch()
const query = computed(() => searchQuery.value)
const query = computed(() => searchQuery.value.trim().replace(/!$/, ''))

// Track if page just loaded (for hiding "Searching..." during view transition)
const hasInteracted = shallowRef(false)
Expand Down Expand Up @@ -452,28 +452,32 @@ async function navigateToPackage(packageName: string) {
// Track the input value when user pressed Enter (for navigating when results arrive)
const pendingEnterQuery = shallowRef<string | null>(null)

// Watch for results to navigate when Enter was pressed before results arrived
watch(displayResults, newResults => {
if (!pendingEnterQuery.value) return

// Check if input is still focused (user hasn't started navigating or clicked elsewhere)
if (document.activeElement?.tagName !== 'INPUT') {
pendingEnterQuery.value = null
return
}

// Navigate if first result matches the query that was entered
const firstResult = newResults[0]
// eslint-disable-next-line no-console
console.log('[search] watcher fired', {
pending: pendingEnterQuery.value,
firstResult: firstResult?.package.name,
})
if (firstResult?.package.name === pendingEnterQuery.value) {
pendingEnterQuery.value = null
navigateToPackage(firstResult.package.name)
}
})
// Watch for results to navigate when Enter was pressed before results arrived,
// or for "I'm feeling lucky" redirection when the query ends with "!" and there is the exact match.
watch(
displayResults,
newResults => {
const rawQuery = normalizeSearchParam(route.query.q)
const isFeelingLucky = rawQuery.endsWith('!')
if (!pendingEnterQuery.value && !isFeelingLucky) return

const target = pendingEnterQuery.value || rawQuery.replace(/!$/, '')
if (!target) return

// Navigate if first result matches the query that was entered
const firstResult = newResults[0]
// eslint-disable-next-line no-console
console.log('[search] watcher fired', {
pending: pendingEnterQuery.value,
firstResult: firstResult?.package.name,
})
if (firstResult?.package.name === target) {
pendingEnterQuery.value = null
navigateToPackage(firstResult.package.name)
}
},
{ immediate: true },
)

/**
* Focus the header search input
Expand Down Expand Up @@ -501,14 +505,15 @@ function handleResultsKeydown(e: KeyboardEvent) {
committedQuery.value = inputValue

// Check if first result matches the input value exactly
const cleanedInputValue = inputValue.replace(/!$/, '')
const firstResult = displayResults.value[0]
if (firstResult?.package.name === inputValue) {
if (firstResult?.package.name === cleanedInputValue) {
pendingEnterQuery.value = null
return navigateToPackage(firstResult.package.name)
}

// No match yet - store input value, watcher will handle navigation when results arrive
pendingEnterQuery.value = inputValue
// No match yet - store cleaned input value, watcher will handle navigation when results arrive
pendingEnterQuery.value = cleanedInputValue
return
}

Expand Down
14 changes: 14 additions & 0 deletions test/e2e/search-feeling-lucky.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { expect, test } from './test-utils'

test.describe('Search "I\'m Feeling Lucky" Redirect', () => {
test('direct URL access with "!" should redirect to package', async ({ page, goto }) => {
await goto('/search?q=nuxt!', { waitUntil: 'hydration' })
await expect(page).toHaveURL(/\/package\/nuxt$/, { timeout: 15000 })
})

test('normal search query (without "!") should not redirect', async ({ page, goto }) => {
await goto('/search?q=nuxt', { waitUntil: 'hydration' })
await expect(page.locator('[data-result-index="0"]').first()).toBeVisible({ timeout: 15000 })
await expect(page).toHaveURL(/\/search\?q=nuxt/)
})
})
Loading