diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 1bf95eec0..e27b606ee 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -13,6 +13,9 @@ export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
+export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
+export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
+
export function changeCompose(text) {
return {
type: COMPOSE_CHANGE,
@@ -129,3 +132,27 @@ export function undoUploadCompose(media_id) {
media_id: media_id
};
};
+
+export function clearComposeSuggestions() {
+ return {
+ type: COMPOSE_SUGGESTIONS_CLEAR
+ };
+};
+
+export function fetchComposeSuggestions(token) {
+ return (dispatch, getState) => {
+ const loadedCandidates = getState().get('accounts').filter(item => item.get('acct').toLowerCase().slice(0, token.length) === token).map(item => ({
+ label: item.get('acct'),
+ completion: item.get('acct').slice(0, token.length)
+ })).toList().toJS();
+
+ dispatch(readyComposeSuggestions(loadedCandidates));
+ };
+};
+
+export function readyComposeSuggestions(accounts) {
+ return {
+ type: COMPOSE_SUGGESTIONS_READY,
+ accounts
+ };
+};
diff --git a/app/assets/javascripts/components/features/ui/components/compose_form.jsx b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
index 5b00fc1b9..464423cf8 100644
--- a/app/assets/javascripts/components/features/ui/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
@@ -4,11 +4,62 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ReplyIndicator from './reply_indicator';
import UploadButton from './upload_button';
+import Autosuggest from 'react-autosuggest';
+
+const getTokenForSuggestions = (str, caretPosition) => {
+ let word;
+
+ let left = str.slice(0, caretPosition).search(/\S+$/);
+ let right = str.slice(caretPosition).search(/\s/);
+
+ if (right < 0) {
+ word = str.slice(left);
+ } else {
+ word = str.slice(left, right + caretPosition);
+ }
+
+ if (!word || word.trim().length < 2 || word[0] !== '@') {
+ return null;
+ }
+
+ word = word.trim().toLowerCase().slice(1);
+
+ if (word.length > 0) {
+ return word;
+ } else {
+ return null;
+ }
+};
+
+const getSuggestionValue = suggestion => suggestion;
+
+const renderSuggestion = suggestion => (
+ {suggestion}
+);
+
+const textareaStyle = {
+ display: 'block',
+ boxSizing: 'border-box',
+ width: '100%',
+ height: '100px',
+ resize: 'none',
+ border: 'none',
+ color: '#282c37',
+ padding: '10px',
+ fontFamily: 'Roboto',
+ fontSize: '14px',
+ margin: '0'
+};
+
+const renderInputComponent = inputProps => (
+
+);
const ComposeForm = React.createClass({
propTypes: {
text: React.PropTypes.string.isRequired,
+ suggestions: React.PropTypes.array,
is_submitting: React.PropTypes.bool,
is_uploading: React.PropTypes.bool,
in_reply_to: ImmutablePropTypes.map,
@@ -35,7 +86,39 @@ const ComposeForm = React.createClass({
componentDidUpdate (prevProps) {
if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to) {
- this.refs.textarea.focus();
+ const node = ReactDOM.findDOMNode(this.refs.autosuggest);
+ const textarea = node.querySelector('textarea');
+
+ if (textarea) {
+ textarea.focus();
+ }
+ }
+ },
+
+ onSuggestionsClearRequested () {
+ this.props.onClearSuggestions();
+ },
+
+ onSuggestionsFetchRequested ({ value }) {
+ const node = ReactDOM.findDOMNode(this.refs.autosuggest);
+ const textarea = node.querySelector('textarea');
+
+ if (textarea) {
+ const token = getTokenForSuggestions(value, textarea.selectionStart);
+
+ if (token !== null) {
+ this.props.onFetchSuggestions(token);
+ }
+ }
+ },
+
+ onSuggestionSelected (e, { suggestionValue, method }) {
+ const node = ReactDOM.findDOMNode(this.refs.autosuggest);
+ const textarea = node.querySelector('textarea');
+
+ if (textarea) {
+ const str = this.props.text;
+ this.props.onChange([str.slice(0, textarea.selectionStart), suggestionValue, str.slice(textarea.selectionStart)].join(''));
}
},
@@ -47,11 +130,29 @@ const ComposeForm = React.createClass({
replyArea =