semaphore/src/routes/_actions/autosuggestAccountSearch.js

105 lines
3.0 KiB
JavaScript

import { database } from '../_database/database'
import { store } from '../_store/store'
import { search } from '../_api/search'
import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
import { concat } from '../_utils/arrays'
import uniqBy from 'lodash-es/uniqBy'
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
import { PromiseThrottler } from '../_utils/PromiseThrottler'
const DATABASE_SEARCH_RESULTS_LIMIT = 30
const promiseThrottler = new PromiseThrottler(200) // Mastodon FE also uses 200ms
function byUsername (a, b) {
let usernameA = a.acct.toLowerCase()
let usernameB = b.acct.toLowerCase()
return usernameA < usernameB ? -1 : usernameA === usernameB ? 0 : 1
}
function byAccountId (a) {
return a.id
}
export function doAccountSearch (searchText) {
let canceled = false
let localResults
let remoteResults
let { currentInstance, accessToken } = store.get()
let controller = typeof AbortController === 'function' && new AbortController()
function abortFetch () {
if (controller) {
controller.abort()
controller = null
}
}
async function searchAccountsLocally (searchText) {
localResults = await database.searchAccountsByUsername(
currentInstance, searchText.substring(1), DATABASE_SEARCH_RESULTS_LIMIT)
}
async function searchAccountsRemotely (searchText) {
// Throttle our XHRs to be a good citizen and not spam the server with one XHR per keystroke
await promiseThrottler.next()
if (canceled) {
return
}
remoteResults = (await search(
currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT, controller && controller.signal
)).accounts
}
function mergeAndTruncateResults () {
// Always include local results; they are more likely to be relevant
// because the user has seen their content before. Otherwise, sort by username.
let results = (localResults || [])
.slice()
.sort(byUsername)
.slice(0, SEARCH_RESULTS_LIMIT)
if (results.length < SEARCH_RESULTS_LIMIT) {
let topRemoteResults = (remoteResults || [])
.sort(byUsername)
.slice(0, SEARCH_RESULTS_LIMIT - results.length)
results = concat(results, topRemoteResults)
results = uniqBy(results, byAccountId)
}
return results
}
function onNewResults () {
if (canceled) {
return
}
let results = mergeAndTruncateResults()
store.setForCurrentAutosuggest({
autosuggestType: 'account',
autosuggestSelected: 0,
autosuggestSearchResults: results
})
}
scheduleIdleTask(() => {
if (canceled) {
return
}
// run the two searches in parallel
searchAccountsLocally(searchText).then(onNewResults).catch(err => {
console.error('could not search locally', err)
})
searchAccountsRemotely(searchText).then(onNewResults).catch(err => {
console.error('could not search remotely', err)
})
})
return {
cancel: () => {
canceled = true
abortFetch()
}
}
}