@ -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 PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | 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'; | import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
const messages = defineMessages({ | const messages = defineMessages({ | ||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' } | 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({ | const Search = React.createClass({ | ||||
contextTypes: { | |||||
router: React.PropTypes.object | |||||
}, | |||||
propTypes: { | propTypes: { | ||||
suggestions: React.PropTypes.array.isRequired, | |||||
value: React.PropTypes.string.isRequired, | value: React.PropTypes.string.isRequired, | ||||
onChange: React.PropTypes.func.isRequired, | onChange: React.PropTypes.func.isRequired, | ||||
onSubmit: React.PropTypes.func.isRequired, | |||||
onClear: 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 | intl: React.PropTypes.object.isRequired | ||||
}, | }, | ||||
mixins: [PureRenderMixin], | 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(); | 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 () { | 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 ( | 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> | </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); |