(wip) fix earlier breaking change (feedback in quizzes) by allowing custom feedback delims so that - doesn't need to conflict with markdown list item

This commit is contained in:
Adam Teichert
2025-12-17 15:10:59 -07:00
parent 3c6ba35bce
commit 1e3ff085f8
9 changed files with 217 additions and 29 deletions

View File

@@ -1,7 +1,22 @@
export interface FeedbackDelimiters {
correct: string;
incorrect: string;
neutral: string;
}
export const defaultFeedbackDelimiters: FeedbackDelimiters = {
correct: "+",
incorrect: "-",
neutral: "...",
};
type feedbackTypeOptions = "correct" | "incorrect" | "neutral" | "none";
export const quizFeedbackMarkdownUtils = {
extractFeedback(lines: string[]): {
extractFeedback(
lines: string[],
delimiters: FeedbackDelimiters = defaultFeedbackDelimiters
): {
correctComments?: string;
incorrectComments?: string;
neutralComments?: string;
@@ -15,20 +30,18 @@ export const quizFeedbackMarkdownUtils = {
const otherLines: string[] = [];
const feedbackIndicators = {
correct: "+",
incorrect: "-",
neutral: "...",
};
const feedbackIndicators = delimiters;
let currentFeedbackType: feedbackTypeOptions = "none";
for (const line of lines.map((l) => l)) {
const lineFeedbackType: feedbackTypeOptions = line.startsWith("+")
const lineFeedbackType: feedbackTypeOptions = line.startsWith(
feedbackIndicators.correct
)
? "correct"
: line.startsWith("-")
: line.startsWith(feedbackIndicators.incorrect)
? "incorrect"
: line.startsWith("...")
: line.startsWith(feedbackIndicators.neutral)
? "neutral"
: "none";
@@ -37,15 +50,12 @@ export const quizFeedbackMarkdownUtils = {
.replace(feedbackIndicators[currentFeedbackType], "")
.trim();
comments[currentFeedbackType].push(lineWithoutIndicator);
} else if (lineFeedbackType !== "none") {
const lineWithoutIndicator = line
.replace(feedbackIndicators[lineFeedbackType], "")
.trim();
currentFeedbackType = lineFeedbackType;
comments[lineFeedbackType].push(lineWithoutIndicator);
} else {
otherLines.push(line);
}
@@ -66,17 +76,18 @@ export const quizFeedbackMarkdownUtils = {
formatFeedback(
correctComments?: string,
incorrectComments?: string,
neutralComments?: string
neutralComments?: string,
delimiters: FeedbackDelimiters = defaultFeedbackDelimiters
): string {
let feedbackText = "";
if (correctComments) {
feedbackText += `+ ${correctComments}\n`;
feedbackText += `${delimiters.correct} ${correctComments}\n`;
}
if (incorrectComments) {
feedbackText += `- ${incorrectComments}\n`;
feedbackText += `${delimiters.incorrect} ${incorrectComments}\n`;
}
if (neutralComments) {
feedbackText += `... ${neutralComments}\n`;
feedbackText += `${delimiters.neutral} ${neutralComments}\n`;
}
return feedbackText;
},

View File

@@ -4,6 +4,7 @@ import {
} from "@/features/local/utils/timeUtils";
import { LocalQuiz } from "../localQuiz";
import { quizQuestionMarkdownUtils } from "./quizQuestionMarkdownUtils";
import { FeedbackDelimiters } from "./quizFeedbackMarkdownUtils";
const extractLabelValue = (input: string, label: string): string => {
const pattern = new RegExp(`${label}: (.*?)\n`);
@@ -103,7 +104,7 @@ const getQuizWithOnlySettings = (settings: string, name: string): LocalQuiz => {
};
export const quizMarkdownUtils = {
toMarkdown(quiz: LocalQuiz): string {
toMarkdown(quiz: LocalQuiz, delimiters?: FeedbackDelimiters): string {
if (!quiz) {
throw Error(`quiz was undefined, cannot parse markdown`);
}
@@ -115,7 +116,7 @@ export const quizMarkdownUtils = {
throw Error(`quiz ${quiz.name} is probably not a quiz`);
}
const questionMarkdownArray = quiz.questions.map((q) =>
quizQuestionMarkdownUtils.toMarkdown(q)
quizQuestionMarkdownUtils.toMarkdown(q, delimiters)
);
const questionDelimiter = "\n\n---\n\n";
const questionMarkdown = questionMarkdownArray.join(questionDelimiter);
@@ -133,7 +134,11 @@ Description: ${quiz.description}
${questionMarkdown}`;
},
parseMarkdown(input: string, name: string): LocalQuiz {
parseMarkdown(
input: string,
name: string,
delimiters?: FeedbackDelimiters
): LocalQuiz {
const splitInput = input.split("---\n");
const settings = splitInput[0];
const quizWithoutQuestions = getQuizWithOnlySettings(settings, name);
@@ -141,7 +146,7 @@ ${questionMarkdown}`;
const rawQuestions = splitInput.slice(1);
const questions = rawQuestions
.filter((str) => str.trim().length > 0)
.map((q, i) => quizQuestionMarkdownUtils.parseMarkdown(q, i));
.map((q, i) => quizQuestionMarkdownUtils.parseMarkdown(q, i, delimiters));
return {
...quizWithoutQuestions,

View File

@@ -1,5 +1,8 @@
import { LocalQuizQuestion, QuestionType } from "../localQuizQuestion";
import { quizFeedbackMarkdownUtils } from "./quizFeedbackMarkdownUtils";
import {
quizFeedbackMarkdownUtils,
FeedbackDelimiters,
} from "./quizFeedbackMarkdownUtils";
import { quizQuestionAnswerMarkdownUtils } from "./quizQuestionAnswerMarkdownUtils";
const splitLinesAndPoints = (input: string[]) => {
@@ -58,7 +61,10 @@ const removeQuestionTypeFromDescriptionLines = (
};
export const quizQuestionMarkdownUtils = {
toMarkdown(question: LocalQuizQuestion): string {
toMarkdown(
question: LocalQuizQuestion,
delimiters?: FeedbackDelimiters
): string {
const answerArray = question.answers.map((a, i) =>
quizQuestionAnswerMarkdownUtils.getAnswerMarkdown(question, a, i)
);
@@ -72,7 +78,8 @@ export const quizQuestionMarkdownUtils = {
const feedbackText = quizFeedbackMarkdownUtils.formatFeedback(
question.correctComments,
question.incorrectComments,
question.neutralComments
question.neutralComments,
delimiters
);
const answersText = answerArray.join("\n");
@@ -87,7 +94,11 @@ export const quizQuestionMarkdownUtils = {
return `Points: ${question.points}\n${question.text}\n${feedbackText}${answersText}${distractorText}${questionTypeIndicator}`;
},
parseMarkdown(input: string, questionIndex: number): LocalQuizQuestion {
parseMarkdown(
input: string,
questionIndex: number,
delimiters?: FeedbackDelimiters
): LocalQuizQuestion {
const { points, lines } = splitLinesAndPoints(input.trim().split("\n"));
const linesWithoutAnswers = getLinesBeforeAnswerLines(lines);
@@ -107,7 +118,10 @@ export const quizQuestionMarkdownUtils = {
incorrectComments,
neutralComments,
otherLines: descriptionLines,
} = quizFeedbackMarkdownUtils.extractFeedback(linesWithoutAnswersAndTypes);
} = quizFeedbackMarkdownUtils.extractFeedback(
linesWithoutAnswersAndTypes,
delimiters
);
const { answers, distractors } = quizQuestionAnswerMarkdownUtils.getAnswers(
lines,

View File

@@ -5,11 +5,15 @@ import {
LocalQuiz,
zodLocalQuiz,
} from "@/features/local/quizzes/models/localQuiz";
import { getCoursePathByName } from "../globalSettings/globalSettingsFileStorageService";
import {
getCoursePathByName,
getGlobalSettings,
} from "../globalSettings/globalSettingsFileStorageService";
import path from "path";
import { promises as fs } from "fs";
import { quizMarkdownUtils } from "./models/utils/quizMarkdownUtils";
import { courseItemFileStorageService } from "../course/courseItemFileStorageService";
import { getFeedbackDelimitersFromSettings } from "../globalSettings/globalSettingsUtils";
export const quizRouter = router({
getQuiz: publicProcedure
@@ -159,7 +163,9 @@ export async function updateQuizFile({
quizName + ".md"
);
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz);
const globalSettings = await getGlobalSettings();
const delimiters = getFeedbackDelimitersFromSettings(globalSettings);
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz, delimiters);
console.log(`Saving quiz ${filePath}`);
await fs.writeFile(filePath, quizMarkdown);
}