Merge pull request #6545 from vitormateusalmeida/issue-6430
Fix chat alert (push and audio) #6430
This commit is contained in:
commit
216c38b08d
@ -40,6 +40,7 @@ export default function handleValidateAuthToken({ body }, meetingId) {
|
||||
$set: {
|
||||
validated: valid,
|
||||
approved: !waitForApproval,
|
||||
loginTime: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -78,7 +78,6 @@ export default function addUser(meetingId, user) {
|
||||
roles: [ROLE_VIEWER.toLowerCase()],
|
||||
sortName: user.name.trim().toLowerCase(),
|
||||
color,
|
||||
logTime: Date.now(),
|
||||
},
|
||||
flat(user),
|
||||
),
|
||||
|
@ -1,41 +1,33 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
play: PropTypes.bool.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
class ChatAudioAlert extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.audio = new Audio(`${Meteor.settings.public.app.basename}/resources/sounds/notify.mp3`);
|
||||
|
||||
this.handleAudioLoaded = this.handleAudioLoaded.bind(this);
|
||||
this.playAudio = this.playAudio.bind(this);
|
||||
this.componentDidUpdate = _.debounce(this.playAudio, 2000);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.audio.addEventListener('loadedmetadata', this.handleAudioLoaded);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.count > this.props.count;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.audio.removeEventListener('loadedmetadata', this.handleAudioLoaded);
|
||||
}
|
||||
|
||||
handleAudioLoaded() {
|
||||
this.componentDidUpdate = _.debounce(this.playAudio, this.audio.duration * 1000);
|
||||
this.componentDidUpdate = this.playAudio;
|
||||
}
|
||||
|
||||
playAudio() {
|
||||
if (!this.props.play) return;
|
||||
|
||||
const { play } = this.props;
|
||||
if (!play) return;
|
||||
this.audio.play();
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,10 @@ import Service from '../service';
|
||||
import { styles } from '../styles';
|
||||
|
||||
const propTypes = {
|
||||
disableNotify: PropTypes.bool.isRequired,
|
||||
openChats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
disableAudio: PropTypes.bool.isRequired,
|
||||
pushAlertDisabled: PropTypes.bool.isRequired,
|
||||
activeChats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
audioAlertDisabled: PropTypes.bool.isRequired,
|
||||
joinTimestamp: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -34,51 +35,105 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const PUBLIC_KEY = 'public';
|
||||
const PRIVATE_KEY = 'private';
|
||||
const ALERT_INTERVAL = 5000; // 5 seconds
|
||||
const ALERT_DURATION = 4000; // 4 seconds
|
||||
|
||||
class ChatAlert extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
notified: Service.getNotified(PRIVATE_KEY),
|
||||
publicNotified: Service.getNotified(PUBLIC_KEY),
|
||||
alertEnabledTimestamp: props.joinTimestamp,
|
||||
lastAlertTimestampByChat: {},
|
||||
pendingNotificationsByChat: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
openChats,
|
||||
disableNotify,
|
||||
pushAlertDisabled,
|
||||
activeChats,
|
||||
joinTimestamp,
|
||||
} = this.props;
|
||||
|
||||
if (nextProps.disableNotify === false && disableNotify === true) {
|
||||
const loadMessages = {};
|
||||
openChats
|
||||
.forEach((c) => {
|
||||
loadMessages[c.id] = c.unreadCounter;
|
||||
});
|
||||
this.setState({ notified: loadMessages });
|
||||
const {
|
||||
alertEnabledTimestamp,
|
||||
lastAlertTimestampByChat,
|
||||
pendingNotificationsByChat,
|
||||
} = this.state;
|
||||
// Avoid alerting messages received before enabling alerts
|
||||
if (prevProps.pushAlertDisabled && !pushAlertDisabled) {
|
||||
const newAlertEnabledTimestamp = Service.getLastMessageTimestampFromChatList(activeChats);
|
||||
this.setAlertEnabledTimestamp(newAlertEnabledTimestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
const notifiedToClear = {};
|
||||
openChats
|
||||
.filter(c => c.unreadCounter === 0)
|
||||
.forEach((c) => {
|
||||
notifiedToClear[c.id] = 0;
|
||||
|
||||
// Keep track of messages that was not alerted yet
|
||||
const unalertedMessagesByChatId = {};
|
||||
|
||||
activeChats
|
||||
.filter(chat => chat.id !== Session.get('idChatOpen'))
|
||||
.filter(chat => chat.unreadCounter > 0)
|
||||
.forEach((chat) => {
|
||||
const chatId = (chat.id === 'public') ? 'MAIN-PUBLIC-GROUP-CHAT' : chat.id;
|
||||
const thisChatUnreadMessages = UnreadMessages.getUnreadMessages(chatId);
|
||||
|
||||
unalertedMessagesByChatId[chatId] = thisChatUnreadMessages.filter((msg) => {
|
||||
const messageChatId = (msg.chatId === 'MAIN-PUBLIC-GROUP-CHAT') ? msg.chatId : msg.sender;
|
||||
const retorno = (msg
|
||||
&& msg.timestamp > alertEnabledTimestamp
|
||||
&& msg.timestamp > joinTimestamp
|
||||
&& msg.timestamp > (lastAlertTimestampByChat[messageChatId] || 0)
|
||||
);
|
||||
return retorno;
|
||||
});
|
||||
|
||||
if (!unalertedMessagesByChatId[chatId].length) delete unalertedMessagesByChatId[chatId];
|
||||
});
|
||||
|
||||
this.setState(({ notified }) => ({
|
||||
notified: {
|
||||
...notified,
|
||||
...notifiedToClear,
|
||||
},
|
||||
}), () => {
|
||||
Service.setNotified(PRIVATE_KEY, this.state.notified);
|
||||
const lastUnalertedMessageTimestampByChat = {};
|
||||
Object.keys(unalertedMessagesByChatId).forEach((chatId) => {
|
||||
lastUnalertedMessageTimestampByChat[chatId] = unalertedMessagesByChatId[chatId]
|
||||
.reduce(Service.maxTimestampReducer, 0);
|
||||
});
|
||||
|
||||
// Keep track of chats that need to be alerted now (considering alert interval)
|
||||
const chatsWithPendingAlerts = Object.keys(lastUnalertedMessageTimestampByChat)
|
||||
.filter(chatId => lastUnalertedMessageTimestampByChat[chatId]
|
||||
> ((lastAlertTimestampByChat[chatId] || 0) + ALERT_INTERVAL)
|
||||
&& !(chatId in pendingNotificationsByChat));
|
||||
|
||||
if (!chatsWithPendingAlerts.length) return;
|
||||
|
||||
const newPendingNotificationsByChat = Object.assign({},
|
||||
...chatsWithPendingAlerts.map(chatId => ({ [chatId]: unalertedMessagesByChatId[chatId] })));
|
||||
|
||||
// Mark messages as alerted
|
||||
const newLastAlertTimestampByChat = { ...lastAlertTimestampByChat };
|
||||
|
||||
chatsWithPendingAlerts.forEach(
|
||||
(chatId) => {
|
||||
newLastAlertTimestampByChat[chatId] = lastUnalertedMessageTimestampByChat[chatId];
|
||||
},
|
||||
);
|
||||
|
||||
if (!pushAlertDisabled) {
|
||||
this.setChatMessagesState(newPendingNotificationsByChat, newLastAlertTimestampByChat);
|
||||
}
|
||||
}
|
||||
|
||||
setAlertEnabledTimestamp(newAlertEnabledTimestamp) {
|
||||
const { alertEnabledTimestamp } = this.state;
|
||||
if (newAlertEnabledTimestamp > 0 && alertEnabledTimestamp !== newAlertEnabledTimestamp) {
|
||||
this.setState({ alertEnabledTimestamp: newAlertEnabledTimestamp });
|
||||
}
|
||||
}
|
||||
|
||||
setChatMessagesState(pendingNotificationsByChat, lastAlertTimestampByChat) {
|
||||
this.setState({ pendingNotificationsByChat, lastAlertTimestampByChat });
|
||||
}
|
||||
|
||||
|
||||
mapContentText(message) {
|
||||
const {
|
||||
intl,
|
||||
@ -86,12 +141,13 @@ class ChatAlert extends PureComponent {
|
||||
const contentMessage = message
|
||||
.map((content) => {
|
||||
if (content.text === 'PUBLIC_CHAT_CLEAR') return intl.formatMessage(intlMessages.publicChatClear);
|
||||
/* this code is to remove html tags that come in the server's messangens */
|
||||
/* this code is to remove html tags that come in the server's messages */
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.text;
|
||||
const textWithoutTag = tempDiv.innerText;
|
||||
return textWithoutTag;
|
||||
});
|
||||
|
||||
return contentMessage;
|
||||
}
|
||||
|
||||
@ -102,155 +158,64 @@ class ChatAlert extends PureComponent {
|
||||
<div className={styles.contentMessage}>
|
||||
{
|
||||
this.mapContentText(message)
|
||||
.reduce((acc, text) => [...acc, (<br />), text], []).splice(1)
|
||||
.reduce((acc, text) => [...acc, (<br key={_.uniqueId('br_')} />), text], [])
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
notifyPrivateChat() {
|
||||
const {
|
||||
disableNotify,
|
||||
openChats,
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
if (disableNotify) return;
|
||||
|
||||
const hasUnread = ({ unreadCounter }) => unreadCounter > 0;
|
||||
const isNotNotified = ({ id, unreadCounter }) => unreadCounter !== this.state.notified[id];
|
||||
const isPrivate = ({ id }) => id !== PUBLIC_KEY;
|
||||
const thisChatClosed = ({ id }) => !Session.equals('idChatOpen', id);
|
||||
|
||||
const chatsNotify = openChats
|
||||
.filter(hasUnread)
|
||||
.filter(isNotNotified)
|
||||
.filter(isPrivate)
|
||||
.filter(thisChatClosed)
|
||||
.map(({
|
||||
id,
|
||||
name,
|
||||
unreadCounter,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
name,
|
||||
unreadCounter,
|
||||
id,
|
||||
message: intl.formatMessage(intlMessages.appToastChatPrivate),
|
||||
}));
|
||||
|
||||
return (
|
||||
<span>
|
||||
{
|
||||
chatsNotify.map(({ id, message, name }) => {
|
||||
const getChatmessages = UnreadMessages.getUnreadMessages(id)
|
||||
.filter(({ fromTime, fromUserId }) => fromTime > (this.state.notified[fromUserId] || 0));
|
||||
|
||||
const reduceMessages = Service
|
||||
.reduceAndMapGroupMessages(getChatmessages);
|
||||
|
||||
if (!reduceMessages.length) return null;
|
||||
|
||||
const flatMessages = _.flatten(reduceMessages
|
||||
.map(msg => this.createMessage(name, msg.content)));
|
||||
const limitingMessages = flatMessages;
|
||||
|
||||
return (<ChatPushAlert
|
||||
key={_.uniqueId('id-')}
|
||||
chatId={id}
|
||||
content={limitingMessages}
|
||||
message={<span >{message}</span>}
|
||||
onOpen={() => {
|
||||
this.setState(({ notified }) => ({
|
||||
notified: {
|
||||
...notified,
|
||||
[id]: new Date().getTime(),
|
||||
},
|
||||
}), () => {
|
||||
Service.setNotified(PRIVATE_KEY, this.state.notified);
|
||||
});
|
||||
}}
|
||||
/>);
|
||||
})
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
notifyPublicChat() {
|
||||
const {
|
||||
publicUserId,
|
||||
intl,
|
||||
disableNotify,
|
||||
} = this.props;
|
||||
|
||||
const publicUnread = UnreadMessages.getUnreadMessages(publicUserId);
|
||||
const publicUnreadReduced = Service.reduceAndMapGroupMessages(publicUnread);
|
||||
|
||||
if (disableNotify) return;
|
||||
if (!Service.hasUnreadMessages(publicUserId)) return;
|
||||
if (Session.equals('idChatOpen', PUBLIC_KEY)) return;
|
||||
|
||||
const checkIfBeenNotified = ({ sender, time }) =>
|
||||
time > (this.state.publicNotified[sender.id] || 0);
|
||||
|
||||
const chatsNotify = publicUnreadReduced
|
||||
.map(msg => ({
|
||||
...msg,
|
||||
sender: {
|
||||
name: msg.sender ? msg.sender.name : intl.formatMessage(intlMessages.appToastChatSystem),
|
||||
...msg.sender,
|
||||
},
|
||||
}))
|
||||
.filter(checkIfBeenNotified);
|
||||
return (
|
||||
<span>
|
||||
{
|
||||
chatsNotify.map(({ sender, time, content }) =>
|
||||
(<ChatPushAlert
|
||||
key={time}
|
||||
chatId={PUBLIC_KEY}
|
||||
name={sender.name}
|
||||
message={
|
||||
<span >
|
||||
{ intl.formatMessage(intlMessages.appToastChatPublic) }
|
||||
</span>
|
||||
}
|
||||
content={this.createMessage(sender.name, content)}
|
||||
onOpen={() => {
|
||||
this.setState(({ notified, publicNotified }) => ({
|
||||
...notified,
|
||||
publicNotified: {
|
||||
...publicNotified,
|
||||
[sender.id]: time,
|
||||
},
|
||||
}), () => {
|
||||
Service.setNotified(PUBLIC_KEY, this.state.publicNotified);
|
||||
});
|
||||
}}
|
||||
/>))
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
openChats,
|
||||
disableAudio,
|
||||
audioAlertDisabled,
|
||||
pushAlertDisabled,
|
||||
intl,
|
||||
} = this.props;
|
||||
const unreadMessagesCount = openChats
|
||||
.map(chat => chat.unreadCounter)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
const shouldPlayAudio = !disableAudio && unreadMessagesCount > 0;
|
||||
const {
|
||||
pendingNotificationsByChat,
|
||||
} = this.state;
|
||||
|
||||
const shouldPlay = Object.keys(pendingNotificationsByChat).length > 0;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<ChatAudioAlert play={shouldPlayAudio} count={unreadMessagesCount} />
|
||||
{ this.notifyPublicChat() }
|
||||
{ this.notifyPrivateChat() }
|
||||
{!audioAlertDisabled ? <ChatAudioAlert play={shouldPlay} /> : null}
|
||||
{
|
||||
!pushAlertDisabled
|
||||
? Object.keys(pendingNotificationsByChat)
|
||||
.map((chatId) => {
|
||||
// Only display the latest group of messages (up to 5 messages)
|
||||
const reducedMessage = Service
|
||||
.reduceAndMapGroupMessages(pendingNotificationsByChat[chatId].slice(-5)).pop();
|
||||
|
||||
if (!reducedMessage) return null;
|
||||
|
||||
const content = this
|
||||
.createMessage(reducedMessage.sender.name, reducedMessage.content);
|
||||
|
||||
return (
|
||||
<ChatPushAlert
|
||||
key={chatId}
|
||||
chatId={chatId}
|
||||
content={content}
|
||||
title={
|
||||
(chatId === 'MAIN-PUBLIC-GROUP-CHAT')
|
||||
? <span>{intl.formatMessage(intlMessages.appToastChatPublic)}</span>
|
||||
: <span>{intl.formatMessage(intlMessages.appToastChatPrivate)}</span>
|
||||
}
|
||||
onOpen={
|
||||
() => {
|
||||
let pendingNotifications = pendingNotificationsByChat;
|
||||
delete pendingNotifications[chatId];
|
||||
pendingNotifications = { ...pendingNotifications };
|
||||
this.setState({ pendingNotificationsByChat: pendingNotifications });
|
||||
}}
|
||||
alertDuration={ALERT_DURATION}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ import { withTracker } from 'meteor/react-meteor-data';
|
||||
import UserListService from '/imports/ui/components/user-list/service';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import ChatAlert from './component';
|
||||
import ChatService from '/imports/ui/components/chat/service.js';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
const ChatAlertContainer = props => (
|
||||
<ChatAlert {...props} />
|
||||
@ -10,12 +13,13 @@ const ChatAlertContainer = props => (
|
||||
|
||||
export default withTracker(() => {
|
||||
const AppSettings = Settings.application;
|
||||
const openChats = UserListService.getOpenChats();
|
||||
|
||||
const activeChats = UserListService.getActiveChats();
|
||||
const loginTime = Users.findOne({ userId: Auth.userID }).loginTime;
|
||||
return {
|
||||
disableAudio: !AppSettings.chatAudioAlerts,
|
||||
disableNotify: !AppSettings.chatPushAlerts,
|
||||
openChats,
|
||||
audioAlertDisabled: !AppSettings.chatAudioAlerts,
|
||||
pushAlertDisabled: !AppSettings.chatPushAlerts,
|
||||
activeChats,
|
||||
publicUserId: Meteor.settings.public.chat.public_group_id,
|
||||
joinTimestamp: loginTime,
|
||||
};
|
||||
})(memo(ChatAlertContainer));
|
||||
|
@ -4,37 +4,44 @@ import _ from 'lodash';
|
||||
import injectNotify from '/imports/ui/components/toast/inject-notify/component';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
const ALERT_INTERVAL = 2000; // 2 seconds
|
||||
const ALERT_LIFETIME = 4000; // 4 seconds
|
||||
|
||||
const propTypes = {
|
||||
notify: PropTypes.func.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
chatId: PropTypes.string.isRequired,
|
||||
message: PropTypes.node.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
alertDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
class ChatPushAlert extends PureComponent {
|
||||
static link(message, chatId) {
|
||||
static link(title, chatId) {
|
||||
let chat = chatId;
|
||||
|
||||
if (chat === 'MAIN-PUBLIC-GROUP-CHAT') {
|
||||
chat = 'public';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={chatId}
|
||||
role="button"
|
||||
aria-label={message}
|
||||
aria-label={title}
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
Session.set('openPanel', 'chat');
|
||||
Session.set('idChatOpen', chatId);
|
||||
Session.set('idChatOpen', chat);
|
||||
}}
|
||||
onKeyPress={() => null}
|
||||
>
|
||||
{ message }
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.showNotify = _.debounce(this.showNotify.bind(this), ALERT_INTERVAL);
|
||||
this.showNotify = this.showNotify.bind(this);
|
||||
|
||||
this.componentDidMount = this.showNotify;
|
||||
this.componentDidUpdate = this.showNotify;
|
||||
@ -45,15 +52,16 @@ class ChatPushAlert extends PureComponent {
|
||||
notify,
|
||||
onOpen,
|
||||
chatId,
|
||||
message,
|
||||
title,
|
||||
content,
|
||||
alertDuration,
|
||||
} = this.props;
|
||||
|
||||
return notify(
|
||||
ChatPushAlert.link(message, chatId),
|
||||
ChatPushAlert.link(title, chatId),
|
||||
'info',
|
||||
'chat',
|
||||
{ onOpen, autoClose: ALERT_LIFETIME },
|
||||
{ onOpen, autoClose: alertDuration },
|
||||
ChatPushAlert.link(content, chatId),
|
||||
true,
|
||||
);
|
||||
|
@ -56,6 +56,7 @@ const Chat = (props) => {
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Session.set('idChatOpen', '');
|
||||
Session.set('openPanel', 'userlist');
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })}
|
||||
@ -75,6 +76,7 @@ const Chat = (props) => {
|
||||
hideLabel
|
||||
onClick={() => {
|
||||
actions.handleClosePrivateChat(chatID);
|
||||
Session.set('idChatOpen', '');
|
||||
Session.set('openPanel', 'userlist');
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
|
||||
|
@ -59,7 +59,7 @@ export default injectIntl(withTracker(({ intl }) => {
|
||||
|
||||
messages = ChatService.getPublicGroupMessages();
|
||||
|
||||
const time = user.logTime;
|
||||
const time = user.loginTime;
|
||||
const welcomeId = `welcome-msg-${time}`;
|
||||
|
||||
const welcomeMsg = {
|
||||
|
@ -92,16 +92,13 @@ const reduceGroupMessages = (previous, current) => {
|
||||
return previous.concat(currentMessage);
|
||||
};
|
||||
|
||||
const reduceAndMapGroupMessages = messages =>
|
||||
(messages.reduce(reduceGroupMessages, []).map(mapGroupMessage));
|
||||
const reduceAndMapGroupMessages = messages => (messages
|
||||
.reduce(reduceGroupMessages, []).map(mapGroupMessage));
|
||||
|
||||
const getPublicGroupMessages = () => {
|
||||
const publicGroupMessages = GroupChatMsg.find({
|
||||
chatId: PUBLIC_GROUP_CHAT_ID,
|
||||
}, {
|
||||
sort: ['timestamp'],
|
||||
}).fetch();
|
||||
|
||||
}, { sort: ['timestamp'] }).fetch();
|
||||
return publicGroupMessages;
|
||||
};
|
||||
|
||||
@ -123,9 +120,7 @@ const getPrivateGroupMessages = () => {
|
||||
|
||||
messages = GroupChatMsg.find({
|
||||
chatId,
|
||||
}, {
|
||||
sort: ['timestamp'],
|
||||
}).fetch();
|
||||
}, { sort: ['timestamp'] }).fetch();
|
||||
}
|
||||
|
||||
return reduceAndMapGroupMessages(messages, []);
|
||||
@ -141,8 +136,8 @@ const isChatLocked = (receiverID) => {
|
||||
const isPubChatLocked = meeting.lockSettingsProp.disablePubChat;
|
||||
const isPrivChatLocked = meeting.lockSettingsProp.disablePrivChat;
|
||||
|
||||
return mapUser(user).isLocked &&
|
||||
((isPublic && isPubChatLocked) || (!isPublic && isPrivChatLocked));
|
||||
return mapUser(user).isLocked
|
||||
&& ((isPublic && isPubChatLocked) || (!isPublic && isPrivChatLocked));
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -206,11 +201,10 @@ const getScrollPosition = (receiverID) => {
|
||||
return scroll.position;
|
||||
};
|
||||
|
||||
const updateScrollPosition =
|
||||
position => ScrollCollection.upsert(
|
||||
{ receiver: Session.get('idChatOpen') },
|
||||
{ $set: { position } },
|
||||
);
|
||||
const updateScrollPosition = position => ScrollCollection.upsert(
|
||||
{ receiver: Session.get('idChatOpen') },
|
||||
{ $set: { position } },
|
||||
);
|
||||
|
||||
const updateUnreadMessage = (timestamp) => {
|
||||
const chatID = Session.get('idChatOpen') || PUBLIC_CHAT_ID;
|
||||
@ -251,16 +245,16 @@ const htmlDecode = (input) => {
|
||||
// Export the chat as [Hour:Min] user: message
|
||||
const exportChat = (messageList) => {
|
||||
const { welcomeProp } = getMeeting();
|
||||
const { logTime } = getUser(Auth.userID);
|
||||
const { loginTime } = getUser(Auth.userID);
|
||||
const { welcomeMsg } = welcomeProp;
|
||||
|
||||
const clearMessage = messageList.filter(message => message.message === PUBLIC_CHAT_CLEAR);
|
||||
|
||||
const hasClearMessage = clearMessage.length;
|
||||
|
||||
if (!hasClearMessage || (hasClearMessage && clearMessage[0].timestamp < logTime)) {
|
||||
if (!hasClearMessage || (hasClearMessage && clearMessage[0].timestamp < loginTime)) {
|
||||
messageList.push({
|
||||
timestamp: logTime,
|
||||
timestamp: loginTime,
|
||||
message: welcomeMsg,
|
||||
type: SYSTEM_CHAT_TYPE,
|
||||
sender: PUBLIC_CHAT_USER_ID,
|
||||
@ -282,28 +276,38 @@ const exportChat = (messageList) => {
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
const setNotified = (chatType, item) => {
|
||||
const notified = Storage.getItem('notified');
|
||||
const key = 'notified';
|
||||
const userChat = { [chatType]: item };
|
||||
if (notified) {
|
||||
Storage.setItem(key, {
|
||||
...notified,
|
||||
...userChat,
|
||||
});
|
||||
return;
|
||||
const getUnreadMessagesFromChatId = chatId => UnreadMessages.getUnreadMessages(chatId);
|
||||
|
||||
const getAllMessages = (chatID) => {
|
||||
const filter = {
|
||||
sender: { $ne: Auth.userID },
|
||||
};
|
||||
if (chatID === PUBLIC_GROUP_CHAT_ID) {
|
||||
filter.chatId = { $eq: chatID };
|
||||
} else {
|
||||
const privateChat = GroupChat.findOne({ users: { $all: [chatID, Auth.userID] } });
|
||||
|
||||
filter.chatId = { $ne: PUBLIC_GROUP_CHAT_ID };
|
||||
|
||||
if (privateChat) {
|
||||
filter.chatId = privateChat.chatId;
|
||||
}
|
||||
}
|
||||
Storage.setItem(key, {
|
||||
...userChat,
|
||||
});
|
||||
const messages = GroupChatMsg.find(filter).fetch();
|
||||
return messages;
|
||||
};
|
||||
|
||||
const getNotified = (chat) => {
|
||||
const key = 'notified';
|
||||
const notified = Storage.getItem(key);
|
||||
if (notified) return notified[chat] || {};
|
||||
return {};
|
||||
};
|
||||
const getlastMessage = lastMessages => lastMessages.sort((a,
|
||||
b) => a.timestamp - b.timestamp).pop();
|
||||
|
||||
const maxTimestampReducer = (max, el) => ((el.timestamp > max) ? el.timestamp : max);
|
||||
|
||||
const maxNumberReducer = (max, el) => ((el > max) ? el : max);
|
||||
|
||||
const getLastMessageTimestampFromChatList = activeChats => activeChats
|
||||
.map(chat => ((chat.id === 'public') ? 'MAIN-PUBLIC-GROUP-CHAT' : chat.id))
|
||||
.map(chatId => getAllMessages(chatId).reduce(maxTimestampReducer, 0))
|
||||
.reduce(maxNumberReducer, 0);
|
||||
|
||||
export default {
|
||||
reduceAndMapGroupMessages,
|
||||
@ -322,6 +326,10 @@ export default {
|
||||
removeFromClosedChatsSession,
|
||||
exportChat,
|
||||
clearPublicChatHistory,
|
||||
setNotified,
|
||||
getNotified,
|
||||
getlastMessage,
|
||||
getUnreadMessagesFromChatId,
|
||||
getAllMessages,
|
||||
maxTimestampReducer,
|
||||
maxNumberReducer,
|
||||
getLastMessageTimestampFromChatList,
|
||||
};
|
||||
|
@ -87,6 +87,16 @@ const openBreakoutJoinConfirmation = (breakout, breakoutName, mountModal) => mou
|
||||
const closeBreakoutJoinConfirmation = mountModal => mountModal(null);
|
||||
|
||||
class NavBar extends PureComponent {
|
||||
static handleToggleUserList() {
|
||||
Session.set(
|
||||
'openPanel',
|
||||
Session.get('openPanel') !== ''
|
||||
? ''
|
||||
: 'userlist',
|
||||
);
|
||||
Session.set('idChatOpen', '');
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -97,7 +107,6 @@ class NavBar extends PureComponent {
|
||||
};
|
||||
|
||||
this.incrementTime = this.incrementTime.bind(this);
|
||||
this.handleToggleUserList = this.handleToggleUserList.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -162,15 +171,6 @@ class NavBar extends PureComponent {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
handleToggleUserList() {
|
||||
Session.set(
|
||||
'openPanel',
|
||||
Session.get('openPanel') !== ''
|
||||
? ''
|
||||
: 'userlist',
|
||||
);
|
||||
}
|
||||
|
||||
inviteUserToBreakout(breakout) {
|
||||
const {
|
||||
mountModal,
|
||||
@ -279,7 +279,7 @@ class NavBar extends PureComponent {
|
||||
<div className={styles.left}>
|
||||
<Button
|
||||
data-test="userListToggleButton"
|
||||
onClick={this.handleToggleUserList}
|
||||
onClick={NavBar.handleToggleUserList}
|
||||
ghost
|
||||
circle
|
||||
hideLabel
|
||||
|
@ -30,7 +30,7 @@ const propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
unreadCounter: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
openChat: PropTypes.string,
|
||||
activeChat: PropTypes.string,
|
||||
compact: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
@ -41,7 +41,7 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
openChat: '',
|
||||
activeChat: '',
|
||||
shortcuts: '',
|
||||
};
|
||||
|
||||
@ -51,13 +51,17 @@ const handleClickToggleChat = (id) => {
|
||||
Session.get('openPanel') === 'chat' && Session.get('idChatOpen') === id
|
||||
? 'userlist' : 'chat',
|
||||
);
|
||||
Session.set('idChatOpen', id);
|
||||
if (Session.equals('openPanel', 'chat')) {
|
||||
Session.set('idChatOpen', id);
|
||||
} else {
|
||||
Session.set('idChatOpen', '');
|
||||
}
|
||||
};
|
||||
|
||||
const ChatListItem = (props) => {
|
||||
const {
|
||||
chat,
|
||||
openChat,
|
||||
activeChat,
|
||||
compact,
|
||||
intl,
|
||||
tabIndex,
|
||||
@ -65,7 +69,7 @@ const ChatListItem = (props) => {
|
||||
shortcuts: TOGGLE_CHAT_PUB_AK,
|
||||
} = props;
|
||||
|
||||
const isCurrentChat = chat.id === openChat;
|
||||
const isCurrentChat = chat.id === activeChat;
|
||||
const linkClasses = {};
|
||||
linkClasses[styles.active] = isCurrentChat;
|
||||
|
||||
|
@ -7,7 +7,7 @@ import CustomLogo from './custom-logo/component';
|
||||
import UserContentContainer from './user-list-content/container';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.arrayOf(String).isRequired,
|
||||
activeChats: PropTypes.arrayOf(String).isRequired,
|
||||
compact: PropTypes.bool,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
@ -42,7 +42,7 @@ class UserList extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
openChats,
|
||||
activeChats,
|
||||
compact,
|
||||
currentUser,
|
||||
isBreakoutRoom,
|
||||
@ -73,14 +73,14 @@ class UserList extends PureComponent {
|
||||
<div className={styles.userList}>
|
||||
{
|
||||
showBranding
|
||||
&& !this.props.compact
|
||||
&& CustomLogoUrl
|
||||
&& !compact
|
||||
&& CustomLogoUrl
|
||||
? <CustomLogo CustomLogoUrl={CustomLogoUrl} /> : null
|
||||
}
|
||||
{<UserContentContainer
|
||||
{...{
|
||||
intl,
|
||||
openChats,
|
||||
activeChats,
|
||||
compact,
|
||||
currentUser,
|
||||
isBreakoutRoom,
|
||||
@ -104,7 +104,7 @@ class UserList extends PureComponent {
|
||||
getUsersId,
|
||||
hasPrivateChatBetweenUsers,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import Service from './service';
|
||||
import UserList from './component';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.arrayOf(String).isRequired,
|
||||
activeChats: PropTypes.arrayOf(String).isRequired,
|
||||
currentUser: PropTypes.shape({}).isRequired,
|
||||
getUsersId: PropTypes.func.isRequired,
|
||||
isBreakoutRoom: PropTypes.bool.isRequired,
|
||||
@ -34,7 +34,7 @@ export default withTracker(({ chatID, compact }) => ({
|
||||
hasBreakoutRoom: Service.hasBreakoutRoom(),
|
||||
getUsersId: Service.getUsersId,
|
||||
currentUser: Service.getCurrentUser(),
|
||||
openChats: Service.getOpenChats(chatID),
|
||||
activeChats: Service.getActiveChats(chatID),
|
||||
isBreakoutRoom: meetingIsBreakout(),
|
||||
getAvailableActions: Service.getAvailableActions,
|
||||
normalizeEmojiName: Service.normalizeEmojiName,
|
||||
|
@ -18,7 +18,7 @@ const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
|
||||
// session for closed chat list
|
||||
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
|
||||
|
||||
const mapOpenChats = (chat) => {
|
||||
const mapActiveChats = (chat) => {
|
||||
const currentUserId = Auth.userID;
|
||||
|
||||
if (chat.sender !== currentUserId) {
|
||||
@ -192,7 +192,7 @@ const getUsersId = () => getUsers().map(u => u.id);
|
||||
|
||||
const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0;
|
||||
|
||||
const getOpenChats = (chatID) => {
|
||||
const getActiveChats = (chatID) => {
|
||||
const privateChat = GroupChat
|
||||
.find({ users: { $all: [Auth.userID] } })
|
||||
.fetch()
|
||||
@ -206,30 +206,30 @@ const getOpenChats = (chatID) => {
|
||||
filter.chatId = { $in: privateChat };
|
||||
}
|
||||
|
||||
let openChats = GroupChatMsg
|
||||
let activeChats = GroupChatMsg
|
||||
.find(filter)
|
||||
.fetch()
|
||||
.map(mapOpenChats);
|
||||
.map(mapActiveChats);
|
||||
|
||||
if (chatID) {
|
||||
openChats.push(chatID);
|
||||
activeChats.push(chatID);
|
||||
}
|
||||
|
||||
openChats = _.uniq(_.compact(openChats));
|
||||
activeChats = _.uniq(_.compact(activeChats));
|
||||
|
||||
openChats = Users
|
||||
.find({ userId: { $in: openChats } })
|
||||
activeChats = Users
|
||||
.find({ userId: { $in: activeChats } })
|
||||
.map(mapUser)
|
||||
.map((op) => {
|
||||
const openChat = op;
|
||||
openChat.unreadCounter = UnreadMessages.count(op.id);
|
||||
return openChat;
|
||||
const activeChat = op;
|
||||
activeChat.unreadCounter = UnreadMessages.count(op.id);
|
||||
return activeChat;
|
||||
});
|
||||
|
||||
const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || [];
|
||||
const filteredChatList = [];
|
||||
|
||||
openChats.forEach((op) => {
|
||||
activeChats.forEach((op) => {
|
||||
// When a new private chat message is received, ensure the conversation view is restored.
|
||||
if (op.unreadCounter > 0) {
|
||||
if (_.indexOf(currentClosedChats, op.id) > -1) {
|
||||
@ -237,24 +237,24 @@ const getOpenChats = (chatID) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Compare openChats with session and push it into filteredChatList
|
||||
// if one of the openChat is not in session.
|
||||
// It will pass to openChats.
|
||||
// Compare activeChats with session and push it into filteredChatList
|
||||
// if one of the activeChat is not in session.
|
||||
// It will pass to activeChats.
|
||||
if (_.indexOf(currentClosedChats, op.id) < 0) {
|
||||
filteredChatList.push(op);
|
||||
}
|
||||
});
|
||||
|
||||
openChats = filteredChatList;
|
||||
activeChats = filteredChatList;
|
||||
|
||||
openChats.push({
|
||||
activeChats.push({
|
||||
id: 'public',
|
||||
name: 'Public Chat',
|
||||
icon: 'group_chat',
|
||||
unreadCounter: UnreadMessages.count(PUBLIC_GROUP_CHAT_ID),
|
||||
});
|
||||
|
||||
return openChats
|
||||
return activeChats
|
||||
.sort(sortChats);
|
||||
};
|
||||
|
||||
@ -268,36 +268,36 @@ const getAvailableActions = (currentUser, user, isBreakoutRoom) => {
|
||||
const allowedToChatPrivately = !user.isCurrent && !isDialInUser;
|
||||
|
||||
const allowedToMuteAudio = hasAuthority
|
||||
&& user.isVoiceUser
|
||||
&& !user.isMuted
|
||||
&& !user.isListenOnly;
|
||||
&& user.isVoiceUser
|
||||
&& !user.isMuted
|
||||
&& !user.isListenOnly;
|
||||
|
||||
const allowedToUnmuteAudio = hasAuthority
|
||||
&& user.isVoiceUser
|
||||
&& !user.isListenOnly
|
||||
&& user.isMuted
|
||||
&& user.isCurrent;
|
||||
&& user.isVoiceUser
|
||||
&& !user.isListenOnly
|
||||
&& user.isMuted
|
||||
&& user.isCurrent;
|
||||
|
||||
const allowedToResetStatus = hasAuthority
|
||||
&& user.emoji.status !== EMOJI_STATUSES.none
|
||||
&& !isDialInUser;
|
||||
&& user.emoji.status !== EMOJI_STATUSES.none
|
||||
&& !isDialInUser;
|
||||
|
||||
// if currentUser is a moderator, allow removing other users
|
||||
const allowedToRemove = currentUser.isModerator && !user.isCurrent && !isBreakoutRoom;
|
||||
|
||||
const allowedToSetPresenter = currentUser.isModerator
|
||||
&& !user.isPresenter
|
||||
&& !isDialInUser;
|
||||
&& !user.isPresenter
|
||||
&& !isDialInUser;
|
||||
|
||||
const allowedToPromote = currentUser.isModerator
|
||||
&& !user.isCurrent
|
||||
&& !user.isModerator
|
||||
&& !isDialInUser;
|
||||
&& !user.isCurrent
|
||||
&& !user.isModerator
|
||||
&& !isDialInUser;
|
||||
|
||||
const allowedToDemote = currentUser.isModerator
|
||||
&& !user.isCurrent
|
||||
&& user.isModerator
|
||||
&& !isDialInUser;
|
||||
&& !user.isCurrent
|
||||
&& user.isModerator
|
||||
&& !isDialInUser;
|
||||
|
||||
const allowedToChangeStatus = user.isCurrent;
|
||||
|
||||
@ -361,7 +361,13 @@ const removeUser = (userId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleVoice = (userId) => { userId === Auth.userID ? makeCall('toggleSelfVoice') : makeCall('toggleVoice', userId); };
|
||||
const toggleVoice = (userId) => {
|
||||
if (userId === Auth.userID) {
|
||||
makeCall('toggleSelfVoice');
|
||||
} else {
|
||||
makeCall('toggleVoice', userId);
|
||||
}
|
||||
};
|
||||
|
||||
const muteAllUsers = (userId) => { makeCall('muteAllUsers', userId); };
|
||||
|
||||
@ -452,7 +458,7 @@ export default {
|
||||
changeRole,
|
||||
getUsers,
|
||||
getUsersId,
|
||||
getOpenChats,
|
||||
getActiveChats,
|
||||
getCurrentUser,
|
||||
getAvailableActions,
|
||||
normalizeEmojiName,
|
||||
|
@ -8,7 +8,7 @@ import UserPolls from './user-polls/component';
|
||||
import BreakoutRoomItem from './breakout-room/component';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.arrayOf(String).isRequired,
|
||||
activeChats: PropTypes.arrayOf(String).isRequired,
|
||||
compact: PropTypes.bool,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
@ -61,7 +61,7 @@ class UserContent extends PureComponent {
|
||||
getEmojiList,
|
||||
getEmoji,
|
||||
isPublicChat,
|
||||
openChats,
|
||||
activeChats,
|
||||
getGroupChatPrivate,
|
||||
pollIsOpen,
|
||||
forcePollOpen,
|
||||
@ -79,7 +79,7 @@ class UserContent extends PureComponent {
|
||||
<UserMessages
|
||||
{...{
|
||||
isPublicChat,
|
||||
openChats,
|
||||
activeChats,
|
||||
compact,
|
||||
intl,
|
||||
roving,
|
||||
|
@ -4,11 +4,11 @@ import PropTypes from 'prop-types';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import cx from 'classnames';
|
||||
import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
|
||||
import ChatListItem from './../../chat-list-item/component';
|
||||
import ChatListItem from '../../chat-list-item/component';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.arrayOf(String).isRequired,
|
||||
openChat: PropTypes.string,
|
||||
activeChats: PropTypes.arrayOf(String).isRequired,
|
||||
activeChat: PropTypes.string,
|
||||
compact: PropTypes.bool,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
@ -19,7 +19,7 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
compact: false,
|
||||
openChat: '',
|
||||
activeChat: '',
|
||||
};
|
||||
|
||||
const listTransition = {
|
||||
@ -46,20 +46,21 @@ class UserMessages extends PureComponent {
|
||||
index: -1,
|
||||
};
|
||||
|
||||
this.openChatRefs = [];
|
||||
this.activeChatRefs = [];
|
||||
this.selectedIndex = -1;
|
||||
|
||||
this.focusOpenChatItem = this.focusOpenChatItem.bind(this);
|
||||
this.focusActiveChatItem = this.focusActiveChatItem.bind(this);
|
||||
this.changeState = this.changeState.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.compact) {
|
||||
const { compact, roving, activeChats } = this.props;
|
||||
if (!compact) {
|
||||
this._msgsList.addEventListener(
|
||||
'keydown',
|
||||
event => this.props.roving(
|
||||
event => roving(
|
||||
event,
|
||||
this.props.openChats.length,
|
||||
activeChats.length,
|
||||
this.changeState,
|
||||
),
|
||||
);
|
||||
@ -67,26 +68,27 @@ class UserMessages extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.index === -1) {
|
||||
const { index } = this.state;
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.index !== prevState.index) {
|
||||
this.focusOpenChatItem(this.state.index);
|
||||
if (index !== prevState.index) {
|
||||
this.focusActiveChatItem(index);
|
||||
}
|
||||
}
|
||||
|
||||
getOpenChats() {
|
||||
getActiveChats() {
|
||||
const {
|
||||
openChats,
|
||||
openChat,
|
||||
activeChats,
|
||||
activeChat,
|
||||
compact,
|
||||
isPublicChat,
|
||||
} = this.props;
|
||||
|
||||
let index = -1;
|
||||
|
||||
return openChats.map(chat => (
|
||||
return activeChats.map(chat => (
|
||||
<CSSTransition
|
||||
classNames={listTransition}
|
||||
appear
|
||||
@ -97,11 +99,11 @@ class UserMessages extends PureComponent {
|
||||
className={cx(styles.chatsList)}
|
||||
key={chat.id}
|
||||
>
|
||||
<div ref={(node) => { this.openChatRefs[index += 1] = node; }}>
|
||||
<div ref={(node) => { this.activeChatRefs[index += 1] = node; }}>
|
||||
<ChatListItem
|
||||
isPublicChat={isPublicChat}
|
||||
compact={compact}
|
||||
openChat={openChat}
|
||||
activeChat={activeChat}
|
||||
chat={chat}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
@ -114,12 +116,12 @@ class UserMessages extends PureComponent {
|
||||
this.setState({ index: newIndex });
|
||||
}
|
||||
|
||||
focusOpenChatItem(index) {
|
||||
if (!this.openChatRefs[index]) {
|
||||
focusActiveChatItem(index) {
|
||||
if (!this.activeChatRefs[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openChatRefs[index].firstChild.focus();
|
||||
this.activeChatRefs[index].firstChild.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -131,10 +133,13 @@ class UserMessages extends PureComponent {
|
||||
return (
|
||||
<div className={styles.messages}>
|
||||
{
|
||||
!compact ?
|
||||
!compact ? (
|
||||
<h2 className={styles.smallTitle}>
|
||||
{intl.formatMessage(intlMessages.messagesTitle)}
|
||||
</h2> : <hr className={styles.separator} />
|
||||
</h2>
|
||||
) : (
|
||||
<hr className={styles.separator} />
|
||||
)
|
||||
}
|
||||
<div
|
||||
role="tabpanel"
|
||||
@ -143,8 +148,8 @@ class UserMessages extends PureComponent {
|
||||
ref={(ref) => { this._msgsList = ref; }}
|
||||
>
|
||||
<div className={styles.list}>
|
||||
<TransitionGroup ref={(ref) => { this._msgItems = ref; }} >
|
||||
{ this.getOpenChats() }
|
||||
<TransitionGroup ref={(ref) => { this._msgItems = ref; }}>
|
||||
{this.getActiveChats()}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -257,7 +257,7 @@ class UserDropdown extends PureComponent {
|
||||
|
||||
if (enablePrivateChat) {
|
||||
actions.push(this.makeDropdownItem(
|
||||
'openChat',
|
||||
'activeChat',
|
||||
intl.formatMessage(messages.ChatLabel),
|
||||
() => {
|
||||
getGroupChatPrivate(currentUser, user);
|
||||
|
@ -30,7 +30,7 @@ const mapUser = (user) => {
|
||||
isPhoneUser: user.phone_user,
|
||||
isOnline: user.connectionStatus === 'online',
|
||||
clientType: user.clientType,
|
||||
logTime: user.logTime,
|
||||
loginTime: user.loginTime,
|
||||
};
|
||||
|
||||
mappedUser.isLocked = user.locked && !(mappedUser.isPresenter || mappedUser.isModerator);
|
||||
|
Loading…
Reference in New Issue
Block a user