Feat: pages Article + navigation blog/article interne
All checks were successful
Deploy / Deploy to Production (push) Successful in 19s

- Router: findSlideIndex() avec fallback parent path
  (/blog/slug → /blog) pour sub-pages
- article.json.php: réécriture — date, intro, cover, body (blocks→HTML),
  related articles (fallback siblings si vide)
- Article.svelte: sous-composant — topbar date+retour, titre Terminal,
  intro, cover, body rich text (styles :global pour blocks Kirby),
  related articles grid, responsive
- Blog.svelte: gère deux modes (liste + article) —
  intercepte les clics article via stopPropagation (avant le router),
  fetch article data, pushState pour URL, popstate pour back/forward,
  direct navigation /blog/slug sur mount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-03-10 16:55:34 +01:00
parent 3ab4b21e8c
commit 0505cc7b8e
4 changed files with 465 additions and 97 deletions

View file

@ -8,16 +8,39 @@ 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) {
const idx = slides.getIndexByPath(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(/\/[^/]+$/, "");
idx = slides.getIndexByPath(parentPath);
if (idx !== -1) slidePath = parentPath;
}
if (idx !== -1) {
const slide = slides.all[idx];
if (slide.loaded || slide.loading) return;
slides.setLoading(path, true);
slides.setLoading(slidePath, true);
}
try {
const response = await fetch(`${path}.json`);
// 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();
@ -28,10 +51,10 @@ async function loadSlide(path) {
siteInitialized = true;
}
slides.setData(path, data);
slides.setData(slidePath, data);
} catch (error) {
console.error(`[router] Failed to load slide ${path}:`, error);
slides.setLoading(path, false);
console.error(`[router] Failed to load slide ${slidePath}:`, error);
slides.setLoading(slidePath, false);
}
}
@ -48,15 +71,17 @@ export function slideTo(path, { skipHistory = false } = {}) {
history.pushState({}, "", path === "/home" ? "/" : path);
}
const idx = slides.getIndexByPath(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(path);
slides.slideTo(slidePath);
if (idx !== -1 && !slides.all[idx].loaded) {
loadSlide(path);
loadSlide(slidePath);
}
}
@ -65,12 +90,14 @@ export async function initRouter() {
await loadSlide(initialPath);
const idx = slides.getIndexByPath(initialPath);
const idx = findSlideIndex(initialPath);
if (idx !== -1) {
slides.setActiveIndex(idx);
}
loadAllSlidesInBackground(initialPath);
loadAllSlidesInBackground(
idx !== -1 ? slides.all[idx].path : initialPath
);
window.addEventListener("popstate", () => {
const path = normalizePath(window.location.pathname);