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:
parent
169d83f532
commit
67f8277526
|
@ -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 }) => (
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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());
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Reference in New Issue