bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/poll/service.js

292 lines
9.1 KiB
JavaScript

import Auth from '/imports/ui/services/auth';
import { CurrentPoll } from '/imports/api/polls';
import { escapeHtml } from '/imports/utils/string-utils';
import { defineMessages } from 'react-intl';
const POLL_AVATAR_COLOR = '#3B48A9';
const MAX_POLL_RESULT_BARS = 20;
// 'YN' = Yes,No
// 'YNA' = Yes,No,Abstention
// 'TF' = True,False
// 'A-2' = A,B
// 'A-3' = A,B,C
// 'A-4' = A,B,C,D
// 'A-5' = A,B,C,D,E
const pollTypes = {
YesNo: 'YN',
YesNoAbstention: 'YNA',
TrueFalse: 'TF',
Letter: 'A-',
A2: 'A-2',
A3: 'A-3',
A4: 'A-4',
A5: 'A-5',
Custom: 'CUSTOM',
Response: 'R-',
};
const pollAnswerIds = {
true: {
id: 'app.poll.answer.true',
description: 'label for poll answer True',
},
false: {
id: 'app.poll.answer.false',
description: 'label for poll answer False',
},
yes: {
id: 'app.poll.answer.yes',
description: 'label for poll answer Yes',
},
no: {
id: 'app.poll.answer.no',
description: 'label for poll answer No',
},
abstention: {
id: 'app.poll.answer.abstention',
description: 'label for poll answer Abstention',
},
a: {
id: 'app.poll.answer.a',
description: 'label for poll answer A',
},
b: {
id: 'app.poll.answer.b',
description: 'label for poll answer B',
},
c: {
id: 'app.poll.answer.c',
description: 'label for poll answer C',
},
d: {
id: 'app.poll.answer.d',
description: 'label for poll answer D',
},
e: {
id: 'app.poll.answer.e',
description: 'label for poll answer E',
},
};
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 checkPollAnswersMatchFormat = (listOfAnswers, formattedLabels) => listOfAnswers.reduce(
(acc, answer) => acc && (formattedLabels.includes(answer.key[0].toLowerCase())
|| formattedLabels.includes(answer.key[0].toUpperCase())), true,
);
const getFormattedAnswerValue = (answerText) => {
// Remove the Letter from the beginning and the following sign, if any, like so:
// "A- the answer is" -> Remove "A-" -> "the answer is"
const listOfForbiddenSignsToStart = ['.', ':', '-'];
const newText = answerText.slice(1).trim();
if (listOfForbiddenSignsToStart.includes(newText[0])) {
return newText.slice(1).trim();
}
return newText;
};
const getAlphabetList = () => Array.from(Array(26))
.map((e, i) => i + 65).map((x) => String.fromCharCode(x));
const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => {
let responded = 0;
let resultString = '';
let optionsString = '';
const alphabetLabels = getAlphabetList();
const isPollAnswerMatchFormat = !isDefaultPoll
? checkPollAnswersMatchFormat(answers, alphabetLabels)
: false;
answers.map((item) => {
responded += item.numVotes;
return item;
}).forEach((item, index) => {
const numResponded = responded === numRespondents ? numRespondents : responded;
const pct = Math.round((item.numVotes / numResponded) * 100);
const pctBars = '|'.repeat((pct * MAX_POLL_RESULT_BARS) / 100);
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
if (isDefaultPoll) {
const translatedKey = pollAnswerIds[item.key.toLowerCase()]
? intl.formatMessage(pollAnswerIds[item.key.toLowerCase()])
: item.key;
resultString += `${translatedKey}: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`;
} else {
if (isPollAnswerMatchFormat) {
resultString += `${alphabetLabels[index]}`;
const formattedAnswerValue = getFormattedAnswerValue(item.key);
optionsString += `${alphabetLabels[index]}: ${formattedAnswerValue}\n`;
} else {
resultString += `${item.id + 1}`;
optionsString += `${item.id + 1}: ${item.key}\n`;
}
resultString += `: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`;
}
});
return { resultString, optionsString };
};
const isDefaultPoll = (pollType) => pollType !== pollTypes.Custom
&& pollType !== pollTypes.Response;
const getPollResultString = (pollResultData, intl) => {
const formatBoldBlack = (s) => s.bold().fontcolor('black');
const sanitize = (value) => escapeHtml(value);
const { answers, numRespondents, questionType } = pollResultData;
const ísDefault = isDefaultPoll(questionType);
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');
const ynPoll = contentString.replace(/\n/g, '').match(ynOptionsRegex) || [];
return ynPoll;
};
const matchYesNoAbstentionPoll = (yesValue, noValue, abstentionValue, contentString) => {
const ynaPollString = `(${yesValue}\\s*\\/\\s*${noValue}\\s*\\/\\s*${abstentionValue})|(${yesValue}\\s*\\/\\s*${abstentionValue}\\s*\\/\\s*${noValue})|(${abstentionValue}\\s*\\/\\s*${yesValue}\\s*\\/\\s*${noValue})|(${abstentionValue}\\s*\\/\\s*${noValue}\\s*\\/\\s*${yesValue})|(${noValue}\\s*\\/\\s*${yesValue}\\s*\\/\\s*${abstentionValue})|(${noValue}\\s*\\/\\s*${abstentionValue}\\s*\\/\\s*${yesValue})`;
const ynaOptionsRegex = new RegExp(ynaPollString, 'gi');
const ynaPoll = contentString.replace(/\n/g, '').match(ynaOptionsRegex) || [];
return ynaPoll;
};
const matchTrueFalsePoll = (trueValue, falseValue, contentString) => {
const tfPollString = `(${trueValue}\\s*\\/\\s*${falseValue})|(${falseValue}\\s*\\/\\s*${trueValue})`;
const tgOptionsRegex = new RegExp(tfPollString, 'gi');
const tfPoll = contentString.match(tgOptionsRegex) || [];
return tfPoll;
};
const checkPollType = (
type,
optList,
yesValue,
noValue,
abstentionValue,
trueValue,
falseValue,
) => {
let _type = type;
let pollString = '';
let defaultMatch = null;
let isDefault = null;
switch (_type) {
case pollTypes.Letter:
pollString = optList.map((x) => x.val.toUpperCase()).sort().join('');
defaultMatch = pollString.match(/^(ABCDEF)|(ABCDE)|(ABCD)|(ABC)|(AB)$/gi);
isDefault = defaultMatch && pollString.length === defaultMatch[0].length;
_type = isDefault ? `${_type}${defaultMatch[0].length}` : pollTypes.Custom;
break;
case pollTypes.TrueFalse:
pollString = optList.map((x) => x.val).join('/');
defaultMatch = matchTrueFalsePoll(trueValue, falseValue, pollString);
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
if (!isDefault) _type = pollTypes.Custom;
break;
case pollTypes.YesNoAbstention:
pollString = optList.map((x) => x.val).join('/');
defaultMatch = matchYesNoAbstentionPoll(yesValue, noValue, abstentionValue, pollString);
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
if (!isDefault) {
// also try to match only yes/no
defaultMatch = matchYesNoPoll(yesValue, noValue, pollString);
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
_type = isDefault ? pollTypes.YesNo : _type = pollTypes.Custom;
}
break;
default:
break;
}
return _type;
};
/**
*
* @param {String} input
*/
const validateInput = (input) => {
let _input = input;
while (/^\s/.test(_input)) _input = _input.substring(1);
return _input;
};
/**
*
* @param {String} input
*/
const removeEmptyLineSpaces = (input) => {
const filteredInput = input.split('\n').filter((val) => val.trim() !== '');
return filteredInput;
};
/**
*
* @param {String|Array} questionAndOptions
*/
const getSplittedQuestionAndOptions = (questionAndOptions) => {
const inputList = Array.isArray(questionAndOptions)
? questionAndOptions
: questionAndOptions.split('\n').filter((val) => val !== '');
const splittedQuestion = inputList.length > 0 ? inputList[0] : questionAndOptions;
const optionsList = inputList.slice(1);
optionsList.forEach((val, i) => { optionsList[i] = { val }; });
return {
splittedQuestion,
optionsList,
};
};
export default {
pollTypes,
currentPoll: () => CurrentPoll.findOne({ meetingId: Auth.meetingID }),
pollAnswerIds,
POLL_AVATAR_COLOR,
isDefaultPoll,
getPollResultString,
matchYesNoPoll,
matchYesNoAbstentionPoll,
matchTrueFalsePoll,
checkPollType,
validateInput,
removeEmptyLineSpaces,
getSplittedQuestionAndOptions,
};