mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-24 17:26:06 +08:00
c7d8f1700d
Summary: When the search index got very big the queries we were running during after keypress would cause jank in the UI which was very noticeable on subsequent keypresses. This diff backgrounds these queries and adds a LIMIT to the fts MATCH clauses to avoid having to send too much stuff across the IPC bridge. Test Plan: Run locally, make sure that typing is smooth while searching Reviewers: juan, evan Reviewed By: juan, evan Differential Revision: https://phab.nylas.com/D3757
221 lines
6.3 KiB
JavaScript
221 lines
6.3 KiB
JavaScript
import NylasStore from 'nylas-store';
|
|
import {
|
|
Thread,
|
|
Actions,
|
|
ContactStore,
|
|
AccountStore,
|
|
DatabaseStore,
|
|
ComponentRegistry,
|
|
FocusedPerspectiveStore,
|
|
} from 'nylas-exports';
|
|
import {parseSearchQuery} from './search-query-parser'
|
|
|
|
import SearchActions from './search-actions';
|
|
import SearchMailboxPerspective from './search-mailbox-perspective';
|
|
|
|
// Stores should closely match the needs of a particular part of the front end.
|
|
// For example, we might create a "MessageStore" that observes this store
|
|
// for changes in selectedThread, "DatabaseStore" for changes to the underlying database,
|
|
// and vends up the array used for that view.
|
|
|
|
class SearchStore extends NylasStore {
|
|
constructor() {
|
|
super();
|
|
|
|
this._searchQuery = FocusedPerspectiveStore.current().searchQuery || "";
|
|
this._searchSuggestionsVersion = 1;
|
|
this._isSearching = false;
|
|
this._extensionData = []
|
|
this._clearResults();
|
|
|
|
this.listenTo(FocusedPerspectiveStore, this._onPerspectiveChanged);
|
|
this.listenTo(SearchActions.querySubmitted, this._onQuerySubmitted);
|
|
this.listenTo(SearchActions.queryChanged, this._onQueryChanged);
|
|
this.listenTo(SearchActions.searchBlurred, this._onSearchBlurred);
|
|
this.listenTo(SearchActions.searchCompleted, this._onSearchCompleted);
|
|
}
|
|
|
|
query() {
|
|
return this._searchQuery;
|
|
}
|
|
|
|
queryPopulated() {
|
|
return this._searchQuery && this._searchQuery.trim().length > 0;
|
|
}
|
|
|
|
suggestions() {
|
|
return this._suggestions;
|
|
}
|
|
|
|
isSearching() {
|
|
return this._isSearching;
|
|
}
|
|
|
|
_onSearchCompleted = () => {
|
|
this._isSearching = false;
|
|
this.trigger();
|
|
}
|
|
|
|
_onPerspectiveChanged = () => {
|
|
this._searchQuery = FocusedPerspectiveStore.current().searchQuery || "";
|
|
this.trigger();
|
|
}
|
|
|
|
_onQueryChanged = (query) => {
|
|
this._searchQuery = query;
|
|
if (this._searchQuery.length <= 1) {
|
|
this.trigger()
|
|
return
|
|
}
|
|
this._compileResults();
|
|
setTimeout(() => this._rebuildResults(), 0);
|
|
}
|
|
|
|
_onQuerySubmitted = (query) => {
|
|
this._searchQuery = query;
|
|
const current = FocusedPerspectiveStore.current();
|
|
|
|
if (this.queryPopulated()) {
|
|
this._isSearching = true;
|
|
if (this._perspectiveBeforeSearch == null) {
|
|
this._perspectiveBeforeSearch = current;
|
|
}
|
|
const next = new SearchMailboxPerspective(current.accountIds, this._searchQuery.trim());
|
|
Actions.focusMailboxPerspective(next);
|
|
} else if (current instanceof SearchMailboxPerspective) {
|
|
if (this._perspectiveBeforeSearch) {
|
|
Actions.focusMailboxPerspective(this._perspectiveBeforeSearch);
|
|
this._perspectiveBeforeSearch = null;
|
|
} else {
|
|
Actions.focusDefaultMailboxPerspectiveForAccounts(AccountStore.accounts());
|
|
}
|
|
}
|
|
|
|
this._clearResults();
|
|
}
|
|
|
|
_onSearchBlurred = () => {
|
|
this._clearResults();
|
|
}
|
|
|
|
_clearResults() {
|
|
this._searchSuggestionsVersion = 1;
|
|
this._threadResults = [];
|
|
this._contactResults = [];
|
|
this._suggestions = [];
|
|
this.trigger();
|
|
}
|
|
|
|
_rebuildResults() {
|
|
if (!this.queryPopulated()) {
|
|
this._clearResults();
|
|
return;
|
|
}
|
|
this._searchSuggestionsVersion += 1;
|
|
const searchExtensions = ComponentRegistry.findComponentsMatching({
|
|
role: "SearchBarResults",
|
|
})
|
|
|
|
Promise.map(searchExtensions, (ext) => {
|
|
return Promise.props({
|
|
label: ext.searchLabel(),
|
|
suggestions: ext.fetchSearchSuggestions(this._searchQuery),
|
|
})
|
|
}).then((extensionData = []) => {
|
|
this._extensionData = extensionData;
|
|
this._compileResults();
|
|
})
|
|
|
|
this._fetchThreadResults();
|
|
this._fetchContactResults();
|
|
}
|
|
|
|
_fetchContactResults() {
|
|
const version = this._searchSuggestionsVersion;
|
|
ContactStore.searchContacts(this._searchQuery, {limit: 10}).then(contacts => {
|
|
if (version !== this._searchSuggestionsVersion) {
|
|
return;
|
|
}
|
|
this._contactResults = contacts;
|
|
this._compileResults();
|
|
});
|
|
}
|
|
|
|
_fetchThreadResults() {
|
|
if (this._fetchingThreadResultsVersion) { return; }
|
|
this._fetchingThreadResultsVersion = this._searchSuggestionsVersion;
|
|
|
|
const {accountIds} = FocusedPerspectiveStore.current();
|
|
let dbQuery = DatabaseStore.findAll(Thread).distinct()
|
|
if (Array.isArray(accountIds) && accountIds.length === 1) {
|
|
dbQuery = dbQuery.where({accountId: accountIds[0]})
|
|
}
|
|
|
|
try {
|
|
const parsedQuery = parseSearchQuery(this._searchQuery);
|
|
// console.info('Successfully parsed and codegened search query', parsedQuery);
|
|
dbQuery = dbQuery.structuredSearch(parsedQuery);
|
|
} catch (e) {
|
|
// console.info('Failed to parse local search query, falling back to generic query', e);
|
|
dbQuery = dbQuery.search(this._searchQuery);
|
|
}
|
|
dbQuery = dbQuery
|
|
.order(Thread.attributes.lastMessageReceivedTimestamp.descending())
|
|
|
|
// console.info(dbQuery.sql());
|
|
|
|
dbQuery.background().then(results => {
|
|
// We've fetched the latest thread results - display them!
|
|
if (this._searchSuggestionsVersion === this._fetchingThreadResultsVersion) {
|
|
this._fetchingThreadResultsVersion = null;
|
|
this._threadResults = results;
|
|
this._compileResults();
|
|
// We're behind and need to re-run the search for the latest results
|
|
} else if (this._searchSuggestionsVersion > this._fetchingThreadResultsVersion) {
|
|
this._fetchingThreadResultsVersion = null;
|
|
this._fetchThreadResults();
|
|
} else {
|
|
this._fetchingThreadResultsVersion = null;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
_compileResults() {
|
|
this._suggestions = [];
|
|
|
|
this._suggestions.push({
|
|
label: `Message Contains: ${this._searchQuery}`,
|
|
value: this._searchQuery,
|
|
});
|
|
|
|
if (this._threadResults.length) {
|
|
this._suggestions.push({divider: 'Threads'});
|
|
for (const thread of this._threadResults) {
|
|
this._suggestions.push({thread});
|
|
}
|
|
}
|
|
|
|
if (this._contactResults.length) {
|
|
this._suggestions.push({divider: 'People'});
|
|
for (const contact of this._contactResults) {
|
|
this._suggestions.push({
|
|
contact: contact,
|
|
value: contact.email,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (this._extensionData.length) {
|
|
for (const {label, suggestions} of this._extensionData) {
|
|
this._suggestions.push({divider: label});
|
|
this._suggestions = this._suggestions.concat(suggestions)
|
|
}
|
|
}
|
|
|
|
this.trigger();
|
|
}
|
|
}
|
|
|
|
export default new SearchStore();
|