astro check restored: tsconfig excludes public/ (the 2 MB vendored CMS bundle was what exhausted the heap) and extends tsconfigs/strict; type annotations in template scripts, z from astro/zod; exact version pins; compressHTML true (lossless whitespace, no words glued to links) and explicit trailingSlash/build.format; README: upgrading section

This commit is contained in:
2026-09-06 15:12:56 +02:00
parent e8b47ac888
commit bd95bacfc5
10 changed files with 53 additions and 25 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ export async function getStaticPaths() {
const chunks = [posts.slice(0, FIRST)];
for (let i = FIRST; i < posts.length; i += REST) chunks.push(posts.slice(i, i + REST));
const lastPage = chunks.length;
const hrefOf = (n) => (n === 1 ? '/news' : `/news/${n}`);
const hrefOf = (n: number) => (n === 1 ? '/news' : `/news/${n}`);
return chunks.map((data, i) => {
const n = i + 1;
return {
@@ -42,7 +42,7 @@ for (let n = 1; n <= page.lastPage; n++) {
if (n === 1 || n === page.lastPage || Math.abs(n - page.currentPage) <= 1) nums.push(n);
else if (nums.at(-1) !== 0) nums.push(0);
}
const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
const hrefFor = (n: number) => (n === 1 ? '/news' : `/news/${n}`);
---
<Base
title={first ? 'CTAO Science Portal - News' : `CTAO Science Portal - News, page ${page.currentPage}`}
+11 -10
View File
@@ -96,10 +96,10 @@ const readMin = readMinOf(post);
// enters it is mapped back to its governing h2/h3, whose TOC link(s) get
// aria-current="true" (set in BOTH renderings — inline <details> and rail).
// Progressive enhancement: without JS the TOC is plain working anchors.
const links = [...document.querySelectorAll('.toc a[href^="#"]')];
const links = [...document.querySelectorAll<HTMLAnchorElement>('.toc a[href^="#"]')];
if (links.length) {
let current = [];
const setCurrent = (id) => {
let current: HTMLAnchorElement[] = [];
const setCurrent = (id: string | null) => {
const next = id ? links.filter((a) => decodeURIComponent(a.hash.slice(1)) === id) : [];
if (next[0] === current[0]) return;
for (const a of current) a.removeAttribute('aria-current');
@@ -108,35 +108,36 @@ const readMin = readMinOf(post);
};
const blocks = [...document.querySelectorAll('.prose > *')];
// Governing heading = the block itself or the nearest h2/h3[id] above it
const headingFor = (el) => {
const headingFor = (el: Element) => {
for (let i = blocks.indexOf(el); i >= 0; i--) {
if (blocks[i].matches('h2[id], h3[id]')) return blocks[i].id;
}
return null; // intro before the first heading — nothing highlighted
};
const onIntersect = (entries) => {
const onIntersect = (entries: IntersectionObserverEntry[]) => {
for (const e of entries) {
if (e.isIntersecting) { setCurrent(headingFor(e.target)); break; }
}
};
let observer;
let observer: IntersectionObserver | undefined;
const observe = () => {
observer?.disconnect();
// Narrow band below the sticky header (--header-h 56px + 12px slack;
// JS can't read the token cheaply, keep in sync) — Starlight's rootMargin trick
const top = 68, band = 64;
observer = new IntersectionObserver(onIntersect, {
const io = new IntersectionObserver(onIntersect, {
rootMargin: `-${top}px 0px ${top + band - document.documentElement.clientHeight}px`,
});
blocks.forEach((b) => observer.observe(b));
observer = io;
blocks.forEach((b) => io.observe(b));
};
observe();
let timer;
let timer: ReturnType<typeof setTimeout> | undefined;
addEventListener('resize', () => { clearTimeout(timer); timer = setTimeout(observe, 200); });
// Short final section: at page bottom, the last heading wins
addEventListener('scroll', () => {
if (innerHeight + scrollY >= document.documentElement.scrollHeight - 4) {
setCurrent(headingFor(blocks.at(-1)));
setCurrent(headingFor(blocks[blocks.length - 1]));
}
}, { passive: true });
}