The code powering m.abunchtell.com https://m.abunchtell.com
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

206 строки
5.8 KiB

  1. import React from 'react';
  2. import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import PropTypes from 'prop-types';
  5. import { isRtl } from '../rtl';
  6. import ImmutablePureComponent from 'react-immutable-pure-component';
  7. import Textarea from 'react-textarea-autosize';
  8. const textAtCursorMatchesToken = (str, caretPosition) => {
  9. let word;
  10. let left = str.slice(0, caretPosition).search(/\S+$/);
  11. let right = str.slice(caretPosition).search(/\s/);
  12. if (right < 0) {
  13. word = str.slice(left);
  14. } else {
  15. word = str.slice(left, right + caretPosition);
  16. }
  17. if (!word || word.trim().length < 2 || word[0] !== '@') {
  18. return [null, null];
  19. }
  20. word = word.trim().toLowerCase().slice(1);
  21. if (word.length > 0) {
  22. return [left + 1, word];
  23. } else {
  24. return [null, null];
  25. }
  26. };
  27. class AutosuggestTextarea extends ImmutablePureComponent {
  28. static propTypes = {
  29. value: PropTypes.string,
  30. suggestions: ImmutablePropTypes.list,
  31. disabled: PropTypes.bool,
  32. placeholder: PropTypes.string,
  33. onSuggestionSelected: PropTypes.func.isRequired,
  34. onSuggestionsClearRequested: PropTypes.func.isRequired,
  35. onSuggestionsFetchRequested: PropTypes.func.isRequired,
  36. onChange: PropTypes.func.isRequired,
  37. onKeyUp: PropTypes.func,
  38. onKeyDown: PropTypes.func,
  39. onPaste: PropTypes.func.isRequired,
  40. autoFocus: PropTypes.bool,
  41. };
  42. static defaultProps = {
  43. autoFocus: true,
  44. };
  45. state = {
  46. suggestionsHidden: false,
  47. selectedSuggestion: 0,
  48. lastToken: null,
  49. tokenStart: 0,
  50. };
  51. onChange = (e) => {
  52. const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
  53. if (token !== null && this.state.lastToken !== token) {
  54. this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
  55. this.props.onSuggestionsFetchRequested(token);
  56. } else if (token === null) {
  57. this.setState({ lastToken: null });
  58. this.props.onSuggestionsClearRequested();
  59. }
  60. this.props.onChange(e);
  61. }
  62. onKeyDown = (e) => {
  63. const { suggestions, disabled } = this.props;
  64. const { selectedSuggestion, suggestionsHidden } = this.state;
  65. if (disabled) {
  66. e.preventDefault();
  67. return;
  68. }
  69. switch(e.key) {
  70. case 'Escape':
  71. if (!suggestionsHidden) {
  72. e.preventDefault();
  73. this.setState({ suggestionsHidden: true });
  74. }
  75. break;
  76. case 'ArrowDown':
  77. if (suggestions.size > 0 && !suggestionsHidden) {
  78. e.preventDefault();
  79. this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
  80. }
  81. break;
  82. case 'ArrowUp':
  83. if (suggestions.size > 0 && !suggestionsHidden) {
  84. e.preventDefault();
  85. this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
  86. }
  87. break;
  88. case 'Enter':
  89. case 'Tab':
  90. // Select suggestion
  91. if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
  92. e.preventDefault();
  93. e.stopPropagation();
  94. this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
  95. }
  96. break;
  97. }
  98. if (e.defaultPrevented || !this.props.onKeyDown) {
  99. return;
  100. }
  101. this.props.onKeyDown(e);
  102. }
  103. onBlur = () => {
  104. // If we hide the suggestions immediately, then this will prevent the
  105. // onClick for the suggestions themselves from firing.
  106. // Setting a short window for that to take place before hiding the
  107. // suggestions ensures that can't happen.
  108. setTimeout(() => {
  109. this.setState({ suggestionsHidden: true });
  110. }, 100);
  111. }
  112. onSuggestionClick = (e) => {
  113. const suggestion = Number(e.currentTarget.getAttribute('data-index'));
  114. e.preventDefault();
  115. this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
  116. this.textarea.focus();
  117. }
  118. componentWillReceiveProps (nextProps) {
  119. if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
  120. this.setState({ suggestionsHidden: false });
  121. }
  122. }
  123. setTextarea = (c) => {
  124. this.textarea = c;
  125. }
  126. onPaste = (e) => {
  127. if (e.clipboardData && e.clipboardData.files.length === 1) {
  128. this.props.onPaste(e.clipboardData.files);
  129. e.preventDefault();
  130. }
  131. }
  132. render () {
  133. const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
  134. const { suggestionsHidden, selectedSuggestion } = this.state;
  135. const style = { direction: 'ltr' };
  136. if (isRtl(value)) {
  137. style.direction = 'rtl';
  138. }
  139. return (
  140. <div className='autosuggest-textarea'>
  141. <Textarea
  142. inputRef={this.setTextarea}
  143. className='autosuggest-textarea__textarea'
  144. disabled={disabled}
  145. placeholder={placeholder}
  146. autoFocus={autoFocus}
  147. value={value}
  148. onChange={this.onChange}
  149. onKeyDown={this.onKeyDown}
  150. onKeyUp={onKeyUp}
  151. onBlur={this.onBlur}
  152. onPaste={this.onPaste}
  153. style={style}
  154. />
  155. <div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
  156. {suggestions.map((suggestion, i) => (
  157. <div
  158. role='button'
  159. tabIndex='0'
  160. key={suggestion}
  161. data-index={suggestion}
  162. className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
  163. onClick={this.onSuggestionClick}>
  164. <AutosuggestAccountContainer id={suggestion} />
  165. </div>
  166. ))}
  167. </div>
  168. </div>
  169. );
  170. }
  171. }
  172. export default AutosuggestTextarea;