diff --git a/bigbluebutton-html5/client/collection-mirror-initializer.js b/bigbluebutton-html5/client/collection-mirror-initializer.js index 90ad295d80..835f019a22 100644 --- a/bigbluebutton-html5/client/collection-mirror-initializer.js +++ b/bigbluebutton-html5/client/collection-mirror-initializer.js @@ -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), diff --git a/bigbluebutton-html5/client/main.jsx b/bigbluebutton-html5/client/main.jsx index 0096d86b20..e11a2bbaef 100755 --- a/bigbluebutton-html5/client/main.jsx +++ b/bigbluebutton-html5/client/main.jsx @@ -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(() => { - , document.getElementById('app'), diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/index.js b/bigbluebutton-html5/imports/api/group-chat-msg/index.js deleted file mode 100644 index aaca255e09..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/index.js +++ /dev/null @@ -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 }; diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/eventHandlers.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/eventHandlers.js deleted file mode 100644 index 5e205e8719..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/eventHandlers.js +++ /dev/null @@ -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); diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/clearPublicGroupChat.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/clearPublicGroupChat.js deleted file mode 100644 index 7fe9749d2d..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/clearPublicGroupChat.js +++ /dev/null @@ -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; -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/groupChatMsgBroadcast.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/groupChatMsgBroadcast.js deleted file mode 100644 index 6c0a0dd64e..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/groupChatMsgBroadcast.js +++ /dev/null @@ -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); - } -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/syncGroupsChat.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/syncGroupsChat.js deleted file mode 100644 index 3db44bdecc..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/syncGroupsChat.js +++ /dev/null @@ -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); -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/userTyping.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/userTyping.js deleted file mode 100644 index 893197e453..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/handlers/userTyping.js +++ /dev/null @@ -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); -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/index.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/index.js index 92451ac76b..ba55c4a16e 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/index.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/index.js @@ -1,3 +1 @@ -import './eventHandlers'; import './methods'; -import './publishers'; diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods.js index ad260c3724..6489e7fd11 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods.js @@ -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, }); diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/chatMessageBeforeJoinCounter.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/chatMessageBeforeJoinCounter.js deleted file mode 100644 index 435a0ec178..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/chatMessageBeforeJoinCounter.js +++ /dev/null @@ -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; -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/fetchMessagePerPage.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/fetchMessagePerPage.js deleted file mode 100644 index ffaf5abf9b..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/fetchMessagePerPage.js +++ /dev/null @@ -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; -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/stopUserTyping.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/stopUserTyping.js deleted file mode 100644 index 98e19b8b9a..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/stopUserTyping.js +++ /dev/null @@ -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}`); - } -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js deleted file mode 100644 index 372bfa0034..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js +++ /dev/null @@ -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}`); - } -} diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js deleted file mode 100644 index 8d78c33cb9..0000000000 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addGroupChatMsg.js +++ /dev/null @@ -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
- 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(' 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); diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx deleted file mode 100644 index 84e3433e20..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx +++ /dev/null @@ -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 ; -}; - -export default withTracker(() => { - const getMeetingName = () => { - const m = Meetings.findOne({ meetingId: Auth.meetingID }, - { fields: { 'meetingProp.name': 1 } }); - return m.meetingProp.name; - }; - - return { - meetingName: getMeetingName(), - }; -})(ChatDropdownContainer); diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx index e64f741b5a..38ecdc1beb 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx @@ -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 = ({ updateUnreadMessages(chatId, ''); setHasErrors(false); setShowEmojiPicker(false); - if (ENABLE_TYPING_INDICATOR) stopUserTyping(); const sentMessageEvent = new CustomEvent(ChatEvents.SENT_MESSAGE); window.dispatchEvent(sentMessageEvent); diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/service.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/service.ts index baf759c85c..53e1225a59 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/service.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/service.ts @@ -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, }; diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx deleted file mode 100755 index 867a272c9e..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ /dev/null @@ -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 ( - - - - - - ); -}; - -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; diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx deleted file mode 100755 index 46bf52eb5c..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ /dev/null @@ -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 ( - - {children} - - ); -}; - -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; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx deleted file mode 100755 index 1ecfb67845..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ /dev/null @@ -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 ( - - this.handleEmojiSelect(emojiObject)} - /> - - ); - } - return null; - } - - renderEmojiButton() { - const { intl } = this.props; - - return ( - 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 ( - { this.form = ref; }} - onSubmit={this.handleSubmit} - > - {this.renderEmojiPicker()} - - { 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()} - { }} - data-test="sendMessageButton" - /> - - - - ); - } - - render() { - if (!isChatEnabled()) return null; - - return ENABLE_EMOJI_PICKER ? ( - this.handleClickOutside()} - > - {this.renderForm()} - - ) : this.renderForm(); - } -} - -MessageForm.propTypes = propTypes; - -export default injectIntl(MessageForm); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/container.jsx deleted file mode 100644 index 92b5689925..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/container.jsx +++ /dev/null @@ -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 ( - - ); -}; - -export default ChatMessageFormContainer; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.js b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.js deleted file mode 100644 index d8d91d50b9..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.js +++ /dev/null @@ -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, -}; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx deleted file mode 100644 index 7d3db7618f..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx +++ /dev/null @@ -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 = ( - - {`${name}`} -  - , - }} - /> - ); - } - - if (isCoupleTyper) { - const {name} = typingUsers[0]; - const {name: name2} = typingUsers[1]; - element = ( - - {`${name}`} -  - , - 1: -  - {`${name2}`} -  - , - }} - /> - ); - } - - if (isMultiTypers) { - element = ( - - {`${intl.formatMessage(messages.severalPeople)}`} - - ); - } - } else { - // Show no names in typing indicator - const isSingleTyper = length === 1; - const isMultiTypers = length > 1; - - if (isSingleTyper) { - element = ( - - {`${intl.formatMessage(messages.someoneTyping)}`} - - ); - } - - if (isMultiTypers) { - element = ( - - {`${intl.formatMessage(messages.severalPeople)}`} - - ); - } - } - - return element; - } - - render() { - const { - error, - indicatorEnabled, - } = this.props; - - const typingElement = indicatorEnabled ? this.renderTypingElement() : null; - - return ( - - {error || typingElement} - - ); - } -} - -TypingIndicator.propTypes = propTypes; - -export default injectIntl(TypingIndicator); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx deleted file mode 100644 index f407663aa6..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx +++ /dev/null @@ -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 => ; - -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); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/styles.js b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/styles.js deleted file mode 100644 index e650c0a5be..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/styles.js +++ /dev/null @@ -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, -}; diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 1d4ed6660f..be620ef51f 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -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, diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx deleted file mode 100644 index 44a2b07aeb..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx +++ /dev/null @@ -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 ( - - - - - - ); - } - - renderUnreadNotification() { - const { - intl, - count, - timeWindowsValues, - } = this.props; - const { userScrolledBack } = this.state; - - if (count && userScrolledBack) { - return ( -