Fixed input handlers used by Textbox and SuggestionBox components to properly update when typing quickly (#3598)

Этот коммит содержится в:
Harrison Healey
2016-07-15 10:37:51 -04:00
коммит произвёл Joram Wilander
родитель 47535ed0c7
Коммит 500b2dce10
7 изменённых файлов: 128 добавлений и 168 удалений

Просмотреть файл

@@ -21,7 +21,7 @@ export default class SwitchChannelModal extends React.Component {
constructor() { constructor() {
super(); super();
this.onUserInput = this.onUserInput.bind(this); this.onInput = this.onInput.bind(this);
this.onShow = this.onShow.bind(this); this.onShow = this.onShow.bind(this);
this.onHide = this.onHide.bind(this); this.onHide = this.onHide.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
@@ -57,8 +57,8 @@ export default class SwitchChannelModal extends React.Component {
this.props.onHide(); this.props.onHide();
} }
onUserInput(message) { onInput(e) {
this.setState({text: message}); this.setState({text: e.target.value});
} }
handleKeyDown(e) { handleKeyDown(e) {
@@ -122,7 +122,7 @@ export default class SwitchChannelModal extends React.Component {
ref='search' ref='search'
className='form-control focused' className='form-control focused'
type='input' type='input'
onUserInput={this.onUserInput} onInput={this.onInput}
value={this.state.text} value={this.state.text}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
listComponent={SuggestionList} listComponent={SuggestionList}

Просмотреть файл

@@ -19,33 +19,14 @@ import * as GlobalActions from 'actions/global_actions.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes; const KeyCodes = Constants.KeyCodes;
const holders = defineMessages({
commentLength: {
id: 'create_comment.commentLength',
defaultMessage: 'Comment length must be less than {max} characters.'
},
comment: {
id: 'create_comment.comment',
defaultMessage: 'Add Comment'
},
addComment: {
id: 'create_comment.addComment',
defaultMessage: 'Add a comment...'
},
commentTitle: {
id: 'create_comment.commentTitle',
defaultMessage: 'Comment'
}
});
import React from 'react'; import React from 'react';
class CreateComment extends React.Component { export default class CreateComment extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -53,7 +34,7 @@ class CreateComment extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this); this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this); this.handleInput = this.handleInput.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleUploadClick = this.handleUploadClick.bind(this); this.handleUploadClick = this.handleUploadClick.bind(this);
this.handleUploadStart = this.handleUploadStart.bind(this); this.handleUploadStart = this.handleUploadStart.bind(this);
@@ -76,8 +57,7 @@ class CreateComment extends React.Component {
previews: draft.previews, previews: draft.previews,
submitting: false, submitting: false,
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
showPostDeletedModal: false, showPostDeletedModal: false
typing: false
}; };
} }
@@ -126,7 +106,15 @@ class CreateComment extends React.Component {
} }
if (post.message.length > Constants.CHARACTER_LIMIT) { if (post.message.length > Constants.CHARACTER_LIMIT) {
this.setState({postError: this.props.intl.formatMessage(holders.commentLength, {max: Constants.CHARACTER_LIMIT})}); this.setState({
postError: (
<FormattedMessage
id='create_comment.commentLength'
defaultMessage='Comment length must be less than {max} characters.'
values={{max: Constants.CHARACTER_LIMIT}}
/>
)
});
return; return;
} }
@@ -175,8 +163,7 @@ class CreateComment extends React.Component {
submitting: false, submitting: false,
postError: null, postError: null,
previews: [], previews: [],
serverError: null, serverError: null
typing: false
}); });
} }
@@ -192,15 +179,16 @@ class CreateComment extends React.Component {
GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId); GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId);
} }
handleUserInput(messageText) { handleInput(e) {
const messageText = e.target.value;
const draft = PostStore.getCommentDraft(this.props.rootId); const draft = PostStore.getCommentDraft(this.props.rootId);
draft.message = messageText; draft.message = messageText;
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
$('.post-right__scroll').parent().scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').parent().scrollTop($('.post-right__scroll')[0].scrollHeight);
const typing = messageText !== ''; this.setState({messageText});
this.setState({messageText, typing});
} }
handleKeyDown(e) { handleKeyDown(e) {
@@ -220,7 +208,7 @@ class CreateComment extends React.Component {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.RECEIVED_EDIT_POST, type: ActionTypes.RECEIVED_EDIT_POST,
refocusId: '#reply_textbox', refocusId: '#reply_textbox',
title: this.props.intl.formatMessage(holders.commentTitle), title: Utils.localizeMessage('create_comment.commentTitle', 'Comment'),
message: lastPost.message, message: lastPost.message,
postId: lastPost.id, postId: lastPost.id,
channelId: lastPost.channel_id, channelId: lastPost.channel_id,
@@ -319,7 +307,7 @@ class CreateComment extends React.Component {
componentWillReceiveProps(newProps) { componentWillReceiveProps(newProps) {
if (newProps.rootId !== this.props.rootId) { if (newProps.rootId !== this.props.rootId) {
const draft = PostStore.getCommentDraft(newProps.rootId); const draft = PostStore.getCommentDraft(newProps.rootId);
this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews, typing: false}); this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
} }
} }
@@ -395,7 +383,6 @@ class CreateComment extends React.Component {
); );
} }
const {formatMessage} = this.props.intl;
return ( return (
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className='post-create'> <div className='post-create'>
@@ -405,12 +392,11 @@ class CreateComment extends React.Component {
> >
<div className='post-body__cell'> <div className='post-body__cell'>
<Textbox <Textbox
onUserInput={this.handleUserInput} onInput={this.handleInput}
onKeyPress={this.commentMsgKeyPress} onKeyPress={this.commentMsgKeyPress}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
messageText={this.state.messageText} messageText={this.state.messageText}
typing={this.state.typing} createMessage={Utils.localizeMessage('create_comment.addComment', 'Add a comment...')}
createMessage={formatMessage(holders.addComment)}
initialText='' initialText=''
supportsCommands={false} supportsCommands={false}
id='reply_textbox' id='reply_textbox'
@@ -436,7 +422,7 @@ class CreateComment extends React.Component {
<input <input
type='button' type='button'
className='btn btn-primary comment-btn pull-right' className='btn btn-primary comment-btn pull-right'
value={formatMessage(holders.comment)} value={Utils.localizeMessage('create_comment.comment', 'Add Comment')}
onClick={this.handleSubmit} onClick={this.handleSubmit}
/> />
{uploadsInProgressText} {uploadsInProgressText}
@@ -455,9 +441,6 @@ class CreateComment extends React.Component {
} }
CreateComment.propTypes = { CreateComment.propTypes = {
intl: intlShape.isRequired,
channelId: React.PropTypes.string.isRequired, channelId: React.PropTypes.string.isRequired,
rootId: React.PropTypes.string.isRequired rootId: React.PropTypes.string.isRequired
}; };
export default injectIntl(CreateComment);

Просмотреть файл

@@ -23,7 +23,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedHTMLMessage} from 'react-intl'; import {FormattedHTMLMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6'; import {browserHistory} from 'react-router/es6';
const Preferences = Constants.Preferences; const Preferences = Constants.Preferences;
@@ -31,24 +31,9 @@ const TutorialSteps = Constants.TutorialSteps;
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes; const KeyCodes = Constants.KeyCodes;
const holders = defineMessages({
comment: {
id: 'create_post.comment',
defaultMessage: 'Comment'
},
post: {
id: 'create_post.post',
defaultMessage: 'Post'
},
write: {
id: 'create_post.write',
defaultMessage: 'Write a message...'
}
});
import React from 'react'; import React from 'react';
class CreatePost extends React.Component { export default class CreatePost extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -57,7 +42,7 @@ class CreatePost extends React.Component {
this.getCurrentDraft = this.getCurrentDraft.bind(this); this.getCurrentDraft = this.getCurrentDraft.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.postMsgKeyPress = this.postMsgKeyPress.bind(this); this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this); this.handleInput = this.handleInput.bind(this);
this.handleUploadClick = this.handleUploadClick.bind(this); this.handleUploadClick = this.handleUploadClick.bind(this);
this.handleUploadStart = this.handleUploadStart.bind(this); this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
@@ -87,8 +72,7 @@ class CreatePost extends React.Component {
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN, fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
showTutorialTip: false, showTutorialTip: false,
showPostDeletedModal: false, showPostDeletedModal: false
typing: false
}; };
} }
@@ -133,7 +117,7 @@ class CreatePost extends React.Component {
MessageHistoryStore.storeMessageInHistory(this.state.messageText); MessageHistoryStore.storeMessageInHistory(this.state.messageText);
this.setState({submitting: true, serverError: null, typing: false}); this.setState({submitting: true, serverError: null});
if (post.message.indexOf('/') === 0) { if (post.message.indexOf('/') === 0) {
ChannelActions.executeCommand( ChannelActions.executeCommand(
@@ -223,9 +207,9 @@ class CreatePost extends React.Component {
GlobalActions.emitLocalUserTypingEvent(this.state.channelId, ''); GlobalActions.emitLocalUserTypingEvent(this.state.channelId, '');
} }
handleUserInput(messageText) { handleInput(e) {
const typing = messageText !== ''; const messageText = e.target.value;
this.setState({messageText, typing}); this.setState({messageText});
const draft = PostStore.getCurrentDraft(); const draft = PostStore.getCurrentDraft();
draft.message = messageText; draft.message = messageText;
@@ -372,7 +356,7 @@ class CreatePost extends React.Component {
if (this.state.channelId !== channelId) { if (this.state.channelId !== channelId) {
const draft = this.getCurrentDraft(); const draft = this.getCurrentDraft();
this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, typing: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
} }
} }
@@ -408,8 +392,13 @@ class CreatePost extends React.Component {
if (!lastPost) { if (!lastPost) {
return; return;
} }
const {formatMessage} = this.props.intl;
var type = (lastPost.root_id && lastPost.root_id.length > 0) ? formatMessage(holders.comment) : formatMessage(holders.post); let type;
if (lastPost.root_id && lastPost.root_id.length > 0) {
type = Utils.localizeMessage('create_post.comment', 'Comment');
} else {
type = Utils.localizeMessage('create_post.post', 'Post');
}
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.RECEIVED_EDIT_POST, type: ActionTypes.RECEIVED_EDIT_POST,
@@ -519,12 +508,11 @@ class CreatePost extends React.Component {
<div className='post-create-body'> <div className='post-create-body'>
<div className='post-body__cell'> <div className='post-body__cell'>
<Textbox <Textbox
onUserInput={this.handleUserInput} onInput={this.handleInput}
onKeyPress={this.postMsgKeyPress} onKeyPress={this.postMsgKeyPress}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
messageText={this.state.messageText} messageText={this.state.messageText}
typing={this.state.typing} createMessage={Utils.localizeMessage('create_post.write', 'Write a message...')}
createMessage={this.props.intl.formatMessage(holders.write)}
channelId={this.state.channelId} channelId={this.state.channelId}
id='post_textbox' id='post_textbox'
ref='textbox' ref='textbox'
@@ -566,9 +554,3 @@ class CreatePost extends React.Component {
); );
} }
} }
CreatePost.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(CreatePost);

Просмотреть файл

@@ -11,31 +11,25 @@ import BrowserStore from 'stores/browser_store.jsx';
import PostStore from 'stores/post_store.jsx'; import PostStore from 'stores/post_store.jsx';
import MessageHistoryStore from 'stores/message_history_store.jsx'; import MessageHistoryStore from 'stores/message_history_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
var KeyCodes = Constants.KeyCodes; var KeyCodes = Constants.KeyCodes;
const holders = defineMessages({
editPost: {
id: 'edit_post.editPost',
defaultMessage: 'Edit the post...'
}
});
import React from 'react'; import React from 'react';
class EditPostModal extends React.Component { export default class EditPostModal extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleEdit = this.handleEdit.bind(this); this.handleEdit = this.handleEdit.bind(this);
this.handleEditInput = this.handleEditInput.bind(this);
this.handleEditKeyPress = this.handleEditKeyPress.bind(this); this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
this.handleEditPostEvent = this.handleEditPostEvent.bind(this); this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleInput = this.handleInput.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onModalHidden = this.onModalHidden.bind(this); this.onModalHidden = this.onModalHidden.bind(this);
this.onModalShow = this.onModalShow.bind(this); this.onModalShow = this.onModalShow.bind(this);
@@ -43,8 +37,9 @@ class EditPostModal extends React.Component {
this.onModalHide = this.onModalHide.bind(this); this.onModalHide = this.onModalHide.bind(this);
this.onModalKeyDown = this.onModalKeyDown.bind(this); this.onModalKeyDown = this.onModalKeyDown.bind(this);
this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: '', typing: false}; this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
} }
handleEdit() { handleEdit() {
var updatedPost = {}; var updatedPost = {};
updatedPost.message = this.state.editText.trim(); updatedPost.message = this.state.editText.trim();
@@ -82,10 +77,13 @@ class EditPostModal extends React.Component {
$('#edit_post').modal('hide'); $('#edit_post').modal('hide');
} }
handleEditInput(editMessage) {
const typing = editMessage !== ''; handleInput(e) {
this.setState({editText: editMessage, typing}); this.setState({
editText: e.target.value
});
} }
handleEditKeyPress(e) { handleEditKeyPress(e) {
if (!this.state.ctrlSend && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) { if (!this.state.ctrlSend && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
@@ -97,6 +95,7 @@ class EditPostModal extends React.Component {
this.handleSubmit(e); this.handleSubmit(e);
} }
} }
handleEditPostEvent(options) { handleEditPostEvent(options) {
this.setState({ this.setState({
editText: options.message || '', editText: options.message || '',
@@ -105,25 +104,28 @@ class EditPostModal extends React.Component {
post_id: options.postId || '', post_id: options.postId || '',
channel_id: options.channelId || '', channel_id: options.channelId || '',
comments: options.comments || 0, comments: options.comments || 0,
refocusId: options.refocusId || '', refocusId: options.refocusId || ''
typing: false
}); });
$(ReactDOM.findDOMNode(this.refs.modal)).modal('show'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
} }
handleKeyDown(e) { handleKeyDown(e) {
if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) { if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
this.handleEdit(e); this.handleEdit(e);
} }
} }
onPreferenceChange() { onPreferenceChange() {
this.setState({ this.setState({
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter') ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
}); });
} }
onModalHidden() { onModalHidden() {
this.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: '', typing: false}); this.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: '', typing: false});
} }
onModalShow(e) { onModalShow(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
if (!button) { if (!button) {
@@ -140,9 +142,11 @@ class EditPostModal extends React.Component {
typing: false typing: false
}); });
} }
onModalShown() { onModalShown() {
this.refs.editbox.focus(); this.refs.editbox.focus();
} }
onModalHide() { onModalHide() {
if (this.state.refocusId !== '') { if (this.state.refocusId !== '') {
setTimeout(() => { setTimeout(() => {
@@ -150,11 +154,13 @@ class EditPostModal extends React.Component {
}); });
} }
} }
onModalKeyDown(e) { onModalKeyDown(e) {
if (e.which === Constants.KeyCodes.ESCAPE) { if (e.which === Constants.KeyCodes.ESCAPE) {
e.stopPropagation(); e.stopPropagation();
} }
} }
componentDidMount() { componentDidMount() {
$(this.refs.modal).on('hidden.bs.modal', this.onModalHidden); $(this.refs.modal).on('hidden.bs.modal', this.onModalHidden);
$(this.refs.modal).on('show.bs.modal', this.onModalShow); $(this.refs.modal).on('show.bs.modal', this.onModalShow);
@@ -164,6 +170,7 @@ class EditPostModal extends React.Component {
PostStore.addEditPostListener(this.handleEditPostEvent); PostStore.addEditPostListener(this.handleEditPostEvent);
PreferenceStore.addChangeListener(this.onPreferenceChange); PreferenceStore.addChangeListener(this.onPreferenceChange);
} }
componentWillUnmount() { componentWillUnmount() {
$(this.refs.modal).off('hidden.bs.modal', this.onModalHidden); $(this.refs.modal).off('hidden.bs.modal', this.onModalHidden);
$(this.refs.modal).off('show.bs.modal', this.onModalShow); $(this.refs.modal).off('show.bs.modal', this.onModalShow);
@@ -173,6 +180,7 @@ class EditPostModal extends React.Component {
PostStore.removeEditPostListner(this.handleEditPostEvent); PostStore.removeEditPostListner(this.handleEditPostEvent);
PreferenceStore.removeChangeListener(this.onPreferenceChange); PreferenceStore.removeChangeListener(this.onPreferenceChange);
} }
render() { render() {
var error = (<div className='form-group'><br/></div>); var error = (<div className='form-group'><br/></div>);
if (this.state.error) { if (this.state.error) {
@@ -212,12 +220,11 @@ class EditPostModal extends React.Component {
</div> </div>
<div className='edit-modal-body modal-body'> <div className='edit-modal-body modal-body'>
<Textbox <Textbox
onUserInput={this.handleEditInput} onInput={this.handleInput}
onKeyPress={this.handleEditKeyPress} onKeyPress={this.handleEditKeyPress}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
messageText={this.state.editText} messageText={this.state.editText}
typing={this.state.typing} createMessage={Utils.localizeMessage('edit_post.editPost', 'Edit the post...')}
createMessage={this.props.intl.formatMessage(holders.editPost)}
supportsCommands={false} supportsCommands={false}
id='edit_textbox' id='edit_textbox'
ref='editbox' ref='editbox'
@@ -252,9 +259,3 @@ class EditPostModal extends React.Component {
); );
} }
} }
EditPostModal.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(EditPostModal);

Просмотреть файл

@@ -3,7 +3,7 @@
import $ from 'jquery'; import $ from 'jquery';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import client from 'utils/web_client.jsx'; import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import SearchStore from 'stores/search_store.jsx'; import SearchStore from 'stores/search_store.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
@@ -11,30 +11,23 @@ import SuggestionBox from './suggestion/suggestion_box.jsx';
import SearchChannelProvider from './suggestion/search_channel_provider.jsx'; import SearchChannelProvider from './suggestion/search_channel_provider.jsx';
import SearchSuggestionList from './suggestion/search_suggestion_list.jsx'; import SearchSuggestionList from './suggestion/search_suggestion_list.jsx';
import SearchUserProvider from './suggestion/search_user_provider.jsx'; import SearchUserProvider from './suggestion/search_user_provider.jsx';
import * as utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
import {Popover} from 'react-bootstrap'; import {Popover} from 'react-bootstrap';
const holders = defineMessages({
search: {
id: 'search_bar.search',
defaultMessage: 'Search'
}
});
import React from 'react'; import React from 'react';
class SearchBar extends React.Component { export default class SearchBar extends React.Component {
constructor() { constructor() {
super(); super();
this.mounted = false; this.mounted = false;
this.onListenerChange = this.onListenerChange.bind(this); this.onListenerChange = this.onListenerChange.bind(this);
this.handleUserInput = this.handleUserInput.bind(this); this.handleInput = this.handleInput.bind(this);
this.handleUserFocus = this.handleUserFocus.bind(this); this.handleUserFocus = this.handleUserFocus.bind(this);
this.handleUserBlur = this.handleUserBlur.bind(this); this.handleUserBlur = this.handleUserBlur.bind(this);
this.performSearch = this.performSearch.bind(this); this.performSearch = this.performSearch.bind(this);
@@ -46,24 +39,28 @@ class SearchBar extends React.Component {
this.suggestionProviders = [new SearchChannelProvider(), new SearchUserProvider()]; this.suggestionProviders = [new SearchChannelProvider(), new SearchUserProvider()];
} }
getSearchTermStateFromStores() { getSearchTermStateFromStores() {
var term = SearchStore.getSearchTerm() || ''; var term = SearchStore.getSearchTerm() || '';
return { return {
searchTerm: term searchTerm: term
}; };
} }
componentDidMount() { componentDidMount() {
SearchStore.addSearchTermChangeListener(this.onListenerChange); SearchStore.addSearchTermChangeListener(this.onListenerChange);
this.mounted = true; this.mounted = true;
} }
componentWillUnmount() { componentWillUnmount() {
SearchStore.removeSearchTermChangeListener(this.onListenerChange); SearchStore.removeSearchTermChangeListener(this.onListenerChange);
this.mounted = false; this.mounted = false;
} }
onListenerChange(doSearch, isMentionSearch) { onListenerChange(doSearch, isMentionSearch) {
if (this.mounted) { if (this.mounted) {
var newState = this.getSearchTermStateFromStores(); var newState = this.getSearchTermStateFromStores();
if (!utils.areObjectsEqual(newState, this.state)) { if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState); this.setState(newState);
} }
if (doSearch) { if (doSearch) {
@@ -71,9 +68,11 @@ class SearchBar extends React.Component {
} }
} }
} }
clearFocus() { clearFocus() {
$('.search-bar__container').removeClass('focused'); $('.search-bar__container').removeClass('focused');
} }
handleClose(e) { handleClose(e) {
e.preventDefault(); e.preventDefault();
@@ -94,30 +93,34 @@ class SearchBar extends React.Component {
postId: null postId: null
}); });
} }
handleUserInput(text) {
var term = text; handleInput(e) {
var term = e.target.value;
SearchStore.storeSearchTerm(term); SearchStore.storeSearchTerm(term);
SearchStore.emitSearchTermChange(false); SearchStore.emitSearchTermChange(false);
this.setState({searchTerm: term}); this.setState({searchTerm: term});
} }
handleUserBlur() { handleUserBlur() {
this.setState({focused: false}); this.setState({focused: false});
} }
handleUserFocus() { handleUserFocus() {
$('.search-bar__container').addClass('focused'); $('.search-bar__container').addClass('focused');
this.setState({focused: true}); this.setState({focused: true});
} }
performSearch(terms, isMentionSearch) { performSearch(terms, isMentionSearch) {
if (terms.length) { if (terms.length) {
this.setState({isSearching: true}); this.setState({isSearching: true});
client.search( Client.search(
terms, terms,
isMentionSearch, isMentionSearch,
(data) => { (data) => {
this.setState({isSearching: false}); this.setState({isSearching: false});
if (utils.isMobile()) { if (Utils.isMobile()) {
ReactDOM.findDOMNode(this.refs.search).value = ''; ReactDOM.findDOMNode(this.refs.search).value = '';
} }
@@ -134,6 +137,7 @@ class SearchBar extends React.Component {
); );
} }
} }
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
this.performSearch(this.state.searchTerm.trim()); this.performSearch(this.state.searchTerm.trim());
@@ -178,11 +182,11 @@ class SearchBar extends React.Component {
<SuggestionBox <SuggestionBox
ref='search' ref='search'
className='form-control search-bar' className='form-control search-bar'
placeholder={this.props.intl.formatMessage(holders.search)} placeholder={Utils.localizeMessage('search_bar.search', 'Search')}
value={this.state.searchTerm} value={this.state.searchTerm}
onFocus={this.handleUserFocus} onFocus={this.handleUserFocus}
onBlur={this.handleUserBlur} onBlur={this.handleUserBlur}
onUserInput={this.handleUserInput} onInput={this.handleInput}
listComponent={SearchSuggestionList} listComponent={SearchSuggestionList}
providers={this.suggestionProviders} providers={this.suggestionProviders}
type='search' type='search'
@@ -203,10 +207,3 @@ class SearchBar extends React.Component {
); );
} }
} }
SearchBar.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(SearchBar);

Просмотреть файл

@@ -21,8 +21,8 @@ export default class SuggestionBox extends React.Component {
this.handleDocumentClick = this.handleDocumentClick.bind(this); this.handleDocumentClick = this.handleDocumentClick.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleCompleteWord = this.handleCompleteWord.bind(this); this.handleCompleteWord = this.handleCompleteWord.bind(this);
this.handleInput = this.handleInput.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.handlePretextChanged = this.handlePretextChanged.bind(this); this.handlePretextChanged = this.handlePretextChanged.bind(this);
@@ -70,27 +70,24 @@ export default class SuggestionBox extends React.Component {
} }
} }
handleChange(e) { handleInput(e) {
const textbox = ReactDOM.findDOMNode(this.refs.textbox); const textbox = ReactDOM.findDOMNode(this.refs.textbox);
const caret = Utils.getCaretPosition(textbox); const caret = Utils.getCaretPosition(textbox);
const pretext = textbox.value.substring(0, caret); const pretext = textbox.value.substring(0, caret);
GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext); GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
if (this.props.onUserInput) { if (this.props.onInput) {
this.props.onUserInput(textbox.value); this.props.onInput(e);
}
if (this.props.onChange) {
this.props.onChange(e);
} }
} }
handleCompleteWord(term, matchedPretext) { handleCompleteWord(term, matchedPretext) {
const textbox = ReactDOM.findDOMNode(this.refs.textbox); const textbox = this.refs.textbox;
const caret = Utils.getCaretPosition(textbox); const caret = Utils.getCaretPosition(textbox);
const text = textbox.value; const text = textbox.value;
const pretext = text.substring(0, caret); const pretext = text.substring(0, caret);
let prefix; let prefix;
if (pretext.endsWith(matchedPretext)) { if (pretext.endsWith(matchedPretext)) {
prefix = pretext.substring(0, pretext.length - matchedPretext.length); prefix = pretext.substring(0, pretext.length - matchedPretext.length);
@@ -104,10 +101,17 @@ export default class SuggestionBox extends React.Component {
const suffix = text.substring(caret); const suffix = text.substring(caret);
if (this.props.onUserInput) { this.refs.textbox.value = prefix + term + ' ' + suffix;
this.props.onUserInput(prefix + term + ' ' + suffix);
if (this.props.onInput) {
// fake an input event to send back to parent components
const e = {
target: this.refs.textbox
};
// don't call handleInput or we'll get into an event loop
this.props.onInput(e);
} }
this.refs.textbox.value = (prefix + term + ' ' + suffix);
// set the caret position after the next rendering // set the caret position after the next rendering
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
@@ -144,18 +148,15 @@ export default class SuggestionBox extends React.Component {
} }
render() { render() {
const newProps = Object.assign({}, this.props, {
onChange: this.handleChange,
onKeyDown: this.handleKeyDown
});
let textbox = null; let textbox = null;
if (this.props.type === 'input') { if (this.props.type === 'input') {
textbox = ( textbox = (
<input <input
ref='textbox' ref='textbox'
type='text' type='text'
{...newProps} {...this.props}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
/> />
); );
} else if (this.props.type === 'search') { } else if (this.props.type === 'search') {
@@ -163,7 +164,9 @@ export default class SuggestionBox extends React.Component {
<input <input
ref='textbox' ref='textbox'
type='search' type='search'
{...newProps} {...this.props}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
/> />
); );
} else if (this.props.type === 'textarea') { } else if (this.props.type === 'textarea') {
@@ -171,7 +174,9 @@ export default class SuggestionBox extends React.Component {
<TextareaAutosize <TextareaAutosize
id={this.suggestionId} id={this.suggestionId}
ref='textbox' ref='textbox'
{...newProps} {...this.props}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
/> />
); );
} }
@@ -213,12 +218,10 @@ SuggestionBox.propTypes = {
listComponent: React.PropTypes.func.isRequired, listComponent: React.PropTypes.func.isRequired,
type: React.PropTypes.oneOf(['input', 'textarea', 'search']).isRequired, type: React.PropTypes.oneOf(['input', 'textarea', 'search']).isRequired,
value: React.PropTypes.string.isRequired, value: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func,
providers: React.PropTypes.arrayOf(React.PropTypes.object), providers: React.PropTypes.arrayOf(React.PropTypes.object),
listStyle: React.PropTypes.string, listStyle: React.PropTypes.string,
// explicitly name any input event handlers we override and need to manually call // explicitly name any input event handlers we override and need to manually call
onChange: React.PropTypes.func, onInput: React.PropTypes.func,
onKeyDown: React.PropTypes.func, onKeyDown: React.PropTypes.func
onHeightChange: React.PropTypes.func
}; };

Просмотреть файл

@@ -176,11 +176,6 @@ export default class Textbox extends React.Component {
</div> </div>
); );
const otherProps = {};
if (!this.props.typing) {
otherProps.value = this.props.messageText;
}
return ( return (
<div <div
ref='wrapper' ref='wrapper'
@@ -194,7 +189,7 @@ export default class Textbox extends React.Component {
spellCheck='true' spellCheck='true'
maxLength={Constants.MAX_POST_LEN} maxLength={Constants.MAX_POST_LEN}
placeholder={this.props.createMessage} placeholder={this.props.createMessage}
onUserInput={this.props.onUserInput} onInput={this.props.onInput}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
onHeightChange={this.handleHeightChange} onHeightChange={this.handleHeightChange}
@@ -202,7 +197,7 @@ export default class Textbox extends React.Component {
listComponent={SuggestionList} listComponent={SuggestionList}
providers={this.suggestionProviders} providers={this.suggestionProviders}
channelId={this.props.channelId} channelId={this.props.channelId}
{...otherProps} value={this.props.messageText}
/> />
<div <div
ref='preview' ref='preview'
@@ -239,10 +234,9 @@ Textbox.propTypes = {
id: React.PropTypes.string.isRequired, id: React.PropTypes.string.isRequired,
channelId: React.PropTypes.string, channelId: React.PropTypes.string,
messageText: React.PropTypes.string.isRequired, messageText: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func.isRequired, onInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired, onKeyPress: React.PropTypes.func.isRequired,
createMessage: React.PropTypes.string.isRequired, createMessage: React.PropTypes.string.isRequired,
onKeyDown: React.PropTypes.func, onKeyDown: React.PropTypes.func,
supportsCommands: React.PropTypes.bool.isRequired, supportsCommands: React.PropTypes.bool.isRequired
typing: React.PropTypes.bool.isRequired
}; };