world-game/src/router/index.js

133 lines
3.5 KiB
JavaScript
Raw Normal View History

import { slides } from "@state/slides.svelte";
import { site } from "@state/site.svelte";
import { locale } from "@state/locale.svelte";
let siteInitialized = false;
function normalizePath(path) {
return path === "/" ? "/home" : path;
}
/**
* Trouve l'index de la slide correspondant au path.
* Si le path exact n'existe pas, essaie le chemin parent
* (ex: /blog/article-slug /blog).
*/
function findSlideIndex(path) {
let idx = slides.getIndexByPath(path);
if (idx !== -1) return idx;
const parentPath = path.replace(/\/[^/]+$/, "");
if (parentPath) return slides.getIndexByPath(parentPath);
return -1;
}
async function loadSlide(path) {
let slidePath = path;
let idx = slides.getIndexByPath(slidePath);
// Sub-page: resolve to parent slide (ex: /blog/slug → /blog)
if (idx === -1) {
const parentPath = path.replace(/\/[^/]+$/, "");
if (parentPath && parentPath !== path) {
const parentIdx = slides.getIndexByPath(parentPath);
if (parentIdx !== -1) {
idx = parentIdx;
slidePath = parentPath;
} else if (!siteInitialized) {
// Slides not yet initialized — assume sub-page, fetch parent to bootstrap
slidePath = parentPath;
}
}
}
if (idx !== -1) {
const slide = slides.all[idx];
if (slide.loaded || slide.loading) return;
slides.setLoading(slidePath, true);
}
try {
// Fetch the actual slide path (parent), not the sub-page
const response = await fetch(`${slidePath}.json`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (!siteInitialized && data.site) {
site.set(data.site);
locale.initialize(data.site.language, data.site.languages);
slides.init(data.site.navigation);
siteInitialized = true;
}
slides.setData(slidePath, data);
} catch (error) {
console.error(`[router] Failed to load slide ${slidePath}:`, error);
slides.setLoading(slidePath, false);
}
}
function loadAllSlidesInBackground(exceptPath) {
slides.all
.filter((s) => s.path !== exceptPath)
.forEach((s) => loadSlide(s.path));
}
export function slideTo(path, { skipHistory = false } = {}) {
path = normalizePath(path);
if (!skipHistory) {
history.pushState({}, "", path === "/home" ? "/" : path);
}
const idx = findSlideIndex(path);
const slidePath = idx !== -1 ? slides.all[idx].path : path;
if (idx !== -1 && slides.all[idx].title) {
document.title = `${slides.all[idx].title} — World Game`;
}
slides.slideTo(slidePath);
if (idx !== -1 && !slides.all[idx].loaded) {
loadSlide(slidePath);
}
}
export async function initRouter() {
const initialPath = normalizePath(window.location.pathname);
await loadSlide(initialPath);
const idx = findSlideIndex(initialPath);
if (idx !== -1) {
slides.setActiveIndex(idx);
}
loadAllSlidesInBackground(
idx !== -1 ? slides.all[idx].path : initialPath
);
window.addEventListener("popstate", () => {
const path = normalizePath(window.location.pathname);
slideTo(path, { skipHistory: true });
});
document.addEventListener("click", (e) => {
const link = e.target.closest("a");
if (!link) return;
const url = new URL(link.href, window.location.origin);
if (
url.origin === window.location.origin &&
!link.target &&
!link.hasAttribute("download")
) {
e.preventDefault();
slideTo(url.pathname);
}
});
}
// Keep navigateTo as alias so existing views don't break
export const navigateTo = slideTo;