Skip to content
Snippets Groups Projects
Commit f0bdfada authored by Eugen Rochko's avatar Eugen Rochko
Browse files

Search component

parent 8152584c
No related branches found
No related tags found
No related merge requests found
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
};
};
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;
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);
......@@ -5,6 +5,7 @@ import UploadFormContainer from '../ui/containers/upload_form_container';
import NavigationContainer from '../ui/containers/navigation_container';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import SuggestionsContainer from './containers/suggestions_container';
import SearchContainer from './containers/search_container';
import { fetchSuggestions } from '../../actions/suggestions';
import { connect } from 'react-redux';
......@@ -24,13 +25,13 @@ const Compose = React.createClass({
return (
<Drawer>
<div style={{ flex: '1 1 auto' }}>
<SearchContainer />
<NavigationContainer />
<ComposeFormContainer />
<UploadFormContainer />
</div>
<SuggestionsContainer />
<FollowFormContainer />
</Drawer>
);
}
......
......@@ -26,6 +26,7 @@ import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS
} from '../actions/statuses';
import { SEARCH_SUGGESTIONS_READY } from '../actions/search';
import Immutable from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account));
......@@ -70,6 +71,7 @@ export default function accounts(state = initialState, action) {
case REBLOGS_FETCH_SUCCESS:
case FAVOURITES_FETCH_SUCCESS:
case COMPOSE_SUGGESTIONS_READY:
case SEARCH_SUGGESTIONS_READY:
return normalizeAccounts(state, action.accounts);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
......
......@@ -10,6 +10,7 @@ import user_lists from './user_lists';
import accounts from './accounts';
import statuses from './statuses';
import relationships from './relationships';
import search from './search';
export default combineReducers({
timelines,
......@@ -22,5 +23,6 @@ export default combineReducers({
user_lists,
accounts,
statuses,
relationships
relationships,
search
});
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;
}
};
......@@ -325,12 +325,22 @@
top: 100%;
width: 100%;
z-index: 99;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);
}
.react-autosuggest__suggestions-list {
.react-autosuggest__section-title {
background: #9baec8;
padding: 4px 10px;
font-weight: 500;
cursor: default;
color: #282c37;
text-transform: uppercase;
font-size: 11px;
}
.react-autosuggest__suggestions-list {
background: #d9e1e8;
color: #282c37;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
font-size: 14px;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment