added escape support on matching text

This commit is contained in:
2025-01-22 08:59:53 -07:00
parent d581569c7a
commit 4005c85d60
7 changed files with 117 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ import {
QuestionType,
} from "@/models/local/quiz/localQuizQuestion";
import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
import { escapeMatchingText } from "@/services/utils/questionHtmlUtils";
export default function QuizPreview({
moduleName,
@@ -78,6 +79,8 @@ export default function QuizPreview({
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
const [settings] = useLocalCourseSettingsQuery();
question.answers.map(a => console.log(escapeMatchingText(a.text)))
return (
<div className="rounded bg-slate-900 px-2">
<div className="flex flex-row justify-between text-slate-400">
@@ -89,7 +92,9 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
<div
className="ms-4 mb-2 markdownPreview"
dangerouslySetInnerHTML={{ __html: markdownToHTMLSafe(question.text, settings) }}
dangerouslySetInnerHTML={{
__html: markdownToHTMLSafe(question.text, settings),
}}
></div>
{question.questionType === QuestionType.MATCHING && (
<div>
@@ -98,8 +103,10 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
key={JSON.stringify(answer)}
className="mx-3 mb-1 bg-dark rounded border border-slate-600 flex flex-row"
>
<div className="text-right my-auto">{answer.text} - </div>
<div className="">{answer.matchedText}</div>
<div className="text-right my-auto flex-1 pe-3">
{escapeMatchingText(answer.text)}
</div>
<div className=" flex-1">{answer.matchedText}</div>
</div>
))}
{question.matchDistractors.map((distractor) => (

View File

@@ -1,6 +1,18 @@
import { QuestionType } from "../localQuizQuestion";
import { LocalQuizQuestionAnswer } from "../localQuizQuestionAnswer";
const parseMatchingAnswer = (input: string) => {
const matchingPattern = /^\^?/;
const textWithoutMatchDelimiter = input.replace(matchingPattern, "");
const [text, ...matchedParts] = textWithoutMatchDelimiter.split(" - ");
const answer: LocalQuizQuestionAnswer = {
correct: true,
text: text.trim(),
matchedText: matchedParts.join("-").trim(),
};
return answer;
};
export const quizQuestionAnswerMarkdownUtils = {
// getHtmlText(): string {
// return MarkdownService.render(this.text);
@@ -10,17 +22,7 @@ export const quizQuestionAnswerMarkdownUtils = {
const isCorrect = input.startsWith("*") || input[1] === "*";
if (questionType === QuestionType.MATCHING) {
const matchingPattern = /^\^ ?/;
const textWithoutMatchDelimiter = input
.replace(matchingPattern, "")
.trim();
const [text, ...matchedParts] = textWithoutMatchDelimiter.split("-");
const answer: LocalQuizQuestionAnswer = {
correct: true,
text: text.trim(),
matchedText: matchedParts.join("-").trim(),
};
return answer;
return parseMatchingAnswer(input);
}
const startingQuestionPattern = /^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^ /;

View File

@@ -69,11 +69,11 @@ const getQuestionType = (
"short_answer"
)
return QuestionType.SHORT_ANSWER;
if (
if (
linesWithoutPoints[linesWithoutPoints.length - 1].toLowerCase().trim() ===
"short_answer="
)
return QuestionType.SHORT_ANSWER_WITH_ANSWERS;
"short_answer="
)
return QuestionType.SHORT_ANSWER_WITH_ANSWERS;
const answerLines = getAnswerStringsWithMultilineSupport(
linesWithoutPoints,
@@ -102,7 +102,11 @@ const getAnswers = (
questionIndex: number,
questionType: string
): LocalQuizQuestionAnswer[] => {
if (questionType == QuestionType.SHORT_ANSWER_WITH_ANSWERS) linesWithoutPoints = linesWithoutPoints.slice(0, linesWithoutPoints.length - 1);
if (questionType == QuestionType.SHORT_ANSWER_WITH_ANSWERS)
linesWithoutPoints = linesWithoutPoints.slice(
0,
linesWithoutPoints.length - 1
);
const answerLines = getAnswerStringsWithMultilineSupport(
linesWithoutPoints,
questionIndex

View File

@@ -131,4 +131,36 @@ Match the following terms & definitions
"^ statement - a single command to be executed\n^ - this is the distractor"
);
});
it("can escape - characters", () => {
const name = "Test Quiz"
const rawMarkdownQuiz = `
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 08/21/2023 23:59:00
LockAt: 08/21/2023 23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description:
---
Match the following terms & definitions
^ git add \-\-all - start tracking all files in the current directory and subdirectories
`;
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
const firstQuestion = quiz.questions[0];
expect(firstQuestion.answers[0].text).toBe("git add --all");
expect(firstQuestion.answers[0].matchedText).toBe("start tracking all files in the current directory and subdirectories");
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz);
expect(quizMarkdown).toContain(
"^ git add \-\-all - start tracking all files in the current directory and subdirectories"
);
});
});

View File

@@ -11,16 +11,24 @@ import {
} from "@/models/local/quiz/localQuizQuestion";
import { CanvasQuizQuestion } from "@/models/canvas/quizzes/canvasQuizQuestionModel";
import { LocalCourseSettings } from "@/models/local/localCourseSettings";
import { escapeMatchingText } from "../utils/questionHtmlUtils";
export const getAnswers = (
question: LocalQuizQuestion,
settings: LocalCourseSettings
) => {
if (question.questionType === QuestionType.MATCHING)
return question.answers.map((a) => ({
answer_match_left: a.text,
answer_match_right: a.matchedText,
}));
return question.answers.map((a) => {
const text =
question.questionType === QuestionType.MATCHING
? escapeMatchingText(a.text)
: a.text;
return {
answer_match_left: text,
answer_match_right: a.matchedText,
};
});
return question.answers.map((answer) => ({
answer_html: markdownToHTMLSafe(answer.text, settings),
@@ -29,11 +37,9 @@ export const getAnswers = (
}));
};
export const getQuestionType = (
question: LocalQuizQuestion
) => {
export const getQuestionType = (question: LocalQuizQuestion) => {
return `${question.questionType.replace("=", "")}_question`;
}
};
const createQuestionOnly = async (
canvasCourseId: number,
@@ -45,6 +51,7 @@ const createQuestionOnly = async (
console.log("Creating individual question"); //, question);
const url = `${canvasApi}/courses/${canvasCourseId}/quizzes/${canvasQuizId}/questions`;
const body = {
question: {
question_text: markdownToHTMLSafe(question.text, settings),
@@ -179,7 +186,12 @@ export const canvasQuizService = {
};
const { data: canvasQuiz } = await axiosClient.post<CanvasQuiz>(url, body);
await createQuizQuestions(canvasCourseId, canvasQuiz.id, localQuiz, settings);
await createQuizQuestions(
canvasCourseId,
canvasQuiz.id,
localQuiz,
settings
);
return canvasQuiz.id;
},
async delete(canvasCourseId: number, canvasQuizId: number) {

View File

@@ -0,0 +1,4 @@
export function escapeMatchingText(input: string){
return input.replaceAll("\\-", "-");
}