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,80 @@
|
||||
import { getState } from '../app-state';
|
||||
import { resolveOrdo } from '../hours';
|
||||
import type { ResolvedPart, ResolvedText, ResolvedVerse } from '../hours/types';
|
||||
import { hourLabel } from './format';
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function cellFor(resolved: ResolvedText | ResolvedVerse, lang: string): string {
|
||||
const status = resolved.status[lang] ?? 'missing';
|
||||
const text = resolved.text[lang];
|
||||
if (status === 'missing' || !text) {
|
||||
return '<span class="text-pending">(translation pending)</span>';
|
||||
}
|
||||
const escaped = escapeHtml(text);
|
||||
return status === 'draft' ? `<span class="text-draft" title="unverified draft text">${escaped}</span>` : escaped;
|
||||
}
|
||||
|
||||
function renderColumns(resolved: ResolvedText | ResolvedVerse, languages: readonly string[]): string {
|
||||
const cols = languages
|
||||
.map((lang) => `<div class="lang-column" lang="${lang}">${cellFor(resolved, lang)}</div>`)
|
||||
.join('');
|
||||
return `<div class="lang-columns lang-columns-${languages.length}">${cols}</div>`;
|
||||
}
|
||||
|
||||
function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
||||
switch (part.kind) {
|
||||
case 'hymn':
|
||||
case 'chapter':
|
||||
case 'responsory':
|
||||
case 'versicle':
|
||||
case 'prayer':
|
||||
return `
|
||||
<section class="ordo-part ordo-part-${part.kind}">
|
||||
<h3 class="ordo-part-label">${hourLabel(part.kind)}</h3>
|
||||
${renderColumns(part.text, languages)}
|
||||
</section>
|
||||
`;
|
||||
case 'psalm':
|
||||
return `
|
||||
<section class="ordo-part ordo-part-psalm">
|
||||
<h3 class="ordo-part-label">Psalm ${part.psalmNumber}</h3>
|
||||
${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
|
||||
<ol class="psalm-verses">
|
||||
${part.verses.map((v) => `<li class="psalm-verse">${renderColumns(v, languages)}</li>`).join('')}
|
||||
</ol>
|
||||
</section>
|
||||
`;
|
||||
case 'canticle':
|
||||
return `
|
||||
<section class="ordo-part ordo-part-canticle">
|
||||
<h3 class="ordo-part-label">Canticle</h3>
|
||||
${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export function renderHourView(container: HTMLElement): void {
|
||||
const { date, selectedHour, languages } = getState();
|
||||
const ordo = resolveOrdo(selectedHour, date);
|
||||
|
||||
if (ordo.notImplemented) {
|
||||
container.innerHTML = `
|
||||
<div class="hour-view hour-view-pending">
|
||||
<h2>${hourLabel(selectedHour)}</h2>
|
||||
<p>This hour hasn't been built yet.</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="hour-view">
|
||||
<h2>${hourLabel(selectedHour)}</h2>
|
||||
${ordo.parts.map((part) => renderPart(part, languages)).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user