diff --git a/css/_chat.scss b/css/_chat.scss index 2ecc31bf5..50e48a95a 100644 --- a/css/_chat.scss +++ b/css/_chat.scss @@ -30,12 +30,18 @@ height: calc(100% - 70px); } +#chat-conversation-container { + // extract message input height + height: calc(100% - 68px); + overflow: hidden; + position: relative; +} + #chatconversation { box-sizing: border-box; flex: 1; font-size: 10pt; - // extract message input height - height: calc(100% - 68px); + height: 100%; line-height: 20px; overflow: auto; padding: 16px; diff --git a/lang/main.json b/lang/main.json index ab57f40a8..9dac7530e 100644 --- a/lang/main.json +++ b/lang/main.json @@ -97,6 +97,7 @@ "messageAccessibleTitleMe": "me says:", "messageTo": "Private message to {{recipient}}", "messagebox": "Type a message", + "newMessages": "new messages", "nickname": { "popover": "Choose a nickname", "title": "Enter a nickname to use chat", diff --git a/react/features/chat/actionTypes.ts b/react/features/chat/actionTypes.ts index 034c275a1..dbf1eba24 100644 --- a/react/features/chat/actionTypes.ts +++ b/react/features/chat/actionTypes.ts @@ -112,4 +112,4 @@ export const SET_IS_POLL_TAB_FOCUSED = 'SET_IS_POLL_TAB_FOCUSED'; * type: REMOVE_LOBBY_CHAT_PARTICIPANT * } */ - export const REMOVE_LOBBY_CHAT_PARTICIPANT = 'REMOVE_LOBBY_CHAT_PARTICIPANT'; \ No newline at end of file + export const REMOVE_LOBBY_CHAT_PARTICIPANT = 'REMOVE_LOBBY_CHAT_PARTICIPANT'; diff --git a/react/features/chat/components/AbstractMessageContainer.js b/react/features/chat/components/AbstractMessageContainer.ts similarity index 76% rename from react/features/chat/components/AbstractMessageContainer.js rename to react/features/chat/components/AbstractMessageContainer.ts index 4500e3d0c..c8bb6b795 100644 --- a/react/features/chat/components/AbstractMessageContainer.js +++ b/react/features/chat/components/AbstractMessageContainer.ts @@ -1,13 +1,13 @@ -// @flow +import { Component } from 'react'; -import { PureComponent } from 'react'; +import { IMessage } from '../reducer'; -export type Props = { +export interface Props { /** * The messages array to render. */ - messages: Array + messages: IMessage[] } /** @@ -15,9 +15,9 @@ export type Props = { * * @augments PureComponent */ -export default class AbstractMessageContainer extends PureComponent

