UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

181 lines (180 loc) • 7.03 kB
/** @jsx jsx */ import { useLayoutEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import striptags from 'striptags'; import { htmlDecode } from 'htmlencode'; import { Text } from '@instructure/ui-text'; import { jsx } from '@instructure/emotion'; import XRegExp from 'xregexp'; import t from '@instructure/quiz-i18n/format-message'; import { IconWarningSolid } from '@instructure/ui-icons'; import { View } from '@instructure/ui-view'; // XRegExp supports unicode so we can use the codepoints for all {L}etters and {N}umbers // See "Unicode Categories" here https://www.regular-expressions.info/unicode.html var WORD_REGEX = XRegExp("[-_'\\p{L}\\p{N}]+", 'gu'); export var countWords = function(content) { if (!content) { return 0; } var matches = content.match(WORD_REGEX); return matches ? matches.length : 0; }; export var getRceEssayContent = function(essay) { return htmlDecode(striptags(essay)); }; export var showWordCount = function(wordCount, rce, isEditing) { if (!wordCount) return false; if (rce && isEditing) return false; return true; }; function WordCountComponent(param) { var essay = param.essay, rce = param.rce, isEditing = param.isEditing, wordCount = param.wordCount, wordLimitEnabled = param.wordLimitEnabled, wordLimitMin = param.wordLimitMin, wordLimitMax = param.wordLimitMax, notifyScreenreader = param.notifyScreenreader; var isFirstRender = useRef(true); var prevPropsRef = useRef({ essay: essay, rce: rce, wordLimitMin: wordLimitMin, wordLimitMax: wordLimitMax, wordLimitEnabled: wordLimitEnabled }); var count = function() { var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : { essay: essay, rce: rce }; return countWords(props.rce ? getRceEssayContent(props.essay) : props.essay); }; var isOverWordLimit = function() { var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : { wordLimitEnabled: wordLimitEnabled, wordLimitMax: wordLimitMax, essay: essay, rce: rce }; return props.wordLimitEnabled && props.wordLimitMax !== null && count(props) > props.wordLimitMax; }; var getErrorMessage = function() { var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : { wordLimitMin: wordLimitMin, wordLimitEnabled: wordLimitEnabled, wordLimitMax: wordLimitMax, essay: essay, rce: rce }; var minError = props.wordLimitMin && count(props) < props.wordLimitMin; var maxError = isOverWordLimit(props); var countError = props.wordLimitEnabled && (minError || maxError); var minErrorText = t("{wordLimitMin, plural,\n =1 {Must use at least # word}\n other {Must use at least # words}}", { wordLimitMin: props.wordLimitMin }); var maxErrorText = t('Maximum words exceeded!'); var errorText = count(props) < props.wordLimitMin ? minErrorText : maxErrorText; return countError ? errorText : ''; }; useLayoutEffect(function() { if (isFirstRender.current) { isFirstRender.current = false; prevPropsRef.current = { essay: essay, rce: rce, wordLimitMin: wordLimitMin, wordLimitMax: wordLimitMax, wordLimitEnabled: wordLimitEnabled }; return; } var currentProps = { essay: essay, rce: rce, wordLimitMin: wordLimitMin, wordLimitMax: wordLimitMax, wordLimitEnabled: wordLimitEnabled }; var prevProps = prevPropsRef.current; var errorMessage = getErrorMessage(prevProps); var nextErrorMessage = getErrorMessage(currentProps); var errorMessageChanged = errorMessage !== nextErrorMessage; var overLimit = nextErrorMessage && isOverWordLimit(currentProps); var wordCountChangedAndOverLimit = overLimit && count(prevProps) !== count(currentProps); if (notifyScreenreader && (errorMessageChanged || wordCountChangedAndOverLimit) && nextErrorMessage) { notifyScreenreader(nextErrorMessage); } prevPropsRef.current = currentProps; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ essay, wordLimitMin, wordLimitMax, wordLimitEnabled, rce ]); var getLimitsText = function() { if (wordLimitEnabled) { var limits = []; if (wordLimitMin) { limits.push(t('{wordLimitMin} min', { wordLimitMin: wordLimitMin })); } if (wordLimitMax) { limits.push(t('{wordLimitMax} max', { wordLimitMax: wordLimitMax })); } var limit = limits.join(' / '); return /*#__PURE__*/ jsx(Text, { color: "secondary", size: "small" }, limit, ' ', /*#__PURE__*/ jsx(Text, { color: "primary", size: "small" }, t('(word limit)'))); } }; var renderWordCount = function() { var maxError = isOverWordLimit(); return /*#__PURE__*/ jsx(View, { as: "span", padding: "0 space12 0 0" }, /*#__PURE__*/ jsx(Text, { size: "small", color: wordLimitEnabled && maxError ? 'danger' : 'primary', weight: "bold" }, t('{wordcount, plural, =1 {# word} other {# words}}', { wordcount: count() }))); }; var maxError = isOverWordLimit(); return /*#__PURE__*/ jsx(View, { className: "fs-mask", textAlign: "end" }, showWordCount(wordCount, rce, isEditing) && renderWordCount(), getLimitsText(), /*#__PURE__*/ jsx(View, null, /*#__PURE__*/ jsx(Text, { color: maxError ? 'danger' : null, size: "small" }, maxError && /*#__PURE__*/ jsx(IconWarningSolid, null), ' ', /*#__PURE__*/ jsx(View, { display: "inline-block", margin: "0 0 0 xxx-small" }, getErrorMessage())))); } WordCountComponent.displayName = 'WordCount'; WordCountComponent.componentId = 'QuizzesWordCount'; WordCountComponent.propTypes = { essay: PropTypes.string, rce: PropTypes.bool, isEditing: PropTypes.bool, wordCount: PropTypes.bool, wordLimitEnabled: PropTypes.bool, wordLimitMin: PropTypes.number, wordLimitMax: PropTypes.number, notifyScreenreader: PropTypes.func }; WordCountComponent.defaultProps = { essay: void 0, rce: void 0, isEditing: true, wordCount: void 0, wordLimitEnabled: void 0, wordLimitMin: void 0, wordLimitMax: void 0, notifyScreenreader: void 0 }; export var WordCount = WordCountComponent;