Merge branch 'compose-refactor' into 'master'
Fix composer issues Closes #95, #86, and #116 See merge request soapbox-pub/soapbox-fe!25
This commit is contained in:
commit
6f76e1796b
3 changed files with 18 additions and 50 deletions
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import CharacterCounter from './character_counter';
|
import CharacterCounter from './character_counter';
|
||||||
import Button from '../../../components/button';
|
import Button from '../../../components/button';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
@ -32,19 +31,11 @@ const messages = defineMessages({
|
||||||
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
|
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
export default @injectIntl
|
||||||
return {
|
|
||||||
maxTootChars: state.getIn(['instance', 'max_toot_chars']),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class ComposeForm extends ImmutablePureComponent {
|
class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
composeFocused: false,
|
composeFocused: false,
|
||||||
caretPosition: 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -60,7 +51,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
spoilerText: PropTypes.string,
|
spoilerText: PropTypes.string,
|
||||||
focusDate: PropTypes.instanceOf(Date),
|
focusDate: PropTypes.instanceOf(Date),
|
||||||
caretPosition: PropTypes.number,
|
caretPosition: PropTypes.number,
|
||||||
preselectDate: PropTypes.instanceOf(Date),
|
|
||||||
isSubmitting: PropTypes.bool,
|
isSubmitting: PropTypes.bool,
|
||||||
isChangingUpload: PropTypes.bool,
|
isChangingUpload: PropTypes.bool,
|
||||||
isUploading: PropTypes.bool,
|
isUploading: PropTypes.bool,
|
||||||
|
@ -87,9 +77,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleChange = (e) => {
|
handleChange = (e) => {
|
||||||
this.props.onChange(e.target.value);
|
this.props.onChange(e.target.value);
|
||||||
this.setState({
|
|
||||||
caretPosition: e.target.selectionStart,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComposeFocus = () => {
|
handleComposeFocus = () => {
|
||||||
|
@ -109,8 +96,10 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
return clickableAreaRef ? clickableAreaRef.current : this.form;
|
return clickableAreaRef ? clickableAreaRef.current : this.form;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldCollapse = (e) => {
|
isClickOutside = (e) => {
|
||||||
return ![
|
return ![
|
||||||
|
// List of elements that shouldn't collapse the composer when clicked
|
||||||
|
// FIXME: Make this less brittle
|
||||||
this.getClickableArea(),
|
this.getClickableArea(),
|
||||||
document.querySelector('.privacy-dropdown__dropdown'),
|
document.querySelector('.privacy-dropdown__dropdown'),
|
||||||
document.querySelector('.emoji-picker-dropdown__menu'),
|
document.querySelector('.emoji-picker-dropdown__menu'),
|
||||||
|
@ -119,7 +108,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
if (this.shouldCollapse(e)) {
|
if (this.isClickOutside(e)) {
|
||||||
this.handleClickOutside();
|
this.handleClickOutside();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,43 +157,25 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this.props.onChangeSpoilerText(e.target.value);
|
this.props.onChangeSpoilerText(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doFocus = () => {
|
||||||
|
if (!this.autosuggestTextarea) return;
|
||||||
|
this.autosuggestTextarea.textarea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor = (start, end = start) => {
|
||||||
|
if (!this.autosuggestTextarea) return;
|
||||||
|
this.autosuggestTextarea.textarea.setSelectionRange(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('click', this.handleClick, true);
|
document.addEventListener('click', this.handleClick, true);
|
||||||
|
this.setCursor(this.props.text.length); // Set cursor at end
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('click', this.handleClick, true);
|
document.removeEventListener('click', this.handleClick, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (!this.autosuggestTextarea) return;
|
|
||||||
|
|
||||||
// This statement does several things:
|
|
||||||
// - If we're beginning a reply, and,
|
|
||||||
// - Replying to zero or one users, places the cursor at the end of the textbox.
|
|
||||||
// - Replying to more than one user, selects any usernames past the first;
|
|
||||||
// this provides a convenient shortcut to drop everyone else from the conversation.
|
|
||||||
let selectionEnd, selectionStart;
|
|
||||||
if (this.props.focusDate !== prevProps.focusDate) {
|
|
||||||
|
|
||||||
if (this.props.preselectDate !== prevProps.preselectDate) {
|
|
||||||
selectionEnd = this.props.text.length;
|
|
||||||
selectionStart = this.props.text.search(/\s/) + 1;
|
|
||||||
} else if (typeof this.state.caretPosition === 'number') {
|
|
||||||
selectionStart = this.state.caretPosition;
|
|
||||||
selectionEnd = this.state.caretPosition;
|
|
||||||
}
|
|
||||||
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
|
||||||
this.autosuggestTextarea.textarea.focus();
|
|
||||||
} else {
|
|
||||||
if (this.props.preselectDate !== this.props.focusDate) {
|
|
||||||
selectionStart = selectionEnd = this.props.text.length + 1;
|
|
||||||
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
|
||||||
this.autosuggestTextarea.textarea.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAutosuggestTextarea = (c) => {
|
setAutosuggestTextarea = (c) => {
|
||||||
this.autosuggestTextarea = c;
|
this.autosuggestTextarea = c;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,13 @@ const mapStateToProps = state => ({
|
||||||
privacy: state.getIn(['compose', 'privacy']),
|
privacy: state.getIn(['compose', 'privacy']),
|
||||||
focusDate: state.getIn(['compose', 'focusDate']),
|
focusDate: state.getIn(['compose', 'focusDate']),
|
||||||
caretPosition: state.getIn(['compose', 'caretPosition']),
|
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
|
||||||
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
||||||
|
maxTootChars: state.getIn(['instance', 'max_toot_chars']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
|
@ -54,7 +54,6 @@ const initialState = ImmutableMap({
|
||||||
text: '',
|
text: '',
|
||||||
focusDate: null,
|
focusDate: null,
|
||||||
caretPosition: null,
|
caretPosition: null,
|
||||||
preselectDate: null,
|
|
||||||
in_reply_to: null,
|
in_reply_to: null,
|
||||||
is_composing: false,
|
is_composing: false,
|
||||||
is_submitting: false,
|
is_submitting: false,
|
||||||
|
@ -237,8 +236,7 @@ export default function compose(state = initialState, action) {
|
||||||
case COMPOSE_CHANGE:
|
case COMPOSE_CHANGE:
|
||||||
return state
|
return state
|
||||||
.set('text', action.text)
|
.set('text', action.text)
|
||||||
.set('idempotencyKey', uuid())
|
.set('idempotencyKey', uuid());
|
||||||
.set('focusDate', new Date());
|
|
||||||
case COMPOSE_COMPOSING_CHANGE:
|
case COMPOSE_COMPOSING_CHANGE:
|
||||||
return state.set('is_composing', action.value);
|
return state.set('is_composing', action.value);
|
||||||
case COMPOSE_REPLY:
|
case COMPOSE_REPLY:
|
||||||
|
@ -248,7 +246,6 @@ export default function compose(state = initialState, action) {
|
||||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||||
map.set('focusDate', new Date());
|
map.set('focusDate', new Date());
|
||||||
map.set('caretPosition', null);
|
map.set('caretPosition', null);
|
||||||
map.set('preselectDate', new Date());
|
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
|
|
||||||
if (action.status.get('spoiler_text').length > 0) {
|
if (action.status.get('spoiler_text').length > 0) {
|
||||||
|
|
Loading…
Reference in a new issue