#10 URL-Routing fuer Podcast-Tiefenlinks
Backend: - SPA-Fallback: catch-all-Route liefert index.html, falls keine statische Datei matcht (mit Ausnahme von /api/* und /audio/*). Dadurch funktionieren Tiefen- Links wie /ldn oder /neu-denken direkt. Frontend: - loadApp() liest pathname und laedt den passenden Podcast direkt, falls die ID in /api/podcasts vorkommt; sonst klassischer Selector. - selectPodcast() updated den Pfad per history.pushState, damit Bookmarks und Sharing funktionieren. - popstate-Handler reagiert auf Browser-Back/Forward. - showPodcastList() setzt den Pfad auf '/'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1f6f18524
commit
6f53f35c09
@ -703,6 +703,25 @@ def startup():
|
||||
if os.path.isdir(AUDIO_DIR):
|
||||
app.mount("/audio", StaticFiles(directory=AUDIO_DIR), name="audio")
|
||||
|
||||
# Serve webapp as static files (fallback)
|
||||
if os.path.isdir(STATIC_DIR):
|
||||
app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")
|
||||
|
||||
# SPA-Routing: erst statische Files versuchen, sonst index.html zurueckliefern.
|
||||
# Damit funktionieren Tiefen-Links wie /ldn oder /neu-denken (Issue #10).
|
||||
@app.get("/")
|
||||
def spa_root():
|
||||
index = Path(STATIC_DIR) / "index.html"
|
||||
if index.is_file():
|
||||
return FileResponse(str(index))
|
||||
raise HTTPException(404)
|
||||
|
||||
|
||||
@app.get("/{path:path}")
|
||||
def spa_fallback(path: str):
|
||||
if path.startswith("api/") or path.startswith("audio/"):
|
||||
raise HTTPException(404)
|
||||
static_path = Path(STATIC_DIR) / path
|
||||
if static_path.is_file():
|
||||
return FileResponse(str(static_path))
|
||||
index = Path(STATIC_DIR) / "index.html"
|
||||
if index.is_file():
|
||||
return FileResponse(str(index))
|
||||
raise HTTPException(404)
|
||||
|
||||
@ -1685,7 +1685,13 @@ async function loadApp() {
|
||||
const resp = await fetch(`${API_BASE}/api/podcasts`);
|
||||
if (resp.ok) {
|
||||
const podcasts = await resp.json();
|
||||
if (podcasts.length === 1) {
|
||||
// URL-Routing: /<podcast-id> oeffnet direkt diesen Podcast
|
||||
const pathPodcast = window.location.pathname.replace(/^\//, '').replace(/\/$/, '').split('/')[0];
|
||||
const fromUrl = pathPodcast && podcasts.find(p => p.id === pathPodcast);
|
||||
if (fromUrl) {
|
||||
ALL_PODCASTS = podcasts;
|
||||
await selectPodcast(fromUrl.id, /*fromUrl*/ true);
|
||||
} else if (podcasts.length === 1) {
|
||||
// Single podcast → load directly
|
||||
await selectPodcast(podcasts[0].id);
|
||||
} else if (podcasts.length > 1) {
|
||||
@ -1694,6 +1700,15 @@ async function loadApp() {
|
||||
} else {
|
||||
throw new Error('No podcasts found');
|
||||
}
|
||||
// Browser-Back/Forward
|
||||
window.addEventListener('popstate', async () => {
|
||||
const p = window.location.pathname.replace(/^\//, '').replace(/\/$/, '').split('/')[0];
|
||||
if (!p) {
|
||||
showPodcastSelector(podcasts);
|
||||
} else if (podcasts.find(x => x.id === p) && p !== CURRENT_PODCAST) {
|
||||
await selectPodcast(p, true);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@ -1712,11 +1727,14 @@ async function loadApp() {
|
||||
}
|
||||
}
|
||||
|
||||
async function selectPodcast(podcastId) {
|
||||
async function selectPodcast(podcastId, fromUrl = false) {
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE}/api/podcasts/${podcastId}`);
|
||||
DATA = await resp.json();
|
||||
CURRENT_PODCAST = podcastId;
|
||||
if (!fromUrl && window.history && window.location.pathname !== `/${podcastId}`) {
|
||||
window.history.pushState({ podcast: podcastId }, '', `/${podcastId}`);
|
||||
}
|
||||
// Reset mindmap area (might have been used for selector)
|
||||
const mindmap = document.getElementById('mindmap');
|
||||
mindmap.style.overflow = 'hidden';
|
||||
@ -1914,6 +1932,9 @@ function showPodcastList() {
|
||||
CURRENT_PODCAST = null;
|
||||
document.getElementById('svg').innerHTML = '';
|
||||
document.getElementById('staffel-filters').innerHTML = '';
|
||||
if (window.history && window.location.pathname !== '/') {
|
||||
window.history.pushState({}, '', '/');
|
||||
}
|
||||
showPodcastSelector(ALL_PODCASTS);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user