You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
6.5 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import ImmutablePureComponent from 'react-immutable-pure-component';
  5. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  6. import IconButton from 'mastodon/components/icon_button';
  7. import Icon from 'mastodon/components/icon';
  8. import AutosuggestInput from 'mastodon/components/autosuggest_input';
  9. import classNames from 'classnames';
  10. const messages = defineMessages({
  11. option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
  12. add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
  13. remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
  14. poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
  15. switchToMultiple: { id: 'compose_form.poll.switch_to_multiple', defaultMessage: 'Change poll to allow multiple choices' },
  16. switchToSingle: { id: 'compose_form.poll.switch_to_single', defaultMessage: 'Change poll to allow for a single choice' },
  17. minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
  18. hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
  19. days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
  20. });
  21. @injectIntl
  22. class Option extends React.PureComponent {
  23. static propTypes = {
  24. title: PropTypes.string.isRequired,
  25. index: PropTypes.number.isRequired,
  26. isPollMultiple: PropTypes.bool,
  27. onChange: PropTypes.func.isRequired,
  28. onRemove: PropTypes.func.isRequired,
  29. onToggleMultiple: PropTypes.func.isRequired,
  30. suggestions: ImmutablePropTypes.list,
  31. onClearSuggestions: PropTypes.func.isRequired,
  32. onFetchSuggestions: PropTypes.func.isRequired,
  33. onSuggestionSelected: PropTypes.func.isRequired,
  34. intl: PropTypes.object.isRequired,
  35. };
  36. handleOptionTitleChange = e => {
  37. this.props.onChange(this.props.index, e.target.value);
  38. };
  39. handleOptionRemove = () => {
  40. this.props.onRemove(this.props.index);
  41. };
  42. handleToggleMultiple = e => {
  43. this.props.onToggleMultiple();
  44. e.preventDefault();
  45. e.stopPropagation();
  46. };
  47. handleCheckboxKeypress = e => {
  48. if (e.key === 'Enter' || e.key === ' ') {
  49. this.handleToggleMultiple(e);
  50. }
  51. }
  52. onSuggestionsClearRequested = () => {
  53. this.props.onClearSuggestions();
  54. }
  55. onSuggestionsFetchRequested = (token) => {
  56. this.props.onFetchSuggestions(token);
  57. }
  58. onSuggestionSelected = (tokenStart, token, value) => {
  59. this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
  60. }
  61. render () {
  62. const { isPollMultiple, title, index, intl } = this.props;
  63. return (
  64. <li>
  65. <label className='poll__text editable'>
  66. <span
  67. className={classNames('poll__input', { checkbox: isPollMultiple })}
  68. onClick={this.handleToggleMultiple}
  69. onKeyPress={this.handleCheckboxKeypress}
  70. role='button'
  71. tabIndex='0'
  72. title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
  73. aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
  74. />
  75. <AutosuggestInput
  76. placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
  77. maxLength={25}
  78. value={title}
  79. onChange={this.handleOptionTitleChange}
  80. suggestions={this.props.suggestions}
  81. onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
  82. onSuggestionsClearRequested={this.onSuggestionsClearRequested}
  83. onSuggestionSelected={this.onSuggestionSelected}
  84. searchTokens={[':']}
  85. />
  86. </label>
  87. <div className='poll__cancel'>
  88. <IconButton disabled={index <= 1} title={intl.formatMessage(messages.remove_option)} icon='times' onClick={this.handleOptionRemove} />
  89. </div>
  90. </li>
  91. );
  92. }
  93. }
  94. export default
  95. @injectIntl
  96. class PollForm extends ImmutablePureComponent {
  97. static propTypes = {
  98. options: ImmutablePropTypes.list,
  99. expiresIn: PropTypes.number,
  100. isMultiple: PropTypes.bool,
  101. onChangeOption: PropTypes.func.isRequired,
  102. onAddOption: PropTypes.func.isRequired,
  103. onRemoveOption: PropTypes.func.isRequired,
  104. onChangeSettings: PropTypes.func.isRequired,
  105. suggestions: ImmutablePropTypes.list,
  106. onClearSuggestions: PropTypes.func.isRequired,
  107. onFetchSuggestions: PropTypes.func.isRequired,
  108. onSuggestionSelected: PropTypes.func.isRequired,
  109. intl: PropTypes.object.isRequired,
  110. };
  111. handleAddOption = () => {
  112. this.props.onAddOption('');
  113. };
  114. handleSelectDuration = e => {
  115. this.props.onChangeSettings(e.target.value, this.props.isMultiple);
  116. };
  117. handleToggleMultiple = () => {
  118. this.props.onChangeSettings(this.props.expiresIn, !this.props.isMultiple);
  119. };
  120. render () {
  121. const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props;
  122. if (!options) {
  123. return null;
  124. }
  125. return (
  126. <div className='compose-form__poll-wrapper'>
  127. <ul>
  128. {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} {...other} />)}
  129. </ul>
  130. <div className='poll__footer'>
  131. <button disabled={options.size >= 4} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
  132. <select value={expiresIn} onChange={this.handleSelectDuration}>
  133. <option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
  134. <option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
  135. <option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
  136. <option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
  137. <option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
  138. <option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
  139. <option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
  140. </select>
  141. </div>
  142. </div>
  143. );
  144. }
  145. }