refactor: remove unused chat code (#19215)

This commit is contained in:
Ramón Souza 2023-11-28 11:18:56 -03:00 committed by GitHub
parent 4aa83d243a
commit 067144bf86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 5 additions and 4165 deletions

View File

@ -10,7 +10,6 @@ import UserSettings from '/imports/api/users-settings';
import VideoStreams from '/imports/api/video-streams';
import VoiceUsers from '/imports/api/voice-users';
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user';
import GroupChat from '/imports/api/group-chat';
import ConnectionStatus from '/imports/api/connection-status';
import Captions from '/imports/api/captions';
import Pads, { PadsSessions, PadsUpdates } from '/imports/api/pads';
@ -21,7 +20,6 @@ import guestUsers from '/imports/api/guest-users';
import Meetings, {
RecordMeetings, ExternalVideoMeetings, MeetingTimeRemaining, Notifications,
} from '/imports/api/meetings';
import { UsersTyping } from '/imports/api/group-chat-msg';
import Users, { CurrentUser } from '/imports/api/users';
// Custom Publishers
@ -40,7 +38,6 @@ export const localCollectionRegistry = {
localVideoStreamsSync: new AbstractCollection(VideoStreams, VideoStreams),
localVoiceUsersSync: new AbstractCollection(VoiceUsers, VoiceUsers),
localWhiteboardMultiUserSync: new AbstractCollection(WhiteboardMultiUser, WhiteboardMultiUser),
localGroupChatSync: new AbstractCollection(GroupChat, GroupChat),
localConnectionStatusSync: new AbstractCollection(ConnectionStatus, ConnectionStatus),
localCaptionsSync: new AbstractCollection(Captions, Captions),
localPadsSync: new AbstractCollection(Pads, Pads),
@ -53,7 +50,6 @@ export const localCollectionRegistry = {
ExternalVideoMeetings,
),
localMeetingTimeRemainingSync: new AbstractCollection(MeetingTimeRemaining, MeetingTimeRemaining),
localUsersTypingSync: new AbstractCollection(UsersTyping, UsersTyping),
localBreakoutsSync: new AbstractCollection(Breakouts, Breakouts),
localBreakoutsHistorySync: new AbstractCollection(BreakoutsHistory, BreakoutsHistory),
localGuestUsersSync: new AbstractCollection(guestUsers, guestUsers),

View File

@ -30,7 +30,6 @@ import IntlStartup from '/imports/startup/client/intl';
import ContextProviders from '/imports/ui/components/context-providers/component';
import ChatAdapter from '/imports/ui/components/components-data/chat-context/adapter';
import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter';
import GroupChatAdapter from '/imports/ui/components/components-data/group-chat-context/adapter';
import GraphqlProvider from '/imports/ui/components/graphql-provider/component';
import { liveDataEventBrokerInitializer } from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker';
// The adapter import is "unused" as far as static code is concerned, but it
@ -95,7 +94,6 @@ Meteor.startup(() => {
</JoinHandler>
<UsersAdapter />
<ChatAdapter />
<GroupChatAdapter />
</>
</ContextProviders>,
document.getElementById('app'),

View File

@ -1,20 +0,0 @@
import { Meteor } from 'meteor/meteor';
const collectionOptions = Meteor.isClient ? {
connection: null,
} : {};
const GroupChatMsg = new Mongo.Collection('group-chat-msg');
const UsersTyping = new Mongo.Collection('users-typing', collectionOptions);
if (Meteor.isServer) {
GroupChatMsg.createIndexAsync({ meetingId: 1, chatId: 1 });
UsersTyping.createIndexAsync({ meetingId: 1, isTypingTo: 1 });
}
// As we store chat in context, skip adding to mini mongo
if (Meteor.isClient) {
GroupChatMsg.onAdded = () => false;
}
export { GroupChatMsg, UsersTyping };

View File

@ -1,12 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleGroupChatMsgBroadcast from './handlers/groupChatMsgBroadcast';
import handleClearPublicGroupChat from './handlers/clearPublicGroupChat';
import handleUserTyping from './handlers/userTyping';
import handleSyncGroupChatMsg from './handlers/syncGroupsChat';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
// RedisPubSub.on('GetGroupChatMsgsRespMsg', processForHTML5ServerOnly(handleSyncGroupChatMsg));
// RedisPubSub.on('GroupChatMessageBroadcastEvtMsg', handleGroupChatMsgBroadcast);
// RedisPubSub.on('ClearPublicChatHistoryEvtMsg', handleClearPublicGroupChat);
// RedisPubSub.on('SyncGetGroupChatMsgsRespMsg', handleSyncGroupChatMsg);
// RedisPubSub.on('UserTypingEvtMsg', handleUserTyping);

View File

@ -1,13 +0,0 @@
import { check } from 'meteor/check';
import clearGroupChatMsg from '../modifiers/clearGroupChatMsg';
export default async function clearPublicChatHistory({ header, body }) {
const { meetingId } = header;
const { chatId } = body;
check(meetingId, String);
check(chatId, String);
const result = clearGroupChatMsg(meetingId, chatId);
return result;
}

View File

@ -1,25 +0,0 @@
import { check } from 'meteor/check';
import { throttle } from '/imports/utils/throttle';
import addGroupChatMsg from '../modifiers/addGroupChatMsg';
import addBulkGroupChatMsgs from '../modifiers/addBulkGroupChatMsgs';
const { bufferChatInsertsMs } = Meteor.settings.public.chat;
const msgBuffer = [];
const bulkFn = throttle(addBulkGroupChatMsgs, bufferChatInsertsMs);
export default async function handleGroupChatMsgBroadcast({ body }, meetingId) {
const { chatId, msg } = body;
check(meetingId, String);
check(chatId, String);
check(msg, Object);
if (bufferChatInsertsMs) {
msgBuffer.push({ meetingId, chatId, msg });
bulkFn(msgBuffer);
} else {
await addGroupChatMsg(meetingId, chatId, msg);
}
}

View File

@ -1,12 +0,0 @@
import { Match, check } from 'meteor/check';
import syncMeetingChatMsgs from '../modifiers/syncMeetingChatMsgs';
export default function handleSyncGroupChat({ body }, meetingId) {
const { chatId, msgs } = body;
check(meetingId, String);
check(chatId, String);
check(msgs, Match.Maybe(Array));
syncMeetingChatMsgs(meetingId, chatId, msgs);
}

View File

@ -1,12 +0,0 @@
import { check } from 'meteor/check';
import startTyping from '../modifiers/startTyping';
export default async function handleUserTyping({ body }, meetingId) {
const { chatId, userId } = body;
check(meetingId, String);
check(userId, String);
check(chatId, String);
await startTyping(meetingId, userId, chatId);
}

View File

@ -1,3 +1 @@
import './eventHandlers';
import './methods';
import './publishers';

View File

@ -1,14 +1,8 @@
import { Meteor } from 'meteor/meteor';
import clearPublicChatHistory from './methods/clearPublicChatHistory';
import startUserTyping from './methods/startUserTyping';
import stopUserTyping from './methods/stopUserTyping';
import chatMessageBeforeJoinCounter from './methods/chatMessageBeforeJoinCounter';
import fetchMessagePerPage from './methods/fetchMessagePerPage';
Meteor.methods({
fetchMessagePerPage,
chatMessageBeforeJoinCounter,
clearPublicChatHistory,
startUserTyping,
stopUserTyping,
});

View File

@ -1,45 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import GroupChat from '/imports/api/group-chat';
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import Users from '/imports/api/users';
import { extractCredentials } from '/imports/api/common/server/helpers';
import Logger from '/imports/startup/server/logger';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
export default async function chatMessageBeforeJoinCounter() {
try {
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
const groupChats = GroupChat.find({
$or: [
{ meetingId, access: PUBLIC_CHAT_TYPE },
{ meetingId, users: { $all: [requesterUserId] } },
],
}).fetch();
const User = await Users.findOneAsync({ userId: requesterUserId, meetingId });
const chatIdWithCounter = groupChats.map((groupChat) => {
const msgCount = GroupChatMsg.find({
meetingId,
chatId: groupChat.chatId,
timestamp: { $lt: User.authTokenValidatedTime },
}).count();
return {
chatId: groupChat.chatId,
count: msgCount,
};
}).filter((chat) => chat.count);
return chatIdWithCounter;
} catch (err) {
Logger.error(`Exception while invoking method chatMessageBeforeJoinCounter ${err.stack}`);
}
//True returned because the function requires a return.
return true;
}

View File

@ -1,37 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import Users from '/imports/api/users';
import { extractCredentials } from '/imports/api/common/server/helpers';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
const CHAT_CONFIG = Meteor.settings.public.chat;
const ITENS_PER_PAGE = CHAT_CONFIG.itemsPerPage;
export default async function fetchMessagePerPage(chatId, page) {
try {
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
check(chatId, String);
check(page, Number);
const User = await Users.findOneAsync({ userId: requesterUserId, meetingId });
const messages = await GroupChatMsg.find(
{ chatId, meetingId, timestamp: { $lt: User.authTokenValidatedTime } },
{
sort: { timestamp: 1 },
skip: page > 0 ? ((page - 1) * ITENS_PER_PAGE) : 0,
limit: ITENS_PER_PAGE,
},
)
.fetchAsync();
return messages;
} catch (err) {
Logger.error(`Exception while invoking method fetchMessagePerPage ${err.stack}`);
}
//True returned because the function requires a return.
return true;
}

View File

@ -1,25 +0,0 @@
import { UsersTyping } from '/imports/api/group-chat-msg';
import stopTyping from '../modifiers/stopTyping';
import { extractCredentials } from '/imports/api/common/server/helpers';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
export default async function stopUserTyping() {
try {
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
const userTyping = await UsersTyping.findOneAsync({
meetingId,
userId: requesterUserId,
});
if (userTyping && meetingId && requesterUserId) {
stopTyping(meetingId, requesterUserId, true);
}
} catch (err) {
Logger.error(`Exception while invoking method stopUserTyping ${err.stack}`);
}
}

View File

@ -1,47 +0,0 @@
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import GroupChat from '/imports/api/group-chat';
import Logger from '/imports/startup/server/logger';
import flat from 'flat';
import { parseMessage } from './addGroupChatMsg';
export default async function addBulkGroupChatMsgs(msgs) {
if (!msgs.length) return;
const mappedMsgs = msgs
.map(({ chatId, meetingId, msg }) => {
const {
sender,
...restMsg
} = msg;
return {
_id: new Mongo.ObjectID()._str,
...restMsg,
meetingId,
chatId,
message: parseMessage(msg.message),
sender: sender.id,
senderName: sender.name,
senderRole: sender.role,
};
})
.map((el) => flat(el, { safe: true }))
.map((msg)=>{
const groupChat = GroupChat.findOne({ meetingId: msg.meetingId, chatId: msg.chatId });
return {
...msg,
participants: [...groupChat.users],
};
});
try {
const { insertedCount } = await GroupChatMsg.rawCollection().insertMany(mappedMsgs);
msgs.length = 0;
if (insertedCount) {
Logger.info(`Inserted ${insertedCount} messages`);
}
} catch (err) {
Logger.error(`Error on bulk insert. ${err}`);
}
}

View File

@ -1,61 +0,0 @@
import { Match, check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import { BREAK_LINE } from '/imports/utils/lineEndings';
import changeHasMessages from '/imports/api/users-persistent-data/server/modifiers/changeHasMessages';
import GroupChat from '/imports/api/group-chat';
export function parseMessage(message) {
let parsedMessage = message || '';
// Replace \r and \n to <br/>
parsedMessage = parsedMessage.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
// Replace flash links to html valid ones
parsedMessage = parsedMessage.split('<a href=\'event:').join('<a target="_blank" href=\'');
parsedMessage = parsedMessage.split('<a href="event:').join('<a target="_blank" href="');
return parsedMessage;
}
export default async function addGroupChatMsg(meetingId, chatId, msg) {
check(meetingId, String);
check(chatId, String);
check(msg, {
id: Match.Maybe(String),
timestamp: Number,
sender: Object,
chatEmphasizedText: Boolean,
message: String,
correlationId: Match.Maybe(String),
});
const {
sender,
...restMsg
} = msg;
const groupChat = GroupChat.findOne({ meetingId, chatId });
const msgDocument = {
...restMsg,
sender: sender.id,
senderName: sender.name,
senderRole: sender.role,
meetingId,
chatId,
participants: [...groupChat.users],
message: parseMessage(msg.message),
};
try {
const insertedId = await GroupChatMsg.insertAsync(msgDocument);
if (insertedId) {
await changeHasMessages(true, sender.id, meetingId, chatId);
Logger.info(`Added group-chat-msg msgId=${msg.id} chatId=${chatId} meetingId=${meetingId}`);
}
} catch (err) {
Logger.error(`Error on adding group-chat-msg to collection: ${err}`);
}
}

View File

@ -1,48 +0,0 @@
import { Match, check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import { BREAK_LINE } from '/imports/utils/lineEndings';
export function parseMessage(message) {
let parsedMessage = message || '';
// Replace \r and \n to <br/>
parsedMessage = parsedMessage.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
// Replace flash links to html valid ones
parsedMessage = parsedMessage.split('<a href=\'event:').join('<a target="_blank" href=\'');
parsedMessage = parsedMessage.split('<a href="event:').join('<a target="_blank" href="');
return parsedMessage;
}
export default async function addSystemMsg(meetingId, chatId, msg) {
check(meetingId, String);
check(chatId, String);
check(msg, {
id: Match.Maybe(String),
timestamp: Number,
sender: Object,
message: String,
messageValues: Match.Maybe(Object),
extra: Match.Maybe(Object),
correlationId: Match.Maybe(String),
});
const msgDocument = {
...msg,
sender: msg.sender.id,
meetingId,
chatId,
message: parseMessage(msg.message),
};
try {
const insertedId = await GroupChatMsg.insertAsync(msgDocument);
if (insertedId) {
Logger.info(`Added system-msg msgId=${msg.id} chatId=${chatId} meetingId=${meetingId}`);
}
} catch (err) {
Logger.error(`Error on adding system-msg to collection: ${err}`);
}
}

View File

@ -1,74 +0,0 @@
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import Logger from '/imports/startup/server/logger';
import addSystemMsg from '/imports/api/group-chat-msg/server/modifiers/addSystemMsg';
import clearChatHasMessages from '/imports/api/users-persistent-data/server/modifiers/clearChatHasMessages';
import UsersPersistentData from '/imports/api/users-persistent-data';
export default async function clearGroupChatMsg(meetingId, chatId) {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_SYSTEM_ID = CHAT_CONFIG.system_userid;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
if (chatId) {
try {
const numberAffected = await GroupChatMsg.removeAsync({ meetingId, chatId });
if (numberAffected) {
Logger.info(`Cleared GroupChatMsg (${meetingId}, ${chatId})`);
const clearMsg = {
id: `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`,
timestamp: Date.now(),
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
name: '',
},
message: CHAT_CLEAR_MESSAGE,
};
await addSystemMsg(meetingId, PUBLIC_GROUP_CHAT_ID, clearMsg);
await clearChatHasMessages(meetingId, chatId);
//clear offline users' data
const selector = {
meetingId,
'shouldPersist.hasConnectionStatus': { $ne: true },
'shouldPersist.hasMessages.private': { $ne: true },
loggedOut: true,
};
await UsersPersistentData.removeAsync(selector);
}
} catch (err) {
Logger.error(`Error on clearing GroupChat (${meetingId}, ${chatId}). ${err}`);
}
return true;
}
if (meetingId) {
try {
const numberAffected = await GroupChatMsg.removeAsync({ meetingId });
if (numberAffected) {
Logger.info(`Cleared GroupChatMsg (${meetingId})`);
}
} catch (err) {
Logger.error(`Error on clearing GroupChatMsg (${meetingId}). ${err}`);
}
} else {
try {
const numberAffected = await GroupChatMsg
.removeAsync({ chatId: { $eq: PUBLIC_GROUP_CHAT_ID } });
if (numberAffected) {
await clearChatHasMessages(meetingId, chatId=PUBLIC_GROUP_CHAT_ID);
Logger.info('Cleared GroupChatMsg (all)');
}
} catch (err) {
Logger.error(`Error on clearing GroupChatMsg (all). ${err}`);
}
}
//True resturned because the function requires a return.
return true;
}

View File

@ -1,23 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import GroupChatMsg from '/imports/api/group-chat-msg';
export default async function removeGroupChatMsg(meetingId, chatId) {
check(meetingId, String);
check(chatId, String);
const selector = {
chatId,
meetingId,
};
try {
const numberAffected = await GroupChatMsg.removeAsync(selector);
if (numberAffected) {
Logger.info(`Removed group-chat-msg id=${chatId} meeting=${meetingId}`);
}
} catch (err) {
Logger.error(`Removing group-chat-msg from collection: ${err}`);
}
}

View File

@ -1,51 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
import { UsersTyping } from '/imports/api/group-chat-msg';
import stopTyping from './stopTyping';
const TYPING_TIMEOUT = 5000;
export default async function startTyping(meetingId, userId, chatId) {
check(meetingId, String);
check(userId, String);
const selector = {
meetingId,
userId,
};
const user = await Users.findOneAsync(selector, { fields: { name: 1, role: 1 } });
const modifier = {
meetingId,
userId,
name: user.name,
isTypingTo: chatId,
role: user.role,
time: (new Date()),
};
const typingUser = await UsersTyping.findOneAsync(selector, {
fields: {
time: 1,
},
});
if (typingUser) {
if (modifier.time - typingUser.time <= TYPING_TIMEOUT - 100) return;
}
try {
const { numberAffected } = await UsersTyping.upsertAsync(selector, modifier);
if (numberAffected) {
Logger.debug('Typing indicator update', { userId, chatId });
Meteor.setTimeout(() => {
stopTyping(meetingId, userId);
}, TYPING_TIMEOUT);
}
} catch (err) {
Logger.error(`Typing indicator update error: ${err}`);
}
}

View File

@ -1,28 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import { UsersTyping } from '/imports/api/group-chat-msg';
export default async function stopTyping(meetingId, userId, sendMsgInitiated = false) {
check(meetingId, String);
check(userId, String);
check(sendMsgInitiated, Boolean);
const selector = {
meetingId,
userId,
};
const user = await UsersTyping.findOneAsync(selector);
const stillTyping = !sendMsgInitiated && user && (new Date()) - user.time < 3000;
if (stillTyping) return;
try {
const numberAffected = await UsersTyping.removeAsync(selector);
if (numberAffected) {
Logger.debug('Stopped typing indicator', { userId });
}
} catch (err) {
Logger.error(`Stop user=${userId} typing indicator error: ${err}`);
}
}

View File

@ -1,49 +0,0 @@
import { Match, check } from 'meteor/check';
import flat from 'flat';
import { GroupChatMsg } from '/imports/api/group-chat-msg';
import Logger from '/imports/startup/server/logger';
import { parseMessage } from './addGroupChatMsg';
export default function syncMeetingChatMsgs(meetingId, chatId, msgs) {
if (!msgs.length) return;
check(meetingId, String);
check(chatId, String);
check(msgs, Match.Maybe(Array));
try {
const bulkOperations = GroupChatMsg.rawCollection().initializeOrderedBulkOp();
msgs
.forEach((msg) => {
const {
sender,
...restMsg
} = msg;
const msgToSync = {
...restMsg,
meetingId,
chatId,
message: parseMessage(msg.message),
sender: sender.id,
};
const modifier = flat(msgToSync, { safe: true });
bulkOperations
.find({ chatId, meetingId, id: msg.id })
.upsert()
.updateOne({
$setOnInsert: { _id: new Mongo.ObjectID()._str },
$set: { ...modifier },
});
});
bulkOperations.execute();
Logger.info('Chat messages synchronized', { chatId, meetingId });
} catch (err) {
Logger.error(`Error on sync chat messages: ${err}`);
}
}

View File

@ -1,71 +0,0 @@
import { GroupChatMsg, UsersTyping } from '/imports/api/group-chat-msg';
import Users from '/imports/api/users';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import GroupChat from '/imports/api/group-chat';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
async function groupChatMsg() {
const tokenValidation = await AuthTokenValidation
.findOneAsync({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
Logger.warn(`Publishing GroupChatMsg was requested by unauth connection ${this.connection.id}`);
return GroupChatMsg.find({ meetingId: '' });
}
const { meetingId, userId } = tokenValidation;
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
Logger.debug('Publishing group-chat-msg', { meetingId, userId });
const chats = await GroupChat.find({
$or: [
{ meetingId, users: { $all: [userId] } },
],
}).fetchAsync();
const chatsIds = chats.map((ct) => ct.chatId);
const User = await Users.findOneAsync({ userId, meetingId });
const selector = {
timestamp: { $gte: User.authTokenValidatedTime },
$or: [
{ meetingId, chatId: { $eq: PUBLIC_GROUP_CHAT_ID } },
{ meetingId, participants: { $in: [userId] } },
],
};
return GroupChatMsg.find(selector, { fields: { participants: 0 } });
}
function publish(...args) {
const boundGroupChat = groupChatMsg.bind(this);
return boundGroupChat(...args);
}
Meteor.publish('group-chat-msg', publish);
function usersTyping() {
const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
Logger.warn(`Publishing users-typing was requested by unauth connection ${this.connection.id}`);
return UsersTyping.find({ meetingId: '' });
}
const { meetingId, userId } = tokenValidation;
Logger.debug('Publishing users-typing', { meetingId, userId });
return UsersTyping.find({ meetingId });
}
function pubishUsersTyping(...args) {
const boundUsersTyping = usersTyping.bind(this);
return boundUsersTyping(...args);
}
Meteor.publish('users-typing', pubishUsersTyping);

View File

@ -1,19 +1,3 @@
import { Meteor } from 'meteor/meteor';
const collectionOptions = Meteor.isClient ? {
connection: null,
} : {};
const GroupChat = new Mongo.Collection('group-chat', collectionOptions);
if (Meteor.isServer) {
GroupChat.createIndexAsync({
meetingId: 1, chatId: 1, access: 1, users: 1,
});
}
export default GroupChat;
const CHAT_ACCESS = {
PUBLIC: 'PUBLIC_ACCESS',
PRIVATE: 'PRIVATE_ACCESS',

View File

@ -1,10 +0,0 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleGroupChats from './handlers/groupChats';
import handleGroupChatCreated from './handlers/groupChatCreated';
import handleGroupChatDestroyed from './handlers/groupChatDestroyed';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
// RedisPubSub.on('GetGroupChatsRespMsg', processForHTML5ServerOnly(handleGroupChats));
// RedisPubSub.on('GroupChatCreatedEvtMsg', handleGroupChatCreated);
// RedisPubSub.on('GroupChatDestroyedEvtMsg', handleGroupChatDestroyed);
// RedisPubSub.on('SyncGetGroupChatsRespMsg', handleGroupChats);

View File

@ -1,9 +0,0 @@
import { check } from 'meteor/check';
import addGroupChat from '../modifiers/addGroupChat';
export default async function handleGroupChatCreated({ body }, meetingId) {
check(meetingId, String);
check(body, Object);
await addGroupChat(meetingId, body);
}

View File

@ -1,9 +0,0 @@
import { check } from 'meteor/check';
import addGroupChat from '../modifiers/addGroupChat';
export default async function handleGroupChatDestroyed({ body }, meetingId) {
check(meetingId, String);
check(body, Object);
await addGroupChat(meetingId, body);
}

View File

@ -1,14 +0,0 @@
import { check } from 'meteor/check';
import addGroupChat from '../modifiers/addGroupChat';
export default async function handleGroupChats({ body }, meetingId) {
const { chats } = body;
check(meetingId, String);
check(chats, Array);
await new Promise
.all(chats.map(async (chat) => {
await addGroupChat(meetingId, chat);
}));
}

View File

@ -1,4 +1,2 @@
import '/imports/api/group-chat-msg/server';
import './eventHandlers';
import './methods';
import './publishers';

View File

@ -1,47 +0,0 @@
import flat from 'flat';
import { Match, check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import GroupChat from '/imports/api/group-chat';
export default async function addGroupChat(meetingId, chat) {
check(meetingId, String);
check(chat, {
id: Match.Maybe(String),
chatId: Match.Maybe(String),
correlationId: Match.Maybe(String),
access: String,
createdBy: Object,
users: Array,
msg: Match.Maybe(Array),
});
const chatDocument = {
meetingId,
chatId: chat.chatId || chat.id,
access: chat.access,
users: chat.users.map((u) => u.id),
participants: chat.users,
createdBy: chat.createdBy.id,
};
const selector = {
chatId: chatDocument.chatId,
meetingId,
};
const modifier = {
$set: flat(chatDocument, { safe: true }),
};
try {
const { insertedId } = await GroupChat.upsertAsync(selector, modifier);
if (insertedId) {
Logger.info(`Added group-chat chatId=${chatDocument.chatId} meetingId=${meetingId}`);
} else {
Logger.info(`Upserted group-chat chatId=${chatDocument.chatId} meetingId=${meetingId}`);
}
} catch (err) {
Logger.error(`Adding group-chat to collection: ${err}`);
}
}

View File

@ -1,16 +0,0 @@
import GroupChat from '/imports/api/group-chat';
import Logger from '/imports/startup/server/logger';
import clearGroupChatMsg from '/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg';
export default async function clearGroupChat(meetingId) {
try {
await clearGroupChatMsg(meetingId);
const numberAffected = await GroupChat.removeAsync({ meetingId });
if (numberAffected) {
Logger.info(`Cleared GroupChat (${meetingId})`);
}
} catch (err) {
Logger.error(`Error on clearing GroupChat (${meetingId}). ${err}`);
}
}

View File

@ -1,25 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import GroupChat from '/imports/api/group-chat';
import clearGroupChatMsg from '/imports/api/group-chat-msg/server/modifiers/clearGroupChatMsg';
export default async function removeGroupChat(meetingId, chatId) {
check(meetingId, String);
check(chatId, String);
const selector = {
chatId,
meetingId,
};
try {
const numberAffected = await GroupChat.removeAsync(selector);
if (numberAffected) {
Logger.info(`Removed group-chat id=${chatId} meeting=${meetingId}`);
clearGroupChatMsg(meetingId, chatId);
}
} catch (err) {
Logger.error(`Removing group-chat from collection: ${err}`);
}
}

View File

@ -1,37 +0,0 @@
import GroupChat from '/imports/api/group-chat';
import { Meteor } from 'meteor/meteor';
import Logger from '/imports/startup/server/logger';
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
async function groupChat() {
const tokenValidation = await AuthTokenValidation
.findOneAsync({ connectionId: this.connection.id });
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
Logger.warn(`Publishing GroupChat was requested by unauth connection ${this.connection.id}`);
return GroupChat.find({ meetingId: '' });
}
const { meetingId, userId } = tokenValidation;
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
Logger.debug('Publishing group-chat', { meetingId, userId });
return GroupChat.find({
$or: [
{ meetingId, access: PUBLIC_CHAT_TYPE },
{ meetingId, users: { $all: [userId] } },
],
});
}
function publish(...args) {
const boundGroupChat = groupChat.bind(this);
return boundGroupChat(...args);
}
Meteor.publish('group-chat', publish);

View File

@ -1,16 +1,14 @@
import { check } from 'meteor/check';
import Meetings, { MeetingTimeRemaining } from '/imports/api/meetings';
import { MeetingTimeRemaining } from '/imports/api/meetings';
import Logger from '/imports/startup/server/logger';
import addSystemMsg from '/imports/api/group-chat-msg/server/modifiers/addSystemMsg';
export default async function handleTimeRemainingUpdate({ body }, meetingId) {
check(meetingId, String);
check(body, {
timeLeftInSec: Number,
timeUpdatedInMinutes: Number,
});
const { timeLeftInSec, timeUpdatedInMinutes } = body;
const { timeLeftInSec } = body;
const selector = {
meetingId,
@ -27,34 +25,4 @@ export default async function handleTimeRemainingUpdate({ body }, meetingId) {
} catch (err) {
Logger.error(`Changing recording time: ${err}`);
}
if (timeUpdatedInMinutes > 0) {
const Meeting = await Meetings.findOneAsync({ meetingId });
if (Meeting.meetingProp.isBreakout) {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const PUBLIC_CHAT_SYSTEM_ID = CHAT_CONFIG.system_userid;
const PUBLIC_CHAT_INFO = CHAT_CONFIG.system_messages_keys.chat_info;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
const messageValues = {
0: timeUpdatedInMinutes,
};
const payload = {
id: `${SYSTEM_CHAT_TYPE}-${PUBLIC_CHAT_INFO}`,
timestamp: Date.now(),
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
name: '',
},
message: 'breakoutDurationUpdated',
messageValues,
};
await addSystemMsg(meetingId, PUBLIC_GROUP_CHAT_ID, payload);
}
}
}

View File

@ -5,7 +5,6 @@ import { removeExternalVideoStreamer } from '/imports/api/external-videos/server
import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
import clearUsersSettings from '/imports/api/users-settings/server/modifiers/clearUsersSettings';
import clearGroupChat from '/imports/api/group-chat/server/modifiers/clearGroupChat';
import clearGuestUsers from '/imports/api/guest-users/server/modifiers/clearGuestUsers';
import clearBreakouts from '/imports/api/breakouts/server/modifiers/clearBreakouts';
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
@ -39,7 +38,6 @@ export default async function meetingHasEnded(meetingId) {
await Promise.all([
clearCaptions(meetingId),
clearPads(meetingId),
clearGroupChat(meetingId),
clearGuestUsers(meetingId),
clearBreakouts(meetingId),
clearPolls(meetingId),

View File

@ -1,8 +1,5 @@
import { check } from 'meteor/check';
import setPublishedPoll from '../../../meetings/server/modifiers/setPublishedPoll';
import handleSendSystemChatForPublishedPoll from './sendPollChatMsg';
const POLL_CHAT_MESSAGE = Meteor.settings.public.poll.chatMessage;
export default function pollPublished({ body }, meetingId) {
const { pollId } = body;
@ -11,8 +8,4 @@ export default function pollPublished({ body }, meetingId) {
check(pollId, String);
setPublishedPoll(meetingId, true);
if (POLL_CHAT_MESSAGE) {
handleSendSystemChatForPublishedPoll({ body }, meetingId);
}
}

View File

@ -1,37 +0,0 @@
import addSystemMsg from '../../../group-chat-msg/server/modifiers/addSystemMsg';
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
export default function sendPollChatMsg({ body }, meetingId) {
const { poll } = body;
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const PUBLIC_CHAT_SYSTEM_ID = CHAT_CONFIG.system_userid;
const CHAT_POLL_RESULTS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_poll_result;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
const pollResultData = poll;
const answers = pollResultData.answers.reduce(caseInsensitiveReducer, []);
const extra = {
type: 'poll',
pollResultData: {
...pollResultData,
answers,
},
};
const payload = {
id: `${SYSTEM_CHAT_TYPE}-${CHAT_POLL_RESULTS_MESSAGE}`,
timestamp: Date.now(),
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
name: '',
},
message: '',
extra,
};
return addSystemMsg(meetingId, PUBLIC_GROUP_CHAT_ID, payload);
}

View File

@ -2,15 +2,10 @@ import { Meteor } from 'meteor/meteor';
import Meetings from '/imports/api/meetings';
import Users from '/imports/api/users';
import addSystemMsg from '/imports/api/group-chat-msg/server/modifiers/addSystemMsg';
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const CHAT_USER_STATUS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_status_message;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
export default function sendAwayStatusChatMsg(meetingId, userId, newAwayStatus) {
export default function sendAwayStatusChatMsg(meetingId, userId) {
const user = Users.findOne(
{ meetingId, userId },
{
@ -40,33 +35,4 @@ export default function sendAwayStatusChatMsg(meetingId, userId, newAwayStatus)
return null;
}
}
// Send message if previous emoji or actual emoji is 'away'
let status;
if (user.away && !newAwayStatus) {
status = 'notAway';
} else if (!user.away && newAwayStatus) {
status = 'away';
} else {
return null;
}
const extra = {
type: 'status',
status,
};
const payload = {
id: `${SYSTEM_CHAT_TYPE}-${CHAT_USER_STATUS_MESSAGE}`,
timestamp: Date.now(),
correlationId: `${userId}-${Date.now()}`,
sender: {
id: userId,
name: user.name,
},
message: '',
extra,
};
return addSystemMsg(meetingId, PUBLIC_GROUP_CHAT_ID, payload);
}

View File

@ -1,161 +0,0 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import BBBMenu from '/imports/ui/components/common/menu/component';
import { getDateString, uniqueId } from '/imports/utils/string-utils';
import Trigger from '/imports/ui/components/common/control-header/right/component';
import ChatService from '../service';
import { addNewAlert } from '../../screenreader-alert/service';
const intlMessages = defineMessages({
clear: {
id: 'app.chat.dropdown.clear',
description: 'Clear button label',
},
save: {
id: 'app.chat.dropdown.save',
description: 'Clear button label',
},
copy: {
id: 'app.chat.dropdown.copy',
description: 'Copy button label',
},
copySuccess: {
id: 'app.chat.copySuccess',
description: 'aria success alert',
},
copyErr: {
id: 'app.chat.copyErr',
description: 'aria error alert',
},
options: {
id: 'app.chat.dropdown.options',
description: 'Chat Options',
},
});
const CHAT_CONFIG = Meteor.settings.public.chat;
const ENABLE_SAVE_AND_COPY_PUBLIC_CHAT = CHAT_CONFIG.enableSaveAndCopyPublicChat;
class ChatDropdown extends PureComponent {
constructor(props) {
super(props);
this.actionsKey = [
uniqueId('action-item-'),
uniqueId('action-item-'),
uniqueId('action-item-'),
];
}
getAvailableActions() {
const {
intl,
isMeteorConnected,
amIModerator,
meetingIsBreakout,
meetingName,
timeWindowsValues,
} = this.props;
const clearIcon = 'delete';
const saveIcon = 'download';
const copyIcon = 'copy';
this.menuItems = [];
if (ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) {
this.menuItems.push(
{
key: this.actionsKey[0],
icon: saveIcon,
dataTest: 'chatSave',
label: intl.formatMessage(intlMessages.save),
onClick: () => {
const link = document.createElement('a');
const mimeType = 'text/plain';
link.setAttribute('download', `bbb-${meetingName}[public-chat]_${getDateString()}.txt`);
link.setAttribute(
'href',
`data: ${mimeType};charset=utf-8,`
+ `${encodeURIComponent(ChatService.exportChat(timeWindowsValues, intl))}`,
);
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
},
},
);
}
if (ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) {
this.menuItems.push(
{
key: this.actionsKey[1],
icon: copyIcon,
id: 'clipboardButton',
dataTest: 'chatCopy',
label: intl.formatMessage(intlMessages.copy),
onClick: () => {
const chatHistory = ChatService.exportChat(timeWindowsValues, intl);
navigator.clipboard.writeText(chatHistory).then(() => {
addNewAlert(intl.formatMessage(intlMessages.copySuccess));
}).catch(() => {
addNewAlert(intl.formatMessage(intlMessages.copyErr));
});
},
},
);
}
if (!meetingIsBreakout && amIModerator && isMeteorConnected) {
this.menuItems.push(
{
key: this.actionsKey[2],
icon: clearIcon,
dataTest: 'chatClear',
label: intl.formatMessage(intlMessages.clear),
onClick: () => ChatService.clearPublicChatHistory(),
},
);
}
return this.menuItems;
}
render() {
const {
intl,
amIModerator,
isRTL,
} = this.props;
if (!amIModerator && !ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) return null;
return (
<>
<BBBMenu
trigger={
<Trigger
data-test="chatOptionsMenu"
icon="more"
label={intl.formatMessage(intlMessages.options)}
aria-label={intl.formatMessage(intlMessages.options)}
onClick={() => null}
/>
}
opts={{
id: 'chat-options-dropdown-menu',
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getcontentanchorel: null,
fullwidth: 'true',
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
}}
actions={this.getAvailableActions()}
/>
</>
);
}
}
export default injectIntl(ChatDropdown);

View File

@ -1,24 +0,0 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings';
import ChatDropdown from './component';
import { layoutSelect } from '../../layout/context';
const ChatDropdownContainer = ({ ...props }) => {
const isRTL = layoutSelect((i) => i.isRTL);
return <ChatDropdown {...{ isRTL, ...props }} />;
};
export default withTracker(() => {
const getMeetingName = () => {
const m = Meetings.findOne({ meetingId: Auth.meetingID },
{ fields: { 'meetingProp.name': 1 } });
return m.meetingProp.name;
};
return {
meetingName: getMeetingName(),
};
})(ChatDropdownContainer);

View File

@ -18,7 +18,6 @@ import useChat from '/imports/ui/core/hooks/useChat';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import {
startUserTyping,
stopUserTyping,
textToMarkdown,
} from './service';
import { Chat } from '/imports/ui/Types/chat';
@ -273,7 +272,6 @@ const ChatMessageForm: React.FC<ChatMessageFormProps> = ({
updateUnreadMessages(chatId, '');
setHasErrors(false);
setShowEmojiPicker(false);
if (ENABLE_TYPING_INDICATOR) stopUserTyping();
const sentMessageEvent = new CustomEvent(ChatEvents.SENT_MESSAGE);
window.dispatchEvent(sentMessageEvent);

View File

@ -13,7 +13,6 @@ export const startUserTyping = throttle(
START_TYPING_THROTTLE_INTERVAL,
{ leading: true, trailing: false },
);
export const stopUserTyping = () => makeCall('stopUserTyping');
export const textToMarkdown = (message: string) => {
let parsedMessage = message || '';
@ -31,6 +30,5 @@ export const textToMarkdown = (message: string) => {
export default {
startUserTyping,
stopUserTyping,
textToMarkdown,
};

View File

@ -1,144 +0,0 @@
import React, { memo, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import { Meteor } from 'meteor/meteor';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import Styled from './styles';
import MessageFormContainer from './message-form/container';
import TimeWindowList from './time-window-list/container';
import ChatDropdownContainer from './chat-dropdown/container';
import { PANELS, ACTIONS } from '../layout/enums';
import { UserSentMessageCollection } from './service';
import Auth from '/imports/ui/services/auth';
import browserInfo from '/imports/utils/browserInfo';
import Header from '/imports/ui/components/common/control-header/component';
import { CLOSE_PRIVATE_CHAT_MUTATION } from '../user-list/user-list-content/user-messages/chat-list/queries';
import ChatPopup from './chat-graphql/chat-popup/component';
import { useMutation, gql } from '@apollo/client';
import ChatHeader from './chat-graphql/chat-header/component';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
const ELEMENT_ID = 'chat-messages';
const intlMessages = defineMessages({
closeChatLabel: {
id: 'app.chat.closeChatLabel',
description: 'aria-label for closing chat button',
},
hideChatLabel: {
id: 'app.chat.hideChatLabel',
description: 'aria-label for hiding chat button',
},
});
const Chat = (props) => {
const {
chatID,
title,
messages,
partnerIsLoggedOut,
isChatLocked,
actions,
intl,
shortcuts,
isMeteorConnected,
lastReadMessageTime,
hasUnreadMessages,
scrollPosition,
amIModerator,
meetingIsBreakout,
timeWindowsValues,
dispatch,
count,
layoutContextDispatch,
syncing,
syncedPercent,
lastTimeWindowValuesBuild,
width,
} = props;
const [updateVisible] = useMutation(CLOSE_PRIVATE_CHAT_MUTATION);
const handleClosePrivateChat = () => {
updateVisible(({
variables: {
chatId: chatID
},
}))
actions.handleClosePrivateChat(chatID);
};
const userSentMessage = UserSentMessageCollection.findOne({ userId: Auth.userID, sent: true });
const { isChrome } = browserInfo;
const HIDE_CHAT_AK = shortcuts.hideprivatechat;
const CLOSE_CHAT_AK = shortcuts.closeprivatechat;
const isPublicChat = chatID === PUBLIC_CHAT_ID;
ChatLogger.debug('ChatComponent::render', props);
return (
<Styled.Chat
isChrome={isChrome}
data-test={isPublicChat ? 'publicChat' : 'privateChat'}
>
<ChatHeader />
<TimeWindowList
id={ELEMENT_ID}
chatId={chatID}
handleScrollUpdate={actions.handleScrollUpdate}
{...{
partnerIsLoggedOut,
lastReadMessageTime,
hasUnreadMessages,
scrollPosition,
messages,
currentUserIsModerator: amIModerator,
timeWindowsValues,
dispatch,
count,
syncing,
syncedPercent,
lastTimeWindowValuesBuild,
userSentMessage,
width,
}}
/>
<MessageFormContainer
title={title}
chatId={chatID}
chatTitle={title}
chatAreaId={ELEMENT_ID}
disabled={isChatLocked || !isMeteorConnected}
connected={isMeteorConnected}
locked={isChatLocked}
partnerIsLoggedOut={partnerIsLoggedOut}
/>
</Styled.Chat>
);
};
export default memo(withShortcutHelper(injectWbResizeEvent(injectIntl(Chat)), ['hidePrivateChat', 'closePrivateChat']));
const propTypes = {
chatID: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
shortcuts: PropTypes.objectOf(PropTypes.string),
partnerIsLoggedOut: PropTypes.bool.isRequired,
isChatLocked: PropTypes.bool.isRequired,
isMeteorConnected: PropTypes.bool.isRequired,
actions: PropTypes.shape({
handleClosePrivateChat: PropTypes.func.isRequired,
}).isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
};
const defaultProps = {
shortcuts: [],
};
Chat.propTypes = propTypes;
Chat.defaultProps = defaultProps;

View File

@ -1,273 +0,0 @@
import React, { useEffect, useContext, useState } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { withTracker } from 'meteor/react-meteor-data';
import { throttle } from '/imports/utils/throttle';
import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import { meetingIsBreakout } from '/imports/ui/components/app/service';
import { ChatContext, getLoginTime } from '../components-data/chat-context/context';
import { GroupChatContext } from '../components-data/group-chat-context/context';
import { UsersContext } from '../components-data/users-context/context';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
// import Chat from '/imports/ui/components/chat/component';
import ChatService from './service';
import { layoutSelect, layoutDispatch } from '../layout/context';
import { escapeHtml } from '/imports/utils/string-utils';
import Chat from './chat-graphql/component';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_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 DEBOUNCE_TIME = 1000;
const sysMessagesIds = {
welcomeId: `${SYSTEM_CHAT_TYPE}-welcome-msg`,
moderatorId: `${SYSTEM_CHAT_TYPE}-moderator-msg`,
syncId: `${SYSTEM_CHAT_TYPE}-sync-msg`,
};
const intlMessages = defineMessages({
[CHAT_CLEAR]: {
id: 'app.chat.clearPublicChatMessage',
description: 'message of when clear the public chat',
},
titlePublic: {
id: 'app.chat.titlePublic',
description: 'Public chat title',
},
titlePrivate: {
id: 'app.chat.titlePrivate',
description: 'Private chat title',
},
partnerDisconnected: {
id: 'app.chat.partnerDisconnected',
description: 'System chat message when the private chat partnet disconnect from the meeting',
},
loading: {
id: 'app.chat.loading',
description: 'loading message',
},
});
let previousChatId = null;
let prevSync = false;
let prevPartnerIsLoggedOut = false;
let globalAppplyStateToProps = () => { };
const throttledFunc = throttle(() => {
globalAppplyStateToProps();
}, DEBOUNCE_TIME, { trailing: true, leading: true });
const ChatContainer = (props) => {
const {
children,
loginTime,
intl,
userLocks,
lockSettings,
isChatLockedPublic,
isChatLockedPrivate,
users: propUsers,
...restProps
} = props;
const idChatOpen = layoutSelect((i) => i.idChatOpen);
const layoutContextDispatch = layoutDispatch();
const isPublicChat = idChatOpen === PUBLIC_CHAT_KEY;
const chatID = idChatOpen;
if (!chatID) return null;
useEffect(() => {
ChatService.removeFromClosedChatsSession();
}, []);
const modOnlyMessage = Storage.getItem('ModeratorOnlyMessage');
const { welcomeProp } = ChatService.getWelcomeProp();
ChatLogger.debug('ChatContainer::render::props', props);
const systemMessages = {
[sysMessagesIds.welcomeId]: {
id: sysMessagesIds.welcomeId,
content: [{
id: sysMessagesIds.welcomeId,
text: welcomeProp.welcomeMsg,
time: loginTime,
}],
key: sysMessagesIds.welcomeId,
time: loginTime,
sender: null,
},
[sysMessagesIds.moderatorId]: {
id: sysMessagesIds.moderatorId,
content: [{
id: sysMessagesIds.moderatorId,
text: modOnlyMessage,
time: loginTime + 1,
}],
key: sysMessagesIds.moderatorId,
time: loginTime + 1,
sender: null,
},
};
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const currentUser = users[Auth.meetingID][Auth.userID];
const amIModerator = currentUser.role === ROLE_MODERATOR;
const systemMessagesIds = [
sysMessagesIds.welcomeId,
amIModerator && modOnlyMessage && sysMessagesIds.moderatorId,
].filter((i) => i);
const usingChatContext = useContext(ChatContext);
const usingGroupChatContext = useContext(GroupChatContext);
const [stateLastMsg, setLastMsg] = useState(null);
const [
stateTimeWindows, setTimeWindows,
] = useState(isPublicChat ? [...systemMessagesIds.map((item) => systemMessages[item])] : []);
const [lastTimeWindowValuesBuild, setLastTimeWindowValuesBuild] = useState(0);
const { groupChat } = usingGroupChatContext;
const participants = groupChat[idChatOpen]?.participants;
const chatName = participants?.filter((user) => user.id !== Auth.userID)[0]?.name;
const title = chatName
? intl.formatMessage(intlMessages.titlePrivate, { 0: chatName })
: intl.formatMessage(intlMessages.titlePublic);
let partnerIsLoggedOut = false;
let isChatLocked;
if (!isPublicChat) {
const idUser = participants?.filter((user) => user.id !== Auth.userID)[0]?.id;
partnerIsLoggedOut = !!(users[Auth.meetingID][idUser]?.loggedOut
|| users[Auth.meetingID][idUser]?.ejected);
isChatLocked = isChatLockedPrivate && !(users[Auth.meetingID][idUser]?.role === ROLE_MODERATOR);
} else {
isChatLocked = isChatLockedPublic;
}
const contextChat = usingChatContext?.chats[isPublicChat ? PUBLIC_GROUP_CHAT_KEY : chatID];
const lastTimeWindow = contextChat?.lastTimewindow;
const lastMsg = contextChat && (isPublicChat
? contextChat?.preJoinMessages[lastTimeWindow] || contextChat?.posJoinMessages[lastTimeWindow]
: contextChat?.messageGroups[lastTimeWindow]);
ChatLogger.debug('ChatContainer::render::chatData', contextChat);
const applyPropsToState = () => {
ChatLogger.debug('ChatContainer::applyPropsToState::chatData', lastMsg, stateLastMsg, contextChat?.syncing);
if (
(lastMsg?.lastTimestamp !== stateLastMsg?.lastTimestamp)
|| (previousChatId !== idChatOpen)
|| (prevSync !== contextChat?.syncing)
|| (prevPartnerIsLoggedOut !== partnerIsLoggedOut)
) {
prevSync = contextChat?.syncing;
prevPartnerIsLoggedOut = partnerIsLoggedOut;
const timeWindowsValues = isPublicChat
? [
...(
!contextChat?.syncing ? Object.values(contextChat?.preJoinMessages || {}) : [
{
id: sysMessagesIds.syncId,
content: [{
id: 'synced',
text: intl.formatMessage(intlMessages.loading, { 0: contextChat?.syncedPercent }),
time: loginTime + 1,
}],
key: sysMessagesIds.syncId,
time: loginTime + 1,
sender: null,
},
]
), ...systemMessagesIds.map((item) => systemMessages[item]),
...Object.values(contextChat?.posJoinMessages || {})]
: [...Object.values(contextChat?.messageGroups || {})];
if (previousChatId !== idChatOpen) {
previousChatId = idChatOpen;
}
if (partnerIsLoggedOut) {
const time = Date.now();
const id = `partner-disconnected-${time}`;
const messagePartnerLoggedOut = {
id,
content: [{
id,
text: escapeHtml(intl.formatMessage(intlMessages.partnerDisconnected, { 0: chatName })),
time,
}],
time,
sender: null,
};
timeWindowsValues.push(messagePartnerLoggedOut);
}
setLastMsg(lastMsg ? { ...lastMsg } : lastMsg);
setTimeWindows(timeWindowsValues);
setLastTimeWindowValuesBuild(Date.now());
}
};
globalAppplyStateToProps = applyPropsToState;
throttledFunc();
ChatService.removePackagedClassAttribute(
['ReactVirtualized__Grid', 'ReactVirtualized__Grid__innerScrollContainer'],
'role',
);
return (
<Chat {...{
idChatOpen,
isChatLocked,
...restProps,
chatID,
amIModerator,
count: (contextChat?.unreadTimeWindows.size || 0),
timeWindowsValues: stateTimeWindows,
dispatch: usingChatContext?.dispatch,
title,
syncing: contextChat?.syncing,
syncedPercent: contextChat?.syncedPercent,
chatName,
contextChat,
layoutContextDispatch,
lastTimeWindowValuesBuild,
partnerIsLoggedOut,
}}
>
{children}
</Chat>
);
};
lockContextContainer(injectIntl(withTracker(({ intl, userLocks }) => {
const isChatLockedPublic = userLocks.userPublicChat;
const isChatLockedPrivate = userLocks.userPrivateChat;
const { connected: isMeteorConnected } = Meteor.status();
return {
intl,
isChatLockedPublic,
isChatLockedPrivate,
isMeteorConnected,
meetingIsBreakout: meetingIsBreakout(),
loginTime: getLoginTime(),
actions: {
handleClosePrivateChat: ChatService.closePrivateChat,
},
};
})(ChatContainer)));
export default Chat;

View File

@ -1,399 +0,0 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { checkText } from 'smile2emoji';
import deviceInfo from '/imports/utils/deviceInfo';
import PropTypes from 'prop-types';
import TypingIndicatorContainer from '/imports/ui/components/chat/chat-graphql/chat-typing-indicator/component';
import Auth from '/imports/ui/services/auth';
import ClickOutside from '/imports/ui/components/click-outside/component';
import Styled from './styles';
import { escapeHtml } from '/imports/utils/string-utils';
import { isChatEnabled } from '/imports/ui/services/features';
const propTypes = {
intl: PropTypes.object.isRequired,
chatId: PropTypes.string.isRequired,
disabled: PropTypes.bool.isRequired,
minMessageLength: PropTypes.number.isRequired,
maxMessageLength: PropTypes.number.isRequired,
chatTitle: PropTypes.string.isRequired,
chatAreaId: PropTypes.string.isRequired,
handleSendMessage: PropTypes.func.isRequired,
UnsentMessagesCollection: PropTypes.objectOf(Object).isRequired,
connected: PropTypes.bool.isRequired,
locked: PropTypes.bool.isRequired,
partnerIsLoggedOut: PropTypes.bool.isRequired,
stopUserTyping: PropTypes.func.isRequired,
startUserTyping: PropTypes.func.isRequired,
};
const messages = defineMessages({
submitLabel: {
id: 'app.chat.submitLabel',
description: 'Chat submit button label',
},
inputLabel: {
id: 'app.chat.inputLabel',
description: 'Chat message input label',
},
emojiButtonLabel: {
id: 'app.chat.emojiButtonLabel',
description: 'Chat message emoji picker button label',
},
inputPlaceholder: {
id: 'app.chat.inputPlaceholder',
description: 'Chat message input placeholder',
},
errorMaxMessageLength: {
id: 'app.chat.errorMaxMessageLength',
},
errorServerDisconnected: {
id: 'app.chat.disconnected',
},
errorChatLocked: {
id: 'app.chat.locked',
},
singularTyping: {
id: 'app.chat.singularTyping',
description: 'used to indicate when 1 user is typing',
},
pluralTyping: {
id: 'app.chat.pluralTyping',
description: 'used to indicate when multiple user are typing',
},
severalPeople: {
id: 'app.chat.severalPeople',
description: 'displayed when 4 or more users are typing',
},
});
const CHAT_CONFIG = Meteor.settings.public.chat;
const AUTO_CONVERT_EMOJI = Meteor.settings.public.chat.autoConvertEmoji;
const ENABLE_EMOJI_PICKER = Meteor.settings.public.chat.emojiPicker.enable;
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
const PUBLIC_CHAT_GROUP_KEY = CHAT_CONFIG.public_group_id;
class MessageForm extends PureComponent {
constructor(props) {
super(props);
this.state = {
message: '',
error: null,
hasErrors: false,
showEmojiPicker: false,
};
this.handleMessageChange = this.handleMessageChange.bind(this);
this.handleMessageKeyDown = this.handleMessageKeyDown.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.setMessageHint = this.setMessageHint.bind(this);
this.handleUserTyping = this.handleUserTyping.bind(this);
this.typingIndicator = CHAT_CONFIG.typingIndicator.enabled;
}
componentDidMount() {
const { isMobile } = deviceInfo;
this.setMessageState();
this.setMessageHint();
if (!isMobile) {
if (this.textarea) this.textarea.focus();
}
}
componentDidUpdate(prevProps) {
const {
chatId,
connected,
locked,
partnerIsLoggedOut,
} = this.props;
const { message } = this.state;
const { isMobile } = deviceInfo;
if (prevProps.chatId !== chatId && !isMobile) {
if (this.textarea) this.textarea.focus();
}
if (prevProps.chatId !== chatId) {
this.updateUnsentMessagesCollection(prevProps.chatId, message);
this.setState(
{
error: null,
hasErrors: false,
}, this.setMessageState(),
);
}
if (
connected !== prevProps.connected
|| locked !== prevProps.locked
|| partnerIsLoggedOut !== prevProps.partnerIsLoggedOut
) {
this.setMessageHint();
}
}
componentWillUnmount() {
const { chatId } = this.props;
const { message } = this.state;
this.updateUnsentMessagesCollection(chatId, message);
this.setMessageState();
}
handleClickOutside() {
const { showEmojiPicker } = this.state;
if (showEmojiPicker) {
this.setState({ showEmojiPicker: false });
}
}
setMessageHint() {
const {
connected,
disabled,
intl,
locked,
partnerIsLoggedOut,
} = this.props;
let chatDisabledHint = null;
if (disabled && !partnerIsLoggedOut) {
if (connected) {
if (locked) {
chatDisabledHint = messages.errorChatLocked;
}
} else {
chatDisabledHint = messages.errorServerDisconnected;
}
}
this.setState({
hasErrors: disabled,
error: chatDisabledHint ? intl.formatMessage(chatDisabledHint) : null,
});
}
setMessageState() {
const { chatId, UnsentMessagesCollection } = this.props;
const unsentMessageByChat = UnsentMessagesCollection.findOne({ chatId },
{ fields: { message: 1 } });
this.setState({ message: unsentMessageByChat ? unsentMessageByChat.message : '' });
}
updateUnsentMessagesCollection(chatId, message) {
const { UnsentMessagesCollection } = this.props;
UnsentMessagesCollection.upsert(
{ chatId },
{ $set: { message } },
);
}
handleMessageKeyDown(e) {
// TODO Prevent send message pressing enter on mobile and/or virtual keyboard
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault();
const event = new Event('submit', {
bubbles: true,
cancelable: true,
});
this.handleSubmit(event);
}
}
handleUserTyping(error) {
const { startUserTyping, chatId } = this.props;
if (error || !this.typingIndicator) return;
startUserTyping(chatId);
}
handleMessageChange(e) {
const {
intl,
maxMessageLength,
} = this.props;
let message = null;
let error = null;
if (AUTO_CONVERT_EMOJI) {
message = checkText(e.target.value);
} else {
message = e.target.value;
}
if (message.length > maxMessageLength) {
error = intl.formatMessage(
messages.errorMaxMessageLength,
{ 0: maxMessageLength },
);
message = message.substring(0, maxMessageLength);
}
this.setState({
message,
error,
}, this.handleUserTyping(error));
}
handleSubmit(e) {
e.preventDefault();
const {
disabled,
minMessageLength,
maxMessageLength,
handleSendMessage,
stopUserTyping,
} = this.props;
const { message } = this.state;
const msg = message.trim();
if (msg.length < minMessageLength) return;
if (disabled
|| msg.length > maxMessageLength) {
this.setState({ hasErrors: true });
return;
}
const callback = this.typingIndicator ? stopUserTyping : null;
handleSendMessage(escapeHtml(msg));
this.setState({ message: '', error: '', hasErrors: false, showEmojiPicker: false }, callback);
}
handleEmojiSelect(emojiObject) {
const { message } = this.state;
const cursor = this.textarea.selectionStart;
this.setState(
{
message: message.slice(0, cursor)
+ emojiObject.native
+ message.slice(cursor),
},
);
const newCursor = cursor + emojiObject.native.length;
setTimeout(() => this.textarea.setSelectionRange(newCursor, newCursor), 10);
}
renderEmojiPicker() {
const { showEmojiPicker } = this.state;
if (showEmojiPicker) {
return (
<Styled.EmojiPickerWrapper>
<Styled.EmojiPicker
onEmojiSelect={(emojiObject) => this.handleEmojiSelect(emojiObject)}
/>
</Styled.EmojiPickerWrapper>
);
}
return null;
}
renderEmojiButton() {
const { intl } = this.props;
return (
<Styled.EmojiButton
onClick={() => this.setState((prevState) => ({
showEmojiPicker: !prevState.showEmojiPicker,
}))}
icon="happy"
color="light"
ghost
type="button"
circle
hideLabel
label={intl.formatMessage(messages.emojiButtonLabel)}
data-test="emojiPickerButton"
/>
);
}
renderForm() {
const {
intl,
chatTitle,
title,
disabled,
idChatOpen,
partnerIsLoggedOut,
} = this.props;
const {
hasErrors, error, message,
} = this.state;
return (
<Styled.Form
ref={(ref) => { this.form = ref; }}
onSubmit={this.handleSubmit}
>
{this.renderEmojiPicker()}
<Styled.Wrapper>
<Styled.Input
id="message-input"
innerRef={(ref) => { this.textarea = ref; return this.textarea; }}
placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: title })}
aria-label={intl.formatMessage(messages.inputLabel, { 0: chatTitle })}
aria-invalid={hasErrors ? 'true' : 'false'}
autoCorrect="off"
autoComplete="off"
spellCheck="true"
disabled={disabled || partnerIsLoggedOut}
value={message}
onChange={this.handleMessageChange}
onKeyDown={this.handleMessageKeyDown}
onPaste={(e) => { e.stopPropagation(); }}
onCut={(e) => { e.stopPropagation(); }}
onCopy={(e) => { e.stopPropagation(); }}
async
/>
{ENABLE_EMOJI_PICKER && this.renderEmojiButton()}
<Styled.SendButton
hideLabel
circle
aria-label={intl.formatMessage(messages.submitLabel)}
type="submit"
disabled={disabled || partnerIsLoggedOut}
label={intl.formatMessage(messages.submitLabel)}
color="primary"
icon="send"
onClick={() => { }}
data-test="sendMessageButton"
/>
</Styled.Wrapper>
<TypingIndicatorContainer
{...{ idChatOpen, error }}
isPrivate={idChatOpen !== PUBLIC_CHAT_KEY}
isTypingTo={idChatOpen === PUBLIC_CHAT_KEY ? PUBLIC_CHAT_GROUP_KEY : idChatOpen}
userId={Auth.userID}
/>
</Styled.Form>
);
}
render() {
if (!isChatEnabled()) return null;
return ENABLE_EMOJI_PICKER ? (
<ClickOutside
onClick={() => this.handleClickOutside()}
>
{this.renderForm()}
</ClickOutside>
) : this.renderForm();
}
}
MessageForm.propTypes = propTypes;
export default injectIntl(MessageForm);

View File

@ -1,41 +0,0 @@
import React from 'react';
import { throttle } from '/imports/utils/throttle';
import { makeCall } from '/imports/ui/services/api';
import MessageForm from './component';
import ChatService from '/imports/ui/components/chat/service';
import ChatMessageFormContainer from '../chat-graphql/chat-message-form/component';
import { layoutSelect } from '../../layout/context';
const CHAT_CONFIG = Meteor.settings.public.chat;
const START_TYPING_THROTTLE_INTERVAL = 2000;
const MessageFormContainer = (props) => {
const idChatOpen = layoutSelect((i) => i.idChatOpen);
const handleSendMessage = (message) => {
ChatService.setUserSentMessage(true);
return ChatService.sendGroupMessage(message, idChatOpen);
};
const startUserTyping = throttle(
(chatId) => makeCall('startUserTyping', chatId),
START_TYPING_THROTTLE_INTERVAL,
);
const stopUserTyping = () => makeCall('stopUserTyping');
return (
<MessageForm
{...{
startUserTyping,
stopUserTyping,
UnsentMessagesCollection: ChatService.UnsentMessagesCollection,
minMessageLength: CHAT_CONFIG.min_message_length,
maxMessageLength: CHAT_CONFIG.max_message_length,
handleSendMessage,
idChatOpen,
...props,
}}
/>
);
};
export default ChatMessageFormContainer;

View File

@ -1,143 +0,0 @@
import styled, { css } from 'styled-components';
import {
colorBlueLight,
colorText,
colorGrayLighter,
colorGrayLight,
colorPrimary,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
smPaddingX,
smPaddingY,
borderRadius,
borderSize,
} from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
import TextareaAutosize from 'react-autosize-textarea';
import EmojiPickerComponent from '/imports/ui/components/emoji-picker/component';
import Button from '/imports/ui/components/common/button/component';
const Form = styled.form`
flex-grow: 0;
flex-shrink: 0;
align-self: flex-end;
width: 100%;
position: relative;
margin-bottom: calc(-1 * ${smPaddingX});
margin-top: .2rem;
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
`;
const Input = styled(TextareaAutosize)`
flex: 1;
background: #fff;
background-clip: padding-box;
margin: 0;
color: ${colorText};
-webkit-appearance: none;
padding: calc(${smPaddingY} * 2.5) calc(${smPaddingX} * 1.25);
resize: none;
transition: none;
border-radius: ${borderRadius};
font-size: ${fontSizeBase};
line-height: 1;
min-height: 2.5rem;
max-height: 10rem;
border: 1px solid ${colorGrayLighter};
box-shadow: 0 0 0 1px ${colorGrayLighter};
&:disabled,
&[disabled] {
cursor: not-allowed;
opacity: .75;
background-color: rgba(167,179,189,0.25);
}
&:focus {
border-radius: ${borderSize};
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
}
&:hover,
&:active,
&:focus {
outline: transparent;
outline-style: dotted;
outline-width: ${borderSize};
}
${({ emojiEnabled }) => emojiEnabled ?
css`
padding-left: calc(${smPaddingX} * 3);
`
: null
}
`;
const SendButton = styled(Button)`
margin:0 0 0 ${smPaddingX};
align-self: center;
font-size: 0.9rem;
[dir="rtl"] & {
margin: 0 ${smPaddingX} 0 0;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
`;
const EmojiButton = styled(Button)`
margin:0 0 0 ${smPaddingX};
align-self: center;
font-size: 0.5rem;
[dir="rtl"] & {
margin: 0 ${smPaddingX} 0 0;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
`;
const EmojiPickerWrapper = styled.div`
em-emoji-picker {
width: 100%;
max-height: 300px;
}
padding-bottom: 5px;
`;
const EmojiPicker = styled(EmojiPickerComponent)``;
const EmojiButtonWrapper = styled.div`
color: ${colorGrayLight};
width: 2.5rem;
height: 2.5rem;
border: none;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
font-size: ${fontSizeBase};
cursor: pointer;
`;
export default {
Form,
Wrapper,
Input,
SendButton,
EmojiButton,
EmojiButtonWrapper,
EmojiPicker,
EmojiPickerWrapper,
};

View File

@ -1,139 +0,0 @@
import React, { PureComponent } from 'react';
import {
defineMessages, injectIntl, FormattedMessage,
} from 'react-intl';
import PropTypes from 'prop-types';
import Styled from './styles';
const propTypes = {
intl: PropTypes.object.isRequired,
typingUsers: PropTypes.arrayOf(Object).isRequired,
};
const messages = defineMessages({
severalPeople: {
id: 'app.chat.multi.typing',
description: 'displayed when 4 or more users are typing',
},
someoneTyping: {
id: 'app.chat.someone.typing',
description: 'label used when one user is typing with disabled name',
},
});
class TypingIndicator extends PureComponent {
constructor(props) {
super(props);
this.renderTypingElement = this.renderTypingElement.bind(this);
}
renderTypingElement() {
const {
typingUsers, indicatorEnabled, indicatorShowNames, intl,
} = this.props;
if (!indicatorEnabled || !typingUsers) return null;
const { length } = typingUsers;
let element = null;
if (indicatorShowNames) {
const isSingleTyper = length === 1;
const isCoupleTyper = length === 2;
const isMultiTypers = length > 2;
if (isSingleTyper) {
const { name } = typingUsers[0];
element = (
<FormattedMessage
id="app.chat.one.typing"
description="label used when one user is typing"
values={{
0: <Styled.SingleTyper>
{`${name}`}
&nbsp;
</Styled.SingleTyper>,
}}
/>
);
}
if (isCoupleTyper) {
const {name} = typingUsers[0];
const {name: name2} = typingUsers[1];
element = (
<FormattedMessage
id="app.chat.two.typing"
description="label used when two users are typing"
values={{
0: <Styled.CoupleTyper>
{`${name}`}
&nbsp;
</Styled.CoupleTyper>,
1: <Styled.CoupleTyper>
&nbsp;
{`${name2}`}
&nbsp;
</Styled.CoupleTyper>,
}}
/>
);
}
if (isMultiTypers) {
element = (
<span>
{`${intl.formatMessage(messages.severalPeople)}`}
</span>
);
}
} else {
// Show no names in typing indicator
const isSingleTyper = length === 1;
const isMultiTypers = length > 1;
if (isSingleTyper) {
element = (
<span>
{`${intl.formatMessage(messages.someoneTyping)}`}
</span>
);
}
if (isMultiTypers) {
element = (
<span>
{`${intl.formatMessage(messages.severalPeople)}`}
</span>
);
}
}
return element;
}
render() {
const {
error,
indicatorEnabled,
} = this.props;
const typingElement = indicatorEnabled ? this.renderTypingElement() : null;
return (
<Styled.TypingIndicatorWrapper
error={!!error}
info={!error}
spacer={!!typingElement}
>
<Styled.TypingIndicator data-test="typingIndicator">{error || typingElement}</Styled.TypingIndicator>
</Styled.TypingIndicatorWrapper>
);
}
}
TypingIndicator.propTypes = propTypes;
export default injectIntl(TypingIndicator);

View File

@ -1,54 +0,0 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import { UsersTyping } from '/imports/api/group-chat-msg';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import TypingIndicator from './component';
const CHAT_CONFIG = Meteor.settings.public.chat;
const USER_CONFIG = Meteor.settings.public.user;
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
const TYPING_INDICATOR_ENABLED = CHAT_CONFIG.typingIndicator.enabled;
const TYPING_SHOW_NAMES = CHAT_CONFIG.typingIndicator.showNames;
const TypingIndicatorContainer = props => <TypingIndicator {...props} />;
export default withTracker(({ idChatOpen }) => {
const meeting = Meetings.findOne({ meetingId: Auth.meetingID }, {
fields: {
'lockSettingsProps.hideUserList': 1,
},
});
const selector = {
meetingId: Auth.meetingID,
isTypingTo: PUBLIC_CHAT_KEY,
userId: { $ne: Auth.userID },
};
if (idChatOpen !== PUBLIC_CHAT_KEY) {
selector.isTypingTo = idChatOpen;
}
const currentUser = Users.findOne({
meetingId: Auth.meetingID,
userId: Auth.userID,
}, {
fields: {
role: 1,
},
});
if (meeting.lockSettingsProps.hideUserList && currentUser?.role === USER_CONFIG.role_viewer) {
selector.role = { $ne: USER_CONFIG.role_viewer };
}
const typingUsers = UsersTyping.find(selector).fetch();
return {
typingUsers,
indicatorEnabled: TYPING_INDICATOR_ENABLED,
indicatorShowNames: TYPING_SHOW_NAMES,
};
})(TypingIndicatorContainer);

View File

@ -1,73 +0,0 @@
import styled from 'styled-components';
import { colorDanger, colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeSmaller, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
const SingleTyper = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
font-size: ${fontSizeSmaller};
max-width: 70%;
`;
const CoupleTyper = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
font-size: ${fontSizeSmaller};
max-width: 25%;
`;
const TypingIndicator = styled.span`
display: flex;
flex-direction: row;
> span {
display: block;
margin-right: 0.05rem;
margin-left: 0.05rem;
}
text-align: left;
[dir="rtl"] & {
text-align: right;
}
`;
const TypingIndicatorWrapper = styled.div`
${({ error }) => error && `
color: ${colorDanger};
font-size: calc(${fontSizeBase} * .75);
color: ${colorGrayDark};
text-align: left;
padding: ${borderSize} 0;
position: relative;
height: .93rem;
max-height: .93rem;
`}
${({ info }) => info && `
font-size: calc(${fontSizeBase} * .75);
color: ${colorGrayDark};
text-align: left;
padding: ${borderSize} 0;
position: relative;
height: .93rem;
max-height: .93rem;
`}
${({ spacer }) => spacer && `
height: .93rem;
max-height: .93rem;
`}
`;
export default {
SingleTyper,
CoupleTyper,
TypingIndicator,
TypingIndicatorWrapper,
};

View File

@ -1,6 +1,5 @@
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings';
import GroupChat from '/imports/api/group-chat';
import Auth from '/imports/ui/services/auth';
import UnreadMessages from '/imports/ui/services/unread-messages';
import Storage from '/imports/ui/services/storage/session';
@ -70,9 +69,6 @@ const setUserSentMessage = (bool) => {
const getUser = (userId) => Users.findOne({ userId });
const getPrivateChatByUsers = (userId) => GroupChat
.findOne({ users: { $all: [userId, Auth.userID] } });
const getWelcomeProp = () => Meetings.findOne({ meetingId: Auth.meetingID },
{ fields: { welcomeProp: 1 } });
@ -183,53 +179,6 @@ const lastReadMessageTime = (receiverID) => {
return UnreadMessages.get(chatType);
};
const sendGroupMessage = (message, idChatOpen) => {
const chatIdToSent = idChatOpen === PUBLIC_CHAT_ID ? PUBLIC_GROUP_CHAT_ID : idChatOpen;
const chat = GroupChat.findOne({ chatId: chatIdToSent },
{ fields: { users: 1 } });
const chatID = idChatOpen === PUBLIC_CHAT_ID
? PUBLIC_GROUP_CHAT_ID
: chat.users.filter((id) => id !== Auth.userID)[0];
const isPublicChat = chatID === PUBLIC_CHAT_ID;
let destinationChatId = PUBLIC_GROUP_CHAT_ID;
const { userID: senderUserId } = Auth;
const receiverId = { id: chatID };
if (!isPublicChat) {
const privateChat = GroupChat.findOne({ users: { $all: [chatID, senderUserId] } },
{ fields: { chatId: 1 } });
if (privateChat) {
const { chatId: privateChatId } = privateChat;
destinationChatId = privateChatId;
}
}
const payload = {
correlationId: `${senderUserId}-${Date.now()}`,
sender: {
id: senderUserId,
name: '',
role: '',
},
chatEmphasizedText: CHAT_EMPHASIZE_TEXT,
message,
};
const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY);
// Remove the chat that user send messages from the session.
if (isChatClosed(receiverId.id)) {
const closedChats = currentClosedChats.filter(closedChat => closedChat.chatId !== receiverId.id);
Storage.setItem(CLOSED_CHAT_LIST_KEY,closedChats);
}
return makeCall('sendGroupChatMsg', destinationChatId, payload);
};
const getScrollPosition = (receiverID) => {
const scroll = ScrollCollection.findOne({ receiver: receiverID },
{ fields: { position: 1 } }) || { position: null };
@ -361,7 +310,6 @@ export default {
reduceAndMapGroupMessages,
reduceAndDontMapGroupMessages,
getUser,
getPrivateChatByUsers,
getWelcomeProp,
getScrollPosition,
lastReadMessageTime,
@ -369,7 +317,6 @@ export default {
isChatClosed,
updateScrollPosition,
updateUnreadMessage,
sendGroupMessage,
closePrivateChat,
removeFromClosedChatsSession,
exportChat,

View File

@ -1,386 +0,0 @@
import React, { PureComponent } from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { debounce } from '/imports/utils/debounce';
import { AutoSizer,CellMeasurer, CellMeasurerCache } from 'react-virtualized';
import Styled from './styles';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import TimeWindowChatItem from './time-window-chat-item/container';
import { convertRemToPixels } from '/imports/utils/dom-utils';
const CHAT_CONFIG = Meteor.settings.public.chat;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear;
const propTypes = {
scrollPosition: PropTypes.number,
chatId: PropTypes.string.isRequired,
handleScrollUpdate: PropTypes.func.isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
id: PropTypes.string.isRequired,
};
const defaultProps = {
scrollPosition: null,
};
const intlMessages = defineMessages({
moreMessages: {
id: 'app.chat.moreMessages',
description: 'Chat message when the user has unread messages below the scroll',
},
emptyLogLabel: {
id: 'app.chat.emptyLogLabel',
description: 'aria-label used when chat log is empty',
},
});
const updateChatSemantics = () => {
setTimeout(() => {
const msgListItem = document.querySelector('span[data-test="msgListItem"]');
if (msgListItem) {
const virtualizedGridInnerScrollContainer = msgListItem.parentElement;
const virtualizedGrid = virtualizedGridInnerScrollContainer.parentElement;
virtualizedGridInnerScrollContainer.setAttribute('role', 'list');
virtualizedGridInnerScrollContainer.setAttribute('tabIndex', 0);
virtualizedGrid.removeAttribute('tabIndex');
virtualizedGrid.removeAttribute('aria-label');
virtualizedGrid.removeAttribute('aria-readonly');
}
}, 300);
}
class TimeWindowList extends PureComponent {
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 18,
keyMapper: (rowIndex) => {
const { timeWindowsValues } = this.props;
const timewindow = timeWindowsValues[rowIndex];
const key = timewindow?.key;
const contentCount = timewindow?.content?.length;
return `${key}-${contentCount}`;
},
});
this.userScrolledBack = false;
this.handleScrollUpdate = debounce(this.handleScrollUpdate.bind(this), 150);
this.rowRender = this.rowRender.bind(this);
this.forceCacheUpdate = this.forceCacheUpdate.bind(this);
this.systemMessagesResized = {};
this.state = {
scrollArea: null,
shouldScrollToPosition: false,
scrollPosition: 0,
userScrolledBack: false,
lastMessage: {},
fontsLoaded: false,
};
this.systemMessageIndexes = [];
this.listRef = null;
this.virualRef = null;
this.lastWidth = 0;
document.fonts.onloadingdone = () => this.setState({fontsLoaded: true});
}
componentDidMount() {
const { scrollPosition: scrollProps } = this.props;
this.setState({
scrollPosition: scrollProps,
});
updateChatSemantics();
}
componentDidUpdate(prevProps) {
ChatLogger.debug('TimeWindowList::componentDidUpdate', { ...this.props }, { ...prevProps });
if (this.virualRef) {
if (this.virualRef.style.direction !== document.documentElement.dir) {
this.virualRef.style.direction = document.documentElement.dir;
}
}
const {
userSentMessage,
setUserSentMessage,
timeWindowsValues,
chatId,
syncing,
syncedPercent,
lastTimeWindowValuesBuild,
scrollPosition: scrollProps,
count,
} = this.props;
const { userScrolledBack } = this.state;
if((count > 0 && !userScrolledBack) || userSentMessage || !scrollProps) {
const lastItemIndex = timeWindowsValues.length - 1;
this.setState({
scrollPosition: lastItemIndex,
}, ()=> this.handleScrollUpdate(lastItemIndex));
}
const {
timeWindowsValues: prevTimeWindowsValues,
chatId: prevChatId,
syncing: prevSyncing,
syncedPercent: prevSyncedPercent
} = prevProps;
if (prevChatId !== chatId) {
this.setState({
scrollPosition: scrollProps,
});
}
const prevTimeWindowsLength = prevTimeWindowsValues.length;
const timeWindowsValuesLength = timeWindowsValues.length;
const prevLastTimeWindow = prevTimeWindowsValues[prevTimeWindowsLength - 1];
const lastTimeWindow = timeWindowsValues[prevTimeWindowsLength - 1];
if ((lastTimeWindow
&& (prevLastTimeWindow?.content.length !== lastTimeWindow?.content.length))) {
if (this.listRef) {
this.cache.clear(timeWindowsValuesLength - 1);
this.listRef.recomputeRowHeights(timeWindowsValuesLength - 1);
}
}
if (userSentMessage && !prevProps.userSentMessage) {
this.setState({
userScrolledBack: false,
}, () => setUserSentMessage(false));
}
// this condition exist to the case where the chat has a single message and the chat is cleared
// The component List from react-virtualized doesn't have a reference to the list of messages
// so I need force the update to fix it
if (
(lastTimeWindow?.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`)
|| (prevSyncing && !syncing)
|| (syncedPercent !== prevSyncedPercent)
|| (chatId !== prevChatId)
|| (lastTimeWindowValuesBuild !== prevProps.lastTimeWindowValuesBuild)
) {
this.listRef.forceUpdateGrid();
}
updateChatSemantics();
}
handleScrollUpdate(position, target) {
const {
handleScrollUpdate,
} = this.props;
if (position !== null && position + target?.offsetHeight === target?.scrollHeight) {
// I used one because the null value is used to notify that
// the user has sent a message and the message list should scroll to bottom
handleScrollUpdate(1);
return;
}
handleScrollUpdate(position || 1);
}
scrollTo(position = null) {
if (position) {
setTimeout(() => this.setState({
shouldScrollToPosition: true,
scrollPosition: position,
}), 200);
}
}
forceCacheUpdate(index) {
if (index >= 0) {
this.cache.clear(index);
this.listRef.recomputeRowHeights(index);
}
}
rowRender({
index,
parent,
style,
key,
}) {
const {
id,
timeWindowsValues,
dispatch,
chatId,
} = this.props;
const { scrollArea } = this.state;
const message = timeWindowsValues[index];
ChatLogger.debug('TimeWindowList::rowRender', this.props);
return (
<CellMeasurer
key={key}
cache={this.cache}
columnIndex={0}
parent={parent}
rowIndex={index}
>
<span
style={style}
key={`span-${key}-${index}`}
role="listitem"
data-test="msgListItem"
>
<TimeWindowChatItem
key={key}
message={message}
messageId={message.id}
chatAreaId={id}
scrollArea={scrollArea}
dispatch={dispatch}
chatId={chatId}
height={style.height}
index={index}
forceCacheUpdate={this.forceCacheUpdate}
/>
</span>
</CellMeasurer>
);
}
renderUnreadNotification() {
const {
intl,
count,
timeWindowsValues,
} = this.props;
const { userScrolledBack } = this.state;
if (count && userScrolledBack) {
return (
<Styled.UnreadButton
aria-hidden="true"
color="primary"
size="sm"
key="unread-messages"
label={intl.formatMessage(intlMessages.moreMessages)}
onClick={() => {
const lastItemIndex = timeWindowsValues.length - 1;
this.handleScrollUpdate(lastItemIndex);
this.setState({
scrollPosition: lastItemIndex,
userScrolledBack: false,
});
}}
/>
);
}
return null;
}
render() {
const {
timeWindowsValues,
width,
} = this.props;
const {
scrollArea,
scrollPosition,
userScrolledBack,
} = this.state;
ChatLogger.debug('TimeWindowList::render', {...this.props}, {...this.state}, new Date());
const shouldAutoScroll = !!(
scrollPosition
&& timeWindowsValues.length >= scrollPosition
&& !userScrolledBack
);
const paddingValue = convertRemToPixels(2);
return (
[
<Styled.MessageListWrapper
onMouseDown={() => {
this.setState({
userScrolledBack: true,
});
}}
onWheel={(e) => {
if (e.deltaY < 0) {
this.setState({
userScrolledBack: true,
});
this.userScrolledBack = true
}
}}
key="chat-list"
data-test="chatMessages"
ref={node => this.messageListWrapper = node}
onCopy={(e) => { e.stopPropagation(); }}
>
<AutoSizer disableWidth>
{({ height }) => {
if (width !== this.lastWidth) {
this.lastWidth = width;
this.cache.clearAll();
}
return (
<Styled.MessageList
ref={(ref) => {
if (ref !== null) {
this.listRef = ref;
if (!scrollArea) {
this.setState({ scrollArea: findDOMNode(this.listRef) });
}
}
}}
isScrolling
rowHeight={this.cache.rowHeight}
rowRenderer={this.rowRender}
rowCount={timeWindowsValues.length}
height={height}
width={width - paddingValue}
overscanRowCount={0}
deferredMeasurementCache={this.cache}
scrollToIndex={shouldAutoScroll ? scrollPosition : undefined}
onRowsRendered={({ stopIndex }) => {
this.handleScrollUpdate(stopIndex);
}}
onScroll={({ clientHeight, scrollHeight, scrollTop }) => {
const scrollSize = scrollTop + clientHeight;
if (scrollSize >= scrollHeight) {
this.setState({
userScrolledBack: false,
});
}
}}
/>
);
}}
</AutoSizer>
</Styled.MessageListWrapper>,
this.renderUnreadNotification(),
]
);
}
}
TimeWindowList.propTypes = propTypes;
TimeWindowList.defaultProps = defaultProps;
export default injectIntl(TimeWindowList);

View File

@ -1,30 +0,0 @@
import React, { PureComponent } from 'react';
import TimeWindowList from './component';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import ChatService from '../service';
import ChatList from '../chat-graphql/chat-message-list/component';
class TimeWindowListContainer extends PureComponent {
render() {
const { chatId, userSentMessage } = this.props;
const scrollPosition = ChatService.getScrollPosition(chatId);
const lastReadMessageTime = ChatService.lastReadMessageTime(chatId);
ChatLogger.debug('TimeWindowListContainer::render', { ...this.props }, new Date());
return (
<TimeWindowList
{
...{
...this.props,
scrollPosition,
lastReadMessageTime,
handleScrollUpdate: ChatService.updateScrollPosition,
userSentMessage,
setUserSentMessage: ChatService.setUserSentMessage,
}
}
/>
);
}
}
export default ChatList;

View File

@ -1,142 +0,0 @@
import styled, { css } from 'styled-components';
import {
smPaddingX,
mdPaddingX,
mdPaddingY,
} from '/imports/ui/stylesheets/styled-components/general';
import { ButtonElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
import { VirtualizedScrollboxVertical } from '/imports/ui/stylesheets/styled-components/scrollable';
import MessageChatItem from '/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component';
const UnreadButton = styled(ButtonElipsis)`
flex-shrink: 0;
width: 100%;
text-transform: uppercase;
margin-bottom: .25rem;
z-index: 3;
`;
const MessageListWrapper = styled.div`
display: flex;
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
position: relative;
overflow-x: hidden;
overflow-y: auto;
padding-left: ${smPaddingX};
margin-left: calc(-1 * ${mdPaddingX});
padding-right: ${smPaddingX};
margin-right: calc(-1 * ${mdPaddingY});
padding-bottom: ${mdPaddingX};
z-index: 2;
[dir="rtl"] & {
padding-right: ${mdPaddingX};
margin-right: calc(-1 * ${mdPaddingX});
padding-left: ${mdPaddingY};
margin-left: calc(-1 * ${mdPaddingX});
}
`;
const MessageList = styled(VirtualizedScrollboxVertical)`
flex-flow: column;
flex-grow: 1;
flex-shrink: 1;
margin: 0 auto 0 0;
right: 0 ${mdPaddingX} 0 0;
padding-top: 0;
width: 100%;
outline-style: none;
[dir="rtl"] & {
margin: 0 0 0 auto;
padding: 0 0 0 ${mdPaddingX};
}
`;
const Time = styled.time`
`;
const Wrapper = styled.div`
`;
const AvatarWrapper = styled.div`
`;
const Content = styled.div`
`;
const Meta = styled.div`
`;
const Name = styled.div`
`;
const Read = styled.div`
`;
const Messages = styled.div`
`;
const SystemMessageChatItem = styled(MessageChatItem)`
${({ messageId }) => messageId ?
css`
`
:
css`
`
}
`;
const Item = styled.div`
`;
const OnlineIndicator = styled.div`
${({ isOnline }) => isOnline ?
css`
color: red;
`
:
css`
color: blue;
`
}
`;
const ChatItem = styled.div`
`;
const Offline = styled.div`
`;
const PollIcon = styled.div`
`;
const PollMessageChatItem = styled.div`
`;
const StatusMessageChatItem = styled(MessageChatItem)`
`;
export default {
UnreadButton,
MessageListWrapper,
MessageList,
Time,
Content,
Meta,
Wrapper,
AvatarWrapper,
Name,
Read,
Messages,
SystemMessageChatItem,
Item,
ChatItem,
OnlineIndicator,
Offline,
PollIcon,
PollMessageChatItem,
StatusMessageChatItem,
};

View File

@ -1,525 +0,0 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedTime, defineMessages, injectIntl } from 'react-intl';
import UserAvatar from '/imports/ui/components/user-avatar/component';
import { Meteor } from 'meteor/meteor';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
import PollService from '/imports/ui/components/poll/service';
import Tooltip from '/imports/ui/components/common/tooltip/component';
import Styled from './styles';
import { uniqueId } from '/imports/utils/string-utils';
const CHAT_CONFIG = Meteor.settings.public.chat;
const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear;
const CHAT_POLL_RESULTS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_poll_result;
const CHAT_USER_STATUS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_status_message;
const CHAT_PUBLIC_ID = CHAT_CONFIG.public_id;
const CHAT_EMPHASIZE_TEXT = CHAT_CONFIG.moderatorChatEmphasized;
const CHAT_EXPORTED_PRESENTATION_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_exported_presentation;
const propTypes = {
user: PropTypes.shape({
color: PropTypes.string,
messageFromModerator: PropTypes.bool,
isOnline: PropTypes.bool,
name: PropTypes.string,
}),
messages: PropTypes.arrayOf(Object).isRequired,
timestamp: PropTypes.number,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
scrollArea: PropTypes.instanceOf(Element),
chatAreaId: PropTypes.string.isRequired,
handleReadMessage: PropTypes.func.isRequired,
lastReadMessageTime: PropTypes.number,
lastReadByPartnerMessageTime: PropTypes.number,
isMessageReadFeedbackEnabled: PropTypes.bool,
};
const defaultProps = {
user: null,
scrollArea: null,
lastReadMessageTime: 0,
lastReadByPartnerMessageTime: 0,
isMessageReadFeedbackEnabled: false,
timestamp: 0,
};
const intlMessages = defineMessages({
offline: {
id: 'app.chat.offline',
description: 'Offline',
},
pollResult: {
id: 'app.chat.pollResult',
description: 'used in place of user name who published poll to chat',
},
[CHAT_CLEAR_MESSAGE]: {
id: 'app.chat.clearPublicChatMessage',
description: 'message of when clear the public chat',
},
breakoutDurationUpdated: {
id: 'app.chat.breakoutDurationUpdated',
description: 'used when the breakout duration is updated',
},
away: {
id: 'app.chat.away',
description: 'away label',
},
notAway: {
id: 'app.chat.notAway',
description: 'not away label',
},
messageReadLabel: {
id: 'app.chat.messageRead',
description: 'Message read tooltip label',
},
});
class TimeWindowChatItem extends PureComponent {
constructor(props) {
super(props);
this.state = {
forcedUpdateCount: 0,
};
}
componentWillMount() {
ChatLogger.debug('TimeWindowChatItem::componentWillMount::props', { ...this.props });
ChatLogger.debug('TimeWindowChatItem::componentWillMount::state', { ...this.state });
}
componentDidUpdate(prevProps, prevState) {
const { height, forceCacheUpdate, index } = this.props;
const elementHeight = this.itemRef ? this.itemRef.clientHeight : null;
if (elementHeight && height !== 'auto' && elementHeight !== height && this.state.forcedUpdateCount < 10) {
// forceCacheUpdate() internally calls forceUpdate(), so we need a stop flag
// and cannot rely on shouldComponentUpdate() and other comparisons.
forceCacheUpdate(index);
this.setState(({ forcedUpdateCount }) => ({ forcedUpdateCount: forcedUpdateCount + 1 }));
}
ChatLogger.debug('TimeWindowChatItem::componentDidUpdate::props', { ...this.props }, { ...prevProps });
ChatLogger.debug('TimeWindowChatItem::componentDidUpdate::state', { ...this.state }, { ...prevState });
}
componentWillUnmount() {
ChatLogger.debug('TimeWindowChatItem::componentWillUnmount::props', { ...this.props });
ChatLogger.debug('TimeWindowChatItem::componentWillUnmount::state', { ...this.state });
}
getText(message, messageValues) {
const { intl } = this.props;
let { text } = message;
if (intlMessages[message.text]) {
text = intl.formatMessage(
intlMessages[message.text],
messageValues || {},
);
}
return text;
}
renderSystemMessage() {
const {
messages,
messageValues,
chatAreaId,
handleReadMessage,
messageKey,
} = this.props;
if (messages && messages[0].id.includes(CHAT_POLL_RESULTS_MESSAGE)) {
return this.renderPollItem();
}
if (messages && messages[0].id.includes(CHAT_EXPORTED_PRESENTATION_MESSAGE)) {
return this.renderExportedPresentationItem();
}
if (messages && messages[0].id.includes(CHAT_USER_STATUS_MESSAGE)) {
return this.renderStatusItem();
}
return (
<Styled.Item
key={`time-window-chat-item-${messageKey}`}
ref={(element) => this.itemRef = element}
>
<Styled.Messages>
{messages.map((message) => (
message.text !== ''
? (
<Styled.SystemMessageChatItem
messageId={message.id}
border={message.id}
key={message.id ? message.id : uniqueId('id-')}
text={this.getText(message, messageValues)}
time={message.time}
isSystemMessage={!!message.id}
systemMessageType={message.text === CHAT_CLEAR_MESSAGE ? 'chatClearMessageText' : 'chatWelcomeMessageText'}
chatAreaId={chatAreaId}
handleReadMessage={handleReadMessage}
/>
) : null
))}
</Styled.Messages>
</Styled.Item>
);
}
renderMessageItem() {
const {
timestamp,
chatAreaId,
scrollArea,
intl,
messages,
messageKey,
dispatch,
chatId,
read,
isFromMe,
lastReadByPartnerMessageTime,
isMessageReadFeedbackEnabled,
name,
color,
messageFromModerator,
avatar,
isOnline,
isSystemSender,
} = this.props;
const dateTime = new Date(timestamp);
const regEx = /<a[^>]+>/i;
ChatLogger.debug('TimeWindowChatItem::renderMessageItem', this.props);
const defaultAvatarString = name?.toLowerCase().slice(0, 2) || ' ';
const shouldRenderPrivateMessageReadFeedback =
isMessageReadFeedbackEnabled &&
chatId !== CHAT_PUBLIC_ID &&
isFromMe &&
lastReadByPartnerMessageTime >= messages[messages.length - 1].time;
const emphasizedText = messageFromModerator && CHAT_EMPHASIZE_TEXT && chatId === CHAT_PUBLIC_ID;
return (
<Styled.Item
key={`time-window-${messageKey}`}
ref={element => this.itemRef = element}
>
<Styled.Wrapper isSystemSender={isSystemSender}>
<Styled.AvatarWrapper>
<UserAvatar
color={color}
moderator={messageFromModerator}
avatar={avatar}
>
{defaultAvatarString}
</UserAvatar>
</Styled.AvatarWrapper>
<Styled.Content>
<Styled.Meta>
<Styled.Name isOnline={isOnline}>
<span>{name}</span>
{isOnline
? null
: (
<Styled.Offline>
{`(${intl.formatMessage(intlMessages.offline)})`}
</Styled.Offline>
)}
</Styled.Name>
<Styled.Time dateTime={dateTime}>
<FormattedTime value={dateTime} />
</Styled.Time>
{shouldRenderPrivateMessageReadFeedback
&& (
<Tooltip
title={intl.formatMessage(intlMessages.messageReadLabel)}
>
<Styled.ReadIcon
iconName="message_read"
/>
</Tooltip>
)}
</Styled.Meta>
<Styled.Messages>
{messages.map((message) => (
<Styled.ChatItem
hasLink={regEx.test(message.text)}
emphasizedMessage={emphasizedText}
key={message.id}
text={message.text}
time={message.time}
chatAreaId={chatAreaId}
dispatch={dispatch}
read={message.read}
chatUserMessageItem={true}
handleReadMessage={(timestamp) => {
if (!read) {
dispatch({
type: 'last_read_message_timestamp_changed',
value: {
chatId,
timestamp,
},
});
}
}}
scrollArea={scrollArea}
/>
))}
</Styled.Messages>
</Styled.Content>
</Styled.Wrapper>
</Styled.Item>
);
}
renderStatusItem() {
const {
timestamp,
color,
intl,
messages,
scrollArea,
chatAreaId,
messageKey,
dispatch,
chatId,
extra,
read,
name,
messageFromModerator,
avatar,
isOnline,
} = this.props;
const dateTime = new Date(timestamp);
ChatLogger.debug('TimeWindowChatItem::renderMessageItem', this.props);
const defaultAvatarString = name?.toLowerCase().slice(0, 2) || ' ';
const emphasizedTextClass = messageFromModerator && CHAT_EMPHASIZE_TEXT
&& chatId === CHAT_PUBLIC_ID;
return messages ? (
<Styled.Item key={`time-window-status-message${messageKey}`}>
<Styled.Wrapper>
<Styled.AvatarWrapper>
<UserAvatar
color={color}
moderator={messageFromModerator}
avatar={avatar}
>
{defaultAvatarString}
</UserAvatar>
</Styled.AvatarWrapper>
<Styled.Content>
<Styled.Meta>
<Styled.Name isOnline={isOnline}>
<span>{name}</span>
{isOnline
? null
: (
<Styled.Offline>
{`(${intl.formatMessage(intlMessages.offline)})`}
</Styled.Offline>
)}
</Styled.Name>
<Styled.Time dateTime={dateTime}>
<FormattedTime value={dateTime} />
</Styled.Time>
</Styled.Meta>
<Styled.Messages>
{messages.map((message) => {
const isSystemMsg = message.id === `SYSTEM_MESSAGE-${CHAT_USER_STATUS_MESSAGE}`;
return (
<Styled.StatusMessageChatItem
isSystemMsg={isSystemMsg}
emphasizedMessage={emphasizedTextClass}
key={message.id}
text={isSystemMsg
? extra.status === 'away'
? `<span styles={{width: '100px', heigth: '100px'}} class='icon-bbb-clear_status'/></span> ${intl.formatMessage(intlMessages.away)}`
: `<span class='icon-bbb-user'></span> ${intl.formatMessage(intlMessages.notAway)}`
: message.text}
time={message.time}
chatAreaId={chatAreaId}
dispatch={dispatch}
read={message.read}
chatUserMessageItem
handleReadMessage={(time) => {
if (!read) {
dispatch({
type: 'last_read_message_timestamp_changed',
value: {
chatId,
timestamp: time,
},
});
}
}}
scrollArea={scrollArea}
/>
);
})}
</Styled.Messages>
</Styled.Content>
</Styled.Wrapper>
</Styled.Item>
) : null;
}
renderPollItem() {
const {
timestamp,
color,
intl,
getPollResultString,
messages,
extra,
scrollArea,
chatAreaId,
lastReadMessageTime,
dispatch,
chatId,
read,
} = this.props;
const dateTime = new Date(timestamp);
return messages ? (
<Styled.Item key={uniqueId('message-poll-item-')}>
<Styled.Wrapper ref={(ref) => { this.item = ref; }}>
<Styled.AvatarWrapper>
<UserAvatar
color={PollService.POLL_AVATAR_COLOR}
moderator={true}
>
{<Styled.PollIcon iconName="polling" />}
</UserAvatar>
</Styled.AvatarWrapper>
<Styled.Content>
<Styled.Meta>
<Styled.Name>
<span>{intl.formatMessage(intlMessages.pollResult)}</span>
</Styled.Name>
<Styled.Time dateTime={dateTime}>
<FormattedTime value={dateTime} />
</Styled.Time>
</Styled.Meta>
<Styled.PollMessageChatItem
type="poll"
key={messages[0].id}
text={getPollResultString(extra.pollResultData, intl)}
time={messages[0].time}
chatAreaId={chatAreaId}
lastReadMessageTime={lastReadMessageTime}
handleReadMessage={(time) => {
if (!read) {
dispatch({
type: 'last_read_message_timestamp_changed',
value: {
chatId,
timestamp: time,
},
});
}
}}
scrollArea={scrollArea}
color={color}
/>
</Styled.Content>
</Styled.Wrapper>
</Styled.Item>
) : null;
}
renderExportedPresentationItem() {
const {
timestamp,
color,
intl,
messages,
extra,
scrollArea,
chatAreaId,
lastReadMessageTime,
handleReadMessage,
dispatch,
read,
chatId,
getExportedPresentationString,
} = this.props;
const dateTime = new Date(timestamp);
return messages ? (
<Styled.Item
key={uniqueId('message-presentation-item-')}
onMouseDown={(e) => { e.stopPropagation(); }}
>
<Styled.PresentationWrapper ref={(ref) => { this.item = ref; }}>
<Styled.AvatarWrapper>
<UserAvatar color="#0F70D7">
<Styled.PollIcon iconName="download" />
</UserAvatar>
</Styled.AvatarWrapper>
<Styled.Content
data-test="downloadPresentationContainer">
<Styled.Meta>
<Styled.Time dateTime={dateTime} style={{ margin: 0 }}>
<FormattedTime value={dateTime} />
</Styled.Time>
</Styled.Meta>
<Styled.PresentationChatItem
type="presentation"
key={messages[0].id}
text={getExportedPresentationString(extra.fileURI,
extra.filename, intl, extra.fileStateType)}
time={messages[0].time}
chatAreaId={chatAreaId}
lastReadMessageTime={lastReadMessageTime}
handleReadMessage={(timestamp) => {
handleReadMessage(timestamp);
if (!read) {
dispatch({
type: 'last_read_message_timestamp_changed',
value: {
chatId,
timestamp,
},
});
}
}}
scrollArea={scrollArea}
color={color}
/>
</Styled.Content>
</Styled.PresentationWrapper>
</Styled.Item>
) : null;
}
render() {
const {
systemMessage,
} = this.props;
ChatLogger.debug('TimeWindowChatItem::render', { ...this.props });
if (systemMessage) {
return this.renderSystemMessage();
}
return this.renderMessageItem();
}
}
TimeWindowChatItem.propTypes = propTypes;
TimeWindowChatItem.defaultProps = defaultProps;
export default injectIntl(TimeWindowChatItem);

View File

@ -1,69 +0,0 @@
import React, { useContext } from 'react';
import TimeWindowChatItem from './component';
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
import ChatService from '../../service';
import { layoutSelect } from '../../../layout/context';
import PollService from '/imports/ui/components/poll/service';
import Auth from '/imports/ui/services/auth';
const CHAT_CONFIG = Meteor.settings.public.chat;
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const TimeWindowChatItemContainer = (props) => {
const { message, messageId } = props;
const idChatOpen = layoutSelect((i) => i.idChatOpen);
const usingUsersContext = useContext(UsersContext);
const { users } = usingUsersContext;
const {
sender,
senderName,
senderRole,
key,
timestamp,
content,
extra,
messageValues,
} = message;
const messages = content;
const user = (sender === 'SYSTEM') ? {
name: senderName,
color: '#01579b',
avatar: '',
role: ROLE_MODERATOR,
loggedOut: false,
} : users[Auth.meetingID][sender];
const messageKey = key;
const handleReadMessage = (tstamp) => ChatService.updateUnreadMessage(tstamp, idChatOpen);
return (
<TimeWindowChatItem
{
...{
color: user?.color,
messageFromModerator: senderRole === ROLE_MODERATOR,
isSystemSender: sender === 'SYSTEM',
isOnline: !user?.loggedOut,
avatar: user?.avatar,
name: user?.name,
read: message.read,
messages,
extra,
messageValues,
getPollResultString: PollService.getPollResultString,
user,
timestamp,
systemMessage: messageId.startsWith(SYSTEM_CHAT_TYPE) || !sender,
messageKey,
handleReadMessage,
getExportedPresentationString: ChatService.getExportedPresentationString,
...props,
}
}
/>
);
};
export default TimeWindowChatItemContainer;

View File

@ -1,189 +0,0 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { debounce } from '/imports/utils/debounce';
import fastdom from 'fastdom';
import { injectIntl } from 'react-intl';
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
const propTypes = {
text: PropTypes.string.isRequired,
time: PropTypes.number.isRequired,
lastReadMessageTime: PropTypes.number,
handleReadMessage: PropTypes.func.isRequired,
scrollArea: PropTypes.instanceOf(Element),
className: PropTypes.string.isRequired,
};
const defaultProps = {
lastReadMessageTime: 0,
scrollArea: undefined,
};
const eventsToBeBound = [
'scroll',
'resize',
];
const isElementInViewport = (el) => {
if (!el) return false;
const rect = el.getBoundingClientRect();
return (
rect.top >= 0
// This condition is for large messages that are bigger than client height
|| rect.top + rect.height >= 0
);
};
class MessageChatItem extends PureComponent {
constructor(props) {
super(props);
this.ticking = false;
this.handleMessageInViewport = debounce(this.handleMessageInViewport.bind(this), 50);
}
componentDidMount() {
this.listenToUnreadMessages();
}
componentDidUpdate(prevProps, prevState) {
ChatLogger.debug('MessageChatItem::componentDidUpdate::props', { ...this.props }, { ...prevProps });
ChatLogger.debug('MessageChatItem::componentDidUpdate::state', { ...this.state }, { ...prevState });
this.listenToUnreadMessages();
}
componentWillUnmount() {
// This was added 3 years ago, but never worked. Leaving it around in case someone returns
// and decides it needs to be fixed like the one in listenToUnreadMessages()
// if (!lastReadMessageTime > time) {
// return;
// }
ChatLogger.debug('MessageChatItem::componentWillUnmount', this.props);
this.removeScrollListeners();
}
addScrollListeners() {
const {
scrollArea,
} = this.props;
if (scrollArea) {
eventsToBeBound.forEach(
e => scrollArea.addEventListener(e, this.handleMessageInViewport),
);
}
}
handleMessageInViewport() {
if (!this.ticking) {
fastdom.measure(() => {
const node = this.text;
const {
handleReadMessage,
time,
read,
} = this.props;
if (read) {
this.removeScrollListeners();
return;
}
if (isElementInViewport(node)) {
handleReadMessage(time);
this.removeScrollListeners();
}
this.ticking = false;
});
}
this.ticking = true;
}
removeScrollListeners() {
const {
scrollArea,
read,
} = this.props;
if (scrollArea && !read) {
eventsToBeBound.forEach(
e => scrollArea.removeEventListener(e, this.handleMessageInViewport),
);
}
}
// depending on whether the message is in viewport or not,
// either read it or attach a listener
listenToUnreadMessages() {
const {
handleReadMessage,
time,
read,
} = this.props;
if (read) {
return;
}
const node = this.text;
fastdom.measure(() => {
const {
read: newRead,
} = this.props;
// this function is called after so we need to get the updated lastReadMessageTime
if (newRead) {
return;
}
if (isElementInViewport(node)) { // no need to listen, the message is already in viewport
handleReadMessage(time);
} else {
this.addScrollListeners();
}
});
}
render() {
const {
text,
type,
className,
isSystemMessage,
chatUserMessageItem,
systemMessageType,
color,
} = this.props;
ChatLogger.debug('MessageChatItem::render', this.props);
if (type === 'poll') {
return (
<p
className={className}
style={{ borderLeft: `3px ${color} solid`, whiteSpace: 'pre-wrap' }}
ref={(ref) => { this.text = ref; }}
dangerouslySetInnerHTML={{ __html: text }}
data-test="chatPollMessageText"
/>
);
} else {
return (
<p
className={className}
ref={(ref) => { this.text = ref; }}
dangerouslySetInnerHTML={{ __html: text }}
data-test={isSystemMessage ? systemMessageType : chatUserMessageItem ? 'chatUserMessageText' : ''}
/>
);
}
}
}
MessageChatItem.propTypes = propTypes;
MessageChatItem.defaultProps = defaultProps;
export default injectIntl(MessageChatItem);

View File

@ -1,285 +0,0 @@
import styled from 'styled-components';
import {
borderRadius,
borderSize,
chatPollMarginSm,
} from '/imports/ui/stylesheets/styled-components/general';
import { lineHeightComputed, fontSizeBase, btnFontWeight } from '/imports/ui/stylesheets/styled-components/typography';
import {
systemMessageBackgroundColor,
systemMessageBorderColor,
systemMessageFontColor,
highlightedMessageBackgroundColor,
highlightedMessageBorderColor,
colorHeading,
colorGrayLight,
palettePlaceholderText,
colorGrayLighter,
colorPrimary,
colorText,
colorSuccess,
} from '/imports/ui/stylesheets/styled-components/palette';
import MessageChatItem from './message-chat-item/component';
import Icon from '/imports/ui/components/common/icon/component';
const Item = styled.div`
padding: calc(${lineHeightComputed} / 4) 0 calc(${lineHeightComputed} / 2) 0;
font-size: ${fontSizeBase};
pointer-events: auto;
[dir="rtl"] & {
direction: rtl;
}
`;
const Messages = styled.div`
> * {
&:first-child {
margin-top: calc(${lineHeightComputed} / 4);
}
}
`;
const StatusMessageChatItem = styled(MessageChatItem)`
background: ${systemMessageBackgroundColor};
border: 1px solid ${systemMessageBorderColor};
border-radius: ${borderRadius};
font-weight: ${btnFontWeight};
padding: ${fontSizeBase};
color: ${systemMessageFontColor};
margin-top: 0;
margin-bottom: 0;
overflow-wrap: break-word;
${({ isSystemMsg }) => !isSystemMsg && `
flex: 1;
margin-top: calc(${lineHeightComputed} / 3);
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
background: unset;
border: unset;
border-radius: unset;
font-weight: unset;
padding: unset;
`}
${({ emphasizedMessage }) => emphasizedMessage && `
font-weight: bold;
`}
`;
const SystemMessageChatItem = styled(MessageChatItem)`
${({ border }) => border && `
background-color: ${systemMessageBackgroundColor};
border: 1px solid ${systemMessageBorderColor};
border-radius: ${borderRadius};
font-weight: ${btnFontWeight};
padding: ${fontSizeBase};
color: ${systemMessageFontColor};
margin-top: 0px;
margin-bottom: 0px;
overflow-wrap: break-word;
`}
${({ border }) => !border && `
color: ${systemMessageFontColor};
margin-top: 0px;
margin-bottom: 0px;
`}
`;
const Wrapper = styled.div`
display: flex;
flex-flow: row;
flex: 1;
position: relative;
margin: ${borderSize} 0 0 ${borderSize};
${({ isSystemSender }) => isSystemSender && `
background-color: ${highlightedMessageBackgroundColor};
border-left: 2px solid ${highlightedMessageBorderColor};
border-radius: 0px 3px 3px 0px;
padding: 8px 2px;
`}
[dir="rtl"] & {
margin: ${borderSize} ${borderSize} 0 0;
}
`;
const AvatarWrapper = styled.div`
flex-basis: 2.25rem;
flex-shrink: 0;
flex-grow: 0;
margin: 0 calc(${lineHeightComputed} / 2) 0 0;
[dir="rtl"] & {
margin: 0 0 0 calc(${lineHeightComputed} / 2);
}
`;
const Content = styled.div`
flex: 1;
display: flex;
flex-flow: column;
overflow-x: hidden;
width: calc(100% - 1.7rem);
`;
const Meta = styled.div`
display: flex;
flex: 1;
flex-flow: row;
line-height: 1.35;
align-items: baseline;
`;
const Name = styled.div`
display: flex;
min-width: 0;
font-weight: 600;
position: relative;
&:first-child {
min-width: 0;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
${({ isOnline }) => isOnline && `
color: ${colorHeading};
`}
${({ isOnline }) => !isOnline && `
text-transform: capitalize;
font-style: italic;
& > span {
text-align: right;
padding: 0 .1rem 0 0;
[dir="rtl"] & {
text-align: left;
padding: 0 0 0 .1rem;
}
}
`}
`;
const Offline = styled.span`
color: ${colorGrayLight};
font-weight: 100;
text-transform: lowercase;
font-style: italic;
font-size: 90%;
line-height: 1;
align-self: center;
`;
const Time = styled.time`
flex-shrink: 0;
flex-grow: 0;
flex-basis: 3.5rem;
color: ${palettePlaceholderText};
text-transform: uppercase;
font-size: 75%;
margin: 0 0 0 calc(${lineHeightComputed} / 2);
[dir="rtl"] & {
margin: 0 calc(${lineHeightComputed} / 2) 0 0;
}
& > span {
vertical-align: sub;
}
`;
const ChatItem = styled(MessageChatItem)`
flex: 1;
margin-top: calc(${lineHeightComputed} / 3);
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
${({ hasLink }) => hasLink && `
& > a {
color: ${colorPrimary};
}
`}
${({ emphasizedMessage }) => emphasizedMessage && `
font-weight: bold;
`}
`;
const PollIcon = styled(Icon)`
bottom: 1px;
`;
const PollMessageChatItem = styled(MessageChatItem)`
flex: 1;
margin-top: calc(${lineHeightComputed} / 3);
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
background-color: ${systemMessageBackgroundColor};
border: solid 1px ${colorGrayLighter};
border-radius: ${borderRadius};
padding: ${chatPollMarginSm};
padding-left: 1rem;
margin-top: ${chatPollMarginSm} !important;
`;
const PresentationWrapper = styled(Wrapper)`
display: flex;
flex-flow: row;
flex: 1;
position: relative;
margin: ${borderSize} 0 0 ${borderSize};
border-left: 2px solid ${colorPrimary};
border-radius: 2px;
padding: 6px 0 6px 6px;
background-color: ${systemMessageBackgroundColor};
[dir="rtl"] & {
margin: ${borderSize} ${borderSize} 0 0;
border-right: 2px solid ${colorPrimary};
border-left: none;
}
`;
const PresentationChatItem = styled(MessageChatItem)`
flex: 1;
margin-top: ${chatPollMarginSm};
margin-bottom: 0;
color: ${colorText};
word-wrap: break-word;
`;
const ReadIcon = styled(Icon)`
color: ${colorSuccess};
align-self: center;
margin-left: auto;
margin-right: .5rem;
`;
export default {
Item,
Messages,
SystemMessageChatItem,
Wrapper,
AvatarWrapper,
Content,
Meta,
Name,
Offline,
Time,
ChatItem,
PollIcon,
PollMessageChatItem,
PresentationChatItem,
PresentationWrapper,
StatusMessageChatItem,
ReadIcon,
};

View File

@ -1,25 +0,0 @@
import { useContext, useEffect } from 'react';
import GroupChat from '/imports/api/group-chat';
import { GroupChatContext, ACTIONS } from './context';
const Adapter = () => {
const usingGroupChatContext = useContext(GroupChatContext);
const { dispatch } = usingGroupChatContext;
useEffect(() => {
const groupChatCursor = GroupChat.find({});
groupChatCursor.observe({
added: (obj) => {
dispatch({
type: ACTIONS.ADDED,
value: {
groupChat: obj,
},
});
},
});
}, []);
};
export default Adapter;

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Resizable from 're-resizable';
import { ACTIONS, PANELS } from '../layout/enums';
import ChatContainer from '/imports/ui/components/chat/container';
import ChatContainer from '/imports/ui/components/chat/chat-graphql/component';
import NotesContainer from '/imports/ui/components/notes/container';
import PollContainer from '/imports/ui/components/poll/container';
import CaptionsContainer from '/imports/ui/components/captions/container';

View File

@ -2,7 +2,6 @@ import { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import logger from '/imports/startup/client/logger';
import GroupChat from '/imports/api/group-chat';
import Users from '/imports/api/users';
import { localCollectionRegistry } from '/client/collection-mirror-initializer';
import SubscriptionRegistry, {
@ -24,7 +23,6 @@ const SUBSCRIPTIONS = [
'users-infos',
'meeting-time-remaining',
'local-settings',
'users-typing',
'record-meetings',
'video-streams',
'connection-status',
@ -40,7 +38,6 @@ const SUBSCRIPTIONS = [
'layout-meetings',
'user-reaction',
'timer',
// 'group-chat'
];
const {
localBreakoutsSync,
@ -96,6 +93,7 @@ export default withTracker(() => {
},
'Error while subscribing to collections'
);
console.log('-------------------------', {error});
Session.set('codeError', error.error);
},
};
@ -151,15 +149,6 @@ export default withTracker(() => {
subscriptionsHandlers = subscriptionsHandlers.filter((obj) => obj);
const ready = subscriptionsHandlers.every((handler) => handler.ready());
let groupChatMessageHandler = {};
// if (isChatEnabled() && ready) {
// const subHandler = {
// ...subscriptionErrorHandler,
// };
// groupChatMessageHandler = Meteor.subscribe('group-chat-msg', subHandler);
// }
// TODO: Refactor all the late subscribers
let usersPersistentDataHandler = {};

View File

@ -1,7 +1,6 @@
import React from 'react';
import Users from '/imports/api/users';
import VoiceUsers from '/imports/api/voice-users';
import GroupChat from '/imports/api/group-chat';
import Breakouts from '/imports/api/breakouts';
import Meetings from '/imports/api/meetings';
import UserReaction from '/imports/api/user-reaction';
@ -620,29 +619,6 @@ const roving = (...args) => {
}
};
const hasPrivateChatBetweenUsers = (senderId, receiverId) => GroupChat
.findOne({ users: { $all: [receiverId, senderId] } });
const getGroupChatPrivate = (senderUserId, receiver) => {
const chat = hasPrivateChatBetweenUsers(senderUserId, receiver.userId);
if (!chat) {
makeCall('createGroupChat', receiver);
} else {
const startedChats = Session.get(STARTED_CHAT_LIST_KEY) || [];
if (indexOf(startedChats, chat.chatId) < 0) {
startedChats.push(chat.chatId);
Session.set(STARTED_CHAT_LIST_KEY, startedChats);
}
const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY);
if (ChatService.isChatClosed(chat.chatId)) {
const closedChats = currentClosedChats.filter(closedChat => closedChat.chatId !== chat.chatId);
Storage.setItem(CLOSED_CHAT_LIST_KEY,closedChats);
}
}
};
const toggleUserLock = (userId, lockStatus) => {
makeCall('toggleUserLock', userId, lockStatus);
};
@ -788,11 +764,9 @@ export default {
isPublicChat,
roving,
getCustomLogoUrl,
getGroupChatPrivate,
hasBreakoutRoom,
getEmojiList: () => EMOJI_STATUSES,
getEmoji,
hasPrivateChatBetweenUsers,
toggleUserLock,
requestUserInformation,
focusFirstDropDownItem,