ba527c071f
Conflicts: - `app/javascript/mastodon/features/compose/components/poll_form.jsx`: Upstream changed how icons are handled, including on a line modified by glitch-soc to bump the number of poll options. Applied upstream's change, while keeping the increased number of poll options.
191 lines
7.2 KiB
JavaScript
191 lines
7.2 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import { PureComponent } from 'react';
|
|
|
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
|
|
import classNames from 'classnames';
|
|
|
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
|
|
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
|
|
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
|
|
|
|
import AutosuggestInput from 'mastodon/components/autosuggest_input';
|
|
import { Icon } from 'mastodon/components/icon';
|
|
import { IconButton } from 'mastodon/components/icon_button';
|
|
|
|
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' },
|
|
switchToMultiple: { id: 'compose_form.poll.switch_to_multiple', defaultMessage: 'Change poll to allow multiple choices' },
|
|
switchToSingle: { id: 'compose_form.poll.switch_to_single', defaultMessage: 'Change poll to allow for a single choice' },
|
|
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}}' },
|
|
});
|
|
|
|
class OptionIntl extends PureComponent {
|
|
|
|
static propTypes = {
|
|
title: PropTypes.string.isRequired,
|
|
lang: PropTypes.string,
|
|
index: PropTypes.number.isRequired,
|
|
isPollMultiple: PropTypes.bool,
|
|
autoFocus: PropTypes.bool,
|
|
onChange: PropTypes.func.isRequired,
|
|
onRemove: PropTypes.func.isRequired,
|
|
onToggleMultiple: PropTypes.func.isRequired,
|
|
suggestions: ImmutablePropTypes.list,
|
|
onClearSuggestions: PropTypes.func.isRequired,
|
|
onFetchSuggestions: PropTypes.func.isRequired,
|
|
onSuggestionSelected: 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);
|
|
};
|
|
|
|
|
|
handleToggleMultiple = e => {
|
|
this.props.onToggleMultiple();
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
};
|
|
|
|
handleCheckboxKeypress = e => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
this.handleToggleMultiple(e);
|
|
}
|
|
};
|
|
|
|
onSuggestionsClearRequested = () => {
|
|
this.props.onClearSuggestions();
|
|
};
|
|
|
|
onSuggestionsFetchRequested = (token) => {
|
|
this.props.onFetchSuggestions(token);
|
|
};
|
|
|
|
onSuggestionSelected = (tokenStart, token, value) => {
|
|
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
|
|
};
|
|
|
|
render () {
|
|
const { isPollMultiple, title, lang, index, autoFocus, intl } = this.props;
|
|
|
|
return (
|
|
<li>
|
|
<label className='poll__option editable'>
|
|
<span
|
|
className={classNames('poll__input', { checkbox: isPollMultiple })}
|
|
onClick={this.handleToggleMultiple}
|
|
onKeyPress={this.handleCheckboxKeypress}
|
|
role='button'
|
|
tabIndex={0}
|
|
title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
|
|
aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
|
|
/>
|
|
|
|
<AutosuggestInput
|
|
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
|
maxLength={100}
|
|
value={title}
|
|
lang={lang}
|
|
spellCheck
|
|
onChange={this.handleOptionTitleChange}
|
|
suggestions={this.props.suggestions}
|
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
|
onSuggestionSelected={this.onSuggestionSelected}
|
|
searchTokens={[':']}
|
|
autoFocus={autoFocus}
|
|
/>
|
|
</label>
|
|
|
|
<div className='poll__cancel'>
|
|
<IconButton disabled={index <= 1} title={intl.formatMessage(messages.remove_option)} icon='times' iconComponent={CloseIcon} onClick={this.handleOptionRemove} />
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
const Option = injectIntl(OptionIntl);
|
|
|
|
class PollForm extends ImmutablePureComponent {
|
|
|
|
static propTypes = {
|
|
options: ImmutablePropTypes.list,
|
|
lang: PropTypes.string,
|
|
expiresIn: PropTypes.number,
|
|
isMultiple: PropTypes.bool,
|
|
onChangeOption: PropTypes.func.isRequired,
|
|
onAddOption: PropTypes.func.isRequired,
|
|
onRemoveOption: PropTypes.func.isRequired,
|
|
onChangeSettings: PropTypes.func.isRequired,
|
|
suggestions: ImmutablePropTypes.list,
|
|
onClearSuggestions: PropTypes.func.isRequired,
|
|
onFetchSuggestions: PropTypes.func.isRequired,
|
|
onSuggestionSelected: PropTypes.func.isRequired,
|
|
intl: PropTypes.object.isRequired,
|
|
};
|
|
|
|
handleAddOption = () => {
|
|
this.props.onAddOption('');
|
|
};
|
|
|
|
handleSelectDuration = e => {
|
|
this.props.onChangeSettings(e.target.value, this.props.isMultiple);
|
|
};
|
|
|
|
handleToggleMultiple = () => {
|
|
this.props.onChangeSettings(this.props.expiresIn, !this.props.isMultiple);
|
|
};
|
|
|
|
render () {
|
|
const { options, lang, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props;
|
|
|
|
if (!options) {
|
|
return null;
|
|
}
|
|
|
|
const autoFocusIndex = options.indexOf('');
|
|
|
|
return (
|
|
<div className='compose-form__poll-wrapper'>
|
|
<ul>
|
|
{options.map((title, i) => <Option title={title} lang={lang} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
|
|
</ul>
|
|
|
|
<div className='poll__footer'>
|
|
<button type='button' disabled={options.size >= 5} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' icon={AddIcon} /> <FormattedMessage {...messages.add_option} /></button>
|
|
|
|
{/* eslint-disable-next-line jsx-a11y/no-onchange */}
|
|
<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={43200}>{intl.formatMessage(messages.hours, { number: 12 })}</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>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
export default injectIntl(PollForm);
|