Skip to content

Commit

Permalink
Revert "Deny local users to posting statuses which includes polls"
Browse files Browse the repository at this point in the history
This reverts commit 7a3715b.
  • Loading branch information
acid-chicken committed Jan 2, 2020
1 parent 94abdfa commit ae2fe65
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/controllers/api/v1/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def create
visibility: status_params[:visibility],
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application,
poll: nil, # Just ignore poll params.
poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'])

render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
Expand Down
55 changes: 55 additions & 0 deletions app/javascript/mastodon/actions/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,16 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';

export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
export const COMPOSE_POLL_OPTION_ADD = 'COMPOSE_POLL_OPTION_ADD';
export const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';

const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
});

export function changeCompose(text) {
Expand Down Expand Up @@ -132,6 +139,7 @@ export function submitCompose(routerHistory) {
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null),
}, {
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
Expand Down Expand Up @@ -203,6 +211,11 @@ export function uploadCompose(files) {
return;
}

if (getState().getIn(['compose', 'poll'])) {
dispatch(showAlert(undefined, messages.uploadErrorPoll));
return;
}

dispatch(uploadComposeRequest());

for (const [i, f] of Array.from(files).entries()) {
Expand Down Expand Up @@ -491,3 +504,45 @@ export function changeComposing(value) {
value,
};
};

export function addPoll() {
return {
type: COMPOSE_POLL_ADD,
};
};

export function removePoll() {
return {
type: COMPOSE_POLL_REMOVE,
};
};

export function addPollOption(title) {
return {
type: COMPOSE_POLL_OPTION_ADD,
title,
};
};

export function changePollOption(index, title) {
return {
type: COMPOSE_POLL_OPTION_CHANGE,
index,
title,
};
};

export function removePollOption(index) {
return {
type: COMPOSE_POLL_OPTION_REMOVE,
index,
};
};

export function changePollSettings(expiresIn, isMultiple) {
return {
type: COMPOSE_POLL_SETTINGS_CHANGE,
expiresIn,
isMultiple,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import PollButtonContainer from '../containers/poll_button_container';
import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../containers/poll_form_container';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
import { isMobile } from '../../../is_mobile';
Expand Down Expand Up @@ -204,11 +206,13 @@ class ComposeForm extends ImmutablePureComponent {

<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />
</div>

<div className='compose-form__buttons-wrapper'>
<div className='compose-form__buttons'>
<UploadButtonContainer />
<PollButtonContainer />
<PrivacyDropdownContainer />
<SpoilerButtonContainer />
</div>
Expand Down
55 changes: 55 additions & 0 deletions app/javascript/mastodon/features/compose/components/poll_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import IconButton from '../../../components/icon_button';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';

const messages = defineMessages({
add_poll: { id: 'poll_button.add_poll', defaultMessage: 'Add a poll' },
remove_poll: { id: 'poll_button.remove_poll', defaultMessage: 'Remove poll' },
});

const iconStyle = {
height: null,
lineHeight: '27px',
};

export default
@injectIntl
class PollButton extends React.PureComponent {

static propTypes = {
disabled: PropTypes.bool,
unavailable: PropTypes.bool,
active: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleClick = () => {
this.props.onClick();
}

render () {
const { intl, active, unavailable, disabled } = this.props;

if (unavailable) {
return null;
}

return (
<div className='compose-form__poll-button'>
<IconButton
icon='tasks'
title={intl.formatMessage(active ? messages.remove_poll : messages.add_poll)}
disabled={disabled}
onClick={this.handleClick}
className={`compose-form__poll-button-icon ${active ? 'active' : ''}`}
size={18}
inverted
style={iconStyle}
/>
</div>
);
}

}
121 changes: 121 additions & 0 deletions app/javascript/mastodon/features/compose/components/poll_form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'mastodon/components/icon_button';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';

const messages = defineMessages({
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
});

@injectIntl
class Option extends React.PureComponent {

static propTypes = {
title: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
isPollMultiple: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleOptionTitleChange = e => {
this.props.onChange(this.props.index, e.target.value);
};

handleOptionRemove = () => {
this.props.onRemove(this.props.index);
};

render () {
const { isPollMultiple, title, index, intl } = this.props;

return (
<li>
<label className='poll__text editable'>
<span className={classNames('poll__input', { checkbox: isPollMultiple })} />

<input
type='text'
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
maxLength={25}
value={title}
onChange={this.handleOptionTitleChange}
/>
</label>

<div className='poll__cancel'>
<IconButton disabled={index <= 1} title={intl.formatMessage(messages.remove_option)} icon='times' onClick={this.handleOptionRemove} />
</div>
</li>
);
}

}

export default
@injectIntl
class PollForm extends ImmutablePureComponent {

static propTypes = {
options: ImmutablePropTypes.list,
expiresIn: PropTypes.number,
isMultiple: PropTypes.bool,
onChangeOption: PropTypes.func.isRequired,
onAddOption: PropTypes.func.isRequired,
onRemoveOption: PropTypes.func.isRequired,
onChangeSettings: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};

handleAddOption = () => {
this.props.onAddOption('');
};

handleSelectDuration = e => {
this.props.onChangeSettings(e.target.value, this.props.isMultiple);
};

render () {
const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;

if (!options) {
return null;
}

return (
<div className='compose-form__poll-wrapper'>
<ul>
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} />)}
</ul>

<div className='poll__footer'>
{options.size < 4 && (
<button className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
)}

<select value={expiresIn} onChange={this.handleSelectDuration}>
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
<option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
<option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
<option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
<option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
<option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
</select>
</div>
</div>
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import PollButton from '../components/poll_button';
import { addPoll, removePoll } from '../../../actions/compose';

const mapStateToProps = state => ({
unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
active: state.getIn(['compose', 'poll']) !== null,
});

const mapDispatchToProps = dispatch => ({

onClick () {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
dispatch(removePoll());
} else {
dispatch(addPoll());
}
});
},

});

export default connect(mapStateToProps, mapDispatchToProps)(PollButton);
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { connect } from 'react-redux';
import PollForm from '../components/poll_form';
import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose';

const mapStateToProps = state => ({
options: state.getIn(['compose', 'poll', 'options']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
});

const mapDispatchToProps = dispatch => ({
onAddOption(title) {
dispatch(addPollOption(title));
},

onRemoveOption(index) {
dispatch(removePollOption(index));
},

onChangeOption(index, title) {
dispatch(changePollOption(index, title));
},

onChangeSettings(expiresIn, isMultiple) {
dispatch(changePollSettings(expiresIn, isMultiple));
},
});

export default connect(mapStateToProps, mapDispatchToProps)(PollForm);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { uploadCompose } from '../../../actions/compose';

const mapStateToProps = state => ({
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
unavailable: state.getIn(['compose', 'poll']) !== null,
resetFileKey: state.getIn(['compose', 'resetFileKey']),
});

Expand Down
Loading

0 comments on commit ae2fe65

Please sign in to comment.