mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
added escape support on matching text
This commit is contained in:
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
jsdom:
|
jsdom:
|
||||||
specifier: ^25.0.0
|
specifier: ^25.0.0
|
||||||
version: 25.0.1
|
version: 25.0.1
|
||||||
|
marked-katex-extension:
|
||||||
|
specifier: ^5.1.4
|
||||||
|
version: 5.1.4(katex@0.16.20)(marked@14.1.4)
|
||||||
next:
|
next:
|
||||||
specifier: ^15.0.2
|
specifier: ^15.0.2
|
||||||
version: 15.1.0(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 15.1.0(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@@ -1105,6 +1108,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
commander@8.3.0:
|
||||||
|
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
@@ -1833,6 +1840,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
|
|
||||||
|
katex@0.16.20:
|
||||||
|
resolution: {integrity: sha512-jjuLaMGD/7P8jUTpdKhA9IoqnH+yMFB3sdAFtq5QdAqeP2PjiSbnC3EaguKPNtv6dXXanHxp1ckwvF4a86LBig==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
@@ -1881,6 +1892,12 @@ packages:
|
|||||||
magic-string@0.30.17:
|
magic-string@0.30.17:
|
||||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||||
|
|
||||||
|
marked-katex-extension@5.1.4:
|
||||||
|
resolution: {integrity: sha512-GQOio4vCp0laxB1IY+2oNVo5nbn82yWMDP/jILRYHmyu2WXMVlXCB+krq2/U2fQn+V9j8aqDmnNdrsgqG2AkGQ==}
|
||||||
|
peerDependencies:
|
||||||
|
katex: '>=0.16 <0.17'
|
||||||
|
marked: '>=4 <16'
|
||||||
|
|
||||||
marked@14.1.4:
|
marked@14.1.4:
|
||||||
resolution: {integrity: sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==}
|
resolution: {integrity: sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
@@ -3647,6 +3664,8 @@ snapshots:
|
|||||||
|
|
||||||
commander@4.1.1: {}
|
commander@4.1.1: {}
|
||||||
|
|
||||||
|
commander@8.3.0: {}
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
@@ -4570,6 +4589,10 @@ snapshots:
|
|||||||
object.assign: 4.1.5
|
object.assign: 4.1.5
|
||||||
object.values: 1.2.0
|
object.values: 1.2.0
|
||||||
|
|
||||||
|
katex@0.16.20:
|
||||||
|
dependencies:
|
||||||
|
commander: 8.3.0
|
||||||
|
|
||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
@@ -4613,6 +4636,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
marked-katex-extension@5.1.4(katex@0.16.20)(marked@14.1.4):
|
||||||
|
dependencies:
|
||||||
|
katex: 0.16.20
|
||||||
|
marked: 14.1.4
|
||||||
|
|
||||||
marked@14.1.4: {}
|
marked@14.1.4: {}
|
||||||
|
|
||||||
math-intrinsics@1.0.0: {}
|
math-intrinsics@1.0.0: {}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
QuestionType,
|
QuestionType,
|
||||||
} from "@/models/local/quiz/localQuizQuestion";
|
} from "@/models/local/quiz/localQuizQuestion";
|
||||||
import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
|
import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
|
||||||
|
import { escapeMatchingText } from "@/services/utils/questionHtmlUtils";
|
||||||
|
|
||||||
export default function QuizPreview({
|
export default function QuizPreview({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -78,6 +79,8 @@ export default function QuizPreview({
|
|||||||
|
|
||||||
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
||||||
const [settings] = useLocalCourseSettingsQuery();
|
const [settings] = useLocalCourseSettingsQuery();
|
||||||
|
|
||||||
|
question.answers.map(a => console.log(escapeMatchingText(a.text)))
|
||||||
return (
|
return (
|
||||||
<div className="rounded bg-slate-900 px-2">
|
<div className="rounded bg-slate-900 px-2">
|
||||||
<div className="flex flex-row justify-between text-slate-400">
|
<div className="flex flex-row justify-between text-slate-400">
|
||||||
@@ -89,7 +92,9 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className="ms-4 mb-2 markdownPreview"
|
className="ms-4 mb-2 markdownPreview"
|
||||||
dangerouslySetInnerHTML={{ __html: markdownToHTMLSafe(question.text, settings) }}
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: markdownToHTMLSafe(question.text, settings),
|
||||||
|
}}
|
||||||
></div>
|
></div>
|
||||||
{question.questionType === QuestionType.MATCHING && (
|
{question.questionType === QuestionType.MATCHING && (
|
||||||
<div>
|
<div>
|
||||||
@@ -98,8 +103,10 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
|||||||
key={JSON.stringify(answer)}
|
key={JSON.stringify(answer)}
|
||||||
className="mx-3 mb-1 bg-dark rounded border border-slate-600 flex flex-row"
|
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="text-right my-auto flex-1 pe-3">
|
||||||
<div className="">{answer.matchedText}</div>
|
{escapeMatchingText(answer.text)}
|
||||||
|
</div>
|
||||||
|
<div className=" flex-1">{answer.matchedText}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{question.matchDistractors.map((distractor) => (
|
{question.matchDistractors.map((distractor) => (
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { QuestionType } from "../localQuizQuestion";
|
import { QuestionType } from "../localQuizQuestion";
|
||||||
import { LocalQuizQuestionAnswer } from "../localQuizQuestionAnswer";
|
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 = {
|
export const quizQuestionAnswerMarkdownUtils = {
|
||||||
// getHtmlText(): string {
|
// getHtmlText(): string {
|
||||||
// return MarkdownService.render(this.text);
|
// return MarkdownService.render(this.text);
|
||||||
@@ -10,17 +22,7 @@ export const quizQuestionAnswerMarkdownUtils = {
|
|||||||
const isCorrect = input.startsWith("*") || input[1] === "*";
|
const isCorrect = input.startsWith("*") || input[1] === "*";
|
||||||
|
|
||||||
if (questionType === QuestionType.MATCHING) {
|
if (questionType === QuestionType.MATCHING) {
|
||||||
const matchingPattern = /^\^ ?/;
|
return parseMatchingAnswer(input);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startingQuestionPattern = /^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^ /;
|
const startingQuestionPattern = /^(\*?[a-z]?\))|\[\s*\]|\[\*\]|\^ /;
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ const getQuestionType = (
|
|||||||
return QuestionType.SHORT_ANSWER;
|
return QuestionType.SHORT_ANSWER;
|
||||||
if (
|
if (
|
||||||
linesWithoutPoints[linesWithoutPoints.length - 1].toLowerCase().trim() ===
|
linesWithoutPoints[linesWithoutPoints.length - 1].toLowerCase().trim() ===
|
||||||
"short_answer="
|
"short_answer="
|
||||||
)
|
)
|
||||||
return QuestionType.SHORT_ANSWER_WITH_ANSWERS;
|
return QuestionType.SHORT_ANSWER_WITH_ANSWERS;
|
||||||
|
|
||||||
const answerLines = getAnswerStringsWithMultilineSupport(
|
const answerLines = getAnswerStringsWithMultilineSupport(
|
||||||
linesWithoutPoints,
|
linesWithoutPoints,
|
||||||
@@ -102,7 +102,11 @@ const getAnswers = (
|
|||||||
questionIndex: number,
|
questionIndex: number,
|
||||||
questionType: string
|
questionType: string
|
||||||
): LocalQuizQuestionAnswer[] => {
|
): 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(
|
const answerLines = getAnswerStringsWithMultilineSupport(
|
||||||
linesWithoutPoints,
|
linesWithoutPoints,
|
||||||
questionIndex
|
questionIndex
|
||||||
|
|||||||
@@ -131,4 +131,36 @@ Match the following terms & definitions
|
|||||||
"^ statement - a single command to be executed\n^ - this is the distractor"
|
"^ 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"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,16 +11,24 @@ import {
|
|||||||
} from "@/models/local/quiz/localQuizQuestion";
|
} from "@/models/local/quiz/localQuizQuestion";
|
||||||
import { CanvasQuizQuestion } from "@/models/canvas/quizzes/canvasQuizQuestionModel";
|
import { CanvasQuizQuestion } from "@/models/canvas/quizzes/canvasQuizQuestionModel";
|
||||||
import { LocalCourseSettings } from "@/models/local/localCourseSettings";
|
import { LocalCourseSettings } from "@/models/local/localCourseSettings";
|
||||||
|
import { escapeMatchingText } from "../utils/questionHtmlUtils";
|
||||||
|
|
||||||
|
|
||||||
export const getAnswers = (
|
export const getAnswers = (
|
||||||
question: LocalQuizQuestion,
|
question: LocalQuizQuestion,
|
||||||
settings: LocalCourseSettings
|
settings: LocalCourseSettings
|
||||||
) => {
|
) => {
|
||||||
if (question.questionType === QuestionType.MATCHING)
|
if (question.questionType === QuestionType.MATCHING)
|
||||||
return question.answers.map((a) => ({
|
return question.answers.map((a) => {
|
||||||
answer_match_left: a.text,
|
const text =
|
||||||
answer_match_right: a.matchedText,
|
question.questionType === QuestionType.MATCHING
|
||||||
}));
|
? escapeMatchingText(a.text)
|
||||||
|
: a.text;
|
||||||
|
return {
|
||||||
|
answer_match_left: text,
|
||||||
|
answer_match_right: a.matchedText,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return question.answers.map((answer) => ({
|
return question.answers.map((answer) => ({
|
||||||
answer_html: markdownToHTMLSafe(answer.text, settings),
|
answer_html: markdownToHTMLSafe(answer.text, settings),
|
||||||
@@ -29,11 +37,9 @@ export const getAnswers = (
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getQuestionType = (
|
export const getQuestionType = (question: LocalQuizQuestion) => {
|
||||||
question: LocalQuizQuestion
|
|
||||||
) => {
|
|
||||||
return `${question.questionType.replace("=", "")}_question`;
|
return `${question.questionType.replace("=", "")}_question`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const createQuestionOnly = async (
|
const createQuestionOnly = async (
|
||||||
canvasCourseId: number,
|
canvasCourseId: number,
|
||||||
@@ -45,6 +51,7 @@ const createQuestionOnly = async (
|
|||||||
console.log("Creating individual question"); //, question);
|
console.log("Creating individual question"); //, question);
|
||||||
|
|
||||||
const url = `${canvasApi}/courses/${canvasCourseId}/quizzes/${canvasQuizId}/questions`;
|
const url = `${canvasApi}/courses/${canvasCourseId}/quizzes/${canvasQuizId}/questions`;
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
question: {
|
question: {
|
||||||
question_text: markdownToHTMLSafe(question.text, settings),
|
question_text: markdownToHTMLSafe(question.text, settings),
|
||||||
@@ -179,7 +186,12 @@ export const canvasQuizService = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { data: canvasQuiz } = await axiosClient.post<CanvasQuiz>(url, body);
|
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;
|
return canvasQuiz.id;
|
||||||
},
|
},
|
||||||
async delete(canvasCourseId: number, canvasQuizId: number) {
|
async delete(canvasCourseId: number, canvasQuizId: number) {
|
||||||
|
|||||||
4
src/services/utils/questionHtmlUtils.ts
Normal file
4
src/services/utils/questionHtmlUtils.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export function escapeMatchingText(input: string){
|
||||||
|
return input.replaceAll("\\-", "-");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user