fix(routing): automatic root chapter redirects, safe workspace link handling, and legacy navigation cleanup
This commit is contained in:
parent
dd43e98748
commit
1f889f9f2f
3 changed files with 98 additions and 11 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
54
server.py
54
server.py
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue