diff options
Diffstat (limited to 'internal/web/render.go')
| -rw-r--r-- | internal/web/render.go | 160 |
1 files changed, 146 insertions, 14 deletions
diff --git a/internal/web/render.go b/internal/web/render.go index 67c7a37..63c1da4 100644 --- a/internal/web/render.go +++ b/internal/web/render.go @@ -18,6 +18,7 @@ import ( "sort" "strings" + "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/liturgy" "github.com/lukaszkasprzak/lectio/internal/render" ) @@ -59,13 +60,70 @@ type blockView struct { Refrain bool } -// RenderReadings builds the reading pane: for each section, a heading (and -// subtitle if present) followed by one column per version, each built by -// calling render.GatherVersion(v, sec, lectionary). Heading, citation -// (subtitle), verse-number and refrain text are wrapped in -// class="heading|citation|vnum|refrain" spans so theme CSS can restyle -// them; verse/paragraph text is escaped by html/template. -func RenderReadings(secs []liturgy.Section, versions []string, lectionary string) template.HTML { +// ilSectionView, ilVerseView and ilLineView are what +// templates/readings-interlinear.html ranges over: one ilSectionView per +// liturgy.Section, one ilVerseView per chapter:verse key (in ordered-union +// order, see buildInterlinearViews), one ilLineView per version that carries +// that verse. Note holds a short escaped message in place of Verses when no +// requested version could be interleaved for the section. +type ilSectionView struct { + Heading, Subtitle, PartID string + Verses []ilVerseView + Note string +} + +type ilVerseView struct { + VNum string + Lines []ilLineView +} + +type ilLineView struct { + Label, Text string +} + +// RenderReadings builds the reading pane fragment for one of three layouts: +// +// - "horizontal" (or anything unrecognized): the original stacked layout, +// one column per version rendered under a shared heading, unchanged. +// - "vertical": the same per-version columns side by side in a +// ".display-vertical" grid, like the CLI `compare` view. +// - "interlinear": versions interleaved verse-by-verse by chapter:verse +// (see buildInterlinearViews); "pl" cannot participate (no verse +// numbers) and is substituted/dropped via render.OfflineVersions' +// pl->wuj transform before gathering. +// +// In every mode, heading, citation (subtitle), verse-number and refrain +// text are wrapped in class="heading|citation|vnum|refrain|version-label" +// spans so theme CSS can restyle them; verse/paragraph text is escaped by +// html/template. +func RenderReadings(secs []liturgy.Section, versions []string, lectionary, display string) template.HTML { + switch display { + case "vertical": + return renderTemplate("readings-vertical.html", buildColumnViews(secs, versions, lectionary)) + case "interlinear": + return renderTemplate("readings-interlinear.html", buildInterlinearViews(secs, versions, lectionary)) + default: + return renderTemplate("readings.html", buildColumnViews(secs, versions, lectionary)) + } +} + +// renderTemplate executes the named embedded template with data, degrading +// to a visible, escaped error paragraph on failure (should be unreachable: +// the templates are embedded and fixed at build time) rather than panicking +// a request handler in the caller. +func renderTemplate(name string, data any) template.HTML { + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, name, data); err != nil { + return template.HTML("<p class=\"error\">" + template.HTMLEscapeString(err.Error()) + "</p>") + } + return template.HTML(buf.String()) +} + +// buildColumnViews gathers each section's per-version columns via +// render.GatherVersion -- the shared data both the "horizontal" +// (readings.html) and "vertical" (readings-vertical.html) templates range +// over; only the surrounding markup differs between the two layouts. +func buildColumnViews(secs []liturgy.Section, versions []string, lectionary string) []sectionView { views := make([]sectionView, 0, len(secs)) for _, sec := range secs { isPsalm := sec.PartID == "psalm" @@ -96,15 +154,89 @@ func RenderReadings(secs []liturgy.Section, versions []string, lectionary string Columns: cols, }) } + return views +} - var buf bytes.Buffer - if err := tmpl.ExecuteTemplate(&buf, "readings.html", views); err != nil { - // Should be unreachable (the template is embedded and fixed at - // build time); degrade to a visible, escaped error rather than - // panicking a request handler in the caller. - return template.HTML("<p class=\"error\">" + template.HTMLEscapeString(err.Error()) + "</p>") +// interlinearVersions maps versions through the same pl->wuj substitution +// render.OfflineVersions performs for offline mode: "pl" (niedziela.pl +// paragraph text) carries no verse numbers and cannot interleave, so it is +// dropped, substituting "wuj" (the Polish-language bible version) in its +// place unless "wuj" was already selected. Reuses render.OfflineVersions +// rather than duplicating its two-line transform. +func interlinearVersions(versions []string) []string { + return render.OfflineVersions(versions) +} + +// buildInterlinearViews gathers each requested version's verses via +// render.GatherVerses (after the pl->wuj substitution, see +// interlinearVersions) and interleaves them by chapter:verse: the ordered +// union of keys is taken from the first versified version's own verse +// order, then any keys that only appear in a later version are appended in +// that version's order (stable, no duplicates). A version that comes back +// unversified (a bible lookup miss) is simply skipped -- the remaining +// versions still render. A section where nothing could be interleaved gets +// a short escaped Note instead of an empty Verses list. +func buildInterlinearViews(secs []liturgy.Section, versions []string, lectionary string) []ilSectionView { + mapped := interlinearVersions(versions) + + type verseSet struct { + label string + verses []bible.Verse } - return template.HTML(buf.String()) + + views := make([]ilSectionView, 0, len(secs)) + for _, sec := range secs { + view := ilSectionView{Heading: sec.Heading, Subtitle: sec.Subtitle, PartID: sec.PartID} + + var sets []verseSet + for _, v := range mapped { + label, verses, versified := render.GatherVerses(v, sec, lectionary) + if !versified { + continue + } + sets = append(sets, verseSet{label: label, verses: verses}) + } + + if len(sets) == 0 { + view.Note = "(brak wersetów do zestawienia interlinearnego)" + views = append(views, view) + continue + } + + type vkey struct{ chapter, verse int } + order := make([]vkey, 0) + seen := map[vkey]bool{} + bySet := make([]map[vkey]bible.Verse, len(sets)) + for i, s := range sets { + m := make(map[vkey]bible.Verse, len(s.verses)) + for _, v := range s.verses { + k := vkey{v.Chapter, v.Verse} + m[k] = v + if !seen[k] { + seen[k] = true + order = append(order, k) + } + } + bySet[i] = m + } + + vviews := make([]ilVerseView, 0, len(order)) + for _, k := range order { + var lines []ilLineView + for i, s := range sets { + if v, ok := bySet[i][k]; ok { + lines = append(lines, ilLineView{Label: s.label, Text: v.Text}) + } + } + vviews = append(vviews, ilVerseView{ + VNum: fmt.Sprintf("%d:%d", k.chapter, k.verse), + Lines: lines, + }) + } + view.Verses = vviews + views = append(views, view) + } + return views } // Themes returns the sorted, deduplicated union of the embedded theme |
