diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2016-10-30 18:13:05 +0100 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2016-10-30 18:13:05 +0100 |
commit | c49f6290eb9c93720bd5407f4320bb0fd6c96ed9 (patch) | |
tree | 6b39ea083c41313b1443d71a6b1adaaf1d31f431 /app/assets/javascripts/components/features | |
parent | fa1cc2d05a12783e166f30bf7a0b3239ebccf732 (diff) |
Basic username autocomplete for text area
Diffstat (limited to 'app/assets/javascripts/components/features')
3 files changed, 126 insertions, 10 deletions
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 => ( + <span>{suggestion}</span> +); + +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 => ( + <textarea {...inputProps} placeholder='What is on your mind?' className='compose-form__textarea' style={textareaStyle} /> +); 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 = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />; } + const inputProps = { + placeholder: 'What is on your mind?', + value: this.props.text, + onKeyUp: this.handleKeyUp, + onChange: this.handleChange, + disabled: disabled + }; + return ( <div style={{ padding: '10px' }}> {replyArea} - <textarea ref='textarea' disabled={disabled} placeholder='What is on your mind?' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='compose-form__textarea' style={{ display: 'block', boxSizing: 'border-box', width: '100%', height: '100px', resize: 'none', border: 'none', color: '#282c37', padding: '10px', fontFamily: 'Roboto', fontSize: '14px', margin: '0' }} /> + <Autosuggest + ref='autosuggest' + suggestions={this.props.suggestions} + onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} + onSuggestionsClearRequested={this.onSuggestionsClearRequested} + onSuggestionSelected={this.onSuggestionSelected} + getSuggestionValue={getSuggestionValue} + renderSuggestion={renderSuggestion} + renderInputComponent={renderInputComponent} + inputProps={inputProps} + /> <div style={{ marginTop: '10px', overflow: 'hidden' }}> <div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={disabled} /></div> diff --git a/app/assets/javascripts/components/features/ui/components/upload_button.jsx b/app/assets/javascripts/components/features/ui/components/upload_button.jsx index d1b093242..9e9fc7298 100644 --- a/app/assets/javascripts/components/features/ui/components/upload_button.jsx +++ b/app/assets/javascripts/components/features/ui/components/upload_button.jsx @@ -24,7 +24,7 @@ const UploadButton = React.createClass({ return ( <div> <Button disabled={this.props.disabled} onClick={this.handleClick} block={true}> - <i className='fa fa-fw fa-photo' /> Add images + <i className='fa fa-fw fa-photo' /> Add media </Button> <input ref='fileElement' type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} /> diff --git a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx index 163d6fa20..dcfeef752 100644 --- a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx @@ -1,7 +1,13 @@ -import { connect } from 'react-redux'; -import ComposeForm from '../components/compose_form'; -import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose'; -import { makeGetStatus } from '../../../selectors'; +import { connect } from 'react-redux'; +import ComposeForm from '../components/compose_form'; +import { + changeCompose, + submitCompose, + cancelReplyCompose, + clearComposeSuggestions, + fetchComposeSuggestions +} from '../../../actions/compose'; +import { makeGetStatus } from '../../../selectors'; const makeMapStateToProps = () => { const getStatus = makeGetStatus(); @@ -9,6 +15,7 @@ const makeMapStateToProps = () => { const mapStateToProps = function (state, props) { return { text: state.getIn(['compose', 'text']), + suggestions: state.getIn(['compose', 'suggestions']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])) @@ -20,16 +27,24 @@ const makeMapStateToProps = () => { const mapDispatchToProps = function (dispatch) { return { - onChange: function (text) { + onChange (text) { dispatch(changeCompose(text)); }, - onSubmit: function () { + onSubmit () { dispatch(submitCompose()); }, - onCancelReply: function () { + onCancelReply () { dispatch(cancelReplyCompose()); + }, + + onClearSuggestions () { + dispatch(clearComposeSuggestions()); + }, + + onFetchSuggestions (token) { + dispatch(fetchComposeSuggestions(token)); } } }; |