diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index a790298ddc1fada14d76b9336652c005b368b504..7dfe11426d45a0714ee3daa6b7f7e9e095320f66 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -9,6 +9,7 @@ import ChatService from './service'; const CHAT_CONFIG = Meteor.settings.public.chat; const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id; const CHAT_CLEAR = CHAT_CONFIG.system_messages_keys.chat_clear; +const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; const CONNECTION_STATUS = 'online'; @@ -78,30 +79,33 @@ export default injectIntl(withTracker(({ intl }) => { sender: null, }; - const moderatorTime = time + 1; - const moderatorId = `moderator-msg-${moderatorTime}`; + let moderatorMsg; + if (amIModerator && welcomeProp.modOnlyMessage) { + const moderatorTime = time + 1; + const moderatorId = `moderator-msg-${moderatorTime}`; - const moderatorMsg = { - id: moderatorId, - content: [{ + moderatorMsg = { id: moderatorId, - text: welcomeProp.modOnlyMessage, + content: [{ + id: moderatorId, + text: welcomeProp.modOnlyMessage, + time: moderatorTime, + }], time: moderatorTime, - }], - time: moderatorTime, - sender: null, - }; + sender: null, + }; + } - const messagesBeforeWelcomeMsg = ChatService.reduceAndMapGroupMessages( + const messagesBeforeWelcomeMsg = ChatService.reduceAndDontMapGroupMessages( messages.filter(message => message.timestamp < time), ); - const messagesAfterWelcomeMsg = ChatService.reduceAndMapGroupMessages( + const messagesAfterWelcomeMsg = ChatService.reduceAndDontMapGroupMessages( messages.filter(message => message.timestamp >= time), ); const messagesFormated = messagesBeforeWelcomeMsg .concat(welcomeMsg) - .concat(amIModerator ? moderatorMsg : []) + .concat(moderatorMsg || []) .concat(messagesAfterWelcomeMsg); messages = messagesFormated.sort((a, b) => (a.time - b.time)); @@ -134,7 +138,7 @@ export default injectIntl(withTracker(({ intl }) => { } messages = messages.map((message) => { - if (message.sender) return message; + if (message.sender && message.sender !== SYSTEM_CHAT_TYPE) return message; return { ...message, diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx index d17355b9feebedd86cc6395131327da21d7589f0..2fb1352e411aeac162b870eb533d0bc2dfb66695 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -8,14 +8,13 @@ import { List, AutoSizer, CellMeasurer, CellMeasurerCache, } from 'react-virtualized'; import { styles } from './styles'; -import MessageListItem from './message-list-item/component'; +import MessageListItemContainer from './message-list-item/container'; const propTypes = { messages: PropTypes.arrayOf(PropTypes.object).isRequired, scrollPosition: PropTypes.number, chatId: PropTypes.string.isRequired, hasUnreadMessages: PropTypes.bool.isRequired, - partnerIsLoggedOut: PropTypes.bool.isRequired, handleScrollUpdate: PropTypes.func.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, @@ -42,22 +41,11 @@ const intlMessages = defineMessages({ }); class MessageList extends Component { - static getDerivedStateFromProps(props, state) { - const { messages: propMessages } = props; - const { messages: stateMessages } = state; - - if (propMessages.length !== 3 && propMessages.length < stateMessages.length) return null; - - return { - messages: propMessages, - }; - } - constructor(props) { super(props); this.cache = new CellMeasurerCache({ fixedWidth: true, - minWidth: 75, + minHeight: 18, }); this.shouldScrollBottom = false; @@ -74,10 +62,11 @@ class MessageList extends Component { shouldScrollToBottom: true, shouldScrollToPosition: false, scrollPosition: 0, - messages: [], }; this.listRef = null; + + this.lastWidth = 0; } componentDidMount() { @@ -87,52 +76,16 @@ class MessageList extends Component { this.scrollTo(scrollPosition); } - componentWillReceiveProps(nextProps) { - const { - chatId, - } = this.props; - - if (chatId !== nextProps.chatId) { - const { scrollArea } = this.state; - this.handleScrollUpdate(scrollArea.scrollTop, scrollArea); - } - } - - shouldComponentUpdate(nextProps, nextState) { - const { - chatId, - hasUnreadMessages, - partnerIsLoggedOut, - } = this.props; - - const { - scrollArea, - } = this.state; - - if (!scrollArea && nextState.scrollArea) return true; - - const switchingCorrespondent = chatId !== nextProps.chatId; - const hasNewUnreadMessages = hasUnreadMessages !== nextProps.hasUnreadMessages; - - // check if the messages include <user has left the meeting> - const lastMessage = nextProps.messages[nextProps.messages.length - 1]; - if (lastMessage) { - const userLeftIsDisplayed = lastMessage.id.includes('partner-disconnected'); - if (!(partnerIsLoggedOut && userLeftIsDisplayed)) return true; - } - - if (switchingCorrespondent || hasNewUnreadMessages) return true; - - return false; - } - - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps) { const { scrollPosition, chatId, + messages, } = this.props; const { scrollPosition: prevScrollPosition, + messages: prevMessages, + chatId: prevChatId, } = prevProps; const { @@ -140,35 +93,36 @@ class MessageList extends Component { shouldScrollToPosition, scrollPosition: scrollPositionState, shouldScrollToBottom, - messages, } = this.state; - const { messages: prevMessages } = prevState; - const compareChatId = prevProps.chatId !== chatId; - if (compareChatId) { + if (prevChatId !== chatId) { + this.cache.clearAll(); setTimeout(() => this.scrollTo(scrollPosition), 300); + } else if (prevMessages && messages) { + if (prevMessages.length > messages.length) { + // the chat has been cleared + this.cache.clearAll(); + } else { + prevMessages.forEach((prevMessage, index) => { + const newMessage = messages[index]; + if (newMessage.content.length > prevMessage.content.length + || newMessage.id !== prevMessage.id) { + this.resizeRow(index); + } + }); + } } if (!shouldScrollToBottom && !scrollPosition && prevScrollPosition) { this.scrollToBottom(); } - const prevLength = prevProps.messages && !!prevProps.messages.length - && prevProps.messages[prevProps.messages.length - 1].content.length; - - const currentLength = messages && !!messages.length - && messages[messages.length - 1].content.length; - - if (!compareChatId && (prevLength !== currentLength && currentLength > prevLength)) { - this.resizeRow(messages.length - 1); - } - if (shouldScrollToPosition && scrollArea.scrollTop === scrollPositionState) { this.setState({ shouldScrollToPosition: false }); } if (prevMessages.length < messages.length) { - this.resizeRow(prevMessages.length - 1); + // this.resizeRow(prevMessages.length - 1); // messages.forEach((i, idx) => this.resizeRow(idx)); } } @@ -217,7 +171,7 @@ class MessageList extends Component { this.cache.clear(idx); if (this.listRef) { this.listRef.recomputeRowHeights(idx); - this.listRef.forceUpdate(); + // this.listRef.forceUpdate(); } } @@ -242,6 +196,7 @@ class MessageList extends Component { } = this.props; const { scrollArea } = this.state; const message = messages[index]; + return ( <CellMeasurer key={key} @@ -250,28 +205,21 @@ class MessageList extends Component { parent={parent} rowIndex={index} > - { - ({ measure }) => ( - <span - style={style} - onLoad={measure} - key={key} - > - <MessageListItem - style={style} - handleReadMessage={handleReadMessage} - key={message.id} - messages={message.content} - user={message.sender} - time={message.time} - chatAreaId={id} - lastReadMessageTime={lastReadMessageTime} - deferredMeasurementCache={this.cache} - scrollArea={scrollArea} - /> - </span> - ) - } + <span + style={style} + key={key} + > + <MessageListItemContainer + style={style} + handleReadMessage={handleReadMessage} + key={key} + message={message} + messageId={message.id} + chatAreaId={id} + lastReadMessageTime={lastReadMessageTime} + scrollArea={scrollArea} + /> + </span> </CellMeasurer> ); } @@ -294,6 +242,7 @@ class MessageList extends Component { className={styles.unreadButton} color="primary" size="sm" + key="unread-messages" label={intl.formatMessage(intlMessages.moreMessages)} onClick={this.scrollToBottom} /> @@ -305,39 +254,28 @@ class MessageList extends Component { render() { const { - intl, - id, + messages, } = this.props; const { scrollArea, shouldScrollToBottom, shouldScrollToPosition, scrollPosition, - messages, } = this.state; - const isEmpty = messages.length === 0; return ( - <div className={styles.messageListWrapper}> - <div - style={ - { - height: '100%', - width: '100%', + [<div className={styles.messageListWrapper} key="chat-list"> + <AutoSizer> + {({ height, width }) => { + if (width !== this.lastWidth) { + this.lastWidth = width; + this.cache.clearAll(); } - } - role="log" - id={id} - aria-live="polite" - aria-atomic="false" - aria-relevant="additions" - aria-label={isEmpty ? intl.formatMessage(intlMessages.emptyLogLabel) : ''} - > - <AutoSizer> - {({ height, width }) => ( + + return ( <List ref={(ref) => { - if (ref != null) { + if (ref !== null) { this.listRef = ref; if (!scrollArea) { @@ -351,7 +289,7 @@ class MessageList extends Component { rowCount={messages.length} height={height} width={width} - overscanRowCount={15} + overscanRowCount={5} deferredMeasurementCache={this.cache} onScroll={this.handleScrollChange} scrollToIndex={shouldScrollToBottom ? messages.length - 1 : undefined} @@ -360,13 +298,13 @@ class MessageList extends Component { && (scrollArea && scrollArea.scrollHeight >= scrollPosition) ? scrollPosition : undefined } - scrollToAlignment="start" + scrollToAlignment="end" /> - )} - </AutoSizer> - </div> - {this.renderUnreadNotification()} - </div> + ); + }} + </AutoSizer> + </div>, + this.renderUnreadNotification()] ); } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx index c3afcca86c83a81e3f33c0260f2700503ac28a47..3b84e86d0760af0cdc6547751a6bc6f6bc73de37 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx @@ -45,21 +45,24 @@ class MessageListItem extends Component { scrollArea, messages, user, + messageId, } = this.props; const { scrollArea: nextScrollArea, messages: nextMessages, user: nextUser, + messageId: nextMessageId, } = nextProps; if (!scrollArea && nextScrollArea) return true; const hasNewMessage = messages.length !== nextMessages.length; + const hasIdChanged = messageId !== nextMessageId; const hasUserChanged = user && nextUser && (user.isModerator !== nextUser.isModerator || user.isOnline !== nextUser.isOnline); - return hasNewMessage || hasUserChanged; + return hasNewMessage || hasIdChanged || hasUserChanged; } renderSystemMessage() { @@ -71,27 +74,22 @@ class MessageListItem extends Component { return ( <div className={styles.item}> - <div className={styles.wrapper} ref={(ref) => { this.item = ref; }}> - <div className={styles.messages}> - <span> - {messages.map(message => ( - message.text !== '' - ? ( - <Message - className={(message.id ? styles.systemMessage : null)} - key={_.uniqueId('id-')} - text={message.text} - time={message.time} - chatAreaId={chatAreaId} - handleReadMessage={handleReadMessage} - /> - ) : null - ))} - </span> - </div> + <div className={styles.messages}> + {messages.map(message => ( + message.text !== '' + ? ( + <Message + className={(message.id ? styles.systemMessage : styles.systemMessageNoBorder)} + key={message.id ? message.id : _.uniqueId('id-')} + text={message.text} + time={message.time} + chatAreaId={chatAreaId} + handleReadMessage={handleReadMessage} + /> + ) : null + ))} </div> </div> - ); } @@ -117,7 +115,7 @@ class MessageListItem extends Component { return ( <div className={styles.item}> - <div className={styles.wrapper} ref={(ref) => { this.item = ref; }}> + <div className={styles.wrapper}> <div className={styles.avatarWrapper}> <UserAvatar className={styles.avatar} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx new file mode 100644 index 0000000000000000000000000000000000000000..50bf4d9f153ca959786ddd3d8ad2f785f0d8f95b --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/container.jsx @@ -0,0 +1,22 @@ +import React, { PureComponent } from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import MessageListItem from './component'; +import ChatService from '../../service'; + +class MessageListItemContainer extends PureComponent { + render() { + return ( + <MessageListItem {...this.props} /> + ); + } +} + +export default withTracker(({ message }) => { + const mappedMessage = ChatService.mapGroupMessage(message); + + return { + messages: mappedMessage.content, + user: mappedMessage.sender, + time: mappedMessage.time, + }; +})(MessageListItemContainer); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index 579b6d488302adaa79eb1ca0452715d90e1c023e..492fd1cc65ddda88252f09a38084ee2612f370a1 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -3,16 +3,12 @@ :root { --systemMessage-background-color: #F9FBFC; --systemMessage-border-color: #C5CDD4; + --systemMessage-font-color: var(--color-dark-grey); } .item { - margin: 1rem 0 1rem 0; + padding: calc(var(--line-height-computed) / 4) 0 calc(var(--line-height-computed) / 2) 0; font-size: var(--font-size-base); - margin-bottom: var(--line-height-computed); - - &:last-child { - margin-bottom: 0 !important; - } } .wrapper { @@ -33,7 +29,16 @@ border-radius: var(--border-radius); font-weight: var(--btn-font-weight); padding: var(--font-size-base); - margin-bottom: var(--line-height-computed); + //margin-bottom: var(--line-height-computed); + color: var(--systemMessage-font-color); + margin-top: 0px; + margin-bottom: 0px; +} + +.systemMessageNoBorder { + color: var(--systemMessage-font-color); + margin-top: 0px; + margin-bottom: 0px; } .avatarWrapper { diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 64cb0ea5317abc7865756f7e9efc825783646188..e4f3f5c0a1f7bc5c5f97cf3c5328c4c36e4d11a5 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -37,13 +37,13 @@ const getWelcomeProp = () => Meetings.findOne({ meetingId: Auth.meetingID }, const mapGroupMessage = (message) => { const mappedMessage = { - id: message._id, + id: message._id || message.id, content: message.content, - time: message.timestamp, + time: message.timestamp || message.time, sender: null, }; - if (message.sender !== SYSTEM_CHAT_TYPE) { + if (message.sender && message.sender !== SYSTEM_CHAT_TYPE) { const sender = Users.findOne({ userId: message.sender }, { fields: { @@ -97,6 +97,9 @@ const reduceGroupMessages = (previous, current) => { const reduceAndMapGroupMessages = messages => (messages .reduce(reduceGroupMessages, []).map(mapGroupMessage)); +const reduceAndDontMapGroupMessages = messages => (messages + .reduce(reduceGroupMessages, [])); + const getPublicGroupMessages = () => { const publicGroupMessages = GroupChatMsg.find({ meetingId: Auth.meetingID, @@ -128,7 +131,7 @@ const getPrivateGroupMessages = () => { }, { sort: ['timestamp'] }).fetch(); } - return reduceAndMapGroupMessages(messages, []); + return reduceAndDontMapGroupMessages(messages, []); }; const isChatLocked = (receiverID) => { @@ -322,7 +325,9 @@ const getLastMessageTimestampFromChatList = activeChats => activeChats .reduce(maxNumberReducer, 0); export default { + mapGroupMessage, reduceAndMapGroupMessages, + reduceAndDontMapGroupMessages, getPublicGroupMessages, getPrivateGroupMessages, getUser,