168 lines
5.1 KiB
JavaScript
168 lines
5.1 KiB
JavaScript
|
|
/* 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
|
||
|
|
};
|
||
|
|
})();
|