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

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