diff --git a/internal_packages/search-bar/lib/main.cjsx b/internal_packages/search-bar/lib/main.cjsx deleted file mode 100644 index f9b9d2dc4..000000000 --- a/internal_packages/search-bar/lib/main.cjsx +++ /dev/null @@ -1,14 +0,0 @@ -path = require 'path' -{ComponentRegistry, WorkspaceStore, React} = require 'nylas-exports' -SearchBar = require './search-bar' - -module.exports = - configDefaults: - showOnRightSide: false - - activate: (@state) -> - ComponentRegistry.register SearchBar, - location: WorkspaceStore.Location.ThreadList.Toolbar - - deactivate: -> - ComponentRegistry.unregister SearchBar diff --git a/internal_packages/search-bar/.gitignore b/internal_packages/thread-search/.gitignore similarity index 100% rename from internal_packages/search-bar/.gitignore rename to internal_packages/thread-search/.gitignore diff --git a/internal_packages/search-bar/README.md b/internal_packages/thread-search/README.md similarity index 100% rename from internal_packages/search-bar/README.md rename to internal_packages/thread-search/README.md diff --git a/internal_packages/search-bar/keymaps/search-bar.cson b/internal_packages/thread-search/keymaps/search-bar.cson similarity index 100% rename from internal_packages/search-bar/keymaps/search-bar.cson rename to internal_packages/thread-search/keymaps/search-bar.cson diff --git a/internal_packages/thread-search/lib/main.es6 b/internal_packages/thread-search/lib/main.es6 new file mode 100644 index 000000000..edf7a0bfe --- /dev/null +++ b/internal_packages/thread-search/lib/main.es6 @@ -0,0 +1,18 @@ +import {ComponentRegistry, WorkspaceStore} from 'nylas-exports' +import SearchBar from './search-bar' + +export default { + configDefaults: { + showOnRightSide: false, + }, + + activate() { + ComponentRegistry.register(SearchBar, { + location: WorkspaceStore.Location.ThreadList.Toolbar, + }) + }, + + deactivate() { + ComponentRegistry.unregister(SearchBar) + }, +} diff --git a/internal_packages/search-bar/lib/search-actions.coffee b/internal_packages/thread-search/lib/search-actions.coffee similarity index 91% rename from internal_packages/search-bar/lib/search-actions.coffee rename to internal_packages/thread-search/lib/search-actions.coffee index d65237c60..dca38ef17 100644 --- a/internal_packages/search-bar/lib/search-actions.coffee +++ b/internal_packages/thread-search/lib/search-actions.coffee @@ -4,6 +4,7 @@ SearchActions = Reflux.createActions [ "querySubmitted" "queryChanged" "searchBlurred" + "searchCompleted" ] for key, action of SearchActions diff --git a/internal_packages/search-bar/lib/search-bar.cjsx b/internal_packages/thread-search/lib/search-bar.cjsx similarity index 81% rename from internal_packages/search-bar/lib/search-bar.cjsx rename to internal_packages/thread-search/lib/search-bar.cjsx index e4c7f7bd8..f9d68c82e 100644 --- a/internal_packages/search-bar/lib/search-bar.cjsx +++ b/internal_packages/thread-search/lib/search-bar.cjsx @@ -8,7 +8,7 @@ classNames = require 'classnames' FocusedPerspectiveStore} = require 'nylas-exports' {Menu, RetinaImg, KeyCommandsRegion} = require 'nylas-component-kit' -SearchSuggestionStore = require './search-suggestion-store' +SearchStore = require './search-store' SearchActions = require './search-actions' class SearchBar extends React.Component @@ -21,7 +21,7 @@ class SearchBar extends React.Component componentDidMount: => @usub = [] - @usub.push SearchSuggestionStore.listen @_onChange + @usub.push SearchStore.listen @_onChange @usub.push WorkspaceStore.listen => @setState(focused: false) if @state.focused @@ -39,6 +39,22 @@ class SearchBar extends React.Component inputClass = classNames 'empty': @state.query.length is 0 + loupeImg = if @state.isSearching + + else + + headerComponents = [ - - + onBlur={@_onBlur} />, + loupeImg, + onClick={@_onClearSearch} />, ] itemContentFunc = (item) => @@ -128,10 +139,12 @@ class SearchBar extends React.Component _doSearch: => SearchActions.querySubmitted(@state.query) - _onChange: => @setState @_getStateFromStores() + _onChange: => + @setState @_getStateFromStores() _getStateFromStores: => - query: SearchSuggestionStore.query() - suggestions: SearchSuggestionStore.suggestions() + query: SearchStore.query() + suggestions: SearchStore.suggestions() + isSearching: SearchStore.isSearching() module.exports = SearchBar diff --git a/internal_packages/thread-search/lib/search-mailbox-perspective.es6 b/internal_packages/thread-search/lib/search-mailbox-perspective.es6 new file mode 100644 index 000000000..6674dc77d --- /dev/null +++ b/internal_packages/thread-search/lib/search-mailbox-perspective.es6 @@ -0,0 +1,46 @@ +import _ from 'underscore' +import {AccountStore, TaskFactory, MailboxPerspective} from 'nylas-exports' +import SearchQuerySubscription from './search-query-subscription' + + +class SearchMailboxPerspective extends MailboxPerspective { + + constructor(accountIds, searchQuery) { + super(accountIds) + this.searchQuery = searchQuery + this.name = 'Search' + + if (!_.isString(this.searchQuery)) { + throw new Error("SearchMailboxPerspective: Expected a `string` search query") + } + return this + } + + emptyMessage() { + return "No search results available" + } + + isEqual(other) { + return super.isEqual(other) && other.searchQuery === this.searchQuery + } + + threads() { + return new SearchQuerySubscription(this.searchQuery, this.accountIds) + } + + canReceiveThreadsFromAccountIds() { + return false + } + + tasksForRemovingItems(threads) { + TaskFactory.tasksForApplyingCategories({ + threads: threads, + categoriesToAdd: (accountId) => { + const account = AccountStore.accountForId(accountId) + return [account.defaultFinishedCategory()] + }, + }) + } +} + +export default SearchMailboxPerspective; diff --git a/src/search-query-subscription.es6 b/internal_packages/thread-search/lib/search-query-subscription.es6 similarity index 86% rename from src/search-query-subscription.es6 rename to internal_packages/thread-search/lib/search-query-subscription.es6 index e79fee5f6..4cf2c0539 100644 --- a/src/search-query-subscription.es6 +++ b/internal_packages/thread-search/lib/search-query-subscription.es6 @@ -1,18 +1,18 @@ import _ from 'underscore' -import Actions from './flux/actions' -import NylasAPI from './flux/nylas-api' -import Thread from './flux/models/thread' -import DatabaseStore from './flux/stores/database-store' -import MutableQuerySubscription from './flux/models/mutable-query-subscription' - -let FocusedPerspectiveStore = null +import { + Actions, + NylasAPI, + Thread, + DatabaseStore, + FocusedContentStore, + MutableQuerySubscription, +} from 'nylas-exports' +import SearchActions from './search-actions' class SearchQuerySubscription extends MutableQuerySubscription { constructor(searchQuery, accountIds) { - FocusedPerspectiveStore = require('./flux/stores/focused-content-store') - super(null, {asResultSet: true}) this._searchQuery = searchQuery this._accountIds = accountIds @@ -21,11 +21,15 @@ class SearchQuerySubscription extends MutableQuerySubscription { this._connections = [] this._unsubscribers = [ - FocusedPerspectiveStore.listen(::this.onFocusedContentChanged), + FocusedContentStore.listen(::this.onFocusedContentChanged), ] _.defer(() => this.performSearch()) } + replaceRange = () => { + // TODO + } + resetData() { this._searchStartedAt = null this._resultsReceivedAt = null @@ -34,10 +38,6 @@ class SearchQuerySubscription extends MutableQuerySubscription { this._focusedThreadCount = 0 } - replaceRange = () => { - // TODO - } - performSearch() { this._searchStartedAt = Date.now() @@ -62,10 +62,11 @@ class SearchQuerySubscription extends MutableQuerySubscription { const accountsSearched = new Set() let resultIds = [] + const allAccountsSearched = () => accountsSearched.size === this._accountIds.length const resultsReturned = () => { // Don't emit a "result" until we have at least one thread to display. // Otherwise it will show "No Results Found" - if (resultIds.length > 0 || accountsSearched.size === this._accountIds.length) { + if (resultIds.length > 0 || allAccountsSearched()) { const currentResults = this._set && this._set.ids().length > 0 if (currentResults) { const currentResultIds = this._set.ids() @@ -95,6 +96,9 @@ class SearchQuerySubscription extends MutableQuerySubscription { onStatusChanged: (conn) => { if (conn.isClosed()) { accountsSearched.add(accountId) + if (allAccountsSearched()) { + SearchActions.searchCompleted() + } resultsReturned() } }, @@ -103,7 +107,7 @@ class SearchQuerySubscription extends MutableQuerySubscription { } onFocusedContentChanged() { - const thread = FocusedPerspectiveStore.focused('thread') + const thread = FocusedContentStore.focused('thread') const shouldRecordChange = ( thread && (this._lastFocusedThread || {}).id !== thread.id @@ -163,4 +167,4 @@ class SearchQuerySubscription extends MutableQuerySubscription { } } -module.exports = SearchQuerySubscription +export default SearchQuerySubscription diff --git a/internal_packages/search-bar/lib/search-suggestion-store.coffee b/internal_packages/thread-search/lib/search-store.coffee similarity index 84% rename from internal_packages/search-bar/lib/search-suggestion-store.coffee rename to internal_packages/thread-search/lib/search-store.coffee index b7f347dcb..f620d313c 100644 --- a/internal_packages/search-bar/lib/search-suggestion-store.coffee +++ b/internal_packages/thread-search/lib/search-store.coffee @@ -1,32 +1,50 @@ _ = require 'underscore' NylasStore = require 'nylas-store' -{Contact, - Thread, +{Thread, + Contact, Actions, - DatabaseStore, + ContactStore, AccountStore, - FocusedPerspectiveStore, - MailboxPerspective, - ContactStore} = require 'nylas-exports' + DatabaseStore, + FocusedPerspectiveStore} = require 'nylas-exports' SearchActions = require './search-actions' +SearchMailboxPerspective = require './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 SearchSuggestionStore extends NylasStore +class SearchStore extends NylasStore constructor: -> @_searchQuery = FocusedPerspectiveStore.current().searchQuery ? "" @_searchSuggestionsVersion = 1 + @_isSearching = false @_clearResults() @listenTo FocusedPerspectiveStore, @_onPerspectiveChanged @listenTo SearchActions.querySubmitted, @_onQuerySubmitted @listenTo SearchActions.queryChanged, @_onQueryChanged @listenTo SearchActions.searchBlurred, @_onSearchBlurred + @listenTo SearchActions.searchCompleted, @_onSearchCompleted + + query: => + @_searchQuery + + queryPopulated: => + @_searchQuery and @_searchQuery.trim().length > 0 + + suggestions: => + @_suggestions + + isSearching: => + @_isSearching + + _onSearchCompleted: => + @_isSearching = false + @trigger() _onPerspectiveChanged: => @_searchQuery = FocusedPerspectiveStore.current().searchQuery ? "" @@ -43,11 +61,12 @@ class SearchSuggestionStore extends NylasStore if @queryPopulated() Actions.recordUserEvent("Commit Search Query", {}) + @_isSearching = true @_perspectiveBeforeSearch ?= current - next = MailboxPerspective.forSearch(current.accountIds, @_searchQuery.trim()) + next = new SearchMailboxPerspective(current.accountIds, @_searchQuery.trim()) Actions.focusMailboxPerspective(next) - else if current.isSearch() + else if current instanceof SearchMailboxPerspective if @_perspectiveBeforeSearch Actions.focusMailboxPerspective(@_perspectiveBeforeSearch) @_perspectiveBeforeSearch = null @@ -127,12 +146,4 @@ class SearchSuggestionStore extends NylasStore @trigger() - # Exposed Data - - query: => @_searchQuery - - queryPopulated: => @_searchQuery and @_searchQuery.trim().length > 0 - - suggestions: => @_suggestions - -module.exports = new SearchSuggestionStore() +module.exports = new SearchStore() diff --git a/internal_packages/search-bar/package.json b/internal_packages/thread-search/package.json similarity index 69% rename from internal_packages/search-bar/package.json rename to internal_packages/thread-search/package.json index 5f3ddf421..06fba07e4 100755 --- a/internal_packages/search-bar/package.json +++ b/internal_packages/thread-search/package.json @@ -1,8 +1,8 @@ { - "name": "search-bar", + "name": "thread-search", "version": "0.1.0", "main": "./lib/main", - "description": "Search bar using React", + "description": "Search for threads", "license": "GPL-3.0", "private": true, "engines": { diff --git a/internal_packages/search-bar/spec/search-bar-spec.cjsx b/internal_packages/thread-search/spec/search-bar-spec.cjsx similarity index 93% rename from internal_packages/search-bar/spec/search-bar-spec.cjsx rename to internal_packages/thread-search/spec/search-bar-spec.cjsx index 1cf10dd3d..0fa9bf002 100644 --- a/internal_packages/search-bar/spec/search-bar-spec.cjsx +++ b/internal_packages/thread-search/spec/search-bar-spec.cjsx @@ -4,7 +4,6 @@ ReactTestUtils = require('react-addons-test-utils') SearchBar = require '../lib/search-bar' SearchActions = require '../lib/search-actions' -SearchSuggestionStore = require '../lib/search-suggestion-store' describe 'SearchBar', -> beforeEach -> diff --git a/internal_packages/search-bar/stylesheets/search-bar.less b/internal_packages/thread-search/stylesheets/search-bar.less similarity index 93% rename from internal_packages/search-bar/stylesheets/search-bar.less rename to internal_packages/thread-search/stylesheets/search-bar.less index a2a505fe9..a631e0e77 100644 --- a/internal_packages/search-bar/stylesheets/search-bar.less +++ b/internal_packages/thread-search/stylesheets/search-bar.less @@ -69,6 +69,13 @@ position: absolute; top: 8px; left: @padding-base-horizontal; + + &.loading { + top: 4px; + left: 7px; + width: 15px; + height: 15px; + } } &.clear { diff --git a/spec/mailbox-perspective-spec.es6 b/spec/mailbox-perspective-spec.es6 index 180dd2791..10dddbf1c 100644 --- a/spec/mailbox-perspective-spec.es6 +++ b/spec/mailbox-perspective-spec.es6 @@ -186,13 +186,6 @@ describe('MailboxPerspective', ()=> { assertMoved('a2').from('label2').to('trash2') }) - it('moves to default finished perspective if viewing search', ()=> { - const perspective = MailboxPerspective.forSearch(this.accountIds, '') - perspective.tasksForRemovingItems(this.threads, Default) - assertMoved('a1').to('archive') - assertMoved('a2').to('trash2') - }); - it('unstars if viewing starred', ()=> { spyOn(TaskFactory, 'taskForInvertingStarred').andReturn({some: 'task'}) const perspective = MailboxPerspective.forStarred(this.accountIds) diff --git a/src/components/empty-list-state.cjsx b/src/components/empty-list-state.cjsx index e8837d3fe..f9bcd4164 100644 --- a/src/components/empty-list-state.cjsx +++ b/src/components/empty-list-state.cjsx @@ -119,14 +119,10 @@ class EmptyListState extends React.Component ContentComponent = EmptyPerspectiveState current = FocusedPerspectiveStore.current() - messageOverride = if current.isSearch() - "No search results available" - else - "Nothing to display" - + messageOverride = current.emptyMessage() if @state.syncing messageOverride = "Please wait while we prepare your mailbox." - else if FocusedPerspectiveStore.current().isInbox() + else if current.isInbox() ContentComponent = EmptyInboxState classes = classNames diff --git a/src/flux/stores/category-store.coffee b/src/flux/stores/category-store.coffee index 922a63602..5c408738b 100644 --- a/src/flux/stores/category-store.coffee +++ b/src/flux/stores/category-store.coffee @@ -134,15 +134,21 @@ class CategoryStore extends NylasStore # account have been loaded into the local cache # whenCategoriesReady: (accountOrId) => - if not accountOrId - Promise.reject('whenCategoriesReady: must pass an account or accountId') + account = null + account = asAccount(accountOrId) if accountOrId - account = asAccount(accountOrId) - categoryCollection = account.categoryCollection() - categoriesReady = => ( - @categories(account).length > 0 and - NylasSyncStatusStore.isSyncCompleteForAccount(account.id, categoryCollection) - ) + isSyncComplete = => + if account + categoryCollection = account.categoryCollection() + NylasSyncStatusStore.isSyncCompleteForAccount(account.id, categoryCollection) + else + accounts = AccountStore.accounts() + _.every(accounts, (acc) => + NylasSyncStatusStore.isSyncCompleteForAccount(acc.id, acc.categoryCollection()) + ) + + categoriesReady = => + @categories(account).length > 0 and isSyncComplete() if not categoriesReady() return new Promise (resolve) => diff --git a/src/flux/stores/focused-perspective-store.coffee b/src/flux/stores/focused-perspective-store.coffee index 0c115ff23..0acd246ba 100644 --- a/src/flux/stores/focused-perspective-store.coffee +++ b/src/flux/stores/focused-perspective-store.coffee @@ -7,22 +7,26 @@ Actions = require '../actions' class FocusedPerspectiveStore extends NylasStore constructor: -> - @_current = @_initCurrentPerspective(NylasEnv.savedState.perspective) + @_current = MailboxPerspective.forNothing() - @listenTo CategoryStore, @_onCategoryStoreChanged - @listenTo Actions.focusMailboxPerspective, @_onFocusPerspective - @listenTo Actions.focusDefaultMailboxPerspectiveForAccounts, @_onFocusAccounts + CategoryStore.whenCategoriesReady().then => + @listenTo CategoryStore, @_onCategoryStoreChanged + @listenTo Actions.focusMailboxPerspective, @_onFocusPerspective + @listenTo Actions.focusDefaultMailboxPerspectiveForAccounts, @_onFocusAccounts + + perspective = @_initCurrentPerspective(NylasEnv.savedState.perspective) + @_setPerspective(perspective) _initCurrentPerspective: (savedPerspective, accounts = AccountStore.accounts()) => if savedPerspective - current = MailboxPerspective.fromJSON(savedPerspective) - if current + perspective = MailboxPerspective.fromJSON(savedPerspective) + if perspective accountIds = _.pluck(accounts, 'id') - accountIdsNotPresent = _.difference(current.accountIds, accountIds) - current = null if accountIdsNotPresent.length > 0 + accountIdsNotPresent = _.difference(perspective.accountIds, accountIds) + perspective = null if accountIdsNotPresent.length > 0 - current ?= @_defaultPerspective() - return current + perspective ?= @_defaultPerspective() + return perspective # Inbound Events diff --git a/src/mailbox-perspective.coffee b/src/mailbox-perspective.coffee index a7f880d4a..a51d2e02e 100644 --- a/src/mailbox-perspective.coffee +++ b/src/mailbox-perspective.coffee @@ -6,7 +6,6 @@ AccountStore = require './flux/stores/account-store' CategoryStore = require './flux/stores/category-store' DatabaseStore = require './flux/stores/database-store' OutboxStore = require './flux/stores/outbox-store' -SearchQuerySubscription = require './search-query-subscription' ThreadCountsStore = require './flux/stores/thread-counts-store' MutableQuerySubscription = require './flux/models/mutable-query-subscription' Thread = require './flux/models/thread' @@ -35,15 +34,13 @@ class MailboxPerspective new CategoryMailboxPerspective(categories) @forStandardCategories: (accountsOrIds, names...) -> + # TODO this method is broken categories = CategoryStore.getStandardCategories(accountsOrIds, names...) @forCategories(categories) @forStarred: (accountsOrIds) -> new StarredMailboxPerspective(accountsOrIds) - @forSearch: (accountsOrIds, query) -> - new SearchMailboxPerspective(accountsOrIds, query) - @forInbox: (accountsOrIds) => @forStandardCategories(accountsOrIds, 'inbox') @@ -52,14 +49,12 @@ class MailboxPerspective if json.type is CategoryMailboxPerspective.name categories = JSON.parse(json.serializedCategories, Utils.registeredObjectReviver) return @forCategories(categories) - else if json.type is SearchMailboxPerspective.name - return @forSearch(json.accountIds, json.searchQuery) else if json.type is StarredMailboxPerspective.name return @forStarred(json.accountIds) else if json.type is DraftsMailboxPerspective.name return @forDrafts(json.accountIds) else - return null + return @forInbox(json.accountIds) catch error NylasEnv.reportError(new Error("Could not restore mailbox perspective: #{error}")) return null @@ -92,8 +87,8 @@ class MailboxPerspective isArchive: => false - isSearch: => - @ instanceof SearchMailboxPerspective + emptyMessage: => + "Nothing to display" categories: => [] @@ -151,38 +146,6 @@ class MailboxPerspective [] -class SearchMailboxPerspective extends MailboxPerspective - constructor: (@accountIds, @searchQuery) -> - super(@accountIds) - @name = 'Search' - - unless _.isString(@searchQuery) - throw new Error("SearchMailboxPerspective: Expected a `string` search query") - - @ - - toJSON: => - json = super - json.searchQuery = @searchQuery - json - - isEqual: (other) => - super(other) and other.searchQuery is @searchQuery - - threads: => - new SearchQuerySubscription(@searchQuery, @accountIds) - - canReceiveThreadsFromAccountIds: => - false - - tasksForRemovingItems: (threads) => - TaskFactory.tasksForApplyingCategories( - threads: threads, - categoriesToAdd: (accountId) => - account = AccountStore.accountForId(accountId) - return [account.defaultFinishedCategory()] - ) - class DraftsMailboxPerspective extends MailboxPerspective constructor: (@accountIds) -> super(@accountIds) @@ -191,9 +154,6 @@ class DraftsMailboxPerspective extends MailboxPerspective @drafts = true # The DraftListStore looks for this @ - fromJSON: => - {type: @constructor.name, accountIds: @accountIds} - threads: => null