fix: do remote search when autosuggesting accounts (#1182)
* fix: do remote search when autosuggesting accounts fixes #1005 * fix emoji search
This commit is contained in:
parent
3fa285447d
commit
78715bc098
79
src/routes/_actions/autosuggestAccountSearch.js
Normal file
79
src/routes/_actions/autosuggestAccountSearch.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { database } from '../_database/database'
|
||||
import { store } from '../_store/store'
|
||||
import { search } from '../_api/search'
|
||||
import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
|
||||
import { USERNAME_LOWERCASE } from '../_database/constants'
|
||||
import { concat } from '../_utils/arrays'
|
||||
import uniqBy from 'lodash-es/uniqBy'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
|
||||
const DATABASE_SEARCH_RESULTS_LIMIT = 30
|
||||
|
||||
function byAccountRelevance (a, b) {
|
||||
// accounts you're following go first
|
||||
if (a.following !== b.following) {
|
||||
return a.following ? -1 : 1
|
||||
}
|
||||
// after that, just sort by username
|
||||
if (a[USERNAME_LOWERCASE] !== b[USERNAME_LOWERCASE]) {
|
||||
return a[USERNAME_LOWERCASE] < b[USERNAME_LOWERCASE] ? -1 : 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function byAccountId (a) {
|
||||
return a.id
|
||||
}
|
||||
|
||||
export function doAccountSearch (searchText) {
|
||||
let canceled = false
|
||||
let localResults
|
||||
let remoteResults
|
||||
let { currentInstance, accessToken } = store.get()
|
||||
|
||||
async function searchAccountsLocally (searchText) {
|
||||
localResults = await database.searchAccountsByUsername(
|
||||
currentInstance, searchText.substring(1), DATABASE_SEARCH_RESULTS_LIMIT)
|
||||
}
|
||||
|
||||
async function searchAccountsRemotely (searchText) {
|
||||
remoteResults = (await search(currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT)).accounts
|
||||
}
|
||||
|
||||
function mergeAndTruncateResults () {
|
||||
return uniqBy(concat(localResults || [], remoteResults || []), byAccountId)
|
||||
.sort(byAccountRelevance)
|
||||
.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
34
src/routes/_actions/autosuggestEmojiSearch.js
Normal file
34
src/routes/_actions/autosuggestEmojiSearch.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { store } from '../_store/store'
|
||||
import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
|
||||
import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
|
||||
|
||||
function searchEmoji (searchText) {
|
||||
searchText = searchText.toLowerCase().substring(1)
|
||||
let { currentCustomEmoji } = store.get()
|
||||
let results = currentCustomEmoji.filter(emoji => emoji.shortcode.toLowerCase().startsWith(searchText))
|
||||
.sort((a, b) => a.shortcode.toLowerCase() < b.shortcode.toLowerCase() ? -1 : 1)
|
||||
.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
return results
|
||||
}
|
||||
|
||||
export function doEmojiSearch (searchText) {
|
||||
let canceled = false
|
||||
|
||||
scheduleIdleTask(() => {
|
||||
if (canceled) {
|
||||
return
|
||||
}
|
||||
let results = searchEmoji(searchText)
|
||||
store.setForCurrentAutosuggest({
|
||||
autosuggestType: 'emoji',
|
||||
autosuggestSelected: 0,
|
||||
autosuggestSearchResults: results
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
cancel: () => {
|
||||
canceled = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
|
||||
import { auth, basename } from './utils'
|
||||
|
||||
export function search (instanceName, accessToken, query) {
|
||||
export function search (instanceName, accessToken, query, resolve = true, limit = 40) {
|
||||
let url = `${basename(instanceName)}/api/v1/search?` + paramsString({
|
||||
q: query,
|
||||
resolve: true
|
||||
resolve,
|
||||
limit
|
||||
})
|
||||
return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
|
||||
}
|
||||
|
|
|
@ -18,19 +18,7 @@ export async function searchAccountsByUsername (instanceName, usernamePrefix, li
|
|||
return dbPromise(db, ACCOUNTS_STORE, 'readonly', (accountsStore, callback) => {
|
||||
let keyRange = createAccountUsernamePrefixKeyRange(usernamePrefix.toLowerCase())
|
||||
accountsStore.index(USERNAME_LOWERCASE).getAll(keyRange, limit).onsuccess = e => {
|
||||
let results = e.target.result
|
||||
results = results.sort((a, b) => {
|
||||
// accounts you're following go first
|
||||
if (a.following !== b.following) {
|
||||
return a.following ? -1 : 1
|
||||
}
|
||||
// after that, just sort by username
|
||||
if (a[USERNAME_LOWERCASE] !== b[USERNAME_LOWERCASE]) {
|
||||
return a[USERNAME_LOWERCASE] < b[USERNAME_LOWERCASE] ? -1 : 1
|
||||
}
|
||||
return 0 // eslint-disable-line
|
||||
})
|
||||
callback(results)
|
||||
callback(e.target.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
1
src/routes/_static/autosuggest.js
Normal file
1
src/routes/_static/autosuggest.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const SEARCH_RESULTS_LIMIT = 4
|
|
@ -1,40 +1,25 @@
|
|||
import { database } from '../../_database/database'
|
||||
import { store } from '../store'
|
||||
|
||||
const SEARCH_RESULTS_LIMIT = 4
|
||||
const DATABASE_SEARCH_RESULTS_LIMIT = 30
|
||||
|
||||
async function searchAccounts (store, searchText) {
|
||||
searchText = searchText.substring(1)
|
||||
let { currentInstance } = store.get()
|
||||
let results = await database.searchAccountsByUsername(
|
||||
currentInstance, searchText, DATABASE_SEARCH_RESULTS_LIMIT)
|
||||
return results.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
}
|
||||
|
||||
function searchEmoji (store, searchText) {
|
||||
searchText = searchText.toLowerCase().substring(1)
|
||||
let { currentCustomEmoji } = store.get()
|
||||
let results = currentCustomEmoji.filter(emoji => emoji.shortcode.toLowerCase().startsWith(searchText))
|
||||
.sort((a, b) => a.shortcode.toLowerCase() < b.shortcode.toLowerCase() ? -1 : 1)
|
||||
.slice(0, SEARCH_RESULTS_LIMIT)
|
||||
return results
|
||||
}
|
||||
import { doEmojiSearch } from '../../_actions/autosuggestEmojiSearch'
|
||||
import { doAccountSearch } from '../../_actions/autosuggestAccountSearch'
|
||||
|
||||
export function autosuggestObservers () {
|
||||
let lastSearch
|
||||
|
||||
store.observe('autosuggestSearchText', async autosuggestSearchText => {
|
||||
let { composeFocused } = store.get()
|
||||
if (!composeFocused || !autosuggestSearchText) {
|
||||
return
|
||||
}
|
||||
let autosuggestType = autosuggestSearchText.startsWith('@') ? 'account' : 'emoji'
|
||||
let results = (autosuggestType === 'account')
|
||||
? await searchAccounts(store, autosuggestSearchText)
|
||||
: await searchEmoji(store, autosuggestSearchText)
|
||||
store.setForCurrentAutosuggest({
|
||||
autosuggestType,
|
||||
autosuggestSelected: 0,
|
||||
autosuggestSearchResults: results
|
||||
})
|
||||
|
||||
if (lastSearch) {
|
||||
lastSearch.cancel()
|
||||
}
|
||||
|
||||
if (autosuggestType === 'emoji') {
|
||||
lastSearch = doEmojiSearch(autosuggestSearchText)
|
||||
} else {
|
||||
lastSearch = doAccountSearch(autosuggestSearchText)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue