@ -0,0 +1,83 @@ | |||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||||
import { FormattedMessage } from 'react-intl'; | |||||
import IconButton from '../../../components/icon_button'; | |||||
const PrivacyDropdown = React.createClass({ | |||||
propTypes: { | |||||
value: React.PropTypes.string.isRequired, | |||||
onChange: React.PropTypes.func.isRequired | |||||
}, | |||||
getInitialState () { | |||||
return { | |||||
open: false | |||||
}; | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
handleToggle () { | |||||
this.setState({ open: !this.state.open }); | |||||
}, | |||||
handleClick (value, e) { | |||||
e.preventDefault(); | |||||
this.setState({ open: false }); | |||||
this.props.onChange(value); | |||||
}, | |||||
onGlobalClick (e) { | |||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { | |||||
this.setState({ open: false }); | |||||
} | |||||
}, | |||||
componentDidMount () { | |||||
window.addEventListener('click', this.onGlobalClick); | |||||
window.addEventListener('touchstart', this.onGlobalClick); | |||||
}, | |||||
componentWillUnmount () { | |||||
window.removeEventListener('click', this.onGlobalClick); | |||||
window.removeEventListener('touchstart', this.onGlobalClick); | |||||
}, | |||||
setRef (c) { | |||||
this.node = c; | |||||
}, | |||||
render () { | |||||
const { value, onChange } = this.props; | |||||
const { open } = this.state; | |||||
const options = [ | |||||
{ icon: 'globe', value: 'public', shortText: 'Public', longText: 'Anyone can see' }, | |||||
{ icon: 'globe', value: 'unlisted', shortText: 'Unlisted', longText: 'Anyone can see' }, | |||||
{ icon: 'lock', value: 'private', shortText: 'Private', longText: 'Followers can see' }, | |||||
{ icon: 'send', value: 'direct', shortText: 'Direct', longText: 'Mentions can see' } | |||||
]; | |||||
const valueOption = options.find(item => item.value === value); | |||||
return ( | |||||
<div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}> | |||||
<div className='privacy-dropdown__value'><IconButton icon={valueOption.icon} size={22} active={open} inverted onClick={this.handleToggle} /></div> | |||||
<div className='privacy-dropdown__dropdown'> | |||||
{options.map(item => | |||||
<div key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}> | |||||
<div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div> | |||||
<div className='privacy-dropdown__option__content'> | |||||
<strong>{item.shortText}</strong> | |||||
{item.longText} | |||||
</div> | |||||
</div> | |||||
)} | |||||
</div> | |||||
</div> | |||||
); | |||||
} | |||||
}); | |||||
export default PrivacyDropdown; |
@ -1,27 +0,0 @@ | |||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||||
import { FormattedMessage } from 'react-intl'; | |||||
import Toggle from 'react-toggle'; | |||||
const PrivateToggle = React.createClass({ | |||||
propTypes: { | |||||
isPrivate: React.PropTypes.bool, | |||||
onChange: React.PropTypes.func.isRequired | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
render () { | |||||
const { isPrivate, onChange } = this.props; | |||||
return ( | |||||
<label className='compose-form__label with-border'> | |||||
<Toggle checked={isPrivate} onChange={onChange} /> | |||||
<span className='compose-form__label__text'><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span> | |||||
</label> | |||||
); | |||||
} | |||||
}); | |||||
export default PrivateToggle; |
@ -0,0 +1,31 @@ | |||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||||
const TextIconButton = React.createClass({ | |||||
propTypes: { | |||||
label: React.PropTypes.string.isRequired, | |||||
title: React.PropTypes.string, | |||||
active: React.PropTypes.bool, | |||||
onClick: React.PropTypes.func.isRequired | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
handleClick (e) { | |||||
e.preventDefault(); | |||||
this.props.onClick(); | |||||
}, | |||||
render () { | |||||
const { label, title, active } = this.props; | |||||
return ( | |||||
<button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} onClick={this.handleClick}> | |||||
{label} | |||||
</button> | |||||
); | |||||
} | |||||
}); | |||||
export default TextIconButton; |
@ -1,32 +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 UnlistedToggle = React.createClass({ | |||||
propTypes: { | |||||
isPrivate: React.PropTypes.bool, | |||||
isUnlisted: React.PropTypes.bool, | |||||
isReplyToOther: React.PropTypes.bool, | |||||
onChangeListability: React.PropTypes.func.isRequired | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
render () { | |||||
const { isPrivate, isUnlisted, isReplyToOther, onChangeListability } = this.props; | |||||
return ( | |||||
<Collapsable isVisible={!(isPrivate || isReplyToOther)} fullHeight={39.5}> | |||||
<label className='compose-form__label'> | |||||
<Toggle checked={isUnlisted} onChange={onChangeListability} /> | |||||
<span className='compose-form__label__text'><FormattedMessage id='compose_form.unlisted' defaultMessage='Do not display on public timelines' /></span> | |||||
</label> | |||||
</Collapsable> | |||||
); | |||||
} | |||||
}); | |||||
export default UnlistedToggle; |
@ -1,17 +1,17 @@ | |||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import PrivateToggle from '../components/private_toggle'; | |||||
import PrivacyDropdown from '../components/privacy_dropdown'; | |||||
import { changeComposeVisibility } from '../../../actions/compose'; | import { changeComposeVisibility } from '../../../actions/compose'; | ||||
const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||
isPrivate: state.getIn(['compose', 'private']) | |||||
value: state.getIn(['compose', 'privacy']) | |||||
}); | }); | ||||
const mapDispatchToProps = dispatch => ({ | const mapDispatchToProps = dispatch => ({ | ||||
onChange (e) { | |||||
dispatch(changeComposeVisibility(e.target.checked)); | |||||
onChange (value) { | |||||
dispatch(changeComposeVisibility(value)); | |||||
} | } | ||||
}); | }); | ||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivateToggle); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); |
@ -0,0 +1,49 @@ | |||||
import { connect } from 'react-redux'; | |||||
import TextIconButton from '../components/text_icon_button'; | |||||
import { changeComposeSensitivity } from '../../../actions/compose'; | |||||
import { Motion, spring } from 'react-motion'; | |||||
import { injectIntl, defineMessages } from 'react-intl'; | |||||
const messages = defineMessages({ | |||||
title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' } | |||||
}); | |||||
const mapStateToProps = state => ({ | |||||
visible: state.getIn(['compose', 'media_attachments']).size > 0, | |||||
active: state.getIn(['compose', 'sensitive']) | |||||
}); | |||||
const mapDispatchToProps = dispatch => ({ | |||||
onClick () { | |||||
dispatch(changeComposeSensitivity()); | |||||
} | |||||
}); | |||||
const SensitiveButton = React.createClass({ | |||||
propTypes: { | |||||
visible: React.PropTypes.bool, | |||||
active: React.PropTypes.bool, | |||||
onClick: React.PropTypes.func.isRequired, | |||||
intl: React.PropTypes.object.isRequired | |||||
}, | |||||
render () { | |||||
const { visible, active, onClick, intl } = this.props; | |||||
return ( | |||||
<Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}> | |||||
{({ scale }) => | |||||
<div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}> | |||||
<TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} /> | |||||
</div> | |||||
} | |||||
</Motion> | |||||
); | |||||
} | |||||
}); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton)); |
@ -1,18 +0,0 @@ | |||||
import { connect } from 'react-redux'; | |||||
import SensitiveToggle from '../components/sensitive_toggle'; | |||||
import { changeComposeSensitivity } from '../../../actions/compose'; | |||||
const mapStateToProps = state => ({ | |||||
hasMedia: state.getIn(['compose', 'media_attachments']).size > 0, | |||||
isSensitive: state.getIn(['compose', 'sensitive']) | |||||
}); | |||||
const mapDispatchToProps = dispatch => ({ | |||||
onChange (e) { | |||||
dispatch(changeComposeSensitivity(e.target.checked)); | |||||
} | |||||
}); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(SensitiveToggle); |
@ -0,0 +1,24 @@ | |||||
import { connect } from 'react-redux'; | |||||
import TextIconButton from '../components/text_icon_button'; | |||||
import { changeComposeSpoilerness } from '../../../actions/compose'; | |||||
import { injectIntl, defineMessages } from 'react-intl'; | |||||
const messages = defineMessages({ | |||||
title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind content warning' } | |||||
}); | |||||
const mapStateToProps = (state, { intl }) => ({ | |||||
label: 'CW', | |||||
title: intl.formatMessage(messages.title), | |||||
active: state.getIn(['compose', 'spoiler']) | |||||
}); | |||||
const mapDispatchToProps = dispatch => ({ | |||||
onClick () { | |||||
dispatch(changeComposeSpoilerness()); | |||||
} | |||||
}); | |||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TextIconButton)); |
@ -1,17 +0,0 @@ | |||||
import { connect } from 'react-redux'; | |||||
import SpoilerToggle from '../components/spoiler_toggle'; | |||||
import { changeComposeSpoilerness } from '../../../actions/compose'; | |||||
const mapStateToProps = state => ({ | |||||
isSpoiler: state.getIn(['compose', 'spoiler']) | |||||
}); | |||||
const mapDispatchToProps = dispatch => ({ | |||||
onChange (e) { | |||||
dispatch(changeComposeSpoilerness(e.target.checked)); | |||||
} | |||||
}); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(SpoilerToggle); |
@ -1,31 +0,0 @@ | |||||
import { connect } from 'react-redux'; | |||||
import UnlistedToggle from '../components/unlisted_toggle'; | |||||
import { makeGetStatus } from '../../../selectors'; | |||||
import { changeComposeListability } from '../../../actions/compose'; | |||||
const makeMapStateToProps = () => { | |||||
const getStatus = makeGetStatus(); | |||||
const mapStateToProps = state => { | |||||
const status = getStatus(state, state.getIn(['compose', 'in_reply_to'])); | |||||
const me = state.getIn(['compose', 'me']); | |||||
return { | |||||
isPrivate: state.getIn(['compose', 'private']), | |||||
isUnlisted: state.getIn(['compose', 'unlisted']), | |||||
isReplyToOther: status ? status.getIn(['account', 'id']) !== me : false | |||||
}; | |||||
}; | |||||
return mapStateToProps; | |||||
}; | |||||
const mapDispatchToProps = dispatch => ({ | |||||
onChangeListability (e) { | |||||
dispatch(changeComposeListability(e.target.checked)); | |||||
} | |||||
}); | |||||
export default connect(makeMapStateToProps, mapDispatchToProps)(UnlistedToggle); |