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.

187 lines
5.3 KiB

  1. import CharacterCounter from './character_counter';
  2. import Button from '../../../components/button';
  3. import PureRenderMixin from 'react-addons-pure-render-mixin';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import ReplyIndicator from './reply_indicator';
  6. import UploadButton from './upload_button';
  7. import Autosuggest from 'react-autosuggest';
  8. import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
  9. import { debounce } from 'react-decoration';
  10. import UploadButtonContainer from '../containers/upload_button_container';
  11. import { defineMessages, injectIntl } from 'react-intl';
  12. const messages = defineMessages({
  13. placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
  14. publish: { id: 'compose_form.publish', defaultMessage: 'Publish' }
  15. });
  16. const getTokenForSuggestions = (str, caretPosition) => {
  17. let word;
  18. let left = str.slice(0, caretPosition).search(/\S+$/);
  19. let right = str.slice(caretPosition).search(/\s/);
  20. if (right < 0) {
  21. word = str.slice(left);
  22. } else {
  23. word = str.slice(left, right + caretPosition);
  24. }
  25. if (!word || word.trim().length < 2 || word[0] !== '@') {
  26. return null;
  27. }
  28. word = word.trim().toLowerCase().slice(1);
  29. if (word.length > 0) {
  30. return word;
  31. } else {
  32. return null;
  33. }
  34. };
  35. const getSuggestionValue = suggestionId => suggestionId;
  36. const renderSuggestion = suggestionId => <AutosuggestAccountContainer id={suggestionId} />;
  37. const textareaStyle = {
  38. display: 'block',
  39. boxSizing: 'border-box',
  40. width: '100%',
  41. height: '100px',
  42. resize: 'none',
  43. border: 'none',
  44. color: '#282c37',
  45. padding: '10px',
  46. fontFamily: 'Roboto',
  47. fontSize: '14px',
  48. margin: '0'
  49. };
  50. const renderInputComponent = inputProps => (
  51. <textarea {...inputProps} className='compose-form__textarea' style={textareaStyle} />
  52. );
  53. const ComposeForm = React.createClass({
  54. propTypes: {
  55. text: React.PropTypes.string.isRequired,
  56. suggestion_token: React.PropTypes.string,
  57. suggestions: React.PropTypes.array,
  58. is_submitting: React.PropTypes.bool,
  59. is_uploading: React.PropTypes.bool,
  60. in_reply_to: ImmutablePropTypes.map,
  61. onChange: React.PropTypes.func.isRequired,
  62. onSubmit: React.PropTypes.func.isRequired,
  63. onCancelReply: React.PropTypes.func.isRequired,
  64. onClearSuggestions: React.PropTypes.func.isRequired,
  65. onFetchSuggestions: React.PropTypes.func.isRequired,
  66. onSuggestionSelected: React.PropTypes.func.isRequired
  67. },
  68. mixins: [PureRenderMixin],
  69. handleChange (e) {
  70. if (typeof e.target.value === 'undefined' || typeof e.target.value === 'number') {
  71. return;
  72. }
  73. this.props.onChange(e.target.value);
  74. },
  75. handleKeyUp (e) {
  76. if (e.keyCode === 13 && e.ctrlKey) {
  77. this.props.onSubmit();
  78. }
  79. },
  80. handleSubmit () {
  81. this.props.onSubmit();
  82. },
  83. componentDidUpdate (prevProps) {
  84. if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to) {
  85. const textarea = this.autosuggest.input;
  86. if (textarea) {
  87. textarea.focus();
  88. }
  89. }
  90. },
  91. onSuggestionsClearRequested () {
  92. this.props.onClearSuggestions();
  93. },
  94. @debounce(500)
  95. onSuggestionsFetchRequested ({ value }) {
  96. const textarea = this.autosuggest.input;
  97. if (textarea) {
  98. const token = getTokenForSuggestions(value, textarea.selectionStart);
  99. if (token !== null) {
  100. this.props.onFetchSuggestions(token);
  101. } else {
  102. this.props.onClearSuggestions();
  103. }
  104. }
  105. },
  106. onSuggestionSelected (e, { suggestionValue }) {
  107. const textarea = this.autosuggest.input;
  108. if (textarea) {
  109. this.props.onSuggestionSelected(textarea.selectionStart, suggestionValue);
  110. }
  111. },
  112. setRef (c) {
  113. this.autosuggest = c;
  114. },
  115. render () {
  116. const { intl } = this.props;
  117. let replyArea = '';
  118. const disabled = this.props.is_submitting || this.props.is_uploading;
  119. if (this.props.in_reply_to) {
  120. replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
  121. }
  122. const inputProps = {
  123. placeholder: intl.formatMessage(messages.placeholder),
  124. value: this.props.text,
  125. onKeyUp: this.handleKeyUp,
  126. onChange: this.handleChange,
  127. disabled: disabled
  128. };
  129. return (
  130. <div style={{ padding: '10px' }}>
  131. {replyArea}
  132. <Autosuggest
  133. ref={this.setRef}
  134. suggestions={this.props.suggestions}
  135. focusFirstSuggestion={true}
  136. onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
  137. onSuggestionsClearRequested={this.onSuggestionsClearRequested}
  138. onSuggestionSelected={this.onSuggestionSelected}
  139. getSuggestionValue={getSuggestionValue}
  140. renderSuggestion={renderSuggestion}
  141. renderInputComponent={renderInputComponent}
  142. inputProps={inputProps}
  143. />
  144. <div style={{ marginTop: '10px', overflow: 'hidden' }}>
  145. <div style={{ float: 'right' }}><Button text={intl.formatMessage(messages.publish)} onClick={this.handleSubmit} disabled={disabled} /></div>
  146. <div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={this.props.text} /></div>
  147. <UploadButtonContainer style={{ paddingTop: '4px' }} />
  148. </div>
  149. </div>
  150. );
  151. }
  152. });
  153. export default injectIntl(ComposeForm);