From 168b08e83d15b01b700f2e94ed037f077e357060 Mon Sep 17 00:00:00 2001 From: Jia Long Lu <451660550@qq.com> Date: Thu, 5 Feb 2026 14:56:32 +0800 Subject: [PATCH 1/2] feat: enter key open the first package when searching (#916) --- app/pages/search.vue | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/pages/search.vue b/app/pages/search.vue index e01985bfe..ba02889f8 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -541,7 +541,48 @@ function focusElement(el: HTMLElement) { el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) } +// Find latest package name +function findLatestPackageName() { + const packageName = displayResults.value?.[0]?.package.name + if (packageName === query.value) { + return packageName.split('/') + } +} + +// Navigate to package page +const navigateToPackage = debounce((packageName?: string[]) => { + router.push({ + name: 'package', + params: { package: packageName }, + }) +}, 500) + function handleResultsKeydown(e: KeyboardEvent) { + // If the active element is an input and there are results, navigate to the first result + if (e.key === 'Enter' && document.activeElement?.tagName === 'INPUT') { + // After entering quickly and pressing Enter, find the latest packages + const latestPackageName = findLatestPackageName() + // Find successful . navigate to package page + if (latestPackageName) return navigateToPackage(latestPackageName) + // Waiting for the latest search results (maximum 1.5 seconds) + let waitSearchResultInterval: ReturnType | null + function clearSearchResultInterval() { + if (waitSearchResultInterval) clearInterval(waitSearchResultInterval) + waitSearchResultInterval = null + } + waitSearchResultInterval = setInterval(() => { + const latestPackageName = findLatestPackageName() + if (latestPackageName) { + clearSearchResultInterval() + return navigateToPackage(latestPackageName) + } + }, 100) + + setTimeout(() => { + clearSearchResultInterval() + }, 1500) + } + if (totalSelectableCount.value <= 0) return const elements = getFocusableElements() From 8db44294a70bd55b795975a6665b197d03f4b491 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 5 Feb 2026 10:10:59 +0000 Subject: [PATCH 2/2] refactor: don't use setTimeout, but watch results --- app/pages/search.vue | 75 +++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/app/pages/search.vue b/app/pages/search.vue index ba02889f8..2faf3f3be 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -541,46 +541,57 @@ function focusElement(el: HTMLElement) { el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) } -// Find latest package name -function findLatestPackageName() { - const packageName = displayResults.value?.[0]?.package.name - if (packageName === query.value) { - return packageName.split('/') - } -} - // Navigate to package page -const navigateToPackage = debounce((packageName?: string[]) => { - router.push({ +async function navigateToPackage(packageName: string) { + await navigateTo({ name: 'package', - params: { package: packageName }, + params: { package: packageName.split('/') }, }) -}, 500) +} + +// Track the input value when user pressed Enter (for navigating when results arrive) +const pendingEnterQuery = shallowRef(null) + +// Watch for results to navigate when Enter was pressed before results arrived +watch(displayResults, results => { + 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 = results[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) + } +}) function handleResultsKeydown(e: KeyboardEvent) { - // If the active element is an input and there are results, navigate to the first result + // If the active element is an input, navigate to exact match or wait for results if (e.key === 'Enter' && document.activeElement?.tagName === 'INPUT') { - // After entering quickly and pressing Enter, find the latest packages - const latestPackageName = findLatestPackageName() - // Find successful . navigate to package page - if (latestPackageName) return navigateToPackage(latestPackageName) - // Waiting for the latest search results (maximum 1.5 seconds) - let waitSearchResultInterval: ReturnType | null - function clearSearchResultInterval() { - if (waitSearchResultInterval) clearInterval(waitSearchResultInterval) - waitSearchResultInterval = null + // Get value directly from input (not from route query, which may be debounced) + const inputValue = (document.activeElement as HTMLInputElement).value.trim() + if (!inputValue) return + + // Check if first result matches the input value exactly + const firstResult = displayResults.value[0] + if (firstResult?.package.name === inputValue) { + pendingEnterQuery.value = null + return navigateToPackage(firstResult.package.name) } - waitSearchResultInterval = setInterval(() => { - const latestPackageName = findLatestPackageName() - if (latestPackageName) { - clearSearchResultInterval() - return navigateToPackage(latestPackageName) - } - }, 100) - setTimeout(() => { - clearSearchResultInterval() - }, 1500) + // No match yet - store input value, watcher will handle navigation when results arrive + pendingEnterQuery.value = inputValue + return } if (totalSelectableCount.value <= 0) return