added overlay function for tarot cards
This commit is contained in:
168
app/quiz-plugin-helpers.js
Normal file
168
app/quiz-plugin-helpers.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/* 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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user