Add secondary toot button (opt-in) (#153)

Add secondary toot button + other toot button enhancements. 
Squashing so it's easy to revert if needed.
This commit is contained in:
Ondřej Hruška 2017-09-23 23:11:02 +02:00 committed by GitHub
parent 169d83f532
commit 67f8277526
5 changed files with 104 additions and 7 deletions

View File

@ -16,6 +16,7 @@ const messages = defineMessages({
layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' }, layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' },
layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' }, layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' },
layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' }, layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' },
side_arm_none: { id: 'settings.side_arm.none', defaultMessage: 'None' },
}); });
@injectIntl @injectIntl
@ -61,6 +62,24 @@ export default class LocalSettingsPage extends React.PureComponent {
> >
<FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' /> <FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' />
</LocalSettingsPageItem> </LocalSettingsPageItem>
<section>
<h2><FormattedMessage id='settings.compose_box_opts' defaultMessage='Compose box options' /></h2>
<LocalSettingsPageItem
settings={settings}
item={['side_arm']}
id='mastodon-settings--side_arm'
options={[
{ value: 'none', message: intl.formatMessage(messages.side_arm_none) },
{ value: 'direct', message: intl.formatMessage({ id: 'privacy.direct.short' }) },
{ value: 'private', message: intl.formatMessage({ id: 'privacy.private.short' }) },
{ value: 'unlisted', message: intl.formatMessage({ id: 'privacy.unlisted.short' }) },
{ value: 'public', message: intl.formatMessage({ id: 'privacy.public.short' }) },
]}
onChange={onChange}
>
<FormattedMessage id='settings.side_arm' defaultMessage='Secondary toot button:' />
</LocalSettingsPageItem>
</section>
</div> </div>
), ),
({ onChange, settings }) => ( ({ onChange, settings }) => (

View File

@ -52,6 +52,7 @@ const initialState = ImmutableMap({
layout : 'auto', layout : 'auto',
stretch : true, stretch : true,
navbar_under : false, navbar_under : false,
side_arm : 'none',
collapsed : ImmutableMap({ collapsed : ImmutableMap({
enabled : true, enabled : true,
auto : ImmutableMap({ auto : ImmutableMap({

View File

@ -51,11 +51,13 @@ export default class ComposeForm extends ImmutablePureComponent {
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
onClearSuggestions: PropTypes.func.isRequired, onClearSuggestions: PropTypes.func.isRequired,
onFetchSuggestions: PropTypes.func.isRequired, onFetchSuggestions: PropTypes.func.isRequired,
onPrivacyChange: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func.isRequired, onSuggestionSelected: PropTypes.func.isRequired,
onChangeSpoilerText: PropTypes.func.isRequired, onChangeSpoilerText: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired, onPaste: PropTypes.func.isRequired,
onPickEmoji: PropTypes.func.isRequired, onPickEmoji: PropTypes.func.isRequired,
showSearch: PropTypes.bool, showSearch: PropTypes.bool,
settings : ImmutablePropTypes.map.isRequired,
}; };
static defaultProps = { static defaultProps = {
@ -72,6 +74,11 @@ export default class ComposeForm extends ImmutablePureComponent {
} }
} }
handleSubmit2 = () => {
this.props.onPrivacyChange(this.props.settings.get('side_arm'));
this.handleSubmit();
}
handleSubmit = () => { handleSubmit = () => {
if (this.props.text !== this.autosuggestTextarea.textarea.value) { if (this.props.text !== this.autosuggestTextarea.textarea.value) {
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly) // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
@ -157,13 +164,42 @@ export default class ComposeForm extends ImmutablePureComponent {
const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : ''; const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join(''); const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
const sideArmVisibility = this.props.settings.get('side_arm');
let showSideArm = sideArmVisibility !== 'none';
let publishText = ''; let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') { const privacyIcons = {
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; none: '',
} else { public: 'globe',
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); unlisted: 'unlock-alt',
} private: 'lock',
direct: 'envelope',
};
publishText = (
<span>
{
(this.props.settings.get('stretch') || !showSideArm) ?
<i
className={`fa fa-${privacyIcons[this.props.privacy]}`}
style={{ paddingRight: '5px' }}
/> :
''
}
{intl.formatMessage(messages.publish)}
</span>
);
// side-arm
let publishText2 = (
<i
className={`fa fa-${privacyIcons[sideArmVisibility]}`}
aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArmVisibility}.short` })}`}
/>
);
const submitDisabled = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0);
return ( return (
<div className='compose-form'> <div className='compose-form'>
@ -215,7 +251,25 @@ export default class ComposeForm extends ImmutablePureComponent {
<div className='compose-form__publish'> <div className='compose-form__publish'>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> <div className='compose-form__publish-button-wrapper'>
{
showSideArm ?
<Button
className='compose-form__publish__side-arm'
text={publishText2}
onClick={this.handleSubmit2}
disabled={submitDisabled}
/> :
''
}
<Button
className='compose-form__publish__primary'
text={publishText}
onClick={this.handleSubmit}
disabled={submitDisabled}
block
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form'; import ComposeForm from '../components/compose_form';
import { uploadCompose } from '../../../actions/compose'; import { changeComposeVisibility, uploadCompose } from '../../../actions/compose';
import { import {
changeCompose, changeCompose,
submitCompose, submitCompose,
@ -25,6 +25,7 @@ const mapStateToProps = state => ({
is_uploading: state.getIn(['compose', 'is_uploading']), is_uploading: state.getIn(['compose', 'is_uploading']),
me: state.getIn(['compose', 'me']), me: state.getIn(['compose', 'me']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
settings: state.get('local_settings'),
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@ -33,6 +34,10 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(changeCompose(text)); dispatch(changeCompose(text));
}, },
onPrivacyChange (value) {
dispatch(changeComposeVisibility(value));
},
onSubmit () { onSubmit () {
dispatch(submitCompose()); dispatch(submitCompose());
}, },

View File

@ -421,6 +421,24 @@
.compose-form__publish-button-wrapper { .compose-form__publish-button-wrapper {
overflow: hidden; overflow: hidden;
padding-top: 10px; padding-top: 10px;
white-space: nowrap;
display: flex;
button {
text-overflow: unset;
}
}
.compose-form__publish__side-arm {
padding: 0 !important;
width: 4em;
text-align: center;
opacity: .8;
margin-right: 2px;
}
.compose-form__publish__primary {
padding: 0 10px !important;
} }
.emojione { .emojione {