Search spelling suggestions across every list
This commit is contained in:
56
frontend/src/composables/searchSuggestion.js
Normal file
56
frontend/src/composables/searchSuggestion.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ref } from 'vue'
|
||||
import api from '../api'
|
||||
|
||||
/**
|
||||
* "Did you mean" for a search that found nothing.
|
||||
*
|
||||
* ONE composable for every list in the product. Thirty routes take a search
|
||||
* parameter across a dozen files, so carrying a suggestion in each of their
|
||||
* responses would be a large change now and a thing every future plugin has to
|
||||
* remember. Instead a page that renders no rows asks for a spelling, and a page
|
||||
* that has not adopted this shows nothing - exactly what it showed before.
|
||||
*
|
||||
* Usage in a list view:
|
||||
*
|
||||
* const { suggestion, checkSuggestion, clearSuggestion } = useSearchSuggestion()
|
||||
* // after a fetch:
|
||||
* items.value.length ? clearSuggestion() : checkSuggestion(searchTerm.value)
|
||||
*
|
||||
* and in the template, beside the empty state:
|
||||
*
|
||||
* <SearchSuggestion :suggestion="suggestion" @pick="applySuggestion" />
|
||||
*/
|
||||
export function useSearchSuggestion() {
|
||||
const suggestion = ref(null)
|
||||
// Guards a slow answer arriving after a newer search has already been typed,
|
||||
// which would offer a spelling for a query nobody is looking at any more.
|
||||
let latest = 0
|
||||
|
||||
function clearSuggestion() {
|
||||
suggestion.value = null
|
||||
latest += 1
|
||||
}
|
||||
|
||||
async function checkSuggestion(query) {
|
||||
const term = (query || '').trim()
|
||||
if (term.length < 2) {
|
||||
clearSuggestion()
|
||||
return
|
||||
}
|
||||
const ticket = ++latest
|
||||
try {
|
||||
const response = await api.get('/search/suggest', { params: { q: term } })
|
||||
if (ticket !== latest) return
|
||||
const offered = response.data?.data?.suggestion || null
|
||||
// Never offer back what was typed: a suggestion identical to the query
|
||||
// reads as a broken feature.
|
||||
suggestion.value = offered && offered !== term.toLowerCase() ? offered : null
|
||||
} catch (err) {
|
||||
// A search that found nothing is already the answer. Failing to improve
|
||||
// on it is not worth an error in front of anyone.
|
||||
if (ticket === latest) suggestion.value = null
|
||||
}
|
||||
}
|
||||
|
||||
return { suggestion, checkSuggestion, clearSuggestion }
|
||||
}
|
||||
Reference in New Issue
Block a user