diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addSystemMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addSystemMsg.js new file mode 100644 index 0000000000000000000000000000000000000000..04d77ef1f4604570a324a4657c5811e698c06557 --- /dev/null +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addSystemMsg.js @@ -0,0 +1,45 @@ +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 function addSystemMsg(meetingId, chatId, msg) { + check(meetingId, String); + check(chatId, String); + check(msg, { + id: Match.Maybe(String), + timestamp: Number, + sender: Object, + message: String, + correlationId: Match.Maybe(String), + }); + const msgDocument = { + ...msg, + meetingId, + chatId, + message: parseMessage(msg.message), + }; + + try { + const insertedId = GroupChatMsg.insert(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}`); + } +} diff --git a/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js b/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js index 13f8802f7c3cb65784e65462d124246626976f89..3f2ef3bdec5a06f9de1428e02c14bbc7a4ed4887 100644 --- a/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/polls/server/eventHandlers.js @@ -2,11 +2,13 @@ import RedisPubSub from '/imports/startup/server/redis'; import handlePollStarted from './handlers/pollStarted'; import handlePollStopped from './handlers/pollStopped'; import handlePollPublished from './handlers/pollPublished'; +import handleSendSystemChatForPublishedPoll from './handlers/sendPollChatMsg'; import handleUserVoted from './handlers/userVoted'; import handleUserResponded from './handlers/userResponded'; import handleUserTypedResponse from './handlers/userTypedResponse'; RedisPubSub.on('PollShowResultEvtMsg', handlePollPublished); +RedisPubSub.on('PollShowResultEvtMsg', handleSendSystemChatForPublishedPoll); RedisPubSub.on('PollStartedEvtMsg', handlePollStarted); RedisPubSub.on('PollStoppedEvtMsg', handlePollStopped); RedisPubSub.on('PollUpdatedEvtMsg', handleUserVoted); diff --git a/bigbluebutton-html5/imports/api/polls/server/handlers/sendPollChatMsg.js b/bigbluebutton-html5/imports/api/polls/server/handlers/sendPollChatMsg.js new file mode 100644 index 0000000000000000000000000000000000000000..686dc31ec5d40eca18636844d7db74705048440d --- /dev/null +++ b/bigbluebutton-html5/imports/api/polls/server/handlers/sendPollChatMsg.js @@ -0,0 +1,38 @@ +import addSystemMsg from '../../../group-chat-msg/server/modifiers/addSystemMsg'; + +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 { answers, numRespondents } = poll; + + let responded = 0; + let resultString = 'bbb-published-poll-\n'; + answers.map((item) => { + responded += item.numVotes; + return item; + }).map((item) => { + const numResponded = responded === numRespondents ? numRespondents : responded; + const pct = Math.round(item.numVotes / numResponded * 100); + const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`; + resultString += `${item.key}: ${item.numVotes || 0} | ${pctFotmatted}\n`; + }); + + 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: resultString, + }; + + return addSystemMsg(meetingId, PUBLIC_GROUP_CHAT_ID, payload); +} diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/component.jsx index 36e56d69eb08aa9b8bb7e2c65a3acd31605f2f63..9b3a7cd24f65992e6a86dc07b66164575013b42b 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/component.jsx @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { FormattedTime, defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; -import Icon from '/imports/ui/components/icon/component'; import UserAvatar from '/imports/ui/components/user-avatar/component'; import cx from 'classnames'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; @@ -77,6 +76,10 @@ class TimeWindowChatItem extends PureComponent { intl, } = this.props; + if (messages && messages[0].text.includes('bbb-published-poll-<br/>')) { + return this.renderPollItem(); + } + return ( <div className={styles.item} key={`time-window-chat-item-${messageKey}`}> <div className={styles.messages}> @@ -102,7 +105,6 @@ class TimeWindowChatItem extends PureComponent { renderMessageItem() { const { - user, time, chatAreaId, scrollArea, @@ -119,10 +121,6 @@ class TimeWindowChatItem extends PureComponent { isOnline, } = this.props; - if (messages && messages[0].text.includes('bbb-published-poll-<br/>')) { - return this.renderPollItem(); - } - const dateTime = new Date(time); const regEx = /<a[^>]+>/i; ChatLogger.debug('TimeWindowChatItem::renderMessageItem', this.props); @@ -190,8 +188,8 @@ class TimeWindowChatItem extends PureComponent { renderPollItem() { const { - user, time, + color, intl, isDefaultPoll, messages, @@ -206,15 +204,6 @@ class TimeWindowChatItem extends PureComponent { return messages ? ( <div className={styles.item} key={_.uniqueId('message-poll-item-')}> <div className={styles.wrapper} ref={(ref) => { this.item = ref; }}> - <div className={styles.avatarWrapper}> - <UserAvatar - className={styles.avatar} - color={user.color} - moderator={user.isModerator} - > - {<Icon className={styles.isPoll} iconName="polling" />} - </UserAvatar> - </div> <div className={styles.content}> <div className={styles.meta}> <div className={styles.name}> @@ -234,7 +223,7 @@ class TimeWindowChatItem extends PureComponent { lastReadMessageTime={lastReadMessageTime} handleReadMessage={handleReadMessage} scrollArea={scrollArea} - color={user.color} + color={color} isDefaultPoll={isDefaultPoll(messages[0].text.replace('bbb-published-poll-<br/>', ''))} /> </div> @@ -245,7 +234,6 @@ class TimeWindowChatItem extends PureComponent { render() { const { - user, systemMessage, } = this.props; ChatLogger.debug('TimeWindowChatItem::render', {...this.props}); diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx index 7fb19d5e992fb47c11ed2ba085ace267e97957fa..c16643d37e2a727bd1c81c42f95587baa27f3b64 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx @@ -177,11 +177,18 @@ class MessageChatItem extends PureComponent { const options = []; entries.map((e) => { options.push([e.slice(0, e.indexOf(':'))]); return e; }); options.map((o, idx) => { - _text = formatBoldBlack(_text.replace(o, idx + 1)); + if (o[0] !== '') { + _text = formatBoldBlack(_text.replace(o, idx + 1)); + } return _text; }); _text += formatBoldBlack(`<br/><br/>${intl.formatMessage(intlMessages.legendTitle)}`); - options.map((o, idx) => { _text += `<br/>${idx + 1}: ${o}`; return _text; }); + options.map((o, idx) => { + if (o[0] !== '') { + _text += `<br/>${idx + 1}: ${o}`; + } + return _text; + }); } return ( diff --git a/bigbluebutton-html5/imports/ui/components/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/component.jsx index d9988a3d5110dc53a82acf5e2107f83ed245ecd5..479238daf3a535469deafb983858a2435b24680a 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/component.jsx @@ -321,7 +321,6 @@ class Poll extends Component { stopPoll, currentPoll, pollAnswerIds, - sendGroupMessage, } = this.props; return ( @@ -335,7 +334,6 @@ class Poll extends Component { stopPoll, currentPoll, pollAnswerIds, - sendGroupMessage, }} handleBackClick={this.handleBackClick} /> diff --git a/bigbluebutton-html5/imports/ui/components/poll/container.jsx b/bigbluebutton-html5/imports/ui/components/poll/container.jsx index 0a2833157a3cc2743c69decdf74598616dfe2900..04404f2111303964336775ef901efb14b0ff0538 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/container.jsx @@ -40,6 +40,5 @@ export default withTracker(() => { resetPollPanel: Session.get('resetPollPanel') || false, pollAnswerIds: Service.pollAnswerIds, isMeteorConnected: Meteor.status().connected, - sendGroupMessage: Service.sendGroupMessage, }; })(PollContainer); diff --git a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx index 6d1c127da552de68d7f9b3ddbe7f47eec3045bd7..28af945b38f2c63fe5760a3ebcd8c9183afed598 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/live-result/component.jsx @@ -145,7 +145,6 @@ class LiveResult extends PureComponent { stopPoll, handleBackClick, currentPoll, - sendGroupMessage, } = this.props; const { userAnswers, pollStats } = this.state; @@ -192,20 +191,6 @@ class LiveResult extends PureComponent { onClick={() => { Session.set('pollInitiated', false); Service.publishPoll(); - const { answers, numRespondents } = currentPoll; - let responded = 0; - let resultString = 'bbb-published-poll-\n'; - answers.map((item) => { - responded += item.numVotes; - return item; - }).map((item) => { - const numResponded = responded === numRespondents ? numRespondents : responded; - const pct = Math.round(item.numVotes / numResponded * 100); - const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`; - resultString += `${item.key}: ${item.numVotes || 0} | ${pctFotmatted}\n`; - }); - - sendGroupMessage(resultString); stopPoll(); }} label={intl.formatMessage(intlMessages.publishLabel)} diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index 38ba164618b3643f76a037edf17e2f2bc959810d..654ba70e1085f9dd3f2e6e9e0bfc460cb5a744d9 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -1,4 +1,3 @@ -import { makeCall } from '/imports/ui/services/api'; import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import Polls from '/imports/api/polls'; @@ -57,24 +56,6 @@ const pollAnswerIds = { }, }; -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 sendGroupMessage = (message) => { - const payload = { - color: '0', - correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`, - sender: { - id: Auth.userID, - name: '', - }, - message, - }; - - return makeCall('sendGroupChatMsg', PUBLIC_GROUP_CHAT_ID, payload); -}; - export default { amIPresenter: () => Users.findOne( { userId: Auth.userID }, @@ -83,6 +64,5 @@ export default { pollTypes, currentPoll: () => Polls.findOne({ meetingId: Auth.meetingID }), pollAnswerIds, - sendGroupMessage, POLL_AVATAR_COLOR, }; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 98a1dda53d433f91ed6f8eb37e405723ef1749dc..266ae80d1036cd1264832d5d42e05d618c0852cc 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -316,6 +316,7 @@ public: storage_key: UNREAD_CHATS system_messages_keys: chat_clear: PUBLIC_CHAT_CLEAR + chat_poll_result: PUBLIC_CHAT_POLL_RESULT typingIndicator: enabled: true note: