From 4d08c3db5529f015c82832ca5fa318a4826a57b0 Mon Sep 17 00:00:00 2001 From: Dennis Eisold Date: Thu, 30 Apr 2026 22:11:55 +0200 Subject: [PATCH 1/4] Add quiz mode toggle to chapter/section toolbar Adds a button to the existing top-right button group on chapter and section pages that hides everything outside of the breadcrumb, the heading, the rendered questions and the link to the next section or chapter at the end of the page. The state is persisted via localStorage so it survives navigation. On pages without questions a small notice is shown instead. Replaces the previously published userscript by integrating the same behaviour directly into the site. --- assets/script.js | 82 +++++++++++++++++++++++++++++++++++++ assets/style-course.css | 40 ++++++++++++++++++ templates/html/chapter.html | 3 +- templates/html/section.html | 3 +- 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/assets/script.js b/assets/script.js index 634aae6..59854d6 100644 --- a/assets/script.js +++ b/assets/script.js @@ -9,6 +9,7 @@ document.addEventListener("DOMContentLoaded", function() { for (var i = 0; i < els.length; i++) { shuffleChildren(els[i]); } + initQuizMode(); }); function answer(el, correct) { @@ -18,6 +19,87 @@ function answer(el, correct) { el.classList.add("wrong") } +(function () { + var NOTICE_ID = "quiz-mode-notice"; + var STORAGE_KEY = "quiz-mode-enabled"; + var QUIZ_CLASS = "quiz-mode-active"; + var HAS_Q_CLASS = "quiz-has-questions"; + + function pageHasQuestions() { + return document.querySelector(".course .question") !== null; + } + + function tagSectionTail() { + var course = document.querySelector(".course"); + if (!course) return false; + var tagged = course.querySelectorAll("[data-quiz-tail]"); + for (var i = 0; i < tagged.length; i++) { + tagged[i].removeAttribute("data-quiz-tail"); + } + var children = course.children; + for (var j = 0; j < children.length; j++) { + var child = children[j]; + if (child.tagName !== "DIV") continue; + var text = (child.textContent || "").toLowerCase(); + if (text.indexOf("weiter zum nächsten abschnitt") !== -1 || + text.indexOf("weiter zum nächsten kapitel") !== -1) { + child.setAttribute("data-quiz-tail", ""); + return true; + } + } + return false; + } + + function showNotice(text) { + var n = document.getElementById(NOTICE_ID); + if (!n) { + n = document.createElement("div"); + n.id = NOTICE_ID; + document.body.appendChild(n); + } + n.textContent = text; + } + + function hideNotice() { + var n = document.getElementById(NOTICE_ID); + if (n) n.parentNode.removeChild(n); + } + + function updateButton(btn, on) { + btn.setAttribute("aria-pressed", on ? "true" : "false"); + btn.classList.toggle("active", on); + btn.title = on ? "Alles anzeigen" : "Nur Fragen anzeigen"; + } + + function setQuizMode(on) { + var hasQ = pageHasQuestions(); + document.body.classList.toggle(HAS_Q_CLASS, hasQ); + if (on && hasQ) tagSectionTail(); + document.body.classList.toggle(QUIZ_CLASS, on); + var btn = document.getElementById("quiz-mode-toggle"); + if (btn) updateButton(btn, on); + if (on && !hasQ) { + showNotice("Keine Fragen auf dieser Seite gefunden."); + } else { + hideNotice(); + } + } + + window.initQuizMode = function () { + var btn = document.getElementById("quiz-mode-toggle"); + if (!btn) return; + btn.addEventListener("click", function () { + var next = !document.body.classList.contains(QUIZ_CLASS); + setQuizMode(next); + try { localStorage.setItem(STORAGE_KEY, next ? "1" : "0"); } catch (e) {} + }); + var initial = false; + try { initial = localStorage.getItem(STORAGE_KEY) === "1"; } catch (e) {} + if (initial) setQuizMode(true); + else updateButton(btn, false); + }; +})(); + /* var els = document.querySelectorAll(".question .question-text"); diff --git a/assets/style-course.css b/assets/style-course.css index 7a12ad3..6a31f23 100644 --- a/assets/style-course.css +++ b/assets/style-course.css @@ -446,3 +446,43 @@ span.formula { .course .katex-display { margin: 1em 0em; } + +/* Quiz-Modus */ +#quiz-mode-toggle { + cursor: pointer; +} +#quiz-mode-toggle.active { + background: #0a7a2a; + border-color: #0a7a2a; + color: #fff; +} +#quiz-mode-toggle.active svg { + fill: #fff; +} + +#quiz-mode-notice { + position: fixed; + top: 12px; + right: 12px; + z-index: 99999; + max-width: 260px; + padding: 8px 12px; + font: 500 13px/1.35 system-ui, -apple-system, sans-serif; + color: #333; + background: #fff3cd; + border: 1px solid #ffe69c; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +body.quiz-mode-active.quiz-has-questions .course > * { + display: none !important; +} +body.quiz-mode-active.quiz-has-questions .course > .course-toolbar, +body.quiz-mode-active.quiz-has-questions .course > h1, +body.quiz-mode-active.quiz-has-questions .course > nav, +body.quiz-mode-active.quiz-has-questions .course > .question, +body.quiz-mode-active.quiz-has-questions .course > [data-quiz-tail], +body.quiz-mode-active.quiz-has-questions .course > [data-quiz-tail] ~ * { + display: revert !important; +} diff --git a/templates/html/chapter.html b/templates/html/chapter.html index 746d539..665c794 100644 --- a/templates/html/chapter.html +++ b/templates/html/chapter.html @@ -1,10 +1,11 @@ -
+
{% if chapter["video_url"] %} {% endif %} +
diff --git a/templates/html/section.html b/templates/html/section.html index 91583f0..640cf4e 100644 --- a/templates/html/section.html +++ b/templates/html/section.html @@ -1,10 +1,11 @@ -
+
{% if section["video_url"] %} {% endif %} +
From a1698c9d3d949c9d81afcb48a009c4489591d522 Mon Sep 17 00:00:00 2001 From: Dennis Eisold Date: Sun, 3 May 2026 20:09:46 +0200 Subject: [PATCH 2/4] Use data-quiz-shown attributes for quiz mode visibility Mark elements that should remain visible in quiz mode at build time via data-quiz-shown attributes (toolbar, breadcrumb, h1, questions, solution links, next-section/-chapter links). The runtime tail detection becomes obsolete and the CSS collapses to a single show/hide rule. Switch the body markers to data-quiz-mode / data-quiz-has-questions attributes, swap the toggle between btn-secondary and btn-primary instead of hardcoded colors, and use Bootstrap's alert alert-info for the no-questions notice. --- assets/script.js | 47 ++++++++++++-------------------- assets/style-course.css | 36 ++---------------------- templates/html/chapter.html | 6 ++-- templates/html/next_chapter.html | 2 +- templates/html/next_section.html | 2 +- templates/html/question.html | 4 +-- templates/html/section.html | 6 ++-- 7 files changed, 30 insertions(+), 73 deletions(-) diff --git a/assets/script.js b/assets/script.js index 59854d6..68b1870 100644 --- a/assets/script.js +++ b/assets/script.js @@ -22,40 +22,27 @@ function answer(el, correct) { (function () { var NOTICE_ID = "quiz-mode-notice"; var STORAGE_KEY = "quiz-mode-enabled"; - var QUIZ_CLASS = "quiz-mode-active"; - var HAS_Q_CLASS = "quiz-has-questions"; + var QUIZ_ATTR = "data-quiz-mode"; + var HAS_Q_ATTR = "data-quiz-has-questions"; function pageHasQuestions() { return document.querySelector(".course .question") !== null; } - function tagSectionTail() { - var course = document.querySelector(".course"); - if (!course) return false; - var tagged = course.querySelectorAll("[data-quiz-tail]"); - for (var i = 0; i < tagged.length; i++) { - tagged[i].removeAttribute("data-quiz-tail"); - } - var children = course.children; - for (var j = 0; j < children.length; j++) { - var child = children[j]; - if (child.tagName !== "DIV") continue; - var text = (child.textContent || "").toLowerCase(); - if (text.indexOf("weiter zum nächsten abschnitt") !== -1 || - text.indexOf("weiter zum nächsten kapitel") !== -1) { - child.setAttribute("data-quiz-tail", ""); - return true; - } - } - return false; - } - function showNotice(text) { var n = document.getElementById(NOTICE_ID); if (!n) { n = document.createElement("div"); n.id = NOTICE_ID; - document.body.appendChild(n); + n.className = "alert alert-info"; + n.setAttribute("role", "alert"); + n.style.clear = "both"; + var course = document.querySelector(".course"); + if (course) { + course.insertBefore(n, course.firstChild); + } else { + document.body.appendChild(n); + } } n.textContent = text; } @@ -67,15 +54,17 @@ function answer(el, correct) { function updateButton(btn, on) { btn.setAttribute("aria-pressed", on ? "true" : "false"); - btn.classList.toggle("active", on); + btn.classList.toggle("btn-primary", on); + btn.classList.toggle("btn-secondary", !on); btn.title = on ? "Alles anzeigen" : "Nur Fragen anzeigen"; } function setQuizMode(on) { var hasQ = pageHasQuestions(); - document.body.classList.toggle(HAS_Q_CLASS, hasQ); - if (on && hasQ) tagSectionTail(); - document.body.classList.toggle(QUIZ_CLASS, on); + if (hasQ) document.body.setAttribute(HAS_Q_ATTR, ""); + else document.body.removeAttribute(HAS_Q_ATTR); + if (on) document.body.setAttribute(QUIZ_ATTR, ""); + else document.body.removeAttribute(QUIZ_ATTR); var btn = document.getElementById("quiz-mode-toggle"); if (btn) updateButton(btn, on); if (on && !hasQ) { @@ -89,7 +78,7 @@ function answer(el, correct) { var btn = document.getElementById("quiz-mode-toggle"); if (!btn) return; btn.addEventListener("click", function () { - var next = !document.body.classList.contains(QUIZ_CLASS); + var next = !document.body.hasAttribute(QUIZ_ATTR); setQuizMode(next); try { localStorage.setItem(STORAGE_KEY, next ? "1" : "0"); } catch (e) {} }); diff --git a/assets/style-course.css b/assets/style-course.css index 6a31f23..ee2c628 100644 --- a/assets/style-course.css +++ b/assets/style-course.css @@ -448,41 +448,9 @@ span.formula { } /* Quiz-Modus */ -#quiz-mode-toggle { - cursor: pointer; -} -#quiz-mode-toggle.active { - background: #0a7a2a; - border-color: #0a7a2a; - color: #fff; -} -#quiz-mode-toggle.active svg { - fill: #fff; -} - -#quiz-mode-notice { - position: fixed; - top: 12px; - right: 12px; - z-index: 99999; - max-width: 260px; - padding: 8px 12px; - font: 500 13px/1.35 system-ui, -apple-system, sans-serif; - color: #333; - background: #fff3cd; - border: 1px solid #ffe69c; - border-radius: 6px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); -} - -body.quiz-mode-active.quiz-has-questions .course > * { +body[data-quiz-mode][data-quiz-has-questions] .course > * { display: none !important; } -body.quiz-mode-active.quiz-has-questions .course > .course-toolbar, -body.quiz-mode-active.quiz-has-questions .course > h1, -body.quiz-mode-active.quiz-has-questions .course > nav, -body.quiz-mode-active.quiz-has-questions .course > .question, -body.quiz-mode-active.quiz-has-questions .course > [data-quiz-tail], -body.quiz-mode-active.quiz-has-questions .course > [data-quiz-tail] ~ * { +body[data-quiz-mode][data-quiz-has-questions] .course > [data-quiz-shown] { display: revert !important; } diff --git a/templates/html/chapter.html b/templates/html/chapter.html index 665c794..d969ca9 100644 --- a/templates/html/chapter.html +++ b/templates/html/chapter.html @@ -1,4 +1,4 @@ -
+
{% if chapter["video_url"] %} @@ -9,7 +9,7 @@
-