Milestone 0/1: shell, calendar/psalter/hours scaffold, Prime
Deploy / deploy (push) Failing after 1m8s
Deploy / deploy (push) Failing after 1m8s
Client-side-first PWA (Vite/TS, no backend) per the approved plan: day- navigable shell listing all 8 hours, Prime fully resolves via the calendar -> psalter -> ordo pipeline, the other 7 hours are registered but flagged not-implemented. Sanctoral/temporal calendar data uses a day -> id indirection layer (saints, easter-offsets, fixed-date-calendar) so reassigning a feast to a different day is a data edit, not a code change. Docker (Caddy-serving-static) + Gitea CI workflow scaffolded to match the eec/drip/bookshop operational pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { getState, setDate, setSelectedHour, subscribe } from './app-state';
|
||||
import { HOUR_IDS, type HourId } from './hours/types';
|
||||
|
||||
// Real paths (reground.org/vu/2026-08-09/prime), not hash routing — the
|
||||
// container's Caddy does SPA fallback via try_files, so there's no need to
|
||||
// avoid a rewrite rule the way a bare static docroot would have.
|
||||
const BASE = import.meta.env.BASE_URL;
|
||||
|
||||
function isHourId(value: string): value is HourId {
|
||||
return (HOUR_IDS as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function parseLocation(): { date?: string; hour?: HourId } {
|
||||
const path = window.location.pathname;
|
||||
if (!path.startsWith(BASE)) return {};
|
||||
const [date, hour] = path.slice(BASE.length).split('/').filter(Boolean);
|
||||
return {
|
||||
date: date && /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined,
|
||||
hour: hour && isHourId(hour) ? hour : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function urlFor(date: string, hour: HourId): string {
|
||||
return `${BASE}${date}/${hour}`;
|
||||
}
|
||||
|
||||
let lastUrl = '';
|
||||
|
||||
function pushUrl(): void {
|
||||
const { date, selectedHour } = getState();
|
||||
const url = urlFor(date, selectedHour);
|
||||
if (url === lastUrl) return;
|
||||
lastUrl = url;
|
||||
window.history.pushState(null, '', url);
|
||||
}
|
||||
|
||||
export function initRouter(): void {
|
||||
const { date, hour } = parseLocation();
|
||||
if (date) setDate(date);
|
||||
if (hour) setSelectedHour(hour);
|
||||
|
||||
const { date: currentDate, selectedHour } = getState();
|
||||
lastUrl = urlFor(currentDate, selectedHour);
|
||||
window.history.replaceState(null, '', lastUrl);
|
||||
|
||||
subscribe(() => pushUrl());
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
const parsed = parseLocation();
|
||||
// Set lastUrl first so the notify() inside setDate/setSelectedHour below
|
||||
// doesn't turn this back-navigation into a new forward pushState.
|
||||
lastUrl = window.location.pathname;
|
||||
if (parsed.date) setDate(parsed.date);
|
||||
if (parsed.hour) setSelectedHour(parsed.hour);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user