parent
8152584cf5
commit
f0bdfadab7
@ -0,0 +1,51 @@ |
|||||||
|
import api from '../api' |
||||||
|
|
||||||
|
export const SEARCH_CHANGE = 'SEARCH_CHANGE'; |
||||||
|
export const SEARCH_SUGGESTIONS_CLEAR = 'SEARCH_SUGGESTIONS_CLEAR'; |
||||||
|
export const SEARCH_SUGGESTIONS_READY = 'SEARCH_SUGGESTIONS_READY'; |
||||||
|
export const SEARCH_RESET = 'SEARCH_RESET'; |
||||||
|
|
||||||
|
export function changeSearch(value) { |
||||||
|
return { |
||||||
|
type: SEARCH_CHANGE, |
||||||
|
value |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function clearSearchSuggestions() { |
||||||
|
return { |
||||||
|
type: SEARCH_SUGGESTIONS_CLEAR |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function readySearchSuggestions(value, accounts) { |
||||||
|
return { |
||||||
|
type: SEARCH_SUGGESTIONS_READY, |
||||||
|
value, |
||||||
|
accounts |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function fetchSearchSuggestions(value) { |
||||||
|
return (dispatch, getState) => { |
||||||
|
if (getState().getIn(['search', 'loaded_value']) === value) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
api(getState).get('/api/v1/accounts/search', { |
||||||
|
params: { |
||||||
|
q: value, |
||||||
|
resolve: true, |
||||||
|
limit: 4 |
||||||
|
} |
||||||
|
}).then(response => { |
||||||
|
dispatch(readySearchSuggestions(value, response.data)); |
||||||
|
}); |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function resetSearch() { |
||||||
|
return { |
||||||
|
type: SEARCH_RESET |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,126 @@ |
|||||||
|
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||||
|
import Autosuggest from 'react-autosuggest'; |
||||||
|
import AutosuggestAccountContainer from '../containers/autosuggest_account_container'; |
||||||
|
|
||||||
|
const getSuggestionValue = suggestion => suggestion.value; |
||||||
|
|
||||||
|
const renderSuggestion = suggestion => { |
||||||
|
if (suggestion.type === 'account') { |
||||||
|
return <AutosuggestAccountContainer id={suggestion.id} />; |
||||||
|
} else { |
||||||
|
return <span>#{suggestion.id}</span> |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const renderSectionTitle = section => ( |
||||||
|
<strong>{section.title}</strong> |
||||||
|
); |
||||||
|
|
||||||
|
const getSectionSuggestions = section => section.items; |
||||||
|
|
||||||
|
const outerStyle = { |
||||||
|
padding: '10px', |
||||||
|
lineHeight: '20px', |
||||||
|
position: 'relative' |
||||||
|
}; |
||||||
|
|
||||||
|
const inputStyle = { |
||||||
|
boxSizing: 'border-box', |
||||||
|
display: 'block', |
||||||
|
width: '100%', |
||||||
|
border: 'none', |
||||||
|
padding: '10px', |
||||||
|
paddingRight: '30px', |
||||||
|
fontFamily: 'Roboto', |
||||||
|
background: '#282c37', |
||||||
|
color: '#9baec8', |
||||||
|
fontSize: '14px', |
||||||
|
margin: '0' |
||||||
|
}; |
||||||
|
|
||||||
|
const iconStyle = { |
||||||
|
position: 'absolute', |
||||||
|
top: '18px', |
||||||
|
right: '20px', |
||||||
|
color: '#9baec8', |
||||||
|
fontSize: '18px', |
||||||
|
pointerEvents: 'none' |
||||||
|
}; |
||||||
|
|
||||||
|
const Search = React.createClass({ |
||||||
|
|
||||||
|
contextTypes: { |
||||||
|
router: React.PropTypes.object |
||||||
|
}, |
||||||
|
|
||||||
|
propTypes: { |
||||||
|
suggestions: React.PropTypes.array.isRequired, |
||||||
|
value: React.PropTypes.string.isRequired, |
||||||
|
onChange: React.PropTypes.func.isRequired, |
||||||
|
onClear: React.PropTypes.func.isRequired, |
||||||
|
onFetch: React.PropTypes.func.isRequired, |
||||||
|
onReset: React.PropTypes.func.isRequired |
||||||
|
}, |
||||||
|
|
||||||
|
mixins: [PureRenderMixin], |
||||||
|
|
||||||
|
onChange (_, { newValue }) { |
||||||
|
if (typeof newValue !== 'string') { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.props.onChange(newValue); |
||||||
|
}, |
||||||
|
|
||||||
|
onSuggestionsClearRequested () { |
||||||
|
this.props.onClear(); |
||||||
|
}, |
||||||
|
|
||||||
|
onSuggestionsFetchRequested ({ value }) { |
||||||
|
value = value.replace('#', ''); |
||||||
|
this.props.onFetch(value.trim()); |
||||||
|
}, |
||||||
|
|
||||||
|
onSuggestionSelected (_, { suggestion }) { |
||||||
|
if (suggestion.type === 'account') { |
||||||
|
this.context.router.push(`/accounts/${suggestion.id}`); |
||||||
|
} else { |
||||||
|
this.context.router.push(`/statuses/tag/${suggestion.id}`); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
render () { |
||||||
|
const inputProps = { |
||||||
|
placeholder: 'Search', |
||||||
|
value: this.props.value, |
||||||
|
onChange: this.onChange, |
||||||
|
style: inputStyle |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div style={outerStyle}> |
||||||
|
<Autosuggest |
||||||
|
multiSection={true} |
||||||
|
suggestions={this.props.suggestions} |
||||||
|
focusFirstSuggestion={true} |
||||||
|
focusInputOnSuggestionClick={false} |
||||||
|
alwaysRenderSuggestions={false} |
||||||
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} |
||||||
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested} |
||||||
|
onSuggestionSelected={this.onSuggestionSelected} |
||||||
|
getSuggestionValue={getSuggestionValue} |
||||||
|
renderSuggestion={renderSuggestion} |
||||||
|
renderSectionTitle={renderSectionTitle} |
||||||
|
getSectionSuggestions={getSectionSuggestions} |
||||||
|
inputProps={inputProps} |
||||||
|
/> |
||||||
|
|
||||||
|
<div style={iconStyle}><i className='fa fa-search' /></div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}, |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
export default Search; |
@ -0,0 +1,35 @@ |
|||||||
|
import { connect } from 'react-redux'; |
||||||
|
import { |
||||||
|
changeSearch, |
||||||
|
clearSearchSuggestions, |
||||||
|
fetchSearchSuggestions, |
||||||
|
resetSearch |
||||||
|
} from '../../../actions/search'; |
||||||
|
import Search from '../components/search'; |
||||||
|
|
||||||
|
const mapStateToProps = state => ({ |
||||||
|
suggestions: state.getIn(['search', 'suggestions']), |
||||||
|
value: state.getIn(['search', 'value']) |
||||||
|
}); |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({ |
||||||
|
|
||||||
|
onChange (value) { |
||||||
|
dispatch(changeSearch(value)); |
||||||
|
}, |
||||||
|
|
||||||
|
onClear () { |
||||||
|
dispatch(clearSearchSuggestions()); |
||||||
|
}, |
||||||
|
|
||||||
|
onFetch (value) { |
||||||
|
dispatch(fetchSearchSuggestions(value)); |
||||||
|
}, |
||||||
|
|
||||||
|
onReset () { |
||||||
|
dispatch(resetSearch()); |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Search); |
@ -0,0 +1,60 @@ |
|||||||
|
import { |
||||||
|
SEARCH_CHANGE, |
||||||
|
SEARCH_SUGGESTIONS_READY, |
||||||
|
SEARCH_RESET |
||||||
|
} from '../actions/search'; |
||||||
|
import Immutable from 'immutable'; |
||||||
|
|
||||||
|
const initialState = Immutable.Map({ |
||||||
|
value: '', |
||||||
|
loaded_value: '', |
||||||
|
suggestions: [] |
||||||
|
}); |
||||||
|
|
||||||
|
const normalizeSuggestions = (state, value, accounts) => { |
||||||
|
let newSuggestions = [ |
||||||
|
{ |
||||||
|
title: 'Account', |
||||||
|
items: accounts.map(item => ({ |
||||||
|
type: 'account', |
||||||
|
id: item.id, |
||||||
|
value: item.acct |
||||||
|
})) |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
if (value.indexOf('@') === -1) { |
||||||
|
newSuggestions.push({ |
||||||
|
title: 'Hashtag', |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
type: 'hashtag', |
||||||
|
id: value, |
||||||
|
value: `#${value}` |
||||||
|
} |
||||||
|
] |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return state.withMutations(map => { |
||||||
|
map.set('suggestions', newSuggestions); |
||||||
|
map.set('loaded_value', value); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
export default function search(state = initialState, action) { |
||||||
|
switch(action.type) { |
||||||
|
case SEARCH_CHANGE: |
||||||
|
return state.set('value', action.value); |
||||||
|
case SEARCH_SUGGESTIONS_READY: |
||||||
|
return normalizeSuggestions(state, action.value, action.accounts); |
||||||
|
case SEARCH_RESET: |
||||||
|
return state.withMutations(map => { |
||||||
|
map.set('suggestions', []); |
||||||
|
map.set('value', ''); |
||||||
|
map.set('loaded_value', ''); |
||||||
|
}); |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue