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';
8 const textAtCursorMatchesToken
= (str
, caretPosition
) => {
11 let left
= str
.slice(0, caretPosition
).search(/\S+$/);
12 let right
= str
.slice(caretPosition
).search(/\s/);
15 word
= str
.slice(left
);
17 word
= str
.slice(left
, right
+ caretPosition
);
20 if (!word
|| word
.trim().length
< 2 || word
[0] !== '@') {
24 word
= word
.trim().toLowerCase().slice(1);
26 if (word
.length
> 0) {
27 return [left
+ 1, word
];
33 class AutosuggestTextarea
extends ImmutablePureComponent
{
36 value: PropTypes
.string
,
37 suggestions: ImmutablePropTypes
.list
,
38 disabled: PropTypes
.bool
,
39 placeholder: PropTypes
.string
,
40 onSuggestionSelected: PropTypes
.func
.isRequired
,
41 onSuggestionsClearRequested: PropTypes
.func
.isRequired
,
42 onSuggestionsFetchRequested: PropTypes
.func
.isRequired
,
43 onChange: PropTypes
.func
.isRequired
,
44 onKeyUp: PropTypes
.func
,
45 onKeyDown: PropTypes
.func
,
46 onPaste: PropTypes
.func
.isRequired
,
47 autoFocus: PropTypes
.bool
,
50 static defaultProps
= {
54 constructor (props
, context
) {
55 super(props
, context
);
57 suggestionsHidden: false,
58 selectedSuggestion: 0,
62 this.onChange
= this.onChange
.bind(this);
63 this.onKeyDown
= this.onKeyDown
.bind(this);
64 this.onBlur
= this.onBlur
.bind(this);
65 this.onSuggestionClick
= this.onSuggestionClick
.bind(this);
66 this.setTextarea
= this.setTextarea
.bind(this);
67 this.onPaste
= this.onPaste
.bind(this);
71 const [ tokenStart
, token
] = textAtCursorMatchesToken(e
.target
.value
, e
.target
.selectionStart
);
73 if (token
!== null && this.state
.lastToken
!== token
) {
74 this.setState({ lastToken: token
, selectedSuggestion: 0, tokenStart
});
75 this.props
.onSuggestionsFetchRequested(token
);
76 } else if (token
=== null) {
77 this.setState({ lastToken: null });
78 this.props
.onSuggestionsClearRequested();
81 // auto-resize textarea
82 e
.target
.style
.height
= 'auto';
83 e
.target
.style
.height
= `${e.target.scrollHeight}px`;
85 this.props
.onChange(e
);
89 const { suggestions
, disabled
} = this.props
;
90 const { selectedSuggestion
, suggestionsHidden
} = this.state
;
99 if (!suggestionsHidden
) {
101 this.setState({ suggestionsHidden: true });
106 if (suggestions
.size
> 0 && !suggestionsHidden
) {
108 this.setState({ selectedSuggestion: Math
.min(selectedSuggestion
+ 1, suggestions
.size
- 1) });
113 if (suggestions
.size
> 0 && !suggestionsHidden
) {
115 this.setState({ selectedSuggestion: Math
.max(selectedSuggestion
- 1, 0) });
122 if (this.state
.lastToken
!== null && suggestions
.size
> 0 && !suggestionsHidden
) {
125 this.props
.onSuggestionSelected(this.state
.tokenStart
, this.state
.lastToken
, suggestions
.get(selectedSuggestion
));
131 if (e
.defaultPrevented
|| !this.props
.onKeyDown
) {
135 this.props
.onKeyDown(e
);
139 // If we hide the suggestions immediately, then this will prevent the
140 // onClick for the suggestions themselves from firing.
141 // Setting a short window for that to take place before hiding the
142 // suggestions ensures that can't happen.
144 this.setState({ suggestionsHidden: true });
148 onSuggestionClick (e
) {
149 const suggestion
= Number(e
.currentTarget
.getAttribute('data-index'));
151 this.props
.onSuggestionSelected(this.state
.tokenStart
, this.state
.lastToken
, suggestion
);
152 this.textarea
.focus();
155 componentWillReceiveProps (nextProps
) {
156 if (nextProps
.suggestions
!== this.props
.suggestions
&& nextProps
.suggestions
.size
> 0 && this.state
.suggestionsHidden
) {
157 this.setState({ suggestionsHidden: false });
166 if (e
.clipboardData
&& e
.clipboardData
.files
.length
=== 1) {
167 this.props
.onPaste(e
.clipboardData
.files
);
173 this.textarea
.style
.height
= 'auto';
177 const { value
, suggestions
, disabled
, placeholder
, onKeyUp
, autoFocus
} = this.props
;
178 const { suggestionsHidden
, selectedSuggestion
} = this.state
;
179 const style
= { direction: 'ltr' };
182 style
.direction
= 'rtl';
186 <div className
='autosuggest-textarea'>
188 ref
={this.setTextarea
}
189 className
='autosuggest-textarea__textarea'
191 placeholder
={placeholder
}
192 autoFocus
={autoFocus
}
194 onChange
={this.onChange
}
195 onKeyDown
={this.onKeyDown
}
198 onPaste
={this.onPaste
}
202 <div className
={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
203 {suggestions
.map((suggestion
, i
) => (
208 data
-index
={suggestion
}
209 className
={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
210 onClick
={this.onSuggestionClick
}>
211 <AutosuggestAccountContainer id
={suggestion
} />
221 export default AutosuggestTextarea
;