@ -1,44 +0,0 @@ | |||
import { Link } from 'react-router'; | |||
import { injectIntl, defineMessages } from 'react-intl'; | |||
const messages = defineMessages({ | |||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, | |||
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' }, | |||
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, | |||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | |||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' } | |||
}); | |||
const Drawer = ({ children, withHeader, intl }) => { | |||
let header = ''; | |||
if (withHeader) { | |||
header = ( | |||
<div className='drawer__header'> | |||
<Link title={intl.formatMessage(messages.start)} className='drawer__tab' to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link> | |||
<Link title={intl.formatMessage(messages.community)} className='drawer__tab' to='/timelines/public/local'><i className='fa fa-fw fa-users' /></Link> | |||
<Link title={intl.formatMessage(messages.public)} className='drawer__tab' to='/timelines/public'><i className='fa fa-fw fa-globe' /></Link> | |||
<a title={intl.formatMessage(messages.preferences)} className='drawer__tab' href='/settings/preferences'><i className='fa fa-fw fa-cog' /></a> | |||
<a title={intl.formatMessage(messages.logout)} className='drawer__tab' href='/auth/sign_out' data-method='delete'><i className='fa fa-fw fa-sign-out' /></a> | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className='drawer'> | |||
{header} | |||
<div className='drawer__inner'> | |||
{children} | |||
</div> | |||
</div> | |||
); | |||
}; | |||
Drawer.propTypes = { | |||
withHeader: React.PropTypes.bool, | |||
children: React.PropTypes.node, | |||
intl: React.PropTypes.object | |||
}; | |||
export default injectIntl(Drawer); |
@ -1,123 +1,67 @@ | |||
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'; | |||
import AutosuggestStatusContainer from '../containers/autosuggest_status_container'; | |||
import { debounce } from 'react-decoration'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
const messages = defineMessages({ | |||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' } | |||
}); | |||
const getSuggestionValue = suggestion => suggestion.value; | |||
const renderSuggestion = suggestion => { | |||
if (suggestion.type === 'account') { | |||
return <AutosuggestAccountContainer id={suggestion.id} />; | |||
} else if (suggestion.type === 'hashtag') { | |||
return <span>#{suggestion.id}</span>; | |||
} else { | |||
return <AutosuggestStatusContainer id={suggestion.id} />; | |||
} | |||
}; | |||
const renderSectionTitle = section => ( | |||
<strong><FormattedMessage id={`search.${section.title}`} defaultMessage={section.title} /></strong> | |||
); | |||
const getSectionSuggestions = section => section.items; | |||
const outerStyle = { | |||
padding: '10px', | |||
lineHeight: '20px', | |||
position: 'relative' | |||
}; | |||
const iconStyle = { | |||
position: 'absolute', | |||
top: '18px', | |||
right: '20px', | |||
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, | |||
onSubmit: React.PropTypes.func.isRequired, | |||
onClear: React.PropTypes.func.isRequired, | |||
onFetch: React.PropTypes.func.isRequired, | |||
onReset: React.PropTypes.func.isRequired, | |||
onShow: React.PropTypes.func.isRequired, | |||
intl: React.PropTypes.object.isRequired | |||
}, | |||
mixins: [PureRenderMixin], | |||
onChange (_, { newValue }) { | |||
if (typeof newValue !== 'string') { | |||
return; | |||
} | |||
this.props.onChange(newValue); | |||
handleChange (e) { | |||
this.props.onChange(e.target.value); | |||
}, | |||
onSuggestionsClearRequested () { | |||
handleClear (e) { | |||
e.preventDefault(); | |||
this.props.onClear(); | |||
}, | |||
@debounce(500) | |||
onSuggestionsFetchRequested ({ value }) { | |||
value = value.replace('#', ''); | |||
this.props.onFetch(value.trim()); | |||
handleKeyDown (e) { | |||
if (e.key === 'Enter') { | |||
e.preventDefault(); | |||
this.props.onSubmit(); | |||
} | |||
}, | |||
onSuggestionSelected (_, { suggestion }) { | |||
if (suggestion.type === 'account') { | |||
this.context.router.push(`/accounts/${suggestion.id}`); | |||
} else if(suggestion.type === 'hashtag') { | |||
this.context.router.push(`/timelines/tag/${suggestion.id}`); | |||
} else { | |||
this.context.router.push(`/statuses/${suggestion.id}`); | |||
} | |||
handleFocus () { | |||
this.props.onShow(); | |||
}, | |||
render () { | |||
const inputProps = { | |||
placeholder: this.props.intl.formatMessage(messages.placeholder), | |||
value: this.props.value, | |||
onChange: this.onChange, | |||
className: 'search__input' | |||
}; | |||
const { intl, value } = this.props; | |||
const hasValue = value.length > 0; | |||
return ( | |||
<div className='search' 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 className='search'> | |||
<input | |||
className='search__input' | |||
type='text' | |||
placeholder={intl.formatMessage(messages.placeholder)} | |||
value={value} | |||
onChange={this.handleChange} | |||
onKeyUp={this.handleKeyDown} | |||
onFocus={this.handleFocus} | |||
/> | |||
<div style={iconStyle}><i className='fa fa-search' /></div> | |||
<div className='search__icon'> | |||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} /> | |||
<i className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} onClick={this.handleClear} /> | |||
</div> | |||
</div> | |||
); | |||
}, | |||
} | |||
}); | |||
@ -0,0 +1,68 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import AccountContainer from '../../../containers/account_container'; | |||
import StatusContainer from '../../../containers/status_container'; | |||
import { Link } from 'react-router'; | |||
const SearchResults = React.createClass({ | |||
propTypes: { | |||
results: ImmutablePropTypes.map.isRequired | |||
}, | |||
mixins: [PureRenderMixin], | |||
render () { | |||
const { results } = this.props; | |||
let accounts, statuses, hashtags; | |||
let count = 0; | |||
if (results.get('accounts') && results.get('accounts').size > 0) { | |||
count += results.get('accounts').size; | |||
accounts = ( | |||
<div className='search-results__section'> | |||
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} | |||
</div> | |||
); | |||
} | |||
if (results.get('statuses') && results.get('statuses').size > 0) { | |||
count += results.get('statuses').size; | |||
statuses = ( | |||
<div className='search-results__section'> | |||
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} | |||
</div> | |||
); | |||
} | |||
if (results.get('hashtags') && results.get('hashtags').size > 0) { | |||
count += results.get('hashtags').size; | |||
hashtags = ( | |||
<div className='search-results__section'> | |||
{results.get('hashtags').map(hashtag => | |||
<Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}> | |||
#{hashtag} | |||
</Link> | |||
)} | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className='search-results'> | |||
<div className='search-results__header'> | |||
<FormattedMessage id='search_results.total' defaultMessage='{count} {count, plural, one {result} other {results}}' values={{ count }} /> | |||
</div> | |||
{accounts} | |||
{statuses} | |||
{hashtags} | |||
</div> | |||
); | |||
} | |||
}); | |||
export default SearchResults; |
@ -1,31 +0,0 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import Toggle from 'react-toggle'; | |||
import Collapsable from '../../../components/collapsable'; | |||
const SensitiveToggle = React.createClass({ | |||
propTypes: { | |||
hasMedia: React.PropTypes.bool, | |||
isSensitive: React.PropTypes.bool, | |||
onChange: React.PropTypes.func.isRequired | |||
}, | |||
mixins: [PureRenderMixin], | |||
render () { | |||
const { hasMedia, isSensitive, onChange } = this.props; | |||
return ( | |||
<Collapsable isVisible={hasMedia} fullHeight={39.5}> | |||
<label className='compose-form__label'> | |||
<Toggle checked={isSensitive} onChange={onChange} /> | |||
<span className='compose-form__label__text'><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span> | |||
</label> | |||
</Collapsable> | |||
); | |||
} | |||
}); | |||
export default SensitiveToggle; |
@ -1,27 +0,0 @@ | |||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import Toggle from 'react-toggle'; | |||
const SpoilerToggle = React.createClass({ | |||
propTypes: { | |||
isSpoiler: React.PropTypes.bool, | |||
onChange: React.PropTypes.func.isRequired | |||
}, | |||
mixins: [PureRenderMixin], | |||
render () { | |||
const { isSpoiler, onChange } = this.props; | |||
return ( | |||
<label className='compose-form__label with-border' style={{ marginTop: '10px' }}> | |||
<Toggle checked={isSpoiler} onChange={onChange} /> | |||
<span className='compose-form__label__text'><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span> | |||
</label> | |||
); | |||
} | |||
}); | |||
export default SpoilerToggle; |
@ -0,0 +1,8 @@ | |||
import { connect } from 'react-redux'; | |||
import SearchResults from '../components/search_results'; | |||
const mapStateToProps = state => ({ | |||
results: state.getIn(['search', 'results']) | |||
}); | |||
export default connect(mapStateToProps)(SearchResults); |