// @flow import React, { Component } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import type { Dispatch } from 'redux'; import { isMobileBrowser } from '../../../base/environment/utils'; import { translate } from '../../../base/i18n'; import { Icon, IconPlane, IconSmile } from '../../../base/icons'; import { connect } from '../../../base/redux'; import { areSmileysDisabled } from '../../functions'; import SmileysPanel from './SmileysPanel'; /** * The type of the React {@code Component} props of {@link ChatInput}. */ type Props = { /** * Invoked to send chat messages. */ dispatch: Dispatch, /** * Optional callback to invoke when the chat textarea has auto-resized to * fit overflowing text. */ onResize: ?Function, /** * Callback to invoke on message send. */ onSend: Function, /** * Invoked to obtain translated strings. */ t: Function, /** * Whether chat emoticons are disabled. */ _areSmileysDisabled: boolean }; /** * The type of the React {@code Component} state of {@link ChatInput}. */ type State = { /** * User provided nickname when the input text is provided in the view. */ message: string, /** * Whether or not the smiley selector is visible. */ showSmileysPanel: boolean }; /** * Implements a React Component for drafting and submitting a chat message. * * @extends Component */ class ChatInput extends Component { _textArea: ?HTMLTextAreaElement; state = { message: '', showSmileysPanel: false }; /** * Initializes a new {@code ChatInput} instance. * * @param {Object} props - The read-only properties with which the new * instance is to be initialized. */ constructor(props: Props) { super(props); this._textArea = null; // Bind event handlers so they are only bound once for every instance. this._onDetectSubmit = this._onDetectSubmit.bind(this); this._onMessageChange = this._onMessageChange.bind(this); this._onSmileySelect = this._onSmileySelect.bind(this); this._onSubmitMessage = this._onSubmitMessage.bind(this); this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this); this._onEscHandler = this._onEscHandler.bind(this); this._onToggleSmileysPanelKeyPress = this._onToggleSmileysPanelKeyPress.bind(this); this._onSubmitMessageKeyPress = this._onSubmitMessageKeyPress.bind(this); this._setTextAreaRef = this._setTextAreaRef.bind(this); } /** * Implements React's {@link Component#componentDidMount()}. * * @inheritdoc */ componentDidMount() { if (isMobileBrowser()) { // Ensure textarea is not focused when opening chat on mobile browser. this._textArea && this._textArea.blur(); } } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const smileysPanelClassName = `${this.state.showSmileysPanel ? 'show-smileys' : 'hide-smileys'} smileys-panel`; return (
{ this.props._areSmileysDisabled ? null : (
) }
); } /** * Place cursor focus on this component's text area. * * @private * @returns {void} */ _focus() { this._textArea && this._textArea.focus(); } _onSubmitMessage: () => void; /** * Submits the message to the chat window. * * @returns {void} */ _onSubmitMessage() { const trimmed = this.state.message.trim(); if (trimmed) { this.props.onSend(trimmed); this.setState({ message: '' }); // Keep the textarea in focus when sending messages via submit button. this._focus(); } } _onDetectSubmit: (Object) => void; /** * Detects if enter has been pressed. If so, submit the message in the chat * window. * * @param {string} event - Keyboard event. * @private * @returns {void} */ _onDetectSubmit(event) { if (event.key === 'Enter' && event.shiftKey === false && event.ctrlKey === false) { event.preventDefault(); event.stopPropagation(); this._onSubmitMessage(); } } _onSubmitMessageKeyPress: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _onSubmitMessageKeyPress(e) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onSubmitMessage(); } } _onMessageChange: (Object) => void; /** * Updates the known message the user is drafting. * * @param {string} event - Keyboard event. * @private * @returns {void} */ _onMessageChange(event) { this.setState({ message: event.target.value }); } _onSmileySelect: (string) => void; /** * Appends a selected smileys to the chat message draft. * * @param {string} smileyText - The value of the smiley to append to the * chat message. * @private * @returns {void} */ _onSmileySelect(smileyText) { if (smileyText) { this.setState({ message: `${this.state.message} ${smileyText}`, showSmileysPanel: false }); } else { this.setState({ showSmileysPanel: false }); } this._focus(); } _onToggleSmileysPanel: () => void; /** * Callback invoked to hide or show the smileys selector. * * @private * @returns {void} */ _onToggleSmileysPanel() { if (this.state.showSmileysPanel) { this._focus(); } this.setState({ showSmileysPanel: !this.state.showSmileysPanel }); } _onEscHandler: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _onEscHandler(e) { // Escape handling does not work in onKeyPress if (this.state.showSmileysPanel && e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); this._onToggleSmileysPanel(); } } _onToggleSmileysPanelKeyPress: (Object) => void; /** * KeyPress handler for accessibility. * * @param {Object} e - The key event to handle. * * @returns {void} */ _onToggleSmileysPanelKeyPress(e) { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onToggleSmileysPanel(); } } _setTextAreaRef: (?HTMLTextAreaElement) => void; /** * Sets the reference to the HTML TextArea. * * @param {HTMLAudioElement} textAreaElement - The HTML text area element. * @private * @returns {void} */ _setTextAreaRef(textAreaElement: ?HTMLTextAreaElement) { this._textArea = textAreaElement; } } /** * Function that maps parts of Redux state tree into component props. * * @param {Object} state - Redux state. * @private * @returns {{ * _areSmileysDisabled: boolean * }} */ const mapStateToProps = state => { return { _areSmileysDisabled: areSmileysDisabled(state) }; }; export default translate(connect(mapStateToProps)(ChatInput));