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,
autoFocus: true,
};
- constructor (props, context) {
- super(props, context);
- this.state = {
- suggestionsHidden: false,
- selectedSuggestion: 0,
- lastToken: null,
- tokenStart: 0,
- };
- this.onChange = this.onChange.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onSuggestionClick = this.onSuggestionClick.bind(this);
- this.setTextarea = this.setTextarea.bind(this);
- this.onPaste = this.onPaste.bind(this);
- }
+ state = {
+ suggestionsHidden: true,
+ focused: false,
+ selectedSuggestion: 0,
+ lastToken: null,
+ tokenStart: 0,
+ };
- onChange (e) {
+ onChange = (e) => {
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
if (token !== null && this.state.lastToken !== token) {
this.props.onSuggestionsClearRequested();
}
- // auto-resize textarea
- e.target.style.height = 'auto';
- e.target.style.height = `${e.target.scrollHeight}px`;
-
this.props.onChange(e);
}
- onKeyDown (e) {
+ onKeyDown = (e) => {
const { suggestions, disabled } = this.props;
const { selectedSuggestion, suggestionsHidden } = this.state;
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 });
}
this.props.onKeyDown(e);
}
- 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);
+ onBlur = () => {
+ this.setState({ suggestionsHidden: true, focused: false });
}
- onSuggestionClick (e) {
- const suggestion = Number(e.currentTarget.getAttribute('data-index'));
+ onFocus = (e) => {
+ this.setState({ focused: true });
+ if (this.props.onFocus) {
+ this.props.onFocus(e);
+ }
+ }
+
+ onSuggestionClick = (e) => {
+ 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 });
}
}
- setTextarea (c) {
+ setTextarea = (c) => {
this.textarea = c;
}
- onPaste (e) {
+ onPaste = (e) => {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files);
e.preventDefault();
}
}
- reset () {
- this.textarea.style.height = 'auto';
- }
-
- 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
- ref={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;