Werde-Informatiklehrerin/www/Steuerungsdateien/finder.js
Sven Lubenau 3a2240b8a2 Implementiere Persona-System mit dynamischen Dropdowns
- Füge personas.js und personas.yml hinzu für Persona-Management
- Implementiere dynamische Bundesland/Ausbildung-Dropdowns in Persona-Texten
- Korrigiere Pfad-Handling in finder.js für Bundeslaender-repo Symlink
- Erweitere CSS für Persona-Auswahl und Dropdown-Styling
- Füge Persona-Bilder und Mapping-Dateien für alle Bundesländer hinzu
- Implementiere Button-basierte Empfehlungsanzeige
- Korrigiere Platzhalter in Persona-Texten (entferne ungefüllte Platzhalter)
2025-09-12 19:10:43 +02:00

488 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// finder.js Logik für den Step-basierten Finder im Tab 2 mit YAML-Ausgabe
let steps = []; // Alle geladenen Schritte aus structure.yml
let answers = []; // User-Antworten
let selectedBundesland = ''; // Aktuelles Bundesland
let currentPath = []; // Aktueller Pfad durch den Entscheidungsbaum
// Lädt Finderstruktur aus YML und speichert sie global
async function loadFinderStructure() {
try {
const response = await fetch('Steuerungsdateien/finder.yml');
const text = await response.text();
const data = jsyaml.load(text);
steps = data.steps;
console.log('[DEBUG] Finderstruktur geladen:', steps);
resetFinder();
renderBundeslandStep();
// Zeige Empfehlungsbereich beim Finder-Tab an
showFinderRecommendation();
} catch (err) {
console.error("Fehler beim Laden der structure.yml:", err);
}
}
// Setzt den Finder zurück
function resetFinder() {
answers = [];
selectedBundesland = '';
currentPath = ['step1'];
document.getElementById('result').style.display = 'none';
document.getElementById('dynamic-steps').innerHTML = '';
}
// Baut das initiale Bundesland-Auswahlfeld mit Arrow und Styling
function renderBundeslandStep() {
const step1 = steps.find(s => s.id === 'step1');
if (!step1) return;
const container = document.getElementById('dynamic-steps');
container.innerHTML = ''; // Reset
const div = document.createElement('div');
div.className = 'step active';
div.id = 'step1';
const label = document.createElement('label');
label.textContent = step1.frage;
div.appendChild(label);
// Erstelle den wrapper mit Klasse für bessere Auswählbarkeit
const wrapper = document.createElement('div');
wrapper.className = 'select-wrapper'; // NEU
wrapper.style.position = 'relative';
wrapper.style.display = 'inline-block'; // NEU
const select = document.createElement('select');
select.className = 'select purple auto-width';
select.id = 'bundesland-steps';
select.name = 'bundesland';
const defaultOpt = document.createElement('option');
defaultOpt.textContent = 'Bitte wählen';
defaultOpt.value = '';
select.appendChild(defaultOpt);
step1.options.forEach(opt => {
const option = document.createElement('option');
option.value = opt.value;
option.textContent = opt.label;
select.appendChild(option);
});
// Vorauswahl des Bundeslandes, falls vorhanden
if (selectedBundesland) {
select.value = selectedBundesland;
}
select.addEventListener('change', (e) => {
const val = e.target.value;
if (val !== '') {
// Wenn Bundesland geändert wird, Pfad neu berechnen und alle nachfolgenden Steps entfernen
recalculatePathFrom('step1', val);
onBundeslandSelected(val);
}
});
const arrow = document.createElement('div');
arrow.className = 'select-arrow';
wrapper.appendChild(select);
wrapper.appendChild(arrow);
div.appendChild(wrapper);
const info = document.createElement('div');
info.className = 'info';
info.textContent = step1.info;
div.appendChild(info);
container.appendChild(div);
setTimeout(() => {
resizeSelect(select);
}, 0);
}
// Wird aufgerufen, wenn das Bundesland gewählt wurde (step1)
function onBundeslandSelected(value) {
selectedBundesland = value;
answers[0] = value;
document.getElementById('result').style.display = 'none';
// Alle nachfolgenden Steps entfernen
removeStepsAfter('step1');
// Step 2 rendern
const step2 = steps.find(s => s.id === 'step2');
if (step2) renderStep(step2);
}
// Rendert einen Step (Frage + Optionen)
function renderStep(step) {
const container = document.getElementById('dynamic-steps');
// Prüfen, ob dieser Step bereits existiert
let stepElement = document.getElementById(step.id);
if (stepElement) {
// Falls der Step bereits existiert, nur Info anzeigen
const info = stepElement.querySelector('.info');
if (info) info.style.display = 'block';
// Falls der Step bereits existiert und eine Antwort hat, markiere den Button
const stepIndex = steps.findIndex(s => s.id === step.id);
const selectedValue = answers[stepIndex];
if (selectedValue) {
const buttons = stepElement.querySelectorAll('button');
buttons.forEach(btn => {
if (btn.dataset.value === selectedValue) {
btn.classList.add('selected');
} else {
btn.classList.remove('selected');
}
});
}
stepElement.scrollIntoView({ behavior: 'smooth' });
return;
}
// Neuen Step erstellen
const div = document.createElement('div');
div.className = 'step active';
div.id = step.id;
const frage = document.createElement('label');
frage.textContent = step.frage;
div.appendChild(frage);
const buttonGroup = document.createElement('div');
buttonGroup.className = 'button-group';
step.options.forEach(opt => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'button';
btn.textContent = opt.label;
btn.dataset.value = opt.value;
// Wenn bereits eine Antwort ausgewählt ist, markieren
const stepIndex = steps.findIndex(s => s.id === step.id);
if (answers[stepIndex] === opt.value) {
btn.classList.add('selected');
}
btn.onclick = () => {
// Vorher ausgewählte Buttons deselektieren
buttonGroup.querySelectorAll('button').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
// Antwort speichern
const stepIndex = steps.findIndex(s => s.id === step.id);
answers[stepIndex] = opt.value;
// Info ausblenden
div.querySelector('.info').style.display = 'none';
// Pfad neu berechnen und alle nachfolgenden Steps entfernen
recalculatePathFrom(step.id, opt.value);
removeStepsAfter(step.id);
// Abhängig von der Option weitergehen oder Ergebnis anzeigen
if (opt.result) {
// Am Ende des Pfades - Ergebnis anzeigen
const code = buildResultCode();
loadResult(code);
} else if (opt.next) {
// Nächsten Step anzeigen
const nextStep = steps.find(s => s.id === opt.next);
if (nextStep) renderStep(nextStep);
}
};
buttonGroup.appendChild(btn);
});
div.appendChild(buttonGroup);
const info = document.createElement('div');
info.className = 'info';
info.textContent = step.info;
div.appendChild(info);
container.appendChild(div);
div.scrollIntoView({ behavior: 'smooth' });
// Step zum aktiven Pfad hinzufügen
if (!currentPath.includes(step.id)) {
currentPath.push(step.id);
}
}
// Berechnet den Pfad ausgehend von einem bestimmten Step neu
function recalculatePathFrom(stepId, selectedValue) {
const stepIndex = steps.findIndex(s => s.id === stepId);
// Aktualisiere den aktuellen Pfad (entferne alle nach dem geänderten Step)
currentPath = currentPath.slice(0, currentPath.indexOf(stepId) + 1);
// Aktualisiere die Antworten (behalte nur Antworten bis zum aktuellen Step)
answers = answers.slice(0, stepIndex + 1);
// Setze die ausgewählte Antwort
answers[stepIndex] = selectedValue;
console.log('[DEBUG] Neuer Pfad berechnet ab', stepId, 'Pfad:', currentPath, 'Antworten:', answers);
// Ergebnis ausblenden, da sich der Pfad geändert hat
document.getElementById('result').style.display = 'none';
}
// Entfernt alle Steps nach dem angegebenen Step aus dem DOM
function removeStepsAfter(stepId) {
const container = document.getElementById('dynamic-steps');
const stepElements = container.querySelectorAll('.step');
let removeFlag = false;
stepElements.forEach(el => {
if (removeFlag) {
container.removeChild(el);
}
if (el.id === stepId) {
removeFlag = true;
}
});
// Ergebnis ausblenden
document.getElementById('result').style.display = 'none';
}
// Generiert den Code für das Ergebnis aus den Antworten
function buildResultCode() {
// Bundesland-Code mapping - korrigiert für die tatsächlichen Verzeichnisnamen
const bundeslandMapping = {
'BW': '1_BW', // Baden-Württemberg
'BY': '2_BY', // Bayern
'BE': '3_BE', // Berlin
'BB': '4_BB', // Brandenburg
'HB': '5_HB', // Bremen
'HH': '6_HH', // Hamburg
'HE': '7_HE', // Hessen
'MV': '8_MV', // Mecklenburg-Vorpommern
'NI': '9_NI', // Niedersachsen
'NW': '10_NW', // Nordrhein-Westfalen
'RP': '11_RP', // Rheinland-Pfalz
'SL': '12_SL', // Saarland
'SN': '13_SN', // Sachsen
'ST': '14_ST', // Sachsen-Anhalt
'SH': '15_SH', // Schleswig-Holstein
'TH': '16_TH' // Thüringen
};
console.log('[DEBUG] Alle Antworten vor Filterung:', answers);
// Stelle sicher, dass answers ein Array ist und alle Elemente definiert sind
const cleanAnswers = answers.filter(answer => answer !== undefined && answer !== null && answer !== '');
console.log('[DEBUG] Bereinigte Antworten:', cleanAnswers);
if (cleanAnswers.length === 0) {
console.error('[ERROR] Keine gültigen Antworten gefunden');
return 'error/no-answers';
}
const bundesland = bundeslandMapping[cleanAnswers[0]] || cleanAnswers[0];
// Spezielle Behandlung für HS (Hochschulstudium)
let restCode;
if (cleanAnswers[1] === 'HS') {
// Bei HS: HS-BA-1F oder HS-MA-2F
restCode = cleanAnswers.slice(1).join('-');
} else {
// Bei LA: LA-BA-1F oder LA-MA-2F
restCode = cleanAnswers.slice(1).join('-');
}
const code = `${bundesland}/${restCode}`;
console.log('[DEBUG] Finaler Code:', code);
return code;
}
// Ergebnis anzeigen am Ende des Flows
async function loadResult(code) {
// 1. Ermittle den Pfad zur .md-Datei-Liste (z.B. Bundeslaender/1_BW/BA-2F.md)
// 2. Lade die Datei mit den Pfaden zu den eigentlichen Texten
// 3. Lade alle .md-Dateien in der Reihenfolge, parse sie und zeige sie als <article> an
let codeFile = `Bundeslaender/${code}.md`;
const resultContainer = document.getElementById('result');
const mainSection = resultContainer.querySelector('.main-section');
mainSection.innerHTML = '';
// Hilfsfunktion: Markdown-Abschnitt parsen und als Article rendern
function renderMarkdownArticle(mdText) {
// Splitte in Abschnitte nach ##
const sections = mdText.split(/\n(?=## )/);
return sections.map(section => {
let lines = section.trim().split('\n');
if (!lines[0].startsWith('##')) return '';
let headline = lines[0].replace(/^## /, '');
let marginal = '';
let textStart = 1;
// Prüfe zweite Zeile auf Link
if (lines[1] && lines[1].startsWith('http')) {
const linkLine = lines[1];
// Prüfe ob es ein Markdown-Link mit Text ist: [Text](URL)
const markdownLinkMatch = linkLine.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
if (markdownLinkMatch) {
// Markdown-Link gefunden: [Text](URL)
const linkText = markdownLinkMatch[1];
const linkUrl = markdownLinkMatch[2];
marginal = `<blockquote class='marginal'><a href="${linkUrl}" target="_blank">${linkText}</a></blockquote>`;
} else {
// Nur URL ohne Text - Fallback verwenden
marginal = `<blockquote class='marginal'><a href="${linkLine}" target="_blank">Externer Link</a></blockquote>`;
}
textStart = 2;
}
let text = lines.slice(textStart).join('\n');
return `<article class="content-article">
<div class="layout">
<div class="main-content">
<h2>${headline}</h2>
<div class="text-content">${marked.parse(text)}</div>
</div>
<div class="sidebar">${marginal}</div>
</div>
</article>`;
}).join('');
}
// Lade die Datei mit den Pfaden zu den eigentlichen Texten
try {
const pfadRes = await fetch(codeFile);
if (!pfadRes.ok) throw new Error('Datei nicht gefunden: ' + codeFile);
const pfadText = await pfadRes.text();
const mdFiles = pfadText.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
let allArticles = '';
for (const mdFile of mdFiles) {
try {
// Entferne führenden Slash falls vorhanden
const cleanPath = mdFile.startsWith('/') ? mdFile.substring(1) : mdFile;
// Füge Bundeslaender-repo/ hinzu, falls nicht bereits vorhanden
const fullPath = cleanPath.startsWith('Bundeslaender-repo/') ? cleanPath : `Bundeslaender-repo/${cleanPath}`;
const mdRes = await fetch(fullPath);
if (!mdRes.ok) throw new Error('Datei nicht gefunden: ' + fullPath);
const mdText = await mdRes.text();
allArticles += renderMarkdownArticle(mdText);
} catch (err) {
allArticles += `<article class="content-article"><div class='main-content'><h2>Datei nicht gefunden</h2><div class='text-content'>${mdFile}</div></div></article>`;
}
}
mainSection.innerHTML = allArticles;
resultContainer.style.display = 'block';
resultContainer.scrollIntoView({ behavior: 'smooth' });
} catch (err) {
mainSection.innerHTML = `<article class="content-article"><div class='main-content'><h2>Fehler</h2><div class='text-content'>${err.message}</div></div></article>`;
resultContainer.style.display = 'block';
}
}
// Lädt und zeigt die zusätzlichen Abschnitte an
function loadAdditionalSections(sections, resultContainer) {
if (!resultContainer) {
console.error('[ERROR] Kein gültiger Container für zusätzliche Abschnitte');
return;
}
// Trennlinie hinzufügen vor den zusätzlichen Abschnitten
const divider = document.createElement('hr');
divider.className = 'section-divider';
resultContainer.appendChild(divider);
// Jeden zusätzlichen Abschnitt laden
sections.forEach((sectionCode, index) => {
const path = `data/finder/texte/${sectionCode}.yml`;
console.log(`[DEBUG] Lade zusätzlichen Abschnitt ${index + 1} von:`, path);
fetch(path)
.then(res => res.ok ? res.text() : null)
.then(txt => txt ? jsyaml.load(txt) : null)
.then(data => {
if (!data) {
console.warn(`[WARN] Zusätzlicher Abschnitt ${sectionCode} konnte nicht geladen werden.`);
return;
}
// Zusätzlichen Abschnitt hinzufügen
appendAdditionalSection(data, index + 1, sectionCode, resultContainer);
})
.catch(err => {
console.error(`[ERROR] Fehler beim Laden des zusätzlichen Abschnitts ${sectionCode}:`, err);
});
});
}
// Fügt einen zusätzlichen Abschnitt zum DOM hinzu
function appendAdditionalSection(data, index, sectionCode, resultContainer) {
if (!resultContainer) {
console.error('[ERROR] Kein gültiger Container zum Anhängen des zusätzlichen Abschnitts');
return;
}
// Neuen Abschnitt erstellen
const section = document.createElement('div');
section.className = 'additional-section';
section.id = `section-${sectionCode}`;
section.innerHTML = `
<h2>${data.Headline || `Zusätzliche Information ${index}`}</h2>
<div class="text-content">${data.Text || '...'}</div>
<div class="tags">${data.Tags || ''}</div>
<div class="marginal">${data.Marginal || ''}</div>
`;
resultContainer.appendChild(section);
}
// Fügt einen Reset-Button hinzu (optional)
function addResetButton() {
const container = document.getElementById('dynamic-steps').parentElement;
let resetBtn = document.getElementById('reset-finder');
if (!resetBtn) {
resetBtn = document.createElement('button');
resetBtn.id = 'reset-finder';
resetBtn.className = 'button secondary';
resetBtn.textContent = 'Neu starten';
resetBtn.onclick = () => {
resetFinder();
renderBundeslandStep();
if (typeof showTab === 'function') showTab('tab2');
};
container.appendChild(resetBtn);
}
}
// Zeigt den Empfehlungsbereich für den Finder-Tab an
function showFinderRecommendation() {
const recommendation = document.querySelector('.recommendation');
if (recommendation) {
recommendation.style.display = 'block';
}
}
// Initialisierung beim Seitenladen
window.addEventListener('DOMContentLoaded', () => {
loadFinderStructure();
addResetButton(); // Optional
observeSelectChanges();
});