9022961762
Ersetzt das Hello-World-Gerüst durch die vollständige Anwendung (com.example.mindflux: 64 Klassen, Thymeleaf-Templates, Static-Assets, Flyway V1–V6). Deploy-Infrastruktur (.gitea, Dockerfile) bleibt erhalten. - pom: volle Abhängigkeiten (Security, Thymeleaf, JPA, Flyway), Spring Boot 3.4.4 / Java 17 - docker-compose: SPRING_DATASOURCE_* unverändert; App lauscht auf 8080 (nginx -> mindflux:8080); Uploads-Volume; Admin-Passwörter via Gitea-Secrets - Workflow: Deploy-Step injiziert MINDFLUX_ADMIN*_PASSWORD aus Secrets - .dockerignore ergänzt; Hello-World (com.mindflux) und Test-Datei 'main' entfernt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
180 lines
5.9 KiB
JavaScript
180 lines
5.9 KiB
JavaScript
document.addEventListener('DOMContentLoaded', async () => {
|
|
const script = document.querySelector('script[data-questions-src]');
|
|
const testRoot = document.querySelector('[data-finalflux-test]');
|
|
|
|
if (!script || !testRoot) {
|
|
return;
|
|
}
|
|
|
|
const elements = {
|
|
questionCounter: testRoot.querySelector('[data-question-counter]'),
|
|
scoreCounter: testRoot.querySelector('[data-score-counter]'),
|
|
categoryTitle: testRoot.querySelector('[data-category-title]'),
|
|
categoryHint: testRoot.querySelector('[data-category-hint]'),
|
|
questionText: testRoot.querySelector('[data-question-text]'),
|
|
answerOptions: testRoot.querySelector('[data-answer-options]'),
|
|
answerFeedback: testRoot.querySelector('[data-answer-feedback]'),
|
|
checkAnswer: testRoot.querySelector('[data-check-answer]'),
|
|
skipQuestion: testRoot.querySelector('[data-skip-question]'),
|
|
nextQuestion: testRoot.querySelector('[data-next-question]'),
|
|
testResult: document.querySelector('[data-test-result]'),
|
|
resultSummary: document.querySelector('[data-result-summary]'),
|
|
categorySummary: document.querySelector('[data-category-summary]'),
|
|
restartTest: document.querySelector('[data-restart-test]')
|
|
};
|
|
|
|
let questions = [];
|
|
let currentIndex = 0;
|
|
let score = 0;
|
|
let answered = false;
|
|
const categoryStats = new Map();
|
|
|
|
function setError(message) {
|
|
elements.questionText.textContent = message;
|
|
elements.answerOptions.innerHTML = '';
|
|
elements.checkAnswer.disabled = true;
|
|
elements.skipQuestion.disabled = true;
|
|
elements.nextQuestion.disabled = true;
|
|
}
|
|
|
|
function ensureCategory(category) {
|
|
if (!categoryStats.has(category)) {
|
|
categoryStats.set(category, { correct: 0, total: 0, skipped: 0 });
|
|
}
|
|
|
|
return categoryStats.get(category);
|
|
}
|
|
|
|
function renderQuestion() {
|
|
const question = questions[currentIndex];
|
|
answered = false;
|
|
|
|
elements.questionCounter.textContent = `Frage ${currentIndex + 1} von ${questions.length}`;
|
|
elements.scoreCounter.textContent = `${score} richtig`;
|
|
elements.categoryTitle.textContent = question.category;
|
|
elements.categoryHint.textContent = question.hint || '';
|
|
elements.questionText.textContent = question.text;
|
|
elements.answerFeedback.textContent = '';
|
|
elements.answerFeedback.className = 'fluxcheck-result';
|
|
elements.nextQuestion.disabled = true;
|
|
elements.checkAnswer.disabled = false;
|
|
elements.skipQuestion.disabled = false;
|
|
|
|
elements.answerOptions.innerHTML = '';
|
|
question.answers.forEach((answer, index) => {
|
|
const label = document.createElement('label');
|
|
const input = document.createElement('input');
|
|
const text = document.createElement('span');
|
|
|
|
input.type = 'radio';
|
|
input.name = 'answer';
|
|
input.value = String(index);
|
|
text.textContent = answer;
|
|
|
|
label.append(input, text);
|
|
elements.answerOptions.append(label);
|
|
});
|
|
}
|
|
|
|
function markAnswered(isCorrect, skipped) {
|
|
const question = questions[currentIndex];
|
|
const stats = ensureCategory(question.category);
|
|
stats.total += 1;
|
|
|
|
if (skipped) {
|
|
stats.skipped += 1;
|
|
}
|
|
|
|
if (isCorrect) {
|
|
stats.correct += 1;
|
|
score += 1;
|
|
}
|
|
}
|
|
|
|
function finishIfDone() {
|
|
if (currentIndex < questions.length - 1) {
|
|
currentIndex += 1;
|
|
renderQuestion();
|
|
return;
|
|
}
|
|
|
|
testRoot.hidden = true;
|
|
elements.testResult.hidden = false;
|
|
elements.resultSummary.textContent = `Du hast ${score} von ${questions.length} Fragen richtig beantwortet.`;
|
|
elements.categorySummary.innerHTML = '';
|
|
Array.from(categoryStats.entries()).forEach(([category, stats]) => {
|
|
const skipped = stats.skipped ? `, ${stats.skipped} übersprungen` : '';
|
|
const item = document.createElement('li');
|
|
const categoryLabel = document.createElement('strong');
|
|
|
|
categoryLabel.textContent = `${category}:`;
|
|
item.append(categoryLabel, ` ${stats.correct} von ${stats.total} richtig${skipped}`);
|
|
elements.categorySummary.append(item);
|
|
});
|
|
}
|
|
|
|
elements.checkAnswer.addEventListener('click', () => {
|
|
if (answered) {
|
|
return;
|
|
}
|
|
|
|
const question = questions[currentIndex];
|
|
const selected = elements.answerOptions.querySelector('input[type="radio"]:checked');
|
|
|
|
if (!selected) {
|
|
elements.answerFeedback.textContent = 'Wähle zuerst eine Antwort aus oder überspringe die Frage.';
|
|
elements.answerFeedback.className = 'fluxcheck-result wrong';
|
|
return;
|
|
}
|
|
|
|
const isCorrect = Number(selected.value) === question.correct;
|
|
markAnswered(isCorrect, false);
|
|
answered = true;
|
|
elements.answerFeedback.textContent = (isCorrect ? 'Richtig. ' : 'Noch nicht. ') + question.explanation;
|
|
elements.answerFeedback.className = 'fluxcheck-result ' + (isCorrect ? 'correct' : 'wrong');
|
|
elements.scoreCounter.textContent = `${score} richtig`;
|
|
elements.nextQuestion.disabled = false;
|
|
elements.checkAnswer.disabled = true;
|
|
elements.skipQuestion.disabled = true;
|
|
});
|
|
|
|
elements.skipQuestion.addEventListener('click', () => {
|
|
if (!answered) {
|
|
markAnswered(false, true);
|
|
}
|
|
|
|
finishIfDone();
|
|
});
|
|
|
|
elements.nextQuestion.addEventListener('click', finishIfDone);
|
|
|
|
elements.restartTest.addEventListener('click', () => {
|
|
currentIndex = 0;
|
|
score = 0;
|
|
categoryStats.clear();
|
|
testRoot.hidden = false;
|
|
elements.testResult.hidden = true;
|
|
renderQuestion();
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(script.dataset.questionsSrc);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
|
|
const payload = await response.json();
|
|
questions = Array.isArray(payload) ? payload : payload.questions;
|
|
|
|
if (!Array.isArray(questions) || questions.length === 0) {
|
|
setError('Dieser Fragenkatalog enthält noch keine Fragen.');
|
|
return;
|
|
}
|
|
|
|
renderQuestion();
|
|
} catch (error) {
|
|
console.error(error);
|
|
setError('Der Fragenkatalog konnte nicht geladen werden.');
|
|
}
|
|
});
|