fix(routing): automatic root chapter redirects, safe workspace link handling, and legacy navigation cleanup

This commit is contained in:
Alex Mueller 2026-09-13 13:57:32 +02:00
parent dd43e98748
commit 1f889f9f2f
3 changed files with 98 additions and 11 deletions

View file

@ -454,11 +454,14 @@
const existingNav = document.getElementById('courseModuleNavigator'); const existingNav = document.getElementById('courseModuleNavigator');
if (existingNav) existingNav.remove(); if (existingNav) existingNav.remove();
// Legacy-Buttons ausblenden // Legacy-Buttons ausblenden / entfernen
const legacyActionDivs = mainEl.querySelectorAll('.card > div[style*="flex"]:last-child, main > div[style*="flex"]:last-child'); const legacyCandidates = mainEl.querySelectorAll(
legacyActionDivs.forEach(div => { '.card > div[style*="flex"]:last-child, main > div[style*="flex"]:last-child, div[style*="flex"]:last-child, ' +
if (div.querySelector('a[href*="workspace.html"]') || div.querySelector('a[href*="index.html"]')) { '.border-t, div[class*="border-t"], div[class*="justify-between"], div.mt-8.pt-6'
div.style.display = 'none'; );
legacyCandidates.forEach(el => {
if (el.querySelector('a[href*="index.html"]') || el.querySelector('a[href*="workspace.html"]') || el.querySelector('.ihk-btn') || el.innerText.includes('Nächstes Modul') || el.innerText.includes('Vorheriges Modul')) {
el.remove();
} }
}); });
@ -566,10 +569,13 @@
const existingPaneNav = theoriePane.querySelector('.course-module-navigator'); const existingPaneNav = theoriePane.querySelector('.course-module-navigator');
if (existingPaneNav) existingPaneNav.remove(); if (existingPaneNav) existingPaneNav.remove();
const legacyActionDivs = theoriePane.querySelectorAll('.card > div[style*="flex"]:last-child, div[style*="flex"]:last-child'); const legacyCandidates = theoriePane.querySelectorAll(
legacyActionDivs.forEach(div => { '.card > div[style*="flex"]:last-child, div[style*="flex"]:last-child, ' +
if (div.querySelector('a[href*="workspace.html"]') || div.querySelector('a[href*="index.html"]')) { '.border-t, div[class*="border-t"], div[class*="justify-between"], div.mt-8.pt-6'
div.style.display = 'none'; );
legacyCandidates.forEach(el => {
if (el.querySelector('a[href*="index.html"]') || el.querySelector('a[href*="workspace.html"]') || el.querySelector('.ihk-btn') || el.innerText.includes('Nächstes Modul') || el.innerText.includes('Vorheriges Modul')) {
el.remove();
} }
}); });

View file

