Rust learning: lessons, notes, and exercise crates

This commit is contained in:
2026-09-06 23:59:08 +07:00
commit 6ddf41aa81
119 changed files with 10873 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
// Shared quiz engine for lessons. Two question types, both graded client-side, no server.
//
// Recall (free-recall self-check — best for storage strength):
// <div class="q" data-type="recall" data-topic="Ownership">
// <p class="topic">Ownership</p>
// <p class="prompt">...question...</p>
// <button class="reveal-btn">Show answer</button>
// <div class="answer hidden">...answer text...</div>
// <div class="grade hidden">
// <button data-grade="hit">Got it</button>
// <button data-grade="miss">Missed it</button>
// </div>
// </div>
//
// Multiple choice (instant feedback):
// <div class="q" data-type="mcq" data-topic="Enums">
// <p class="topic">Enums</p>
// <p class="prompt">...question...</p>
// <div class="options">
// <button class="opt" data-correct="true">...</button>
// <button class="opt" data-correct="false">...</button>
// </div>
// <div class="explain hidden">...explanation...</div>
// </div>
//
// Bottom of page needs:
// <div id="summary"><div id="summary-body"></div><p id="summary-total"></p>
// <button id="report-btn">Copy report</button><pre id="report-output" class="hidden"></pre></div>
(function () {
function all(sel, ctx) {
return Array.from((ctx || document).querySelectorAll(sel));
}
function initRecall(q) {
var revealBtn = q.querySelector(".reveal-btn");
var answer = q.querySelector(".answer");
var grade = q.querySelector(".grade");
if (!revealBtn || !answer || !grade) return;
revealBtn.addEventListener("click", function () {
answer.classList.remove("hidden");
grade.classList.remove("hidden");
revealBtn.classList.add("hidden");
});
all("button", grade).forEach(function (btn) {
btn.addEventListener("click", function () {
q.dataset.result = btn.dataset.grade === "hit" ? "hit" : "miss";
q.classList.add("graded");
all("button", grade).forEach(function (b) {
b.disabled = true;
});
btn.classList.add(btn.dataset.grade === "hit" ? "opt-correct" : "opt-incorrect");
updateSummary();
});
});
}
function initMcq(q) {
var opts = all(".opt", q);
var explain = q.querySelector(".explain");
opts.forEach(function (opt) {
opt.addEventListener("click", function () {
if (q.classList.contains("graded")) return;
var correct = opt.dataset.correct === "true";
q.classList.add("graded");
q.dataset.result = correct ? "hit" : "miss";
opts.forEach(function (o) {
o.disabled = true;
if (o.dataset.correct === "true") o.classList.add("correct");
});
if (!correct) opt.classList.add("incorrect");
if (explain) explain.classList.remove("hidden");
updateSummary();
});
});
}
function byTopic() {
var map = {};
all(".q").forEach(function (q) {
var topic = q.dataset.topic || "General";
map[topic] = map[topic] || { hit: 0, graded: 0, total: 0 };
map[topic].total++;
if (q.dataset.result) {
map[topic].graded++;
if (q.dataset.result === "hit") map[topic].hit++;
}
});
return map;
}
function updateSummary() {
var body = document.getElementById("summary-body");
var total = document.getElementById("summary-total");
var reportBtn = document.getElementById("report-btn");
if (!body) return;
var map = byTopic();
var lines = [];
var sumHit = 0,
sumGraded = 0,
sumAll = 0;
Object.keys(map).forEach(function (topic) {
var t = map[topic];
sumHit += t.hit;
sumGraded += t.graded;
sumAll += t.total;
var pct = t.graded ? Math.round((100 * t.hit) / t.graded) : null;
lines.push(
topic +
": " +
t.hit +
"/" +
t.graded +
(t.graded < t.total ? " (of " + t.total + ")" : "") +
(pct === null ? "" : " — " + pct + "%")
);
});
body.textContent = lines.join("\n");
if (total) total.textContent = sumGraded + " of " + sumAll + " answered, " + sumHit + " correct.";
if (reportBtn) reportBtn.disabled = sumGraded === 0;
}
function buildReport() {
var map = byTopic();
var weak = [];
var solid = [];
Object.keys(map).forEach(function (topic) {
var t = map[topic];
var entry = topic + " (" + t.hit + "/" + t.total + ")";
if (t.hit < t.total) weak.push(entry);
else solid.push(entry);
});
var lines = ["Rust diagnostic result:"];
lines.push(weak.length ? "Weak: " + weak.join(", ") : "Weak: none");
lines.push(solid.length ? "Solid: " + solid.join(", ") : "Solid: none");
return lines.join("\n");
}
document.addEventListener("DOMContentLoaded", function () {
all(".q").forEach(function (q) {
if (q.dataset.type === "recall") initRecall(q);
if (q.dataset.type === "mcq") initMcq(q);
});
updateSummary();
var reportBtn = document.getElementById("report-btn");
if (reportBtn) {
reportBtn.addEventListener("click", function () {
var report = buildReport();
var out = document.getElementById("report-output");
if (out) {
out.textContent = report;
out.classList.remove("hidden");
}
if (navigator.clipboard) navigator.clipboard.writeText(report).catch(function () {});
});
}
});
})();