{ +export default class AbstractMessageContainer

extends Component { static defaultProps = { - messages: [] + messages: [] as IMessage[] }; /** @@ -29,8 +29,8 @@ export default class AbstractMessageContainer extends PureComponent

*/ _getMessagesGroupedBySender() { const messagesCount = this.props.messages.length; - const groups = []; - let currentGrouping = []; + const groups: IMessage[][] = []; + let currentGrouping: IMessage[] = []; let currentGroupParticipantId; for (let i = 0; i < messagesCount; i++) { diff --git a/react/features/chat/components/web/Chat.js b/react/features/chat/components/web/Chat.js index 678e8f256..6b4f7d1ab 100644 --- a/react/features/chat/components/web/Chat.js +++ b/react/features/chat/components/web/Chat.js @@ -44,34 +44,11 @@ class Chat extends AbstractChat { // Bind event handlers so they are only bound once for every instance. this._onChatTabKeyDown = this._onChatTabKeyDown.bind(this); - this._onChatInputResize = this._onChatInputResize.bind(this); this._onEscClick = this._onEscClick.bind(this); this._onPollsTabKeyDown = this._onPollsTabKeyDown.bind(this); this._onToggleChat = this._onToggleChat.bind(this); } - /** - * Implements {@code Component#componentDidMount}. - * - * @inheritdoc - */ - componentDidMount() { - this._scrollMessageContainerToBottom(true); - } - - /** - * Implements {@code Component#componentDidUpdate}. - * - * @inheritdoc - */ - componentDidUpdate(prevProps) { - if (this.props._messages !== prevProps._messages) { - this._scrollMessageContainerToBottom(true); - } else if (this.props._isOpen && !prevProps._isOpen) { - this._scrollMessageContainerToBottom(false); - } - } - /** * Implements React's {@link Component#render()}. * @@ -98,19 +75,6 @@ class Chat extends AbstractChat { ); } - _onChatInputResize: () => void; - - /** - * Callback invoked when {@code ChatInput} changes height. Preserves - * displaying the latest message if it is scrolled to. - * - * @private - * @returns {void} - */ - _onChatInputResize() { - this._messageContainerRef.current.maybeUpdateBottomScroll(); - } - _onChatTabKeyDown: (KeyboardEvent) => void; /** @@ -172,7 +136,7 @@ class Chat extends AbstractChat { if (_isPollsTabFocused) { return ( <> - {_isPollsEnabled && this._renderTabs()} + { _isPollsEnabled && this._renderTabs() }

{ return ( <> - {_isPollsEnabled && this._renderTabs()} + { _isPollsEnabled && this._renderTabs() }
+ messages = { this.props._messages } /> + @@ -222,8 +186,7 @@ class Chat extends AbstractChat { aria-controls = 'chat-panel' aria-label = { t('chat.tabs.chat') } aria-selected = { !_isPollsTabFocused } - className = { `chat-tab ${ - _isPollsTabFocused ? '' : 'chat-tab-focus' + className = { `chat-tab ${_isPollsTabFocused ? '' : 'chat-tab-focus' }` } id = 'chat-tab' onClick = { this._onToggleChatTab } @@ -232,21 +195,20 @@ class Chat extends AbstractChat { tabIndex = '0'> - {t('chat.tabs.chat')} + { t('chat.tabs.chat') } - {this.props._isPollsTabFocused + { this.props._isPollsTabFocused && _nbUnreadMessages > 0 && ( - {_nbUnreadMessages} + { _nbUnreadMessages } - )} + ) }
{ role = 'tab' tabIndex = '0'> - {t('chat.tabs.polls')} + { t('chat.tabs.polls') } - {!_isPollsTabFocused + { !_isPollsTabFocused && this.props._nbUnreadPolls > 0 && ( - {_nbUnreadPolls} + { _nbUnreadPolls } - )} + ) }
); } - /** - * Scrolls the chat messages so the latest message is visible. - * - * @param {boolean} withAnimation - Whether or not to show a scrolling - * animation. - * @private - * @returns {void} - */ - _scrollMessageContainerToBottom(withAnimation) { - if (this._messageContainerRef.current) { - this._messageContainerRef.current.scrollToBottom(withAnimation); - } - } - _onSendMessage: (string) => void; _onToggleChat: () => void; @@ -295,7 +243,6 @@ class Chat extends AbstractChat { } _onTogglePollsTab: () => void; _onToggleChatTab: () => void; - } export default translate(connect(_mapStateToProps)(Chat)); diff --git a/react/features/chat/components/web/ChatInput.tsx b/react/features/chat/components/web/ChatInput.tsx index ef70df72b..8e2baa5e1 100644 --- a/react/features/chat/components/web/ChatInput.tsx +++ b/react/features/chat/components/web/ChatInput.tsx @@ -1,5 +1,5 @@ /* eslint-disable lines-around-comment */ -import React, { Component } from 'react'; +import React, { Component, RefObject } from 'react'; import { WithTranslation } from 'react-i18next'; import type { Dispatch } from 'redux'; @@ -31,17 +31,10 @@ interface Props extends WithTranslation { */ 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 - } /** @@ -66,7 +59,7 @@ type State = { * @augments Component */ class ChatInput extends Component { - _textArea?: HTMLTextAreaElement; + _textArea?: RefObject; state = { message: '', @@ -82,7 +75,7 @@ class ChatInput extends Component { constructor(props: Props) { super(props); - this._textArea = undefined; + this._textArea = React.createRef(); // Bind event handlers so they are only bound once for every instance. this._onDetectSubmit = this._onDetectSubmit.bind(this); @@ -90,7 +83,6 @@ class ChatInput extends Component { this._onSmileySelect = this._onSmileySelect.bind(this); this._onSubmitMessage = this._onSubmitMessage.bind(this); this._toggleSmileysPanel = this._toggleSmileysPanel.bind(this); - this._setTextAreaRef = this._setTextAreaRef.bind(this); } /** @@ -101,7 +93,7 @@ class ChatInput extends Component { componentDidMount() { if (isMobileBrowser()) { // Ensure textarea is not focused when opening chat on mobile browser. - this._textArea && this._textArea.blur(); + this._textArea?.current && this._textArea.current.blur(); } } @@ -134,7 +126,7 @@ class ChatInput extends Component { onChange = { this._onMessageChange } onKeyPress = { this._onDetectSubmit } placeholder = { this.props.t('chat.messagebox') } - ref = { this._setTextAreaRef } + ref = { this._textArea } textarea = { true } value = { this.state.message } /> + ); +} + +export default translate(NewMessagesButton); diff --git a/react/features/chat/reducer.ts b/react/features/chat/reducer.ts index e53514618..31fbf14d5 100644 --- a/react/features/chat/reducer.ts +++ b/react/features/chat/reducer.ts @@ -27,7 +27,7 @@ const DEFAULT_STATE = { isLobbyChatActive: false }; -interface IMessage { +export interface IMessage { displayName: string; error?: Object; id: string;