jiti-meet/react/features/chat/components/web/ChatInput.js

377 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @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<any>,
/**
* 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.
*
* @augments Component
*/
class ChatInput extends Component<Props, State> {
_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 (
<div className = { `chat-input-container${this.state.message.trim().length ? ' populated' : ''}` }>
<div id = 'chat-input' >
{ this.props._areSmileysDisabled ? null : (
<div className = 'smiley-input'>
<div id = 'smileysarea'>
<div id = 'smileys'>
<div
aria-expanded = { this.state.showSmileysPanel }
aria-haspopup = 'smileysContainer'
aria-label = { this.props.t('chat.smileysPanel') }
className = 'smiley-button'
onClick = { this._onToggleSmileysPanel }
onKeyDown = { this._onEscHandler }
onKeyPress = { this._onToggleSmileysPanelKeyPress }
role = 'button'
tabIndex = { 0 }>
<Icon src = { IconSmile } />
</div>
</div>
</div>
<div
className = { smileysPanelClassName } >
<SmileysPanel
onSmileySelect = { this._onSmileySelect } />
</div>
</div>
) }
<div className = 'usrmsg-form'>
<TextareaAutosize
autoComplete = 'off'
autoFocus = { true }
id = 'usermsg'
maxRows = { 5 }
onChange = { this._onMessageChange }
onHeightChange = { this.props.onResize }
onKeyDown = { this._onDetectSubmit }
placeholder = { this.props.t('chat.messagebox') }
ref = { this._setTextAreaRef }
tabIndex = { 0 }
value = { this.state.message } />
</div>
<div className = 'send-button-container'>
<div
aria-label = { this.props.t('chat.sendButton') }
className = 'send-button'
onClick = { this._onSubmitMessage }
onKeyPress = { this._onSubmitMessageKeyPress }
role = 'button'
tabIndex = { this.state.message.trim() ? 0 : -1 } >
<Icon src = { IconPlane } />
</div>
</div>
</div>
</div>
);
}
/**
* 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) {
// Composition events used to add accents to characters
// despite their absence from standard US keyboards,
// to build up logograms of many Asian languages
// from their base components or categories and so on.
if (event.isComposing || event.keyCode === 229) {
// keyCode 229 means that user pressed some button,
// but input method is still processing that.
// This is a standard behavior for some input methods
// like entering japanese or сhinese hieroglyphs.
return;
}
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));