Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 7x 22x 22x 22x 22x 7x 10x 1x 1x 1x 1x 10x 1x 1x 1x 1x 7x 12x 12x 12x 12x 12x 7x 10x 10x 10x 10x 10x 10x 12x 12x 10x 18x | import { Box, Typography } from "@mui/material";
import { alpha, useTheme } from "@mui/material/styles";
import React, { memo, useEffect } from "react";
import TinyMCETextEditorReadOnly from "@/utils/components/TinyMCETextEditorReadOnly";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import { sanitizeRichText } from "@/utils/helpers/sanitizeHtml";
import {
inlineBlankZoneColors,
questionCardSurfaceBg,
questionItemNumberSx,
questionStemTypographySx,
} from "@/utils/components/question-bank/question-display/utils/questionCardStyles";
import { extractAnswerContent } from "@/utils/components/question-bank/question-display/utils/questionAnswerUtils";
const unwrapBlock = (html) => {
const el = document.createElement("div");
el.innerHTML = html;
Eif (el.children.length === 1 && el.children[0].tagName === "P") {
return el.children[0].innerHTML;
}
return html;
};
const injectMathLiveCSS = () => {
if (!document.querySelector('link[href*="mathlive-fonts"]')) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.jsdelivr.net/npm/mathlive@0.108.2/mathlive-fonts.css";
document.head.appendChild(link);
}
if (!document.querySelector('link[href*="mathlive-static"]')) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.jsdelivr.net/npm/mathlive@0.108.2/mathlive-static.css";
document.head.appendChild(link);
}
};
const buildDropZoneSpan = (index, showAnswers, blank, theme) => {
const answerContent = extractAnswerContent(blank);
const isAnswer = showAnswers && answerContent;
const colors = inlineBlankZoneColors(theme, isAnswer);
const answerHtml = isAnswer ? unwrapBlock(answerContent) : "Drop here";
return (
`<span style="display:inline-block;padding:2px 16px;margin:0 10px;border-radius:8px;` +
`border:2px ${colors.borderStyle} ${colors.borderColor};background-color:${colors.backgroundColor};` +
`vertical-align:middle;white-space:nowrap;line-height:normal;font-size:inherit;` +
`font-weight:${isAnswer ? 500 : 400};color:${colors.color};">${answerHtml}</span>`
);
};
const DragDropViewOnly = ({ question, showAnswers, itemNumber }) => {
const theme = useTheme();
const cardBg = questionCardSurfaceBg(theme);
useEffect(() => {
injectMathLiveCSS();
}, []);
let blankIndex = 0;
// Modified by Allan Ninal — optional chaining here. A question saved without a
// correct answer threw on `.answers`, and because QuestionDisplayRenderer draws
// these inside question LISTS the throw took the whole list down rather than the
// one bad card. Every other renderer in this tree already reads it this way
// (MultipleChoiceOption, CheckboxOption, NonEmptyGraph); these two blank-based
// ones were the exception. extractAnswerContent maps undefined to "", which is
// exactly the unanswered-blank case it already handles.
const content = question.question.replace(/__blank__/g, () => {
const idx = blankIndex++;
return buildDropZoneSpan(idx, showAnswers, question.correctAnswer?.answers?.[idx], theme);
});
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 3, width: "100%" }}>
<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1 }}>
{itemNumber != null && itemNumber !== false && (
<Typography component="span" sx={{ ...questionItemNumberSx, flexShrink: 0 }}>
{itemNumber}.
</Typography>
)}
<Box sx={{ flex: 1, ...questionStemTypographySx }}>
<TinyMCETextEditorReadOnly content={content} backgroundColor={cardBg} />
</Box>
</Box>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "center",
gap: 1,
p: 1.5,
bgcolor: alpha(theme.palette.grey[500], 0.06),
borderRadius: 1.5,
border: `1px solid ${alpha(theme.palette.grey[500], 0.12)}`,
ml: itemNumber ? 3.5 : 0,
}}
>
{/* Same reason as the correctAnswer guard above: a question with no
choices must lose its chips, not the list it is rendered in. */}
{(question.choices ?? []).map((choiceObj, index) => (
<Box
key={index}
sx={{
display: "flex",
alignItems: "center",
gap: 0.5,
px: 1.25,
py: 0.65,
bgcolor: alpha(theme.palette.primary.main, 0.12),
color: "primary.dark",
borderRadius: 1.5,
border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
}}
>
<DragIndicatorIcon sx={{ fontSize: 18, opacity: 0.8, flexShrink: 0 }} />
<Box
dangerouslySetInnerHTML={{ __html: sanitizeRichText(unwrapBlock(choiceObj.text)) }}
sx={{
fontWeight: 500,
fontSize: "0.9375rem",
lineHeight: "normal",
"& p": { margin: 0, padding: 0 },
"& .mfe-formula": { color: "inherit", bgcolor: "transparent" },
}}
/>
</Box>
))}
</Box>
</Box>
);
};
export default memo(DragDropViewOnly);
|