diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx index c635c5eb10..f2fed25ca2 100644 --- a/webapp/components/search_bar.jsx +++ b/webapp/components/search_bar.jsx @@ -171,7 +171,16 @@ export default class SearchBar extends React.Component { handleSubmit(e) { e.preventDefault(); - this.handleSearch(this.state.searchTerm.trim()); + const terms = this.state.searchTerm.trim(); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_SEARCH_TERM, + term: terms, + do_search: true, + is_mention_search: false + }); + + this.handleSearch(terms); this.search.blur(); } @@ -221,7 +230,7 @@ export default class SearchBar extends React.Component { var isSearching = null; if (this.state.isSearching) { - isSearching = ; + isSearching = ; } let helpClass = 'search-help-popover'; diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx index 1d1627950e..1499c8abbc 100644 --- a/webapp/components/search_results.jsx +++ b/webapp/components/search_results.jsx @@ -39,7 +39,8 @@ function getStateFromStores() { results, channels, searchTerm: SearchStore.getSearchTerm(), - flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST) + flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST), + loading: SearchStore.isLoading() }; } @@ -71,6 +72,7 @@ export default class SearchResults extends React.Component { componentDidMount() { this.mounted = true; + SearchStore.addSearchTermChangeListener(this.onSearchTermChange); SearchStore.addSearchChangeListener(this.onChange); ChannelStore.addChangeListener(this.onChange); PreferenceStore.addChangeListener(this.onPreferenceChange); @@ -113,6 +115,7 @@ export default class SearchResults extends React.Component { componentWillUnmount() { this.mounted = false; + SearchStore.removeSearchTermChangeListener(this.onSearchTermChange); SearchStore.removeSearchChangeListener(this.onChange); ChannelStore.removeChangeListener(this.onChange); PreferenceStore.removeChangeListener(this.onPreferenceChange); @@ -144,6 +147,14 @@ export default class SearchResults extends React.Component { }); } + onSearchTermChange(doSearch) { + if (this.mounted && doSearch) { + this.setState({ + loading: true + }); + } + } + onChange() { if (this.mounted) { this.setState(getStateFromStores()); @@ -175,7 +186,20 @@ export default class SearchResults extends React.Component { var ctls = null; - if (this.props.isFlaggedPosts && noResults) { + if (this.state.loading) { + ctls = + ( +
+
+ + +
+
+ ); + } else if (this.props.isFlaggedPosts && noResults) { ctls = (
    @@ -319,6 +343,7 @@ export default class SearchResults extends React.Component { isFlaggedPosts={this.props.isFlaggedPosts} isPinnedPosts={this.props.isPinnedPosts} channelDisplayName={this.props.channelDisplayName} + isLoading={this.state.loading} />
  • If you're searching a partial phrase (ex. searching \"rea\", looking for \"reach\" or \"reaction\"), append a * to your search term.
  • Two letter searches and common words like \"this\", \"a\" and \"is\" won't appear in search results due to excessive results returned.
", "search_results.noResults": "No results found. Try again?", + "search_results.searching": "Searching...", "search_results.usage": "
  • Use \"quotation marks\" to search for phrases
  • Use from: to find posts from specific users and in: to find posts in specific channels
", "search_results.usageFlag1": "You haven't flagged any messages yet.", "search_results.usageFlag2": "You can add a flag to messages and comments by clicking the ", diff --git a/webapp/sass/components/_search.scss b/webapp/sass/components/_search.scss index c9b8d4c020..4461b1da15 100644 --- a/webapp/sass/components/_search.scss +++ b/webapp/sass/components/_search.scss @@ -42,10 +42,6 @@ width: 40px; } -.search-form__container { - -} - .search__form { position: relative; @@ -71,15 +67,16 @@ width: 100%; } - .icon--refresh { + .fa-spin { @include opacity(0.5); + font-size: 1.2em; position: absolute; right: 27px; top: 27px; .search-bar__container & { right: 12px; - top: 11px; + top: 10px; } } } @@ -93,6 +90,14 @@ position: relative; } +.search-items-container div.loading { + text-align: center; +} + +.search-items-container img { + display: inline-block; +} + .search-results-header { border-bottom: $border-gray; color: #999999; diff --git a/webapp/sass/layout/_sidebar-right.scss b/webapp/sass/layout/_sidebar-right.scss index 6d31c16065..d7a18b587e 100644 --- a/webapp/sass/layout/_sidebar-right.scss +++ b/webapp/sass/layout/_sidebar-right.scss @@ -224,6 +224,15 @@ text-transform: uppercase; } + .sidebar--right__loading { + @include opacity(.7); + text-align: center; + + .fa { + margin-right: 5px; + } + } + .sidebar--right__subheader { font-size: 1em; padding: .5em 1em 0; diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss index 8f44a883f3..d39797efb1 100644 --- a/webapp/sass/responsive/_mobile.scss +++ b/webapp/sass/responsive/_mobile.scss @@ -1008,6 +1008,11 @@ margin-top: 9px; width: 100%; + .fa-spin { + font-size: 1.1em; + top: 9px; + } + .search-bar { font-size: 14px; height: 32px; diff --git a/webapp/stores/search_store.jsx b/webapp/stores/search_store.jsx index d57c630cb4..dd9b6dbdf7 100644 --- a/webapp/stores/search_store.jsx +++ b/webapp/stores/search_store.jsx @@ -24,6 +24,7 @@ class SearchStoreClass extends EventEmitter { this.isPinnedPosts = false; this.isVisible = false; this.searchTerm = ''; + this.loading = false; } emitChange() { @@ -157,6 +158,14 @@ class SearchStoreClass extends EventEmitter { results.order.splice(index, 1); } } + + setLoading(loading) { + this.loading = loading; + } + + isLoading() { + return this.loading; + } } var SearchStore = new SearchStoreClass(); @@ -173,10 +182,17 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => { // ignore pin posts update after switch to a new channel return; } + SearchStore.setLoading(false); SearchStore.storeSearchResults(action.results, action.is_mention_search, action.is_flagged_posts, action.is_pinned_posts); SearchStore.emitSearchChange(); break; case ActionTypes.RECEIVED_SEARCH_TERM: + if (action.do_search) { + // while a search is in progress, hide results from previous search + SearchStore.setLoading(true); + SearchStore.storeSearchResults(null, false, false, false); + SearchStore.emitSearchChange(); + } SearchStore.storeSearchTerm(action.term); SearchStore.emitSearchTermChange(action.do_search, action.is_mention_search); break;