1 import React
from 'react';
2 import AutosuggestAccountContainer
from '../features/compose/containers/autosuggest_account_container';
3 import AutosuggestEmoji
from './autosuggest_emoji';
4 import ImmutablePropTypes
from 'react-immutable-proptypes';
5 import PropTypes
from 'prop-types';
6 import { isRtl
} from '../rtl';
7 import ImmutablePureComponent
from 'react-immutable-pure-component';
8 import Textarea
from 'react-textarea-autosize';
9 import classNames
from 'classnames';
11 const textAtCursorMatchesToken
= (str
, caretPosition
) => {
14 let left
= str
.slice(0, caretPosition
).search(/\S+$/);
15 let right
= str
.slice(caretPosition
).search(/\s/);
18 word
= str
.slice(left
);
20 word
= str
.slice(left
, right
+ caretPosition
);
23 if (!word
|| word
.trim().length
< 3 || ['@', ':', '#'].indexOf(word
[0]) === -1) {
27 word
= word
.trim().toLowerCase();
29 if (word
.length
> 0) {
30 return [left
+ 1, word
];
36 export default class AutosuggestTextarea
extends ImmutablePureComponent
{
39 value: PropTypes
.string
,
40 suggestions: ImmutablePropTypes
.list
,
41 disabled: PropTypes
.bool
,
42 placeholder: PropTypes
.string
,
43 onSuggestionSelected: PropTypes
.func
.isRequired
,
44 onSuggestionsClearRequested: PropTypes
.func
.isRequired
,
45 onSuggestionsFetchRequested: PropTypes
.func
.isRequired
,
46 onChange: PropTypes
.func
.isRequired
,
47 onKeyUp: PropTypes
.func
,
48 onKeyDown: PropTypes
.func
,
49 onPaste: PropTypes
.func
.isRequired
,
50 autoFocus: PropTypes
.bool
,
53 static defaultProps
= {
58 suggestionsHidden: false,
59 selectedSuggestion: 0,
65 const [ tokenStart
, token
] = textAtCursorMatchesToken(e
.target
.value
, e
.target
.selectionStart
);
67 if (token
!== null && this.state
.lastToken
!== token
) {
68 this.setState({ lastToken: token
, selectedSuggestion: 0, tokenStart
});
69 this.props
.onSuggestionsFetchRequested(token
);
70 } else if (token
=== null) {
71 this.setState({ lastToken: null });
72 this.props
.onSuggestionsClearRequested();
75 this.props
.onChange(e
);
79 const { suggestions
, disabled
} = this.props
;
80 const { selectedSuggestion
, suggestionsHidden
} = this.state
;
87 if (e
.which
=== 229 || e
.isComposing
) {
88 // Ignore key events during text composition
89 // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac)
95 if (suggestions
.size
=== 0 || suggestionsHidden
) {
96 document
.querySelector('.ui').parentElement
.focus();
99 this.setState({ suggestionsHidden: true });
104 if (suggestions
.size
> 0 && !suggestionsHidden
) {
106 this.setState({ selectedSuggestion: Math
.min(selectedSuggestion
+ 1, suggestions
.size
- 1) });
111 if (suggestions
.size
> 0 && !suggestionsHidden
) {
113 this.setState({ selectedSuggestion: Math
.max(selectedSuggestion
- 1, 0) });
120 if (this.state
.lastToken
!== null && suggestions
.size
> 0 && !suggestionsHidden
) {
123 this.props
.onSuggestionSelected(this.state
.tokenStart
, this.state
.lastToken
, suggestions
.get(selectedSuggestion
));
129 if (e
.defaultPrevented
|| !this.props
.onKeyDown
) {
133 this.props
.onKeyDown(e
);
137 this.setState({ suggestionsHidden: true });
140 onSuggestionClick
= (e
) => {
141 const suggestion
= this.props
.suggestions
.get(e
.currentTarget
.getAttribute('data-index'));
143 this.props
.onSuggestionSelected(this.state
.tokenStart
, this.state
.lastToken
, suggestion
);
144 this.textarea
.focus();
147 componentWillReceiveProps (nextProps
) {
148 if (nextProps
.suggestions
!== this.props
.suggestions
&& nextProps
.suggestions
.size
> 0 && this.state
.suggestionsHidden
) {
149 this.setState({ suggestionsHidden: false });
153 setTextarea
= (c
) => {
158 if (e
.clipboardData
&& e
.clipboardData
.files
.length
=== 1) {
159 this.props
.onPaste(e
.clipboardData
.files
);
164 renderSuggestion
= (suggestion
, i
) => {
165 const { selectedSuggestion
} = this.state
;
168 if (typeof suggestion
=== 'object') {
169 inner
= <AutosuggestEmoji emoji
={suggestion
} />;
171 } else if (suggestion
[0] === '#') {
175 inner
= <AutosuggestAccountContainer id
={suggestion
} />;
180 <div role
='button' tabIndex
='0' key
={key
} data
-index
={i
} className
={classNames('autosuggest-textarea__suggestions__item', { selected: i
=== selectedSuggestion
})} onMouseDown
={this.onSuggestionClick
}>
187 const { value
, suggestions
, disabled
, placeholder
, onKeyUp
, autoFocus
} = this.props
;
188 const { suggestionsHidden
} = this.state
;
189 const style
= { direction: 'ltr' };
192 style
.direction
= 'rtl';
196 <div className
='autosuggest-textarea'>
198 <span style
={{ display: 'none' }}>{placeholder
}</span
>
201 inputRef
={this.setTextarea
}
202 className
='autosuggest-textarea__textarea'
204 placeholder
={placeholder
}
205 autoFocus
={autoFocus
}
207 onChange
={this.onChange
}
208 onKeyDown
={this.onKeyDown
}
211 onPaste
={this.onPaste
}
213 aria
-autocomplete
='list'
217 <div className
={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
218 {suggestions
.map(this.renderSuggestion
)}