From c9f3bf46f0de09edb796e35f3dcff349febf75c9 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Fri, 24 Jul 2026 10:32:28 +0200 Subject: dayinfo: show feast/day name + colour (modern niedziela + traditional missalemeum) in cli/tui/web; v0.5.0 --- internal/web/render.go | 39 ++++++++++++++++++++++++++---- internal/web/render_test.go | 56 +++++++++++++++++++++++++++++++++++++------- internal/web/server.go | 23 +++++++++--------- internal/web/server_test.go | 9 +++++-- internal/web/static/base.css | 17 ++++++++++++++ 5 files changed, 118 insertions(+), 26 deletions(-) (limited to 'internal/web') diff --git a/internal/web/render.go b/internal/web/render.go index 1afee1c..9e6280c 100644 --- a/internal/web/render.go +++ b/internal/web/render.go @@ -98,16 +98,45 @@ type ilLineView struct { // spans so theme CSS can restyle them; verse/paragraph text is escaped by // html/template. lang localises the version-column labels and each section // heading's part-label word (render.LocalizeHeading, brief §3b); the -// citation/verse text is never touched. -func RenderReadings(secs []liturgy.Section, versions []string, lectionary, display, lang string) template.HTML { +// citation/verse text is never touched. dayInfo is rendered once, ahead of +// every layout's own sections, as the day's celebration header (see +// renderDayInfo) -- never translated (source-language, like the readings/ +// citations), and simply omitted when the source carried none. +func RenderReadings(secs []liturgy.Section, versions []string, lectionary, display, lang string, dayInfo liturgy.DayInfo) template.HTML { + var body template.HTML switch display { case "vertical": - return renderTemplate("readings-vertical.html", buildColumnViews(secs, versions, lectionary, lang)) + body = renderTemplate("readings-vertical.html", buildColumnViews(secs, versions, lectionary, lang)) case "interlinear": - return renderTemplate("readings-interlinear.html", buildInterlinearViews(secs, versions, lectionary, lang)) + body = renderTemplate("readings-interlinear.html", buildInterlinearViews(secs, versions, lectionary, lang)) default: - return renderTemplate("readings.html", buildColumnViews(secs, versions, lectionary, lang)) + body = renderTemplate("readings.html", buildColumnViews(secs, versions, lectionary, lang)) } + return renderDayInfo(dayInfo) + body +} + +// renderDayInfo builds the "

" header RenderReadings +// prepends to the reading pane: the celebration Name as its heading-role +// span, the temporal Season (if any) as its citation-role span. Reusing +// those two role classes (rather than inventing new ones) means every +// existing theme restyles it identically without a new CSS rule -- see +// base.css/docs/THEMES.md. Returns "" (the header is simply omitted, never +// an error) when the source yielded no DayInfo (info.Name == ""). +func renderDayInfo(info liturgy.DayInfo) template.HTML { + if info.Name == "" { + return "" + } + var b strings.Builder + b.WriteString(`

`) + b.WriteString(template.HTMLEscapeString(info.Name)) + b.WriteString(``) + if info.Season != "" { + b.WriteString(` `) + b.WriteString(template.HTMLEscapeString(info.Season)) + b.WriteString(``) + } + b.WriteString(`

`) + return template.HTML(b.String()) } // renderTemplate executes the named embedded template with data, degrading diff --git a/internal/web/render_test.go b/internal/web/render_test.go index b7f5b1e..de3c7c1 100644 --- a/internal/web/render_test.go +++ b/internal/web/render_test.go @@ -11,12 +11,52 @@ import ( func TestRenderReadings(t *testing.T) { secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} - html := string(RenderReadings(secs, []string{"wuj"}, "new", "horizontal", "pl")) + html := string(RenderReadings(secs, []string{"wuj"}, "new", "horizontal", "pl", liturgy.DayInfo{})) if !strings.Contains(html, "Ewangelia") || !strings.Contains(html, "class=") { t.Errorf("reading pane missing heading/classes: %q", html[:min(200, len(html))]) } } +// TestRenderReadingsDayInfoHeader checks RenderReadings prepends a +// "

" header carrying the celebration Name (heading role) +// and Season (citation role, muted) ahead of the reading sections, and that +// it reuses the existing role classes rather than inventing new ones. +func TestRenderReadingsDayInfoHeader(t *testing.T) { + secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} + info := liturgy.DayInfo{Name: "Święto św. Marii Magdaleny", Colour: "white"} + html := string(RenderReadings(secs, []string{"wuj"}, "new", "horizontal", "pl", info)) + if !strings.Contains(html, `class="dayinfo"`) { + t.Errorf("missing dayinfo header: %q", html[:min(300, len(html))]) + } + if !strings.Contains(html, "Święto św. Marii Magdaleny") { + t.Errorf("dayinfo header missing the day name: %q", html[:min(300, len(html))]) + } + if i := strings.Index(html, `class="dayinfo"`); i > strings.Index(html, "reading-section") && strings.Index(html, "reading-section") != -1 { + t.Errorf("dayinfo header should come before the reading sections: %q", html[:min(400, len(html))]) + } +} + +// TestRenderReadingsDayInfoSeason checks the Season (traditional lectionary +// only) renders as a muted citation-role span alongside Name. +func TestRenderReadingsDayInfoSeason(t *testing.T) { + secs := []liturgy.Section{{Heading: "Gospel", PartID: "evangelium"}} + info := liturgy.DayInfo{Name: "St. Mary Magdalene", Season: "Feria IV after VIII Sunday after Pentecost", Colour: "white"} + html := string(RenderReadings(secs, []string{"wuj"}, "traditional", "horizontal", "en", info)) + if !strings.Contains(html, "Feria IV after VIII Sunday after Pentecost") { + t.Errorf("dayinfo header missing Season: %q", html[:min(400, len(html))]) + } +} + +// TestRenderReadingsNoDayInfoHeaderWhenEmpty checks the header is entirely +// omitted (never an empty/error stub) when the source yielded no DayInfo. +func TestRenderReadingsNoDayInfoHeaderWhenEmpty(t *testing.T) { + secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} + html := string(RenderReadings(secs, []string{"wuj"}, "new", "horizontal", "pl", liturgy.DayInfo{})) + if strings.Contains(html, `class="dayinfo"`) { + t.Errorf("dayinfo header should be omitted when Name is empty: %q", html) + } +} + func TestBuiltinThemes(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) // no user themes for _, name := range []string{ @@ -45,7 +85,7 @@ func TestRenderReadingsEscapesScriptText(t *testing.T) { PartID: "pierwsze_czytanie", Paragraphs: [][]string{{""}}, }} - html := string(RenderReadings(secs, []string{"bt"}, "new", "horizontal", "pl")) + html := string(RenderReadings(secs, []string{"bt"}, "new", "horizontal", "pl", liturgy.DayInfo{})) if strings.Contains(html, "") { t.Errorf("raw