Add search results pagination to web UI (#11409)
* Add search results pagination to web UI Fix #10737 * Fix code style issue
This commit is contained in:
		
							parent
							
								
									b9fbcbfe4e
								
							
						
					
					
						commit
						8a4674f2c3
					
				
					 5 changed files with 76 additions and 8 deletions
				
			
		| 
						 | 
					@ -10,6 +10,10 @@ export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
 | 
				
			||||||
export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
 | 
					export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
 | 
				
			||||||
export const SEARCH_FETCH_FAIL    = 'SEARCH_FETCH_FAIL';
 | 
					export const SEARCH_FETCH_FAIL    = 'SEARCH_FETCH_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
 | 
				
			||||||
 | 
					export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
 | 
				
			||||||
 | 
					export const SEARCH_EXPAND_FAIL    = 'SEARCH_EXPAND_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function changeSearch(value) {
 | 
					export function changeSearch(value) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: SEARCH_CHANGE,
 | 
					    type: SEARCH_CHANGE,
 | 
				
			||||||
| 
						 | 
					@ -77,8 +81,50 @@ export function fetchSearchFail(error) {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function showSearch() {
 | 
					export const expandSearch = type => (dispatch, getState) => {
 | 
				
			||||||
  return {
 | 
					  const value  = getState().getIn(['search', 'value']);
 | 
				
			||||||
    type: SEARCH_SHOW,
 | 
					  const offset = getState().getIn(['search', 'results', type]).size;
 | 
				
			||||||
  };
 | 
					
 | 
				
			||||||
 | 
					  dispatch(expandSearchRequest());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  api(getState).get('/api/v2/search', {
 | 
				
			||||||
 | 
					    params: {
 | 
				
			||||||
 | 
					      q: value,
 | 
				
			||||||
 | 
					      type,
 | 
				
			||||||
 | 
					      offset,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }).then(({ data }) => {
 | 
				
			||||||
 | 
					    if (data.accounts) {
 | 
				
			||||||
 | 
					      dispatch(importFetchedAccounts(data.accounts));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (data.statuses) {
 | 
				
			||||||
 | 
					      dispatch(importFetchedStatuses(data.statuses));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dispatch(expandSearchSuccess(data, value, type));
 | 
				
			||||||
 | 
					    dispatch(fetchRelationships(data.accounts.map(item => item.id)));
 | 
				
			||||||
 | 
					  }).catch(error => {
 | 
				
			||||||
 | 
					    dispatch(expandSearchFail(error));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const expandSearchRequest = () => ({
 | 
				
			||||||
 | 
					  type: SEARCH_EXPAND_REQUEST,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const expandSearchSuccess = (results, searchTerm, searchType) => ({
 | 
				
			||||||
 | 
					  type: SEARCH_EXPAND_SUCCESS,
 | 
				
			||||||
 | 
					  results,
 | 
				
			||||||
 | 
					  searchTerm,
 | 
				
			||||||
 | 
					  searchType,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const expandSearchFail = error => ({
 | 
				
			||||||
 | 
					  type: SEARCH_EXPAND_FAIL,
 | 
				
			||||||
 | 
					  error,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showSearch = () => ({
 | 
				
			||||||
 | 
					  type: SEARCH_SHOW,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
				
			||||||
import Hashtag from '../../../components/hashtag';
 | 
					import Hashtag from '../../../components/hashtag';
 | 
				
			||||||
import Icon from 'mastodon/components/icon';
 | 
					import Icon from 'mastodon/components/icon';
 | 
				
			||||||
import { searchEnabled } from '../../../initial_state';
 | 
					import { searchEnabled } from '../../../initial_state';
 | 
				
			||||||
 | 
					import LoadMore from 'mastodon/components/load_more';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
 | 
					  dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
 | 
				
			||||||
| 
						 | 
					@ -20,15 +21,24 @@ class SearchResults extends ImmutablePureComponent {
 | 
				
			||||||
    results: ImmutablePropTypes.map.isRequired,
 | 
					    results: ImmutablePropTypes.map.isRequired,
 | 
				
			||||||
    suggestions: ImmutablePropTypes.list.isRequired,
 | 
					    suggestions: ImmutablePropTypes.list.isRequired,
 | 
				
			||||||
    fetchSuggestions: PropTypes.func.isRequired,
 | 
					    fetchSuggestions: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    expandSearch: PropTypes.func.isRequired,
 | 
				
			||||||
    dismissSuggestion: PropTypes.func.isRequired,
 | 
					    dismissSuggestion: PropTypes.func.isRequired,
 | 
				
			||||||
    searchTerm: PropTypes.string,
 | 
					    searchTerm: PropTypes.string,
 | 
				
			||||||
    intl: PropTypes.object.isRequired,
 | 
					    intl: PropTypes.object.isRequired,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount () {
 | 
					  componentDidMount () {
 | 
				
			||||||
    this.props.fetchSuggestions();
 | 
					    if (this.props.searchTerm === '') {
 | 
				
			||||||
 | 
					      this.props.fetchSuggestions();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadMoreAccounts = () => this.props.expandSearch('accounts');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadMoreStatuses = () => this.props.expandSearch('statuses');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadMoreHashtags = () => this.props.expandSearch('hashtags');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props;
 | 
					    const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,6 +75,8 @@ class SearchResults extends ImmutablePureComponent {
 | 
				
			||||||
          <h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
 | 
					          <h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
 | 
					          {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {results.get('accounts').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -76,6 +88,8 @@ class SearchResults extends ImmutablePureComponent {
 | 
				
			||||||
          <h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
 | 
					          <h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
 | 
					          {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {results.get('statuses').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
 | 
					    } else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
 | 
				
			||||||
| 
						 | 
					@ -97,6 +111,8 @@ class SearchResults extends ImmutablePureComponent {
 | 
				
			||||||
          <h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
 | 
					          <h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
 | 
					          {results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {results.get('hashtags').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
import SearchResults from '../components/search_results';
 | 
					import SearchResults from '../components/search_results';
 | 
				
			||||||
import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
 | 
					import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions';
 | 
				
			||||||
 | 
					import { expandSearch } from 'mastodon/actions/search';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = state => ({
 | 
					const mapStateToProps = state => ({
 | 
				
			||||||
  results: state.getIn(['search', 'results']),
 | 
					  results: state.getIn(['search', 'results']),
 | 
				
			||||||
| 
						 | 
					@ -10,6 +11,7 @@ const mapStateToProps = state => ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapDispatchToProps = dispatch => ({
 | 
					const mapDispatchToProps = dispatch => ({
 | 
				
			||||||
  fetchSuggestions: () => dispatch(fetchSuggestions()),
 | 
					  fetchSuggestions: () => dispatch(fetchSuggestions()),
 | 
				
			||||||
 | 
					  expandSearch: type => dispatch(expandSearch(type)),
 | 
				
			||||||
  dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
 | 
					  dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import {
 | 
				
			||||||
  SEARCH_CLEAR,
 | 
					  SEARCH_CLEAR,
 | 
				
			||||||
  SEARCH_FETCH_SUCCESS,
 | 
					  SEARCH_FETCH_SUCCESS,
 | 
				
			||||||
  SEARCH_SHOW,
 | 
					  SEARCH_SHOW,
 | 
				
			||||||
 | 
					  SEARCH_EXPAND_SUCCESS,
 | 
				
			||||||
} from '../actions/search';
 | 
					} from '../actions/search';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  COMPOSE_MENTION,
 | 
					  COMPOSE_MENTION,
 | 
				
			||||||
| 
						 | 
					@ -42,6 +43,8 @@ export default function search(state = initialState, action) {
 | 
				
			||||||
      statuses: ImmutableList(action.results.statuses.map(item => item.id)),
 | 
					      statuses: ImmutableList(action.results.statuses.map(item => item.id)),
 | 
				
			||||||
      hashtags: fromJS(action.results.hashtags),
 | 
					      hashtags: fromJS(action.results.hashtags),
 | 
				
			||||||
    })).set('submitted', true).set('searchTerm', action.searchTerm);
 | 
					    })).set('submitted', true).set('searchTerm', action.searchTerm);
 | 
				
			||||||
 | 
					  case SEARCH_EXPAND_SUCCESS:
 | 
				
			||||||
 | 
					    return state.updateIn(['results', action.searchType], list => list.concat(action.results[action.searchType].map(item => item.id)));
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4006,8 +4006,9 @@ a.status-card.compact:hover {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.search-results__info {
 | 
					.search-results__info {
 | 
				
			||||||
  padding: 10px;
 | 
					  padding: 20px;
 | 
				
			||||||
  color: $secondary-text-color;
 | 
					  color: $darker-text-color;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.modal-root {
 | 
					.modal-root {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue