478 lines
16 KiB
JavaScript
478 lines
16 KiB
JavaScript
// 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();
|
||
} 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
|
||
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 {
|
||
// Pfad für TYPO3 fileadmin - verwende den Symlink
|
||
// Entferne führenden Slash falls vorhanden
|
||
const cleanPath = mdFile.startsWith('/') ? mdFile.substring(1) : mdFile;
|
||
// Ersetze Bundeslaender durch den Symlink
|
||
const fullPath = cleanPath.replace(/^Bundeslaender\//, 'Bundeslaender-repo/');
|
||
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);
|
||
}
|
||
}
|
||
|
||
// Initialisierung beim Seitenladen
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
loadFinderStructure();
|
||
addResetButton(); // Optional
|
||
observeSelectChanges();
|
||
}); |