@ -44,6 +44,41 @@ def is_trusted_source(ip: str) -> bool:
except ValueError: except ValueError:
return False return False
# ------------------------------------------------------------------------------
# 0. AUTOMATISCHES ROUTING FÜR ALLE 264 KURS-KAPITEL
# ------------------------------------------------------------------------------
CHAPTER_ROUTES = {}
def load_chapter_routes():
global CHAPTER_ROUTES
manifest_path = BASE_DIR / "assets" / "courses_manifest.js"
if not manifest_path.exists():
return
try:
content = manifest_path.read_text(encoding="utf-8")
idx = content.find("{")
if idx != -1:
json_str = content[idx:].strip()
if json_str.endswith(";"):
json_str = json_str[:-1]
manifest = json.loads(json_str)
routes = {}
for cid, c in manifest.items():
for tr in c.get("tracks", []):
tid = tr.get("id", "")
for ch in tr.get("chapters", []):
f = ch.get("folder", "")
if f:
if cid == "python":
routes[f] = f"/{tid}/{f}"
else:
routes[f] = f"/courses/{cid}/{f}"
CHAPTER_ROUTES = routes
except Exception as e:
print(f"Warnung beim Laden der Kapitel-Routen: {e}")
load_chapter_routes()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# 1. PERSISTENTER SECRET KEY # 1. PERSISTENTER SECRET KEY
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -357,6 +392,25 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
if self.is_path_blocked(path): if self.is_path_blocked(path):
return self.send_json({"error": "Zugriff verweigert (Geschützte Datei)"}, 403) return self.send_json({"error": "Zugriff verweigert (Geschützte Datei)"}, 403)
# Automatische Weiterleitung für Direktaufrufe von Kapitel-Ordnern an der Root-Ebene
# (z. B. /02_lf2_arbeitsplaetze_ausstatten/index.html -> /courses/ihk_ap1/02_lf2_arbeitsplaetze_ausstatten/index.html)
path_parts = [p for p in path.split("/") if p]
if path_parts and not path.startswith("/api/"):
first_part = path_parts[0]
if first_part in CHAPTER_ROUTES:
target_base = CHAPTER_ROUTES[first_part]
sub_path = "/".join(path_parts[1:])
if not sub_path:
sub_path = "index.html"
target_url = f"{target_base}/{sub_path}"
if parsed.query:
target_url += f"?{parsed.query}"
self.send_response(302)
self.send_header("Location", target_url)
self.send_header("Cache-Control", "no-cache")
self.end_headers()
return
# 1. ÖFFENTLICHER STATUS-ENDPUNKT (Wartungsmodus, Ankündigung, Registrierung) # 1. ÖFFENTLICHER STATUS-ENDPUNKT (Wartungsmodus, Ankündigung, Registrierung)
if path == "/api/platform/status": if path == "/api/platform/status":
return self.send_json({ return self.send_json({

View file

@ -533,8 +533,35 @@
if (htmlDoc) { if (htmlDoc) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(htmlDoc, "text/html"); const doc = parser.parseFromString(htmlDoc, "text/html");
const mainContent = doc.querySelector("main.container") || doc.body; const mainContent = doc.querySelector("main.container") || doc.querySelector("main") || doc.querySelector(".container") || doc.body;
document.getElementById("tab-content-theorie").innerHTML = mainContent.innerHTML;
// Legacy-Navigationsleisten und Dashboard-Breadcrumbs im IDE-Theorietext entfernen
const legacyBars = mainContent.querySelectorAll(
'.border-t, div[class*="border-t"], div[class*="justify-between"], div.mt-8.pt-6, .course-module-navigator, .course-sidebar-mobile-btn'
);
legacyBars.forEach(el => {
if (el.querySelector('a[href*="index.html"]') || el.querySelector('a[href*="dashboard.html"]') || el.querySelector('.ihk-btn') || el.innerText.includes('Nächstes Modul') || el.innerText.includes('Vorheriges Modul') || el.innerText.includes('← Zurück zum Dashboard')) {
el.remove();
}
});
const theorieContainer = document.getElementById("tab-content-theorie");
theorieContainer.innerHTML = mainContent.innerHTML;
// Alle Links innerhalb des Theorie-Panels absichern:
// Kapitel-Links schalten die Web-IDE um, anstatt die IDE zu verlassen
theorieContainer.querySelectorAll("a").forEach(link => {
const rawHref = link.getAttribute("href") || "";
const chapMatch = rawHref.match(/([0-9]{2}_[a-zA-Z0-9_-]+)/);
if (chapMatch && !rawHref.startsWith("http") && !rawHref.includes("workspace.html")) {
link.addEventListener("click", (e) => {
e.preventDefault();
const targetChapter = chapMatch[1];
window.location.href = `workspace.html?course=${encodeURIComponent(courseId)}&chapter=${encodeURIComponent(targetChapter)}`;
});
}
});
if (window.renderWorkspaceTheoryNavigator) { if (window.renderWorkspaceTheoryNavigator) {
window.renderWorkspaceTheoryNavigator(); window.renderWorkspaceTheoryNavigator();
} }