From 5c7fa6ece5022d0587b46c5bb9bfc1f6cdf5dd89 Mon Sep 17 00:00:00 2001 From: germanocaumo <germanocaumo@gmail.com> Date: Sat, 29 May 2021 00:28:47 +0000 Subject: [PATCH] fix: export poll chat message Include the new poll text in the chat export function. Refactor some chat type constants. Also fixes empty lines in the export chat file. --- .../ui/components/chat/alert/component.jsx | 5 +- .../chat/chat-dropdown/component.jsx | 4 +- .../imports/ui/components/chat/service.js | 21 ++++- .../chat/time-window-list/component.jsx | 3 +- .../time-window-chat-item/component.jsx | 8 +- .../time-window-chat-item/container.jsx | 1 - .../message-chat-item/component.jsx | 58 +++++--------- .../poll-list-item/component.jsx | 78 ------------------- .../poll-list-item/styles.scss | 3 - .../components-data/chat-context/adapter.jsx | 4 +- .../imports/ui/components/poll/service.js | 52 ++++++++++++- .../private/config/settings.yml | 2 +- 12 files changed, 104 insertions(+), 135 deletions(-) delete mode 100644 bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/component.jsx delete mode 100644 bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/styles.scss diff --git a/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx index 95b7854264..1a67f173ca 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx @@ -8,6 +8,9 @@ import ChatPushAlert from './push-alert/component'; import Service from '../service'; import { styles } from '../styles'; +const CHAT_CONFIG = Meteor.settings.public.chat; +const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear; + const propTypes = { pushAlertDisabled: PropTypes.bool.isRequired, activeChats: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -152,7 +155,7 @@ class ChatAlert extends PureComponent { } = this.props; const contentMessage = message .map((content) => { - if (content.text === 'PUBLIC_CHAT_CLEAR') return intl.formatMessage(intlMessages.publicChatClear); + if (content.text === PUBLIC_CHAT_CLEAR) return intl.formatMessage(intlMessages.publicChatClear); /* this code is to remove html tags that come in the server's messages */ const tempDiv = document.createElement('div'); tempDiv.innerHTML = content.text; diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx index 3811d4e585..b2442f2518 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/component.jsx @@ -103,8 +103,8 @@ class ChatDropdown extends PureComponent { link.setAttribute('download', `bbb-${meetingName}[public-chat]_${dateString}.txt`); link.setAttribute( 'href', - `data: ${mimeType} ;charset=utf-8, - ${encodeURIComponent(ChatService.exportChat(timeWindowsValues, users, intl))}`, + `data: ${mimeType} ;charset=utf-8,`+ + `${encodeURIComponent(ChatService.exportChat(timeWindowsValues, users, intl))}`, ); link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); }} diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 65c3dcd0f3..89d94ad4df 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -8,6 +8,7 @@ import { makeCall } from '/imports/ui/services/api'; import _ from 'lodash'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; import { defineMessages } from 'react-intl'; +import PollService from '/imports/ui/components/poll/service'; const CHAT_CONFIG = Meteor.settings.public.chat; const GROUPING_MESSAGES_WINDOW = CHAT_CONFIG.grouping_messages_window; @@ -17,6 +18,7 @@ const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system; const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id; const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id; +const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear; const CHAT_POLL_RESULTS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_poll_result; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -35,6 +37,10 @@ const intlMessages = defineMessages({ id: 'app.chat.clearPublicChatMessage', description: 'message of when clear the public chat', }, + pollResult: { + id: 'app.chat.pollResult', + description: 'used in place of user name who published poll to chat', + }, }); const setUserSentMessage = (bool) => { @@ -251,10 +257,21 @@ const exportChat = (timeWindowList, users, intl) => { const hour = date.getHours().toString().padStart(2, 0); const min = date.getMinutes().toString().padStart(2, 0); const hourMin = `[${hour}:${min}]`; - const userName = message.id.startsWith('SYSTEM_MESSAGE') + let userName = message.id.startsWith(SYSTEM_CHAT_TYPE) ? '' : `${users[timeWindow.sender].name}: `; - const messageText = (message.text === 'PUBLIC_CHAT_CLEAR') ? intl.formatMessage(intlMessages.publicChatClear) : message.text; + let messageText = ''; + if (message.text === PUBLIC_CHAT_CLEAR) { + message.text = intl.formatMessage(intlMessages.publicChatClear); + } else if (message.id.includes(CHAT_POLL_RESULTS_MESSAGE)) { + userName = `${intl.formatMessage(intlMessages.pollResult)}:\n`; + const { pollResultData } = timeWindow.extra; + const pollText = htmlDecode(PollService.getPollResultString(pollResultData, intl)); + // remove last \n to avoid empty line + messageText = pollText.slice(0, -1); + } else { + messageText = message.text; + } return `${hourMin} ${userName}${htmlDecode(messageText)}`; }); 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 index 372d9c721b..270488b44b 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx @@ -12,6 +12,7 @@ import TimeWindowChatItem from './time-window-chat-item/container'; const CHAT_CONFIG = Meteor.settings.public.chat; const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system; const CHAT_POLL_RESULTS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_poll_result; +const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear; const propTypes = { scrollPosition: PropTypes.number, @@ -151,7 +152,7 @@ class TimeWindowList extends PureComponent { // 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_MESSAGE-PUBLIC_CHAT_CLEAR') + (lastTimeWindow?.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`) || (prevSyncing && !syncing) || (syncedPercent !== prevSyncedPercent) || (chatId !== prevChatId) 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 ff9b39f927..df6afc5fee 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 @@ -95,7 +95,7 @@ class TimeWindowChatItem extends PureComponent { text={intlMessages[message.text] ? intl.formatMessage(intlMessages[message.text]) : message.text } time={message.time} isSystemMessage={message.id ? true : false} - systemMessageType={message.text === 'PUBLIC_CHAT_CLEAR' ? 'chatClearMessageText' : 'chatWelcomeMessageText'} + systemMessageType={message.text === CHAT_CLEAR_MESSAGE ? 'chatClearMessageText' : 'chatWelcomeMessageText'} chatAreaId={chatAreaId} handleReadMessage={handleReadMessage} /> @@ -194,7 +194,6 @@ class TimeWindowChatItem extends PureComponent { timestamp, color, intl, - isDefaultPoll, getPollResultString, messages, extra, @@ -231,16 +230,13 @@ class TimeWindowChatItem extends PureComponent { type="poll" className={cx(styles.message, styles.pollWrapper)} key={messages[0].id} - text={messages[0].text} - pollResultData={extra.pollResultData} + text={getPollResultString(extra.pollResultData, intl)} time={messages[0].time} chatAreaId={chatAreaId} lastReadMessageTime={lastReadMessageTime} handleReadMessage={handleReadMessage} scrollArea={scrollArea} color={color} - isDefaultPoll={isDefaultPoll(extra.pollResultData.pollType)} - getPollResultString={getPollResultString} /> </div> </div> diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/container.jsx index cc18f6de86..8e4c63edd8 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/container.jsx @@ -37,7 +37,6 @@ export default function TimeWindowChatItemContainer(props) { read: message.read, messages, extra, - isDefaultPoll: PollService.isDefaultPoll, getPollResultString: PollService.getPollResultString, user, timestamp, 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 837881c70f..81b1ab313c 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 @@ -2,9 +2,8 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import fastdom from 'fastdom'; -import { defineMessages, injectIntl } from 'react-intl'; +import { injectIntl } from 'react-intl'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; -import PollListItem from './poll-list-item/component'; const propTypes = { text: PropTypes.string.isRequired, @@ -36,17 +35,6 @@ const isElementInViewport = (el) => { ); }; -const intlMessages = defineMessages({ - legendTitle: { - id: 'app.polling.pollingTitle', - description: 'heading for chat poll legend', - }, - pollQuestionTitle: { - id: 'app.polling.pollQuestionTitle', - description: 'title displayed before poll question', - }, -}); - class MessageChatItem extends PureComponent { constructor(props) { super(props); @@ -163,7 +151,6 @@ class MessageChatItem extends PureComponent { render() { const { - intl, text, type, className, @@ -171,31 +158,28 @@ class MessageChatItem extends PureComponent { chatUserMessageItem, systemMessageType, color, - isDefaultPoll, - getPollResultString, - pollResultData, } = this.props; ChatLogger.debug('MessageChatItem::render', this.props); - if (type === 'poll') return ( - <PollListItem - intl={intl} - text={text} - pollResultData={pollResultData} - className={className} - color={color} - isDefaultPoll={isDefaultPoll} - getPollResultString={getPollResultString} - /> - ) - - return ( - <p - className={className} - ref={(ref) => { this.text = ref; }} - dangerouslySetInnerHTML={{ __html: text }} - data-test={isSystemMessage ? systemMessageType : chatUserMessageItem ? 'chatUserMessageText' : ''} - /> - ); + 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' : ''} + /> + ); + } } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/component.jsx deleted file mode 100644 index 91856398e6..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/component.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; -import cx from 'classnames'; -import { styles } from './styles'; - -const propTypes = { - intl: PropTypes.shape({ - formatMessage: PropTypes.func.isRequired, - }).isRequired, - text: PropTypes.string.isRequired, - pollResultData: PropTypes.object.isRequired, - isDefaultPoll: PropTypes.bool.isRequired, - getPollResultString: PropTypes.func.isRequired, -}; - -const intlMessages = defineMessages({ - legendTitle: { - id: 'app.polling.pollingTitle', - description: 'heading for chat poll legend', - }, - pollQuestionTitle: { - id: 'app.polling.pollQuestionTitle', - description: 'title displayed before poll question', - }, -}); - -function PollListItem(props) { - const { - intl, - pollResultData, - className, - color, - isDefaultPoll, - getPollResultString, - } = props; - - const formatBoldBlack = s => s.bold().fontcolor('black'); - - // Sanitize. See: https://gist.github.com/sagewall/47164de600df05fb0f6f44d48a09c0bd - const sanitize = (value) => { - const div = document.createElement('div'); - div.appendChild(document.createTextNode(value)); - return div.innerHTML; - }; - - const { answers, numRespondents } = pollResultData; - let { resultString, optionsString } = getPollResultString(isDefaultPoll, answers, numRespondents, intl) - resultString = sanitize(resultString); - optionsString = sanitize(optionsString); - - let pollText = formatBoldBlack(resultString); - if (!isDefaultPoll) { - pollText += formatBoldBlack(`<br/><br/>${intl.formatMessage(intlMessages.legendTitle)}<br/>`); - pollText += optionsString; - } - - const pollQuestion = pollResultData.title; - if (pollQuestion.trim() !== '') { - const sanitizedPollQuestion = sanitize(pollQuestion.split('<br#>').join(' ')); - - pollText = `${formatBoldBlack(intl.formatMessage(intlMessages.pollQuestionTitle))}<br/>${sanitizedPollQuestion}<br/><br/>${pollText}`; - } - - return ( - <p - className={cx(className, styles.pollText)} - style={{ borderLeft: `3px ${color} solid`}} - ref={(ref) => { this.text = ref; }} - dangerouslySetInnerHTML={{ __html: pollText }} - data-test="chatPollMessageText" - /> - ); -} - -PollListItem.propTypes = propTypes; - -export default injectIntl(PollListItem); \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/styles.scss deleted file mode 100644 index dbb506d3b7..0000000000 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/poll-list-item/styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.pollText { - white-space: pre-wrap; -} \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx index a36b24de63..db9c29bb66 100644 --- a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx +++ b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx @@ -11,6 +11,8 @@ let currentUserData = {}; let messageQueue = []; 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 ITENS_PER_PAGE = CHAT_CONFIG.itemsPerPage; const TIME_BETWEEN_FETCHS = CHAT_CONFIG.timeBetweenFetchs; const EVENT_NAME = 'bbb-group-chat-messages-subscription-has-stoppped'; @@ -116,7 +118,7 @@ const Adapter = () => { const parsedMsg = JSON.parse(msg.data); if (parsedMsg.msg === 'added') { const { fields } = parsedMsg; - if (fields.id === 'SYSTEM_MESSAGE-PUBLIC_CHAT_CLEAR') { + if (fields.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`) { messageQueue = []; dispatch({ type: ACTIONS.REMOVED, diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index 654482d29b..8b61817579 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -2,6 +2,7 @@ import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import Polls from '/imports/api/polls'; import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer'; +import { defineMessages } from 'react-intl'; const POLL_AVATAR_COLOR = '#3B48A9'; const MAX_POLL_RESULT_BARS = 20; @@ -69,7 +70,18 @@ const pollAnswerIds = { }, }; -const getPollResultString = (isDefaultPoll, answers, numRespondents, intl) => { +const intlMessages = defineMessages({ + legendTitle: { + id: 'app.polling.pollingTitle', + description: 'heading for chat poll legend', + }, + pollQuestionTitle: { + id: 'app.polling.pollQuestionTitle', + description: 'title displayed before poll question', + }, +}); + +const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => { let responded = 0; let resultString = ''; let optionsString = ''; @@ -94,6 +106,42 @@ const getPollResultString = (isDefaultPoll, answers, numRespondents, intl) => { return { resultString, optionsString }; } +const isDefaultPoll = (pollType) => { + return pollType !== pollTypes.Custom && pollType !== pollTypes.Response +} + +const getPollResultString = (pollResultData, intl) => { + const formatBoldBlack = s => s.bold().fontcolor('black'); + + // Sanitize. See: https://gist.github.com/sagewall/47164de600df05fb0f6f44d48a09c0bd + const sanitize = (value) => { + const div = document.createElement('div'); + div.appendChild(document.createTextNode(value)); + return div.innerHTML; + }; + + const { answers, numRespondents, pollType } = pollResultData; + const ÃsDefault = isDefaultPoll(pollType) + let { resultString, optionsString } = getPollResultsText(ÃsDefault, answers, numRespondents, intl) + resultString = sanitize(resultString); + optionsString = sanitize(optionsString); + + let pollText = formatBoldBlack(resultString); + if (!ÃsDefault) { + pollText += formatBoldBlack(`<br/><br/>${intl.formatMessage(intlMessages.legendTitle)}<br/>`); + pollText += optionsString; + } + + const pollQuestion = pollResultData.questionText; + if (pollQuestion.trim() !== '') { + const sanitizedPollQuestion = sanitize(pollQuestion.split('<br#>').join(' ')); + + pollText = `${formatBoldBlack(intl.formatMessage(intlMessages.pollQuestionTitle))}<br/>${sanitizedPollQuestion}<br/><br/>${pollText}`; + } + + return pollText; +} + const matchYesNoPoll = (yesValue, noValue, contentString) => { const ynPollString = `(${yesValue}\\s*\\/\\s*${noValue})|(${noValue}\\s*\\/\\s*${yesValue})`; const ynOptionsRegex = new RegExp(ynPollString, 'gi'); @@ -160,7 +208,7 @@ export default { currentPoll: () => Polls.findOne({ meetingId: Auth.meetingID }), pollAnswerIds, POLL_AVATAR_COLOR, - isDefaultPoll: (pollType) => { return pollType !== pollTypes.Custom && pollType !== pollTypes.Response}, + isDefaultPoll: isDefaultPoll, getPollResultString: getPollResultString, matchYesNoPoll: matchYesNoPoll, matchYesNoAbstentionPoll: matchYesNoAbstentionPoll, diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 10bc805d10..09bc2fa79b 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -380,7 +380,7 @@ public: poll: enabled: true max_custom: 5 - allowDragAndDropFile: false + allowDragAndDropFile: true maxTypedAnswerLength: 45 captions: enabled: true -- GitLab