From ff9bafe77a5e0dd84b613bb2aae0bf2a9cb84359 Mon Sep 17 00:00:00 2001
From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>
Date: Thu, 5 Nov 2026 13:40:32 +0900
Subject: events: 年度別アーカイブとRSSフィードを追加
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
月別ページに加えて、4月〜翌3月の年度ページ(/kodaira/events/r8/)を
追加した。月別リンクは年度ごとにまとめ、今後の予定はfeed.xmlで
配信する。
---
src/pages/kodaira/events/[month].astro | 72 -------------------
src/pages/kodaira/events/[period].astro | 124 ++++++++++++++++++++++++++++++++
src/pages/kodaira/events/feed.xml.ts | 79 ++++++++++++++++++++
3 files changed, 203 insertions(+), 72 deletions(-)
delete mode 100644 src/pages/kodaira/events/[month].astro
create mode 100644 src/pages/kodaira/events/[period].astro
create mode 100644 src/pages/kodaira/events/feed.xml.ts
(limited to 'src/pages')
diff --git a/src/pages/kodaira/events/[month].astro b/src/pages/kodaira/events/[month].astro
deleted file mode 100644
index 88ce61f..0000000
--- a/src/pages/kodaira/events/[month].astro
+++ /dev/null
@@ -1,72 +0,0 @@
----
-// 月別アーカイブ(/kodaira/events/2026-11/ など)。
-// 行事予定のデータ(src/data/schedule/*.json)から、その月の予定だけを静的に生成する。
-import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
-import ScheduleTable from '@/components/ScheduleTable.astro';
-import { monthKeys, monthLabel, rowsInMonth } from '@/lib/schedule-months';
-import { SCHEDULE_BASE_PATH } from '@/data/schedule/config';
-
-export function getStaticPaths() {
- return monthKeys.map((month, i) => ({
- params: { month },
- props: {
- month,
- prev: monthKeys[i - 1] ?? null,
- next: monthKeys[i + 1] ?? null,
- },
- }));
-}
-
-const { month, prev, next } = Astro.props as {
- month: string;
- prev: string | null;
- next: string | null;
-};
-
-const label = monthLabel(month);
-const meetingRows = rowsInMonth(month, 'meeting');
-const eventRows = rowsInMonth(month, 'event');
-
-const url = (key: string) => `${SCHEDULE_BASE_PATH}${key}/`;
----
-
-
- ← 小平市のイベント・行事予定カレンダー(今後)に戻る
-
-
- {label}の小平市のイベントと、審議会等・市議会の開催予定です。開催日が過ぎたものも記録として掲載しています。
- 変更になることがありますので、各問い合わせ先へご確認ください。
-
-
- {
- meetingRows.length > 0 && (
- <>
- 審議会等・市議会の開催予定
-
-
-
- >
- )
- }
-
- {
- eventRows.length > 0 && (
- <>
- イベント
-
-
-
- >
- )
- }
-
-
-
diff --git a/src/pages/kodaira/events/[period].astro b/src/pages/kodaira/events/[period].astro
new file mode 100644
index 0000000..7ef4599
--- /dev/null
+++ b/src/pages/kodaira/events/[period].astro
@@ -0,0 +1,124 @@
+---
+// 月別・年度別アーカイブ(/kodaira/events/2026-11/、/kodaira/events/r8/ など)。
+// 行事予定のデータ(src/data/schedule/*.json)から、その期間の予定だけを静的に生成する。
+import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
+import ScheduleTable from '@/components/ScheduleTable.astro';
+import {
+ fiscalYearKeys,
+ fiscalYearLabel,
+ fiscalYearOf,
+ monthCounts,
+ monthKeys,
+ monthLabel,
+ monthsInFiscalYear,
+ rowsInFiscalYear,
+ rowsInMonth,
+} from '@/lib/schedule-months';
+import { SCHEDULE_BASE_PATH } from '@/data/schedule/config';
+
+export function getStaticPaths() {
+ return [
+ ...monthKeys.map((key, i) => ({
+ params: { period: key },
+ props: {
+ kind: 'month' as const,
+ key,
+ prev: monthKeys[i - 1] ?? null,
+ next: monthKeys[i + 1] ?? null,
+ },
+ })),
+ ...fiscalYearKeys.map((key, i) => ({
+ params: { period: key },
+ props: {
+ kind: 'fiscal' as const,
+ key,
+ prev: fiscalYearKeys[i - 1] ?? null,
+ next: fiscalYearKeys[i + 1] ?? null,
+ },
+ })),
+ ];
+}
+
+const { kind, key, prev, next } = Astro.props as {
+ kind: 'month' | 'fiscal';
+ key: string;
+ prev: string | null;
+ next: string | null;
+};
+
+const label = kind === 'fiscal' ? fiscalYearLabel(key) : monthLabel(key);
+const periodLabel = (period: string) =>
+ kind === 'fiscal' ? fiscalYearLabel(period) : monthLabel(period);
+
+const meetingRows =
+ kind === 'fiscal' ? rowsInFiscalYear(key, 'meeting') : rowsInMonth(key, 'meeting');
+const eventRows =
+ kind === 'fiscal' ? rowsInFiscalYear(key, 'event') : rowsInMonth(key, 'event');
+
+// 年度ページでは、その年度の月別ページへの一覧も並べる。
+const months = kind === 'fiscal' ? monthsInFiscalYear(key) : [];
+
+const url = (period: string) => `${SCHEDULE_BASE_PATH}${period}/`;
+---
+
+
+ ← 小平市のイベント・行事予定カレンダー(今後)に戻る
+
+
+ {label}の小平市のイベントと、審議会等・市議会の開催予定です。開催日が過ぎたものも記録として掲載しています。
+ 変更になることがありますので、各問い合わせ先へご確認ください。
+
+
+ {
+ kind === 'fiscal' && (
+
+ )
+ }
+
+ {
+ meetingRows.length > 0 && (
+ <>
+ 審議会等・市議会の開催予定
+
+
+
+ >
+ )
+ }
+
+ {
+ eventRows.length > 0 && (
+ <>
+ イベント
+
+
+
+ >
+ )
+ }
+
+ {
+ kind === 'month' && (
+
+ {fiscalYearLabel(fiscalYearOf(key))}の予定をまとめて見る
+
+ )
+ }
+
+
+
diff --git a/src/pages/kodaira/events/feed.xml.ts b/src/pages/kodaira/events/feed.xml.ts
new file mode 100644
index 0000000..860f5f2
--- /dev/null
+++ b/src/pages/kodaira/events/feed.xml.ts
@@ -0,0 +1,79 @@
+// これからの予定のRSSフィード(/kodaira/events/feed.xml)。
+// ビルド時点の日付以降に開催される予定を、開催日の早い順に最大60件出力する。
+import type { APIRoute } from 'astro';
+import { items } from '@/lib/schedule-months';
+import { entryDates } from '@/lib/schedule-dates';
+import { SCHEDULE_BASE_PATH, SITE_URL } from '@/data/schedule/config';
+
+const stripTags = (value: string) =>
+ value
+ .replace(/<[^>]*>/g, '')
+ .replace(/ /g, ' ')
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/\s+/g, ' ')
+ .trim();
+
+const escapeXml = (value: string) =>
+ value
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+
+const pad = (n: number) => String(n).padStart(2, '0');
+const now = new Date();
+const todayKey = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
+
+const toRfc822 = (date: string) => new Date(`${date}T00:00:00+09:00`).toUTCString();
+
+const entries = items
+ .map((item) => {
+ const dates = entryDates(item.type, item.row.date, item.row.place);
+ return { item, upcoming: dates.filter((date) => date >= todayKey) };
+ })
+ .filter(({ upcoming }) => upcoming.length > 0)
+ .sort((a, b) => a.upcoming[0].localeCompare(b.upcoming[0]))
+ .slice(0, 60);
+
+const itemXml = ({ item: { type, row }, upcoming }: (typeof entries)[number]) => {
+ const title = stripTags(row.title);
+ const description = [stripTags(row.place), stripTags(row.note)].filter(Boolean).join('/');
+ const datesLabel = upcoming
+ .map((date) => `${Number(date.slice(5, 7))}月${Number(date.slice(8, 10))}日`)
+ .join('、');
+ const link = `${SITE_URL}${SCHEDULE_BASE_PATH}${upcoming[0].slice(0, 7)}/`;
+ const guid = `${SITE_URL}${SCHEDULE_BASE_PATH}#${type}-${upcoming[0]}-${encodeURIComponent(title)}`;
+
+ return ` -
+ ${escapeXml(title)}
+ ${escapeXml(link)}
+ ${escapeXml(guid)}
+ ${type === 'meeting' ? '審議会等・市議会' : 'イベント'}
+ ${toRfc822(upcoming[0])}
+ ${escapeXml(`開催日:${datesLabel}${description ? `/${description}` : ''}`)}
+
`;
+};
+
+export const GET: APIRoute = () => {
+ const xml = `
+
+
+ 小平市のイベント・行事予定(審議会・市議会の日程も掲載)
+ ${SITE_URL}${SCHEDULE_BASE_PATH}
+ 小平市のイベントと、審議会等・市議会の開催予定です。市報こだいら・市公式X・LoGoフォーム・市議会の行事予定表などをもとにまとめています。
+ ja
+ ${now.toUTCString()}
+${entries.map(itemXml).join('\n')}
+
+
+`;
+
+ return new Response(xml, {
+ headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
+ });
+};
--
cgit v1.3.1