@ -9,13 +9,15 @@ import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes' ;
import ImmutablePropTypes from 'react-immutable-proptypes' ;
import { connect } from 'react-redux' ;
import { connect } from 'react-redux' ;
import { expandSearch } from 'mastodon/actions/search' ;
import { submitSearch, expandSearch } from 'mastodon/actions/search' ;
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag' ;
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag' ;
import { LoadMore } from 'mastodon/components/load_more ';
import { Icon } from 'mastodon/components/icon ';
import { LoadingIndicator } from 'mastodon/components/loading_indicator ';
import ScrollableList from 'mastodon/components/scrollable_list ';
import Account from 'mastodon/containers/account_container' ;
import Account from 'mastodon/containers/account_container' ;
import Status from 'mastodon/containers/status_container' ;
import Status from 'mastodon/containers/status_container' ;
import { SearchSection } from './components/search_section' ;
const messages = defineMessages ( {
const messages = defineMessages ( {
title : { id : 'search_results.title' , defaultMessage : 'Search for {q}' } ,
title : { id : 'search_results.title' , defaultMessage : 'Search for {q}' } ,
} ) ;
} ) ;
@ -24,85 +26,175 @@ const mapStateToProps = state => ({
isLoading : state . getIn ( [ 'search' , 'isLoading' ] ) ,
isLoading : state . getIn ( [ 'search' , 'isLoading' ] ) ,
results : state . getIn ( [ 'search' , 'results' ] ) ,
results : state . getIn ( [ 'search' , 'results' ] ) ,
q : state . getIn ( [ 'search' , 'searchTerm' ] ) ,
q : state . getIn ( [ 'search' , 'searchTerm' ] ) ,
submittedType : state . getIn ( [ 'search' , 'type' ] ) ,
} ) ;
} ) ;
const appendLoadMore = ( id , list , onLoadMore ) => {
const INITIAL _PAGE _LIMIT = 10 ;
if ( list . size >= 5 ) {
const INITIAL _DISPLAY = 4 ;
return list . push ( < LoadMore key = { ` ${ id } -load-more ` } visible onClick = { onLoadMore } / > ) ;
const hidePeek = list => {
if ( list . size > INITIAL _PAGE _LIMIT && list . size % INITIAL _PAGE _LIMIT === 1 ) {
return list . skipLast ( 1 ) ;
} else {
} else {
return list ;
return list ;
}
}
} ;
} ;
const renderAccounts = ( results , onLoadMore ) => appendLoadMore ( 'accounts' , results . get ( 'accounts' , ImmutableList ( ) ) . map ( item => (
const renderAccounts = accounts => hidePeek ( accounts ) . map ( id => (
< Account key = { ` account- ${ item } ` } id = { item } / >
< Account key = { id } id = { id } / >
) ) , onLoadMore ) ;
) ) ;
const renderHashtags = ( results , onLoadMore ) => appendLoadMore ( 'hashtags' , results . get ( 'hashtags' , ImmutableList ( ) ) . map ( item => (
const renderHashtags = hashtags => hidePeek ( hashtags ) . map ( hashtag => (
< Hashtag key = { ` tag- ${ item . get ( 'name' ) } ` } hashtag = { item } / >
< Hashtag key = { hashtag . get ( 'name' ) } hashtag = { hashtag } / >
) ) , onLoadMore ) ;
) ) ;
const renderStatuses = ( results , onLoadMore ) => appendLoadMore ( 'statuses' , results . get ( 'statuses' , ImmutableList ( ) ) . map ( item => (
const renderStatuses = statuses => hidePeek ( statuses ) . map ( id => (
< Status key = { ` status- ${ item } ` } id = { item } / >
< Status key = { id } id = { id } / >
) ) , onLoadMore ) ;
) ) ;
class Results extends PureComponent {
class Results extends PureComponent {
static propTypes = {
static propTypes = {
results : ImmutablePropTypes . map ,
results : ImmutablePropTypes . contains ( {
accounts : ImmutablePropTypes . orderedSet ,
statuses : ImmutablePropTypes . orderedSet ,
hashtags : ImmutablePropTypes . orderedSet ,
} ) ,
isLoading : PropTypes . bool ,
isLoading : PropTypes . bool ,
multiColumn : PropTypes . bool ,
multiColumn : PropTypes . bool ,
dispatch : PropTypes . func . isRequired ,
dispatch : PropTypes . func . isRequired ,
q : PropTypes . string ,
q : PropTypes . string ,
intl : PropTypes . object ,
intl : PropTypes . object ,
submittedType : PropTypes . oneOf ( [ 'accounts' , 'statuses' , 'hashtags' ] ) ,
} ;
} ;
state = {
state = {
type : 'all' ,
type : this . props . submittedType || 'all' ,
} ;
static getDerivedStateFromProps ( props , state ) {
if ( props . submittedType !== state . type ) {
return {
type : props . submittedType || 'all' ,
} ;
}
return null ;
} ;
handleSelectAll = ( ) => {
const { submittedType , dispatch } = this . props ;
/ / I f w e o r i g i n a l l y s e a r c h e d f o r a s p e c i f i c t y p e , w e n e e d t o r e s u b m i t
/ / t h e q u e r y t o g e t a l l t y p e s o f r e s u l t s
if ( submittedType ) {
dispatch ( submitSearch ( ) ) ;
}
this . setState ( { type : 'all' } ) ;
} ;
handleSelectAccounts = ( ) => {
const { submittedType , dispatch } = this . props ;
/ / I f w e o r i g i n a l l y s e a r c h e d f o r s o m e t h i n g e l s e ( b u t n o t e v e r y t h i n g ) ,
/ / w e n e e d t o r e s u b m i t t h e q u e r y f o r t h i s s p e c i f i c t y p e
if ( submittedType !== 'accounts' ) {
dispatch ( submitSearch ( 'accounts' ) ) ;
}
this . setState ( { type : 'accounts' } ) ;
} ;
} ;
handleSelectAll = ( ) => this . setState ( { type : 'all' } ) ;
handleSelectHashtags = ( ) => {
handleSelectAccounts = ( ) => this . setState ( { type : 'accounts' } ) ;
const { submittedType , dispatch } = this . props ;
handleSelectHashtags = ( ) => this . setState ( { type : 'hashtags' } ) ;
handleSelectStatuses = ( ) => this . setState ( { type : 'statuses' } ) ;
/ / I f w e o r i g i n a l l y s e a r c h e d f o r s o m e t h i n g e l s e ( b u t n o t e v e r y t h i n g ) ,
handleLoadMoreAccounts = ( ) => this . loadMore ( 'accounts' ) ;
/ / w e n e e d t o r e s u b m i t t h e q u e r y f o r t h i s s p e c i f i c t y p e
handleLoadMoreStatuses = ( ) => this . loadMore ( 'statuses' ) ;
if ( submittedType !== 'hashtags' ) {
handleLoadMoreHashtags = ( ) => this . loadMore ( 'hashtags' ) ;
dispatch ( submitSearch ( 'hashtags' ) ) ;
}
this . setState ( { type : 'hashtags' } ) ;
}
handleSelectStatuses = ( ) => {
const { submittedType , dispatch } = this . props ;
/ / I f w e o r i g i n a l l y s e a r c h e d f o r s o m e t h i n g e l s e ( b u t n o t e v e r y t h i n g ) ,
/ / w e n e e d t o r e s u b m i t t h e q u e r y f o r t h i s s p e c i f i c t y p e
if ( submittedType !== 'statuses' ) {
dispatch ( submitSearch ( 'statuses' ) ) ;
}
this . setState ( { type : 'statuses' } ) ;
}
handleLoadMoreAccounts = ( ) => this . _loadMore ( 'accounts' ) ;
handleLoadMoreStatuses = ( ) => this . _loadMore ( 'statuses' ) ;
handleLoadMoreHashtags = ( ) => this . _loadMore ( 'hashtags' ) ;
loadMore ( type ) {
_ loadMore ( type ) {
const { dispatch } = this . props ;
const { dispatch } = this . props ;
dispatch ( expandSearch ( type ) ) ;
dispatch ( expandSearch ( type ) ) ;
}
}
handleLoadMore = ( ) => {
const { type } = this . state ;
if ( type !== 'all' ) {
this . _loadMore ( type ) ;
}
} ;
render ( ) {
render ( ) {
const { intl , isLoading , q , results } = this . props ;
const { intl , isLoading , q , results } = this . props ;
const { type } = this . state ;
const { type } = this . state ;
let filteredResults = ImmutableList ( ) ;
/ / W e r e q u e s t 1 m o r e r e s u l t t h a n w e d i s p l a y s o w e c a n t e l l i f t h e r e ' d b e a n e x t p a g e
const hasMore = type !== 'all' ? results . get ( type , ImmutableList ( ) ) . size > INITIAL _PAGE _LIMIT && results . get ( type ) . size % INITIAL _PAGE _LIMIT === 1 : false ;
let filteredResults ;
if ( ! isLoading ) {
if ( ! isLoading ) {
const accounts = results . get ( 'accounts' , ImmutableList ( ) ) ;
const hashtags = results . get ( 'hashtags' , ImmutableList ( ) ) ;
const statuses = results . get ( 'statuses' , ImmutableList ( ) ) ;
switch ( type ) {
switch ( type ) {
case 'all' :
case 'all' :
filteredResults = filteredResults . concat ( renderAccounts ( results , this . handleLoadMoreAccounts ) , renderHashtags ( results , this . handleLoadMoreHashtags ) , renderStatuses ( results , this . handleLoadMoreStatuses ) ) ;
filteredResults = ( accounts . size + hashtags . size + statuses . size ) > 0 ? (
< >
{ accounts . size > 0 && (
< SearchSection key = 'accounts' title = { < > < Icon id = 'users' fixedWidth / > < FormattedMessage id = 'search_results.accounts' defaultMessage = 'Profiles' / > < / > } onClickMore = { this . handleLoadMoreAccounts } >
{ accounts . take ( INITIAL _DISPLAY ) . map ( id => < Account key = { id } id = { id } / > ) }
< / SearchSection >
) }
{ hashtags . size > 0 && (
< SearchSection key = 'hashtags' title = { < > < Icon id = 'hashtag' fixedWidth / > < FormattedMessage id = 'search_results.hashtags' defaultMessage = 'Hashtags' / > < / > } onClickMore = { this . handleLoadMoreHashtags } >
{ hashtags . take ( INITIAL _DISPLAY ) . map ( hashtag => < Hashtag key = { hashtag . get ( 'name' ) } hashtag = { hashtag } / > ) }
< / SearchSection >
) }
{ statuses . size > 0 && (
< SearchSection key = 'statuses' title = { < > < Icon id = 'quote-right' fixedWidth / > < FormattedMessage id = 'search_results.statuses' defaultMessage = 'Posts' / > < / > } onClickMore = { this . handleLoadMoreStatuses } >
{ statuses . take ( INITIAL _DISPLAY ) . map ( id => < Status key = { id } id = { id } / > ) }
< / SearchSection >
) }
< / >
) : [ ] ;
break ;
break ;
case 'accounts' :
case 'accounts' :
filteredResults = filteredResults . concat ( renderAccounts ( results , this . handleLoadMoreAccounts ) ) ;
filteredResults = renderAccounts( accounts) ;
break ;
break ;
case 'hashtags' :
case 'hashtags' :
filteredResults = filteredResults . concat ( renderHashtags ( results , this . handleLoadMoreHashtags ) ) ;
filteredResults = renderHashtags( hashtags) ;
break ;
break ;
case 'statuses' :
case 'statuses' :
filteredResults = filteredResults . concat ( renderStatuses ( results , this . handleLoadMoreStatuses ) ) ;
filteredResults = renderStatuses( statuses) ;
break ;
break ;
}
}
if ( filteredResults . size === 0 ) {
filteredResults = (
< div className = 'empty-column-indicator' >
< FormattedMessage id = 'search_results.nothing_found' defaultMessage = 'Could not find anything for these search terms' / >
< / div >
) ;
}
}
}
return (
return (
@ -115,7 +207,16 @@ class Results extends PureComponent {
< / div >
< / div >
< div className = 'explore__search-results' >
< div className = 'explore__search-results' >
{ isLoading ? < LoadingIndicator / > : filteredResults }
< ScrollableList
scrollKey = 'search-results'
isLoading = { isLoading }
onLoadMore = { this . handleLoadMore }
hasMore = { hasMore }
emptyMessage = { < FormattedMessage id = 'search_results.nothing_found' defaultMessage = 'Could not find anything for these search terms' / > }
bindToDocument
>
{ filteredResults }
< / ScrollableList >
< / div >
< / div >
< Helmet >
< Helmet >