Files
TaroTime/app/quiz-plugin-helpers.js

168 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

2026-03-08 03:52:25 -07:00
/* quiz-plugin-helpers.js — Shared utilities for dynamic quiz plugins */
(function () {
"use strict";
function normalizeOption(value) {
return String(value || "").trim();
}
function normalizeKey(value) {
return normalizeOption(value).toLowerCase();
}
function toUniqueOptionList(values) {
const seen = new Set();
const unique = [];
(values || []).forEach((value) => {
const formatted = normalizeOption(value);
if (!formatted) {
return;
}
const key = normalizeKey(formatted);
if (seen.has(key)) {
return;
}
seen.add(key);
unique.push(formatted);
});
return unique;
}
function makeTemplate(key, categoryId, category, prompt, answer, pool) {
const promptText = normalizeOption(prompt);
const answerText = normalizeOption(answer);
const optionPool = toUniqueOptionList(pool || []);
if (!key || !categoryId || !category || !promptText || !answerText) {
return null;
}
if (!optionPool.some((value) => normalizeKey(value) === normalizeKey(answerText))) {
optionPool.push(answerText);
}
const distractorCount = optionPool.filter((value) => normalizeKey(value) !== normalizeKey(answerText)).length;
if (distractorCount < 3) {
return null;
}
return {
key,
categoryId,
category,
promptByDifficulty: promptText,
answerByDifficulty: answerText,
poolByDifficulty: optionPool
};
}
function buildTemplatesFromSpec(spec) {
const rows = Array.isArray(spec?.entries) ? spec.entries : [];
const categoryId = normalizeOption(spec?.categoryId);
const category = normalizeOption(spec?.category);
const keyPrefix = normalizeOption(spec?.keyPrefix);
const getPrompt = spec?.getPrompt;
const getAnswer = spec?.getAnswer;
const getKey = spec?.getKey;
if (!rows.length || !categoryId || !category || !keyPrefix) {
return [];
}
if (typeof getPrompt !== "function" || typeof getAnswer !== "function") {
return [];
}
const pool = toUniqueOptionList(rows.map((entry) => getAnswer(entry)).filter(Boolean));
if (pool.length < 4) {
return [];
}
return rows
.map((entry, index) => {
const keyValue = typeof getKey === "function" ? getKey(entry, index) : String(index);
return makeTemplate(
`${keyPrefix}:${keyValue}`,
categoryId,
category,
getPrompt(entry),
getAnswer(entry),
pool
);
})
.filter(Boolean);
}
function buildTemplatesFromVariants(spec) {
const variants = Array.isArray(spec?.variants) ? spec.variants : [];
if (!variants.length) {
return [];
}
return variants.flatMap((variant) => {
const forwardTemplates = buildTemplatesFromSpec({
entries: spec.entries,
categoryId: variant.categoryId || spec.categoryId,
category: variant.category || spec.category,
keyPrefix: variant.keyPrefix || spec.keyPrefix,
getKey: variant.getKey || spec.getKey,
getPrompt: variant.getPrompt,
getAnswer: variant.getAnswer
});
const inverse = variant.inverse;
if (!inverse || typeof inverse.getPrompt !== "function" || typeof inverse.getAnswer !== "function") {
return forwardTemplates;
}
const entries = Array.isArray(spec.entries) ? spec.entries : [];
const rows = entries.filter((entry) => {
const uniquenessValue = typeof inverse.getUniquenessKey === "function"
? inverse.getUniquenessKey(entry)
: variant.getAnswer(entry);
return normalizeOption(uniquenessValue) && normalizeOption(inverse.getAnswer(entry));
});
const occurrenceCountByKey = new Map();
rows.forEach((entry) => {
const uniquenessValue = typeof inverse.getUniquenessKey === "function"
? inverse.getUniquenessKey(entry)
: variant.getAnswer(entry);
const key = normalizeKey(uniquenessValue);
occurrenceCountByKey.set(key, (occurrenceCountByKey.get(key) || 0) + 1);
});
const uniqueRows = rows.filter((entry) => {
const uniquenessValue = typeof inverse.getUniquenessKey === "function"
? inverse.getUniquenessKey(entry)
: variant.getAnswer(entry);
return occurrenceCountByKey.get(normalizeKey(uniquenessValue)) === 1;
});
const inverseTemplates = buildTemplatesFromSpec({
entries: uniqueRows,
categoryId: inverse.categoryId || variant.categoryId || spec.categoryId,
category: inverse.category || variant.category || spec.category,
keyPrefix: inverse.keyPrefix || `${variant.keyPrefix || spec.keyPrefix}-reverse`,
getKey: inverse.getKey || variant.getKey || spec.getKey,
getPrompt: inverse.getPrompt,
getAnswer: inverse.getAnswer
});
return [...forwardTemplates, ...inverseTemplates];
});
}
window.QuizPluginHelpers = {
normalizeOption,
normalizeKey,
toUniqueOptionList,
makeTemplate,
buildTemplatesFromSpec,
buildTemplatesFromVariants
};
})();