Files
mostlymatter/webapp/components/search_results.jsx
Jonathan 76bfd279b3 PLT-7140: On slow connection searching should clear RHS and show spinner (#7014)
* Added a RECEIVED_SEARCH_TERM event on search form submit, attempted to modify Search Results Header title when loading search results

* Fixed RHS behaviour so that loading icon is shown while waiting for search results on slow connections.

* PLT-7140: Fixed all eslint issues

* PLT-7140: reverted changes to config/config.json that were accidentally committed

* PLT-7140: Removed all static function decorators that I previously added to jsx files. These were suggested by eslint, but can cause issues for functions that override parent functionality. still can't reproduce the errors seen on spinmint locally, so I'm guessing at this point

* PLT-7140: Changed var to const

* Updating UI for search results loading (#7096)
2017-08-02 15:38:54 -04:00

368 строки
14 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import SearchResultsHeader from './search_results_header.jsx';
import SearchResultsItem from './search_results_item.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
const Preferences = Constants.Preferences;
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
function getStateFromStores() {
const results = JSON.parse(JSON.stringify(SearchStore.getSearchResults()));
const channels = new Map();
if (results && results.order) {
const channelIds = results.order.map((postId) => results.posts[postId].channel_id);
for (const id of channelIds) {
if (channels.has(id)) {
continue;
}
channels.set(id, ChannelStore.get(id));
}
}
return {
results,
channels,
searchTerm: SearchStore.getSearchTerm(),
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST),
loading: SearchStore.isLoading()
};
}
export default class SearchResults extends React.Component {
constructor(props) {
super(props);
this.mounted = false;
this.onChange = this.onChange.bind(this);
this.onUserChange = this.onUserChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onBusy = this.onBusy.bind(this);
this.resize = this.resize.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onStatusChange = this.onStatusChange.bind(this);
this.handleResize = this.handleResize.bind(this);
const state = getStateFromStores();
state.windowWidth = Utils.windowWidth();
state.windowHeight = Utils.windowHeight();
state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
state.compactDisplay = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT;
state.isBusy = WebrtcStore.isBusy();
state.statuses = Object.assign({}, UserStore.getStatuses());
this.state = state;
}
componentDidMount() {
this.mounted = true;
SearchStore.addSearchTermChangeListener(this.onSearchTermChange);
SearchStore.addSearchChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
UserStore.addChangeListener(this.onUserChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
WebrtcStore.addBusyListener(this.onBusy);
this.resize();
window.addEventListener('resize', this.handleResize);
if (!Utils.isMobile()) {
$('.sidebar--right .search-items-container').perfectScrollbar();
}
}
shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
return true;
}
if (!Utils.areObjectsEqual(this.props, nextProps)) {
return true;
}
if (!Utils.areObjectsEqual(this.state, nextState)) {
return true;
}
if (nextState.compactDisplay !== this.state.compactDisplay) {
return true;
}
if (nextState.isBusy !== this.state.isBusy) {
return true;
}
return false;
}
componentWillUnmount() {
this.mounted = false;
SearchStore.removeSearchTermChangeListener(this.onSearchTermChange);
SearchStore.removeSearchChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
UserStore.removeChangeListener(this.onUserChange);
UserStore.removeStatusesChangeListener(this.onStatusChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
WebrtcStore.removeBusyListener(this.onBusy);
window.removeEventListener('resize', this.handleResize);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.searchTerm !== prevState.searchTerm) {
this.resize();
}
}
handleResize() {
this.setState({
windowWidth: Utils.windowWidth(),
windowHeight: Utils.windowHeight()
});
}
onPreferenceChange() {
this.setState({
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
});
}
onSearchTermChange(doSearch) {
if (this.mounted && doSearch) {
this.setState({
loading: true
});
}
}
onChange() {
if (this.mounted) {
this.setState(getStateFromStores());
}
}
onUserChange() {
this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
}
onBusy(isBusy) {
this.setState({isBusy});
}
onStatusChange() {
this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
}
resize() {
$('#search-items-container').scrollTop(0);
}
render() {
var results = this.state.results;
var noResults = (!results || !results.order || !results.order.length);
const searchTerm = this.state.searchTerm;
const profiles = this.state.profiles || {};
const flagIcon = Constants.FLAG_ICON_SVG;
var ctls = null;
if (this.state.loading) {
ctls =
(
<div className='sidebar--right__subheader'>
<div className='sidebar--right__loading'>
<i className='fa fa-spinner fa-spin'/>
<FormattedMessage
id='search_header.loading'
defaultMessage='Searching...'
/>
</div>
</div>
);
} else if (this.props.isFlaggedPosts && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
<ul>
<li>
<FormattedHTMLMessage
id='search_results.usageFlag1'
defaultMessage="You haven't flagged any messages yet."
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usageFlag2'
defaultMessage='You can add a flag to messages and comments by clicking the '
/>
<span
className='usage__icon'
dangerouslySetInnerHTML={{__html: flagIcon}}
/>
<FormattedHTMLMessage
id='search_results.usageFlag3'
defaultMessage=' icon next to the timestamp.'
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usageFlag4'
defaultMessage='Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.'
/>
</li>
</ul>
</div>
);
} else if (this.props.isPinnedPosts && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
<ul>
<li>
<FormattedHTMLMessage
id='search_results.usagePin1'
defaultMessage='There are no pinned messages yet.'
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usagePin2'
defaultMessage='All members of this channel can pin important or useful messages.'
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usagePin3'
defaultMessage='Pinned messages are visible to all channel members.'
/>
</li>
<li>
<FormattedHTMLMessage
id='search_results.usagePin4'
defaultMessage={'To pin a message: Go to the message that you want to pin and click [...] > "Pin to channel".'}
/>
</li>
</ul>
</div>
);
} else if (!searchTerm && noResults) {
ctls = (
<div className='sidebar--right__subheader'>
<FormattedHTMLMessage
id='search_results.usage'
defaultMessage='<ul><li>Use <b>"quotation marks"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>'
/>
</div>
);
} else if (noResults) {
ctls =
(
<div className='sidebar--right__subheader'>
<h4>
<FormattedMessage
id='search_results.noResults'
defaultMessage='No results found. Try again?'
/>
</h4>
<FormattedHTMLMessage
id='search_results.because'
defaultMessage='<ul>
<li>If you&#39;re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term.</li>
<li>Two letter searches and common words like "this", "a" and "is" won&#39;t appear in search results, due to the excessive results returned.</li>
</ul>'
/>
</div>
);
} else {
ctls = results.order.map(function searchResults(id, idx, arr) {
const post = results.posts[id];
let profile;
if (UserStore.getCurrentId() === post.user_id) {
profile = UserStore.getCurrentUser();
} else {
profile = profiles[post.user_id];
}
let status = 'offline';
if (this.state.statuses) {
status = this.state.statuses[post.user_id] || 'offline';
}
let isFlagged = false;
if (this.state.flaggedPosts) {
isFlagged = this.state.flaggedPosts.get(post.id) != null;
}
const reverseCount = arr.length - idx - 1;
return (
<SearchResultsItem
key={post.id}
channel={this.state.channels.get(post.channel_id)}
compactDisplay={this.state.compactDisplay}
post={post}
lastPostCount={(reverseCount >= 0 && reverseCount < Constants.TEST_ID_COUNT) ? reverseCount : -1}
user={profile}
term={searchTerm}
isMentionSearch={this.props.isMentionSearch}
isFlaggedSearch={this.props.isFlaggedPosts}
useMilitaryTime={this.props.useMilitaryTime}
shrink={this.props.shrink}
isFlagged={isFlagged}
isBusy={this.state.isBusy}
status={status}
/>
);
}, this);
}
return (
<div className='sidebar-right__body'>
<SearchResultsHeader
isMentionSearch={this.props.isMentionSearch}
toggleSize={this.props.toggleSize}
shrink={this.props.shrink}
isFlaggedPosts={this.props.isFlaggedPosts}
isPinnedPosts={this.props.isPinnedPosts}
channelDisplayName={this.props.channelDisplayName}
isLoading={this.state.loading}
/>
<div
id='search-items-container'
className='search-items-container'
>
{ctls}
</div>
</div>
);
}
}
SearchResults.propTypes = {
isMentionSearch: PropTypes.bool,
useMilitaryTime: PropTypes.bool.isRequired,
toggleSize: PropTypes.func,
shrink: PropTypes.func,
isFlaggedPosts: PropTypes.bool,
isPinnedPosts: PropTypes.bool,
channelDisplayName: PropTypes.string.isRequired
};