import React from 'react';
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
+import AutosuggestEmoji from './autosuggest_emoji';
+import AutosuggestHashtag from './autosuggest_hashtag';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
+import classNames from 'classnames';
const textAtCursorMatchesToken = (str, caretPosition) => {
let word;
word = str.slice(left, right + caretPosition);
}
- if (!word || word.trim().length < 2 || word[0] !== '@') {
+ if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
return [null, null];
}
- word = word.trim().toLowerCase().slice(1);
+ word = word.trim().toLowerCase();
if (word.length > 0) {
return [left + 1, word];
}
};
-class AutosuggestTextarea extends ImmutablePureComponent {
+export default class AutosuggestTextarea extends ImmutablePureComponent {
static propTypes = {
value: PropTypes.string,
};
state = {
- suggestionsHidden: false,
+ suggestionsHidden: true,
+ focused: false,
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
return;
}
+ if (e.which === 229 || e.isComposing) {
+ // Ignore key events during text composition
+ // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
+ return;
+ }
+
switch(e.key) {
case 'Escape':
- if (!suggestionsHidden) {
+ if (suggestions.size === 0 || suggestionsHidden) {
+ document.querySelector('.ui').parentElement.focus();
+ } else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
}
}
onBlur = () => {
- // If we hide the suggestions immediately, then this will prevent the
- // onClick for the suggestions themselves from firing.
- // Setting a short window for that to take place before hiding the
- // suggestions ensures that can't happen.
- setTimeout(() => {
- this.setState({ suggestionsHidden: true });
- }, 100);
+ this.setState({ suggestionsHidden: true, focused: false });
+ }
+
+ onFocus = (e) => {
+ this.setState({ focused: true });
+ if (this.props.onFocus) {
+ this.props.onFocus(e);
+ }
}
onSuggestionClick = (e) => {
- const suggestion = Number(e.currentTarget.getAttribute('data-index'));
+ const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus();
}
componentWillReceiveProps (nextProps) {
- if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
+ if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
this.setState({ suggestionsHidden: false });
}
}
}
}
- render () {
- const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
- const { suggestionsHidden, selectedSuggestion } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
+ renderSuggestion = (suggestion, i) => {
+ const { selectedSuggestion } = this.state;
+ let inner, key;
+
+ if (suggestion.type === 'emoji') {
+ inner = <AutosuggestEmoji emoji={suggestion} />;
+ key = suggestion.id;
+ } else if (suggestion.type === 'hashtag') {
+ inner = <AutosuggestHashtag tag={suggestion} />;
+ key = suggestion.name;
+ } else if (suggestion.type === 'account') {
+ inner = <AutosuggestAccountContainer id={suggestion.id} />;
+ key = suggestion.id;
}
return (
- <div className='autosuggest-textarea'>
- <Textarea
- inputRef={this.setTextarea}
- className='autosuggest-textarea__textarea'
- disabled={disabled}
- placeholder={placeholder}
- autoFocus={autoFocus}
- value={value}
- onChange={this.onChange}
- onKeyDown={this.onKeyDown}
- onKeyUp={onKeyUp}
- onBlur={this.onBlur}
- onPaste={this.onPaste}
- style={style}
- />
+ <div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
+ {inner}
+ </div>
+ );
+ }
+
+ render () {
+ const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
+ const { suggestionsHidden } = this.state;
+
+ return [
+ <div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
+ <div className='autosuggest-textarea'>
+ <label>
+ <span style={{ display: 'none' }}>{placeholder}</span>
+
+ <Textarea
+ ref={this.setTextarea}
+ className='autosuggest-textarea__textarea'
+ disabled={disabled}
+ placeholder={placeholder}
+ autoFocus={autoFocus}
+ value={value}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}
+ onKeyUp={onKeyUp}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ onPaste={this.onPaste}
+ dir='auto'
+ aria-autocomplete='list'
+ />
+ </label>
+ </div>
+ {children}
+ </div>,
+ <div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
- {suggestions.map((suggestion, i) => (
- <div
- role='button'
- tabIndex='0'
- key={suggestion}
- data-index={suggestion}
- className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
- onClick={this.onSuggestionClick}>
- <AutosuggestAccountContainer id={suggestion} />
- </div>
- ))}
+ {suggestions.map(this.renderSuggestion)}
</div>
- </div>
- );
+ </div>,
+ ];
}
}
-
-export default AutosuggestTextarea;