aboutsummaryrefslogtreecommitdiff
path: root/internal/web/render.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 12:48:05 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 12:48:05 +0200
commit55a5c793d04b55892fcddfc753d8526bd5748f03 (patch)
treeb045946a62a0b849b5c77a4100294d935ab741dd /internal/web/render.go
parented34fd574b4fc2e0a52c655ececddd1be29b271e (diff)
downloadlectio-55a5c793d04b55892fcddfc753d8526bd5748f03.tar.gz
lectio-55a5c793d04b55892fcddfc753d8526bd5748f03.zip
web reader: /reader book picker + chapter nav + version compare, reusing pane templates; v0.9.0
Diffstat (limited to 'internal/web/render.go')
-rw-r--r--internal/web/render.go160
1 files changed, 125 insertions, 35 deletions
diff --git a/internal/web/render.go b/internal/web/render.go
index 9e6280c..35c30b9 100644
--- a/internal/web/render.go
+++ b/internal/web/render.go
@@ -201,6 +201,46 @@ func interlinearVersions(versions []string) []string {
return render.OfflineVersions(versions)
}
+// verseSet is one version's verses plus its column label, the unit both the
+// interlinear readings view and the reader passage view interleave over.
+type verseSet struct {
+ label string
+ verses []bible.Verse
+}
+
+// interleaveVerses interleaves several versions' verses by chapter:verse: the
+// ordered union of keys follows the first set's verse order, then any key only
+// a later set has is appended in that set's order (stable, no duplicates).
+func interleaveVerses(sets []verseSet) []ilVerseView {
+ 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})
+ }
+ return vviews
+}
+
// buildInterlinearViews gathers each requested version's verses via
// render.GatherVerses (after the bt->wuj substitution, see
// interlinearVersions) and interleaves them by chapter:verse: the ordered
@@ -214,11 +254,6 @@ func interlinearVersions(versions []string) []string {
func buildInterlinearViews(secs []liturgy.Section, versions []string, lectionary, lang string) []ilSectionView {
mapped := interlinearVersions(versions)
- type verseSet struct {
- label string
- verses []bible.Verse
- }
-
views := make([]ilSectionView, 0, len(secs))
for _, sec := range secs {
view := ilSectionView{Heading: render.LocalizeHeading(sec.Heading, sec.PartID, lang), Subtitle: sec.Subtitle, PartID: sec.PartID}
@@ -238,40 +273,95 @@ func buildInterlinearViews(secs []liturgy.Section, versions []string, lectionary
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
+ view.Verses = interleaveVerses(sets)
+ views = append(views, view)
+ }
+ return views
+}
+
+// webVersionLabel is the localized column label for a version, falling back to
+// the bare code (mirrors render's unexported versionLabel, which web can't reach).
+func webVersionLabel(v, lang string) string {
+ if l, ok := i18n.Get(lang).Version[v]; ok {
+ return l
+ }
+ return v
+}
+
+// readerCorpusVersions are the versions the /reader offers: the four with an
+// embedded full-text corpus. "bt" (the niedziela.pl scrape) has no corpus and
+// cannot be read chapter-by-chapter.
+var readerCorpusVersions = []string{"wuj", "vul", "grb", "drb"}
+
+// UnionChapters returns the sorted union of chapter numbers a book has across
+// all corpus versions, so the reader's chapter navigation is stable regardless
+// of which versions are currently selected (a version lacking the book just
+// shows a note).
+func UnionChapters(canonical string) []int {
+ seen := map[int]bool{}
+ for _, v := range readerCorpusVersions {
+ for _, c := range bible.Chapters(v, canonical) {
+ seen[c] = true
}
+ }
+ chaps := make([]int, 0, len(seen))
+ for c := range seen {
+ chaps = append(chaps, c)
+ }
+ sort.Ints(chaps)
+ return chaps
+}
- 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,
- })
+// RenderPassage builds the reader pane for one book+chapter across versions, in
+// the same three layouts as RenderReadings (columns / vertical / interlinear),
+// reusing the very same templates and role classes. name is the book's display
+// name in the sigla dialect (the section heading is "<name> <chap>"). versions
+// are corpus versions; one lacking the chapter shows a localized "(not in X)"
+// note (columns) or is skipped (interlinear). lang localizes the version labels.
+func RenderPassage(canonical, name string, chap int, versions []string, display, lang string) template.HTML {
+ heading := fmt.Sprintf("%s %d", name, chap)
+ switch display {
+ case "interlinear":
+ return renderTemplate("readings-interlinear.html", passageInterlinear(canonical, heading, chap, versions, lang))
+ case "vertical":
+ return renderTemplate("readings-vertical.html", passageColumns(canonical, heading, chap, versions, lang))
+ default:
+ return renderTemplate("readings.html", passageColumns(canonical, heading, chap, versions, lang))
+ }
+}
+
+func passageColumns(canonical, heading string, chap int, versions []string, lang string) []sectionView {
+ cols := make([]columnView, 0, len(versions))
+ for _, v := range versions {
+ verses := bible.Verses(v, canonical, chap)
+ var blocks []blockView
+ if len(verses) == 0 {
+ blocks = append(blocks, blockView{Text: fmt.Sprintf(i18n.Get(lang).NoVersion, v)})
}
- view.Verses = vviews
- views = append(views, view)
+ for _, ve := range verses {
+ blocks = append(blocks, blockView{VNum: fmt.Sprintf("%d:%d", ve.Chapter, ve.Verse), Text: ve.Text})
+ }
+ cols = append(cols, columnView{Label: webVersionLabel(v, lang), Blocks: blocks})
}
- return views
+ return []sectionView{{Heading: heading, PartID: "reader", Columns: cols}}
+}
+
+func passageInterlinear(canonical, heading string, chap int, versions []string, lang string) []ilSectionView {
+ var sets []verseSet
+ for _, v := range versions {
+ verses := bible.Verses(v, canonical, chap)
+ if len(verses) == 0 {
+ continue
+ }
+ sets = append(sets, verseSet{label: webVersionLabel(v, lang), verses: verses})
+ }
+ view := ilSectionView{Heading: heading, PartID: "reader"}
+ if len(sets) == 0 {
+ view.Note = i18n.Get(lang).NoInterlinearVerses
+ return []ilSectionView{view}
+ }
+ view.Verses = interleaveVerses(sets)
+ return []ilSectionView{view}
}
// Themes returns the sorted, deduplicated union of the embedded theme