@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
181 lines (180 loc) • 7.03 kB
JavaScript
/** @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;