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 { 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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
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'
|
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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue