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.

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