mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
adding feedback
This commit is contained in:
@@ -19,15 +19,15 @@ services:
|
|||||||
- ~/projects/facultyFiles:/app/public/images/facultyFiles
|
- ~/projects/facultyFiles:/app/public/images/facultyFiles
|
||||||
|
|
||||||
|
|
||||||
redis:
|
# redis:
|
||||||
image: redis
|
# image: redis
|
||||||
container_name: redis
|
# container_name: redis
|
||||||
volumes:
|
# volumes:
|
||||||
- redis-data:/data
|
# - redis-data:/data
|
||||||
restart: unless-stopped
|
# restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
# volumes:
|
||||||
redis-data:
|
# redis-data:
|
||||||
|
|
||||||
# https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
|
# https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
|
||||||
# https://github.com/jonas-merkle/container-cloudflare-tunnel
|
# https://github.com/jonas-merkle/container-cloudflare-tunnel
|
||||||
|
|||||||
@@ -70,7 +70,29 @@ this is a matching question
|
|||||||
^ left answer - right dropdown
|
^ left answer - right dropdown
|
||||||
^ other thing - another option
|
^ other thing - another option
|
||||||
^ - distractor
|
^ - distractor
|
||||||
^ - other distractor`;
|
^ - other distractor
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
FEEDBACK EXAMPLE
|
||||||
|
What is 2+3?
|
||||||
|
+ Correct! Good job
|
||||||
|
- Incorrect, try again
|
||||||
|
... This is general feedback shown regardless
|
||||||
|
*a) 4
|
||||||
|
*b) 5
|
||||||
|
c) 6
|
||||||
|
---
|
||||||
|
Points: 2
|
||||||
|
FEEDBACK EXAMPLE
|
||||||
|
Multiline feedback example
|
||||||
|
+
|
||||||
|
Great work!
|
||||||
|
You understand the concept.
|
||||||
|
-
|
||||||
|
Not quite right.
|
||||||
|
Review the material and try again.
|
||||||
|
*a) correct answer
|
||||||
|
b) wrong answer`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EditQuiz({
|
export default function EditQuiz({
|
||||||
|
|||||||
@@ -80,6 +80,42 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MarkdownDisplay markdown={question.text} className="ms-4 mb-2" />
|
<MarkdownDisplay markdown={question.text} className="ms-4 mb-2" />
|
||||||
|
|
||||||
|
{/* Feedback Section */}
|
||||||
|
{(question.correctComments ||
|
||||||
|
question.incorrectComments ||
|
||||||
|
question.neutralComments) && (
|
||||||
|
<div className=" m-2 ps-2 py-1 rounded flex bg-slate-950/50">
|
||||||
|
<div>Feedback</div>
|
||||||
|
<div className="mx-4 space-y-1">
|
||||||
|
{question.correctComments && (
|
||||||
|
<div className="border-l-2 border-green-700 pl-2 py-1">
|
||||||
|
<span className="text-green-500">+ </span>
|
||||||
|
<span className="text-slate-300">
|
||||||
|
{question.correctComments}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{question.incorrectComments && (
|
||||||
|
<div className="border-l-2 border-red-700 pl-2 py-1">
|
||||||
|
<span className="text-red-500">- </span>
|
||||||
|
<span className="text-slate-300">
|
||||||
|
{question.incorrectComments}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{question.neutralComments && (
|
||||||
|
<div className="border-l-2 border-blue-800 pl-2 py-1">
|
||||||
|
<span className="text-blue-500">... </span>
|
||||||
|
<span className="text-slate-300">
|
||||||
|
{question.neutralComments}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{question.questionType === QuestionType.MATCHING && (
|
{question.questionType === QuestionType.MATCHING && (
|
||||||
<div>
|
<div>
|
||||||
{question.answers.map((answer) => (
|
{question.answers.map((answer) => (
|
||||||
|
|||||||
@@ -281,4 +281,298 @@ b) false
|
|||||||
expect(quizHtml).toContain("<mi>x</mi>");
|
expect(quizHtml).toContain("<mi>x</mi>");
|
||||||
expect(quizHtml).not.toContain("x_2");
|
expect(quizHtml).not.toContain("x_2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can parse question with correct feedback", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What is the purpose of a context switch?
|
||||||
|
+ Correct! The context switch is used to change the current process by swapping the registers and other state with a new process
|
||||||
|
*a) To change the current window you are on
|
||||||
|
b) To change the current process's status
|
||||||
|
*c) To swap the current process's registers for a new process's registers
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.correctComments).toBe(
|
||||||
|
"Correct! The context switch is used to change the current process by swapping the registers and other state with a new process"
|
||||||
|
);
|
||||||
|
expect(question.incorrectComments).toBeUndefined();
|
||||||
|
expect(question.neutralComments).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse question with incorrect feedback", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What state does a process need to be in to be able to be scheduled?
|
||||||
|
- Incorrect! A process in ready state can be scheduled
|
||||||
|
*a) Ready
|
||||||
|
b) Running
|
||||||
|
c) Zombie
|
||||||
|
d) Embryo
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.incorrectComments).toBe(
|
||||||
|
"Incorrect! A process in ready state can be scheduled"
|
||||||
|
);
|
||||||
|
expect(question.correctComments).toBeUndefined();
|
||||||
|
expect(question.neutralComments).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse question with correct and incorrect feedback", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What is the purpose of a context switch?
|
||||||
|
+ Correct! The context switch is used to change the current process
|
||||||
|
- Incorrect! The context switch is NOT used to change windows
|
||||||
|
*a) To change the current window you are on
|
||||||
|
b) To change the current process's status
|
||||||
|
*c) To swap the current process's registers for a new process's registers
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.correctComments).toBe(
|
||||||
|
"Correct! The context switch is used to change the current process"
|
||||||
|
);
|
||||||
|
expect(question.incorrectComments).toBe(
|
||||||
|
"Incorrect! The context switch is NOT used to change windows"
|
||||||
|
);
|
||||||
|
expect(question.neutralComments).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse question with neutral feedback", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What is a prime number?
|
||||||
|
... This feedback will be shown regardless of the answer
|
||||||
|
*a) A number divisible only by 1 and itself
|
||||||
|
b) Any odd number
|
||||||
|
c) Any even number
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.neutralComments).toBe(
|
||||||
|
"This feedback will be shown regardless of the answer"
|
||||||
|
);
|
||||||
|
expect(question.correctComments).toBeUndefined();
|
||||||
|
expect(question.incorrectComments).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse question with all three feedback types", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What is the purpose of a context switch?
|
||||||
|
+ Great job! You understand context switching
|
||||||
|
- Try reviewing the material on process management
|
||||||
|
... Context switches are a fundamental operating system concept
|
||||||
|
*a) To change the current window you are on
|
||||||
|
b) To change the current process's status
|
||||||
|
*c) To swap the current process's registers for a new process's registers
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.correctComments).toBe(
|
||||||
|
"Great job! You understand context switching"
|
||||||
|
);
|
||||||
|
expect(question.incorrectComments).toBe(
|
||||||
|
"Try reviewing the material on process management"
|
||||||
|
);
|
||||||
|
expect(question.neutralComments).toBe(
|
||||||
|
"Context switches are a fundamental operating system concept"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse multiline feedback", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 3
|
||||||
|
What is the purpose of a context switch?
|
||||||
|
+ Correct! The context switch is used to change the current process.
|
||||||
|
This is additional information on a new line.
|
||||||
|
- Incorrect! You should review the material.
|
||||||
|
Check your notes on process management.
|
||||||
|
*a) To change the current window you are on
|
||||||
|
b) To change the current process's status
|
||||||
|
*c) To swap the current process's registers for a new process's registers
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.correctComments).toBe(
|
||||||
|
"Correct! The context switch is used to change the current process.\nThis is additional information on a new line."
|
||||||
|
);
|
||||||
|
expect(question.incorrectComments).toBe(
|
||||||
|
"Incorrect! You should review the material.\nCheck your notes on process management."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("feedback can serialize to markdown", () => {
|
||||||
|
const quiz: LocalQuiz = {
|
||||||
|
name: "Test Quiz",
|
||||||
|
description: "quiz description",
|
||||||
|
lockAt: new Date(8640000000000000).toISOString(),
|
||||||
|
dueAt: new Date(8640000000000000).toISOString(),
|
||||||
|
shuffleAnswers: true,
|
||||||
|
oneQuestionAtATime: false,
|
||||||
|
localAssignmentGroupName: "Assignments",
|
||||||
|
allowedAttempts: -1,
|
||||||
|
showCorrectAnswers: false,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
text: "What is the purpose of a context switch?",
|
||||||
|
questionType: QuestionType.MULTIPLE_CHOICE,
|
||||||
|
points: 3,
|
||||||
|
correctComments: "Correct! Good job",
|
||||||
|
incorrectComments: "Incorrect! Try again",
|
||||||
|
neutralComments: "Context switches are important",
|
||||||
|
answers: [
|
||||||
|
{ correct: false, text: "To change the current window you are on" },
|
||||||
|
{ correct: true, text: "To swap registers" },
|
||||||
|
],
|
||||||
|
matchDistractors: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdown = quizMarkdownUtils.toMarkdown(quiz);
|
||||||
|
|
||||||
|
expect(markdown).toContain("+ Correct! Good job");
|
||||||
|
expect(markdown).toContain("- Incorrect! Try again");
|
||||||
|
expect(markdown).toContain("... Context switches are important");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse question with alternative format using ellipsis for general feedback", () => {
|
||||||
|
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: An addition question
|
||||||
|
---
|
||||||
|
Points: 2
|
||||||
|
What is 2+3?
|
||||||
|
... General question feedback.
|
||||||
|
+ Feedback for correct answer.
|
||||||
|
- Feedback for incorrect answer.
|
||||||
|
a) 6
|
||||||
|
b) 1
|
||||||
|
*c) 5
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.text).toBe("What is 2+3?");
|
||||||
|
expect(question.points).toBe(2);
|
||||||
|
expect(question.neutralComments).toBe("General question feedback.");
|
||||||
|
expect(question.correctComments).toBe("Feedback for correct answer.");
|
||||||
|
expect(question.incorrectComments).toBe("Feedback for incorrect answer.");
|
||||||
|
expect(question.answers).toHaveLength(3);
|
||||||
|
expect(question.answers[0].text).toBe("6");
|
||||||
|
expect(question.answers[0].correct).toBe(false);
|
||||||
|
expect(question.answers[1].text).toBe("1");
|
||||||
|
expect(question.answers[1].correct).toBe(false);
|
||||||
|
expect(question.answers[2].text).toBe("5");
|
||||||
|
expect(question.answers[2].correct).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can parse multiline general feedback with ellipsis", () => {
|
||||||
|
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: quiz description
|
||||||
|
---
|
||||||
|
Points: 2
|
||||||
|
What is 2+3?
|
||||||
|
...
|
||||||
|
General question feedback.
|
||||||
|
This continues on multiple lines.
|
||||||
|
+ Feedback for correct answer.
|
||||||
|
- Feedback for incorrect answer.
|
||||||
|
a) 6
|
||||||
|
b) 1
|
||||||
|
*c) 5
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
|
const question = quiz.questions[0];
|
||||||
|
|
||||||
|
expect(question.neutralComments).toBe(
|
||||||
|
"General question feedback.\nThis continues on multiple lines."
|
||||||
|
);
|
||||||
|
expect(question.correctComments).toBe("Feedback for correct answer.");
|
||||||
|
expect(question.incorrectComments).toBe("Feedback for incorrect answer.");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export interface LocalQuizQuestion {
|
|||||||
points: number;
|
points: number;
|
||||||
answers: LocalQuizQuestionAnswer[];
|
answers: LocalQuizQuestionAnswer[];
|
||||||
matchDistractors: string[];
|
matchDistractors: string[];
|
||||||
|
correctComments?: string;
|
||||||
|
incorrectComments?: string;
|
||||||
|
neutralComments?: string;
|
||||||
}
|
}
|
||||||
export const zodLocalQuizQuestion = z.object({
|
export const zodLocalQuizQuestion = z.object({
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
@@ -37,4 +40,7 @@ export const zodLocalQuizQuestion = z.object({
|
|||||||
points: z.number(),
|
points: z.number(),
|
||||||
answers: zodLocalQuizQuestionAnswer.array(),
|
answers: zodLocalQuizQuestionAnswer.array(),
|
||||||
matchDistractors: z.array(z.string()),
|
matchDistractors: z.array(z.string()),
|
||||||
|
correctComments: z.string().optional(),
|
||||||
|
incorrectComments: z.string().optional(),
|
||||||
|
neutralComments: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,98 @@ const _validFirstAnswerDelimiters = [
|
|||||||
];
|
];
|
||||||
const _multipleChoicePrefix = ["a)", "*a)", "*)", ")"];
|
const _multipleChoicePrefix = ["a)", "*a)", "*)", ")"];
|
||||||
const _multipleAnswerPrefix = ["[ ]", "[*]", "[]"];
|
const _multipleAnswerPrefix = ["[ ]", "[*]", "[]"];
|
||||||
|
const _feedbackPrefixes = ["+", "-", "..."];
|
||||||
|
|
||||||
|
const extractFeedback = (
|
||||||
|
linesWithoutPoints: string[]
|
||||||
|
): {
|
||||||
|
correctComments?: string;
|
||||||
|
incorrectComments?: string;
|
||||||
|
neutralComments?: string;
|
||||||
|
linesWithoutFeedback: string[];
|
||||||
|
} => {
|
||||||
|
let correctComments: string | undefined;
|
||||||
|
let incorrectComments: string | undefined;
|
||||||
|
let neutralComments: string | undefined;
|
||||||
|
const linesWithoutFeedback: string[] = [];
|
||||||
|
|
||||||
|
let currentFeedbackType: "+" | "-" | "..." | null = null;
|
||||||
|
let currentFeedbackLines: string[] = [];
|
||||||
|
|
||||||
|
for (const line of linesWithoutPoints) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
|
||||||
|
// Check if this is a new feedback line
|
||||||
|
if (trimmed.startsWith("+ ") || trimmed === "+") {
|
||||||
|
// Save previous feedback if any
|
||||||
|
if (currentFeedbackType && currentFeedbackLines.length > 0) {
|
||||||
|
const feedbackText = currentFeedbackLines.join("\n");
|
||||||
|
if (currentFeedbackType === "+") correctComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "-") incorrectComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "...") neutralComments = feedbackText;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFeedbackType = "+";
|
||||||
|
currentFeedbackLines = trimmed === "+" ? [] : [trimmed.substring(2)]; // Remove "+ " or handle standalone "+"
|
||||||
|
} else if (trimmed.startsWith("- ") || trimmed === "-") {
|
||||||
|
// Save previous feedback if any
|
||||||
|
if (currentFeedbackType && currentFeedbackLines.length > 0) {
|
||||||
|
const feedbackText = currentFeedbackLines.join("\n");
|
||||||
|
if (currentFeedbackType === "+") correctComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "-") incorrectComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "...") neutralComments = feedbackText;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFeedbackType = "-";
|
||||||
|
currentFeedbackLines = trimmed === "-" ? [] : [trimmed.substring(2)]; // Remove "- " or handle standalone "-"
|
||||||
|
} else if (trimmed.startsWith("... ") || trimmed === "...") {
|
||||||
|
// Save previous feedback if any
|
||||||
|
if (currentFeedbackType && currentFeedbackLines.length > 0) {
|
||||||
|
const feedbackText = currentFeedbackLines.join("\n");
|
||||||
|
if (currentFeedbackType === "+") correctComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "-") incorrectComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "...") neutralComments = feedbackText;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFeedbackType = "...";
|
||||||
|
currentFeedbackLines = trimmed === "..." ? [] : [trimmed.substring(4)]; // Remove "... " or handle standalone "..."
|
||||||
|
} else if (
|
||||||
|
currentFeedbackType &&
|
||||||
|
!_validFirstAnswerDelimiters.some((prefix) => trimmed.startsWith(prefix))
|
||||||
|
) {
|
||||||
|
// This is a continuation of the current feedback
|
||||||
|
currentFeedbackLines.push(line);
|
||||||
|
} else {
|
||||||
|
// Save any pending feedback
|
||||||
|
if (currentFeedbackType && currentFeedbackLines.length > 0) {
|
||||||
|
const feedbackText = currentFeedbackLines.join("\n");
|
||||||
|
if (currentFeedbackType === "+") correctComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "-") incorrectComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "...") neutralComments = feedbackText;
|
||||||
|
currentFeedbackType = null;
|
||||||
|
currentFeedbackLines = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a regular line
|
||||||
|
linesWithoutFeedback.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save any remaining feedback
|
||||||
|
if (currentFeedbackType && currentFeedbackLines.length > 0) {
|
||||||
|
const feedbackText = currentFeedbackLines.join("\n");
|
||||||
|
if (currentFeedbackType === "+") correctComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "-") incorrectComments = feedbackText;
|
||||||
|
else if (currentFeedbackType === "...") neutralComments = feedbackText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
correctComments,
|
||||||
|
incorrectComments,
|
||||||
|
neutralComments,
|
||||||
|
linesWithoutFeedback,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const getAnswerStringsWithMultilineSupport = (
|
const getAnswerStringsWithMultilineSupport = (
|
||||||
linesWithoutPoints: string[],
|
linesWithoutPoints: string[],
|
||||||
@@ -153,6 +245,18 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
? question.matchDistractors?.map((d) => `\n^ - ${d}`).join("") ?? ""
|
? question.matchDistractors?.map((d) => `\n^ - ${d}`).join("") ?? ""
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
// Build feedback lines
|
||||||
|
let feedbackText = "";
|
||||||
|
if (question.correctComments) {
|
||||||
|
feedbackText += `+ ${question.correctComments}\n`;
|
||||||
|
}
|
||||||
|
if (question.incorrectComments) {
|
||||||
|
feedbackText += `- ${question.incorrectComments}\n`;
|
||||||
|
}
|
||||||
|
if (question.neutralComments) {
|
||||||
|
feedbackText += `... ${question.neutralComments}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
const answersText = answerArray.join("\n");
|
const answersText = answerArray.join("\n");
|
||||||
const questionTypeIndicator =
|
const questionTypeIndicator =
|
||||||
question.questionType === "essay" ||
|
question.questionType === "essay" ||
|
||||||
@@ -162,7 +266,7 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
? `\n${QuestionType.SHORT_ANSWER_WITH_ANSWERS}`
|
? `\n${QuestionType.SHORT_ANSWER_WITH_ANSWERS}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return `Points: ${question.points}\n${question.text}\n${answersText}${distractorText}${questionTypeIndicator}`;
|
return `Points: ${question.points}\n${question.text}\n${feedbackText}${answersText}${distractorText}${questionTypeIndicator}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
parseMarkdown(input: string, questionIndex: number): LocalQuizQuestion {
|
parseMarkdown(input: string, questionIndex: number): LocalQuizQuestion {
|
||||||
@@ -182,7 +286,15 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
|
|
||||||
const linesWithoutPoints = firstLineIsPoints ? lines.slice(1) : lines;
|
const linesWithoutPoints = firstLineIsPoints ? lines.slice(1) : lines;
|
||||||
|
|
||||||
const { linesWithoutAnswers } = linesWithoutPoints.reduce(
|
// Extract feedback comments first
|
||||||
|
const {
|
||||||
|
correctComments,
|
||||||
|
incorrectComments,
|
||||||
|
neutralComments,
|
||||||
|
linesWithoutFeedback,
|
||||||
|
} = extractFeedback(linesWithoutPoints);
|
||||||
|
|
||||||
|
const { linesWithoutAnswers } = linesWithoutFeedback.reduce(
|
||||||
({ linesWithoutAnswers, taking }, currentLine) => {
|
({ linesWithoutAnswers, taking }, currentLine) => {
|
||||||
if (!taking)
|
if (!taking)
|
||||||
return { linesWithoutAnswers: linesWithoutAnswers, taking: false };
|
return { linesWithoutAnswers: linesWithoutAnswers, taking: false };
|
||||||
@@ -200,7 +312,7 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
},
|
},
|
||||||
{ linesWithoutAnswers: [] as string[], taking: true }
|
{ linesWithoutAnswers: [] as string[], taking: true }
|
||||||
);
|
);
|
||||||
const questionType = getQuestionType(linesWithoutPoints, questionIndex);
|
const questionType = getQuestionType(linesWithoutFeedback, questionIndex);
|
||||||
|
|
||||||
const questionTypesWithoutAnswers = [
|
const questionTypesWithoutAnswers = [
|
||||||
"essay",
|
"essay",
|
||||||
@@ -212,10 +324,9 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
questionType.toLowerCase()
|
questionType.toLowerCase()
|
||||||
)
|
)
|
||||||
? linesWithoutAnswers
|
? linesWithoutAnswers
|
||||||
.slice(0, linesWithoutPoints.length)
|
.slice(0, linesWithoutFeedback.length)
|
||||||
.filter(
|
.filter(
|
||||||
(line) =>
|
(line) => !questionTypesWithoutAnswers.includes(line.toLowerCase())
|
||||||
!questionTypesWithoutAnswers.includes(line.toLowerCase())
|
|
||||||
)
|
)
|
||||||
: linesWithoutAnswers;
|
: linesWithoutAnswers;
|
||||||
|
|
||||||
@@ -228,7 +339,7 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
"short_answer=",
|
"short_answer=",
|
||||||
];
|
];
|
||||||
const answers = typesWithAnswers.includes(questionType)
|
const answers = typesWithAnswers.includes(questionType)
|
||||||
? getAnswers(linesWithoutPoints, questionIndex, questionType)
|
? getAnswers(linesWithoutFeedback, questionIndex, questionType)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const answersWithoutDistractors =
|
const answersWithoutDistractors =
|
||||||
@@ -247,6 +358,9 @@ export const quizQuestionMarkdownUtils = {
|
|||||||
points,
|
points,
|
||||||
answers: answersWithoutDistractors,
|
answers: answersWithoutDistractors,
|
||||||
matchDistractors: distractors,
|
matchDistractors: distractors,
|
||||||
|
correctComments,
|
||||||
|
incorrectComments,
|
||||||
|
neutralComments,
|
||||||
};
|
};
|
||||||
return question;
|
return question;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user