-
-
Notifications
You must be signed in to change notification settings - Fork 424
Expand file tree
/
Copy pathuseGlobalSearch.ts
More file actions
110 lines (96 loc) · 3.53 KB
/
useGlobalSearch.ts
File metadata and controls
110 lines (96 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { normalizeSearchParam } from '#shared/utils/url'
import { debounce } from 'perfect-debounce'
// Pages that have their own local filter using ?q
const pagesWithLocalFilter = new Set(['~username', 'org'])
export function useGlobalSearch(place: 'header' | 'content' = 'content') {
const { settings } = useSettings()
const { searchProvider } = useSearchProvider()
const searchProviderValue = computed(() => {
const p = normalizeSearchParam(route.query.p)
if (p === 'npm' || searchProvider.value === 'npm') return 'npm'
return 'algolia'
})
const router = useRouter()
const route = useRoute()
// Internally used searchQuery state
const searchQuery = useState<string>('search-query', () => {
if (pagesWithLocalFilter.has(route.name as string)) {
return ''
}
return normalizeSearchParam(route.query.q)
})
// Committed search query: last value submitted by user
// Syncs instantly when instantSearch is on, but only on Enter press when off
const committedSearchQuery = useState<string>('committed-search-query', () => searchQuery.value)
// This is basically doing instant search as user types
watch(searchQuery, val => {
if (settings.value.instantSearch) {
committedSearchQuery.value = val
}
})
// clean search input when navigating away from search page
watch(
() => route.query.q,
urlQuery => {
const value = normalizeSearchParam(urlQuery)
if (!value) searchQuery.value = ''
if (!searchQuery.value) searchQuery.value = value
},
)
// Updates URL when search query changes (immediately for instantSearch or after Enter hit otherwise)
const updateUrlQueryImpl = (value: string, provider: SearchProvider) => {
const isSameQuery = route.query.q === value && route.query.p === provider
// Don't navigate away from pages that use ?q for local filtering
if ((pagesWithLocalFilter.has(route.name as string) && place === 'content') || isSameQuery) {
return
}
if (route.name === 'search') {
router.replace({
query: {
...route.query,
q: value || undefined,
p: provider === 'npm' ? 'npm' : undefined,
},
})
return
}
router.push({
name: 'search',
query: {
q: value,
p: provider === 'npm' ? 'npm' : undefined,
},
})
}
const updateUrlQuery = debounce(updateUrlQueryImpl, 250)
function flushUpdateUrlQuery() {
// Commit the current query when explicitly submitted (Enter pressed)
committedSearchQuery.value = searchQuery.value
// When instant search is off the debounce queue is empty, so call directly
if (!settings.value.instantSearch) {
updateUrlQueryImpl(searchQuery.value, searchProvider.value)
} else {
updateUrlQuery.flush()
}
}
const searchQueryValue = computed({
get: () => searchQuery.value,
set: async (value: string) => {
searchQuery.value = value
// When instant search is off, skip debounced URL updates
// Only explicitly called flushUpdateUrlQuery commits and navigates
if (!settings.value.instantSearch) return
// Leading debounce implementation as it doesn't work properly out of the box (https://github.com/unjs/perfect-debounce/issues/43)
if (!updateUrlQuery.isPending()) {
updateUrlQueryImpl(value, searchProvider.value)
}
updateUrlQuery(value, searchProvider.value)
},
})
return {
model: searchQueryValue,
committedModel: committedSearchQuery,
provider: searchProviderValue,
startSearch: flushUpdateUrlQuery,
}
}