From c5d6502a3500806ae28c40c714842883149c3065 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Sat, 22 Aug 2026 02:26:33 -0400 Subject: [PATCH 1/2] feat(news): newest-first, 5-per-page pagination, community-discussions window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #73. The announcements feed rendered oldest-first (REST creation order, never sorted) — every feed now sorts descending at the fetch layer, and the announcements cap rises 10 -> 30 so the archive has room. News paginates at five per static page: /{locale}/news/ plus /news/2/… with per-page canonical + hreflang parity, prev/next arrows and numbered links; the subscribe form, Show and tell, and the new community section render on page 1 only — archive pages are pure list. The community window answers 'why isn't #472 on the site': News read only Announcements (+ Show and tell), and #472 lives in Compliance & classification. Rather than flooding the archive with support threads, a third section shows the five newest discussions from every other category — original language only (D4/D5), deep links to GitHub — so the community's voice (including #472) is visible without changing what Announcements means. --- scripts/fetch-discussions.mjs | 33 +++++-- src/components/pages/NewsPage.astro | 133 ++++++++++++++++++++++++++-- src/i18n/pages/news.ts | 34 +++++-- src/pages/ar/news.astro | 4 - src/pages/ar/news/[page].astro | 14 +++ src/pages/ar/news/index.astro | 4 + src/pages/en/news.astro | 4 - src/pages/en/news/[page].astro | 14 +++ src/pages/en/news/index.astro | 4 + src/pages/fr/news.astro | 4 - src/pages/fr/news/[page].astro | 14 +++ src/pages/fr/news/index.astro | 4 + 12 files changed, 235 insertions(+), 31 deletions(-) delete mode 100644 src/pages/ar/news.astro create mode 100644 src/pages/ar/news/[page].astro create mode 100644 src/pages/ar/news/index.astro delete mode 100644 src/pages/en/news.astro create mode 100644 src/pages/en/news/[page].astro create mode 100644 src/pages/en/news/index.astro delete mode 100644 src/pages/fr/news.astro create mode 100644 src/pages/fr/news/[page].astro create mode 100644 src/pages/fr/news/index.astro diff --git a/scripts/fetch-discussions.mjs b/scripts/fetch-discussions.mjs index 37dc20e..7ad3949 100644 --- a/scripts/fetch-discussions.mjs +++ b/scripts/fetch-discussions.mjs @@ -27,10 +27,22 @@ mkdirSync(DATA_DIR, { recursive: true }); const API = "https://api.github.com/repos/CodeGateSoftware/keel/discussions?per_page=50&state=open"; -/** One output feed: category slug, destination file, item cap (#13). */ +/** + * One output feed: category slug, destination file, item cap. `exclude` + * instead of `category` = everything but those slugs (#73: the community's + * own threads — Q&A, compliance classification, ideas — get a window + * without flooding the announcements archive). + */ const FEEDS = [ - { category: "announcements", file: "discussions.json", maxItems: 10 }, + { category: "announcements", file: "discussions.json", maxItems: 30 }, { category: "show-and-tell", file: "show-and-tell.json", maxItems: 5 }, + { + category: null, + exclude: ["announcements", "show-and-tell"], + file: "community.json", + maxItems: 5, + label: "discussions", + }, ]; const headers = { @@ -61,7 +73,12 @@ const categoryUrl = (slug) => /** Write one feed's file from the fetched discussions. */ function writeFeed(feed, discussions) { const items = discussions - .filter((discussion) => discussion.category?.slug === feed.category) + .filter((discussion) => + feed.category + ? discussion.category?.slug === feed.category + : !feed.exclude.includes(discussion.category?.slug ?? ""), + ) + .sort((a, b) => b.created_at.localeCompare(a.created_at)) // newest first (#73) .slice(0, feed.maxItems) .map((discussion) => ({ number: discussion.number, @@ -76,8 +93,8 @@ function writeFeed(feed, discussions) { join(DATA_DIR, feed.file), JSON.stringify( { - category: feed.category, - categoryUrl: categoryUrl(feed.category), + category: feed.category ?? feed.label, + categoryUrl: feed.category ? categoryUrl(feed.category) : "https://github.com/CodeGateSoftware/keel/discussions", fetchedAt: new Date().toISOString(), items, }, @@ -85,7 +102,7 @@ function writeFeed(feed, discussions) { 2, ) + "\n", ); - console.log(` discussions: ${items.length} ${feed.category}(s) -> data/${feed.file}`); + console.log(` discussions: ${items.length} ${feed.category ?? feed.label}(s) -> data/${feed.file}`); } /** Degrade one feed's file: keep last-known data, else an empty stub. */ @@ -99,8 +116,8 @@ function degradeFeed(feed, reason) { out, JSON.stringify( { - category: feed.category, - categoryUrl: categoryUrl(feed.category), + category: feed.category ?? feed.label, + categoryUrl: feed.category ? categoryUrl(feed.category) : "https://github.com/CodeGateSoftware/keel/discussions", fetchedAt: null, items: [], }, diff --git a/src/components/pages/NewsPage.astro b/src/components/pages/NewsPage.astro index 3f20aa2..f5067b1 100644 --- a/src/components/pages/NewsPage.astro +++ b/src/components/pages/NewsPage.astro @@ -7,9 +7,11 @@ import { t } from "../../i18n/ui"; interface Props { locale: Locale; + /** 1-based page number; page 1 renders at /news/, archives at /news/2/… (#73). */ + page?: number; } -const { locale } = Astro.props; +const { locale, page = 1 } = Astro.props; const c = news[locale]; const chrome = t(locale); @@ -48,15 +50,35 @@ const showAndTell: DiscussionsFile = fetchedAt: null, items: [], }; + +/** Community window (#73) — newest threads from every other category. */ +const community: DiscussionsFile = + readDataFile("community.json") ?? { + category: "discussions", + categoryUrl: "https://github.com/CodeGateSoftware/keel/discussions", + fetchedAt: null, + items: [], + }; + +/** Pagination (#73) — five announcements per static page. */ +const PAGE_SIZE = 5; +const totalPages = Math.max(1, Math.ceil(feed.items.length / PAGE_SIZE)); +const current = Math.min(Math.max(1, page), totalPages); +const pageItems = feed.items.slice((current - 1) * PAGE_SIZE, current * PAGE_SIZE); +const pageHref = (n: number) => (n <= 1 ? localePath(locale, "news") : `/${locale}/news/${n}/`); --- 1 ? `${c.title} — ${current}` : c.title} description={c.description} - path={localePath(locale, "news")} - alternates={alternatesFor("news")} + path={current > 1 ? `/${locale}/news/${current}/` : localePath(locale, "news")} + alternates={ + current > 1 + ? alternatesFor("news").map((alt) => ({ ...alt, path: `/${alt.locale}/news/${current}/` })) + : alternatesFor("news") + } >
@@ -80,7 +102,7 @@ const showAndTell: DiscussionsFile =
) : (