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:
Nolan Lawson 2019-05-05 19:16:02 -07:00 committed by GitHub
parent 3fa285447d
commit 78715bc098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 44 deletions

View 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
}
}
}

View 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
}
}
}

View file

@ -1,10 +1,11 @@
import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax' import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
import { auth, basename } from './utils' 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({ let url = `${basename(instanceName)}/api/v1/search?` + paramsString({
q: query, q: query,
resolve: true resolve,
limit
}) })
return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT }) return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
} }

View file

@ -18,19 +18,7 @@ export async function searchAccountsByUsername (instanceName, usernamePrefix, li
return dbPromise(db, ACCOUNTS_STORE, 'readonly', (accountsStore, callback) => { return dbPromise(db, ACCOUNTS_STORE, 'readonly', (accountsStore, callback) => {
let keyRange = createAccountUsernamePrefixKeyRange(usernamePrefix.toLowerCase()) let keyRange = createAccountUsernamePrefixKeyRange(usernamePrefix.toLowerCase())
accountsStore.index(USERNAME_LOWERCASE).getAll(keyRange, limit).onsuccess = e => { accountsStore.index(USERNAME_LOWERCASE).getAll(keyRange, limit).onsuccess = e => {
let results = e.target.result callback(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)
} }
}) })
} }

View file

@ -0,0 +1 @@
export const SEARCH_RESULTS_LIMIT = 4

View file

@ -1,40 +1,25 @@
import { database } from '../../_database/database'
import { store } from '../store' import { store } from '../store'
import { doEmojiSearch } from '../../_actions/autosuggestEmojiSearch'
const SEARCH_RESULTS_LIMIT = 4 import { doAccountSearch } from '../../_actions/autosuggestAccountSearch'
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
}
export function autosuggestObservers () { export function autosuggestObservers () {
let lastSearch
store.observe('autosuggestSearchText', async autosuggestSearchText => { store.observe('autosuggestSearchText', async autosuggestSearchText => {
let { composeFocused } = store.get() let { composeFocused } = store.get()
if (!composeFocused || !autosuggestSearchText) { if (!composeFocused || !autosuggestSearchText) {
return return
} }
let autosuggestType = autosuggestSearchText.startsWith('@') ? 'account' : 'emoji' let autosuggestType = autosuggestSearchText.startsWith('@') ? 'account' : 'emoji'
let results = (autosuggestType === 'account')
? await searchAccounts(store, autosuggestSearchText) if (lastSearch) {
: await searchEmoji(store, autosuggestSearchText) lastSearch.cancel()
store.setForCurrentAutosuggest({ }
autosuggestType,
autosuggestSelected: 0, if (autosuggestType === 'emoji') {
autosuggestSearchResults: results lastSearch = doEmojiSearch(autosuggestSearchText)
}) } else {
lastSearch = doAccountSearch(autosuggestSearchText)
}
}) })
} }