diff --git a/app/assets/javascripts/components/components/autosuggest_textarea.jsx b/app/assets/javascripts/components/components/autosuggest_textarea.jsx
index 8d9da16014..39ccbcaf96 100644
--- a/app/assets/javascripts/components/components/autosuggest_textarea.jsx
+++ b/app/assets/javascripts/components/components/autosuggest_textarea.jsx
@@ -32,6 +32,7 @@ const AutosuggestTextarea = React.createClass({
value: React.PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: React.PropTypes.bool,
+ fileDropDate: React.PropTypes.instanceOf(Date),
placeholder: React.PropTypes.string,
onSuggestionSelected: React.PropTypes.func.isRequired,
onSuggestionsClearRequested: React.PropTypes.func.isRequired,
@@ -42,6 +43,8 @@ const AutosuggestTextarea = React.createClass({
getInitialState () {
return {
+ isFileDragging: false,
+ fileDraggingDate: undefined,
suggestionsHidden: false,
selectedSuggestion: 0,
lastToken: null,
@@ -120,21 +123,51 @@ const AutosuggestTextarea = React.createClass({
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
this.setState({ suggestionsHidden: false });
}
+
+ const fileDropDate = nextProps.fileDropDate;
+ const { isFileDragging, fileDraggingDate } = this.state;
+
+ /*
+ * We can't detect drop events, because they might not be on the textarea (the app allows dropping anywhere in the
+ * window). Instead, on-drop, we notify this textarea to stop its hover effect by passing in a prop with the
+ * drop-date.
+ */
+ if (isFileDragging && fileDraggingDate && fileDropDate // if dragging when props updated, and dates aren't undefined
+ && fileDropDate > fileDraggingDate) { // and if the drop date is now greater than when we started dragging
+ // then we should stop dragging
+ this.setState({
+ isFileDragging: false
+ });
+ }
},
setTextarea (c) {
this.textarea = c;
},
+ onDragEnter () {
+ this.setState({
+ isFileDragging: true,
+ fileDraggingDate: new Date()
+ })
+ },
+
+ onDragExit () {
+ this.setState({
+ isFileDragging: false
+ })
+ },
+
render () {
- const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
- const { suggestionsHidden, selectedSuggestion } = this.state;
+ const { value, suggestions, fileDropDate, disabled, placeholder, onKeyUp } = this.props;
+ const { isFileDragging, suggestionsHidden, selectedSuggestion } = this.state;
+ const className = isFileDragging ? 'autosuggest-textarea__textarea file-drop' : 'autosuggest-textarea__textarea';
return (
0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
index 012e39c911..55f361b0b7 100644
--- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
@@ -27,6 +27,7 @@ const ComposeForm = React.createClass({
sensitive: React.PropTypes.bool,
unlisted: React.PropTypes.bool,
private: React.PropTypes.bool,
+ fileDropDate: React.PropTypes.instanceOf(Date),
is_submitting: React.PropTypes.bool,
is_uploading: React.PropTypes.bool,
in_reply_to: ImmutablePropTypes.map,
@@ -110,6 +111,7 @@ const ComposeForm = React.createClass({
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
disabled={disabled}
+ fileDropDate={this.props.fileDropDate}
value={this.props.text}
onChange={this.handleChange}
suggestions={this.props.suggestions}
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
index 1d8f20ca75..2b6ee1ae7c 100644
--- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
@@ -24,6 +24,7 @@ const makeMapStateToProps = () => {
sensitive: state.getIn(['compose', 'sensitive']),
unlisted: state.getIn(['compose', 'unlisted']),
private: state.getIn(['compose', 'private']),
+ fileDropDate: state.getIn(['compose', 'fileDropDate']),
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'])),
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index 742272e6f2..16215684e7 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -30,6 +30,7 @@ const initialState = Immutable.Map({
unlisted: false,
private: false,
text: '',
+ fileDropDate: null,
in_reply_to: null,
is_submitting: false,
is_uploading: false,
@@ -116,7 +117,10 @@ export default function compose(state = initialState, action) {
case COMPOSE_SUBMIT_FAIL:
return state.set('is_submitting', false);
case COMPOSE_UPLOAD_REQUEST:
- return state.set('is_uploading', true);
+ return state.withMutations(map => {
+ map.set('is_uploading', true);
+ map.set('fileDropDate', new Date());
+ });
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, Immutable.fromJS(action.media));
case COMPOSE_UPLOAD_FAIL:
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 290b370a9a..832b9e9b1b 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -549,13 +549,19 @@
width: 100%;
height: 100px;
resize: none;
- border: none;
color: #282c37;
- padding: 10px;
+ padding: 7px;
font-family: 'Roboto';
font-size: 14px;
margin: 0;
resize: vertical;
+
+ border: 3px dashed transparent;
+ transition: border-color 0.3s ease;
+
+ &.file-drop {
+ border-color: #aaa;
+ }
}
.autosuggest-textarea__suggestions {