From 9ec2c438a39542ec485e221d485c43b04d692aca Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 23 Jul 2026 23:28:15 +0200 Subject: tui: fix width-wrap garbling + straight-aligned verse column (export render.Wrap, hanging indent); strip niedziela summary subtitle (tui/cli/web); web verse grid alignment; refrain-italic pl-only --- internal/cli/cli.go | 3 -- internal/render/render.go | 7 ++- internal/tui/tui.go | 57 +++++++++++++++++++----- internal/web/static/base.css | 20 +++++++++ internal/web/templates/readings-interlinear.html | 1 - internal/web/templates/readings-vertical.html | 3 +- internal/web/templates/readings.html | 3 +- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 38de4c1..720c165 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -371,9 +371,6 @@ func renderSection(sec liturgy.Section, version, lectionary, lang string, width var lines []string if !raw { lines = append(lines, render.LocalizeHeading(sec.Heading, sec.PartID, lang)) - if sec.Subtitle != "" { - lines = append(lines, sec.Subtitle) - } lines = append(lines, "") } _, blocks := render.GatherVersion(version, sec, lectionary, lang) diff --git a/internal/render/render.go b/internal/render/render.go index 265ed18..8a36f12 100644 --- a/internal/render/render.go +++ b/internal/render/render.go @@ -271,7 +271,7 @@ func compareSection(sec liturgy.Section, versions []string, width int, lectionar label, blocks := GatherVersion(v, sec, lectionary, lang) var lines []string for _, b := range blocks { - lines = append(lines, strings.Split(wrap(b, w), "\n")...) + lines = append(lines, strings.Split(Wrap(b, w), "\n")...) lines = append(lines, "") } if len(lines) > 0 { @@ -313,7 +313,10 @@ func compareSection(sec liturgy.Section, versions []string, width int, lectionar // line and never splitting a word (even one longer than width), matching // ewangelia.py's textwrap.fill(..., break_long_words=False, // break_on_hyphens=False). -func wrap(line string, width int) string { +// Wrap greedily word-wraps line to width without padding (each output line is +// at most width runes, no trailing spaces), never splitting a word. Shared by +// the CLI, the TUI reader and Compare so all three wrap identically. +func Wrap(line string, width int) string { words := strings.Fields(line) if len(words) == 0 { return "" diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 06ad19f..a4c14b9 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -306,16 +306,16 @@ func (m Model) bodyLines(w int) []string { } heading := render.LocalizeHeading(sec.Heading, sec.PartID, m.cfg.UILanguage) lines = append(lines, headingStyle.Render(heading)) - if sec.Subtitle != "" { - lines = append(lines, citationStyle.Render(sec.Subtitle)) - } lines = append(lines, "") _, blocks := render.GatherVersion(ver, sec, m.cfg.Lectionary, m.cfg.UILanguage) - isPsalm := sec.PartID == "psalm" + numW := maxNumWidth(blocks) + // The refrain-italic only applies to the pl responsorial-psalm block + // (its first, deduped paragraph); bible versions have no refrain block. + isPsalm := sec.PartID == "psalm" && ver == "pl" for bi, b := range blocks { refrain := isPsalm && bi == 0 - lines = append(lines, styleBlock(b, refrain, w)...) + lines = append(lines, styleBlock(b, refrain, w, numW)...) lines = append(lines, "") } } @@ -330,18 +330,53 @@ var verseNumRe = regexp.MustCompile(`^(\d+:\d+) (.*)$`) // block gets its "chapter:verse" prefix in the muted verse-number style and // its text in the default verse style; a psalm's first (refrain) block // renders italic; everything else renders in the default verse style. -func styleBlock(b string, refrain bool, w int) []string { +func styleBlock(b string, refrain bool, w, numW int) []string { + if w < 1 { + w = 1 + } if g := verseNumRe.FindStringSubmatch(b); g != nil { num, text := g[1], g[2] - wrapped := verseTextStyle.Width(w).Render(text) - wlines := strings.Split(wrapped, "\n") - wlines[0] = verseNumStyle.Render(num+" ") + wlines[0] - return wlines + col := numW + 2 // verse-number column: widest "chapter:verse" + 2 spaces + indent := strings.Repeat(" ", col) + tw := w - col + if tw < 1 { + tw = 1 + } + lines := strings.Split(render.Wrap(text, tw), "\n") + out := make([]string, 0, len(lines)) + for i, ln := range lines { + if i == 0 { + pad := strings.Repeat(" ", col-len([]rune(num))) + out = append(out, verseNumStyle.Render(num)+pad+verseTextStyle.Render(ln)) + } else { + out = append(out, indent+verseTextStyle.Render(ln)) + } + } + return out } style := verseTextStyle if refrain { style = refrainStyle } - return strings.Split(style.Width(w).Render(b), "\n") + lines := strings.Split(render.Wrap(b, w), "\n") + out := make([]string, 0, len(lines)) + for _, ln := range lines { + out = append(out, style.Render(ln)) + } + return out +} + +// maxNumWidth returns the widest "chapter:verse" prefix rune-width among the +// verse blocks, so styleBlock can align every verse's text to one column. +func maxNumWidth(blocks []string) int { + m := 0 + for _, b := range blocks { + if g := verseNumRe.FindStringSubmatch(b); g != nil { + if n := len([]rune(g[1])); n > m { + m = n + } + } + } + return m } diff --git a/internal/web/static/base.css b/internal/web/static/base.css index 4e942d5..b5ce9a5 100644 --- a/internal/web/static/base.css +++ b/internal/web/static/base.css @@ -154,6 +154,26 @@ body.mono { font-style: italic; } +/* Verse block: a fixed number column + a text column, so every verse's text + starts at the same left edge (a straight margin) and continuation lines + hang-indent under it -- matching the CLI/TUI reader. */ +.block.verse { + display: grid; + grid-template-columns: 3rem 1fr; + column-gap: 0.4rem; + align-items: baseline; +} + +.block.verse .vnum { + min-width: 0; + margin-right: 0; + text-align: right; +} + +.block.verse .vtext { + min-width: 0; +} + .vnum { display: inline-block; min-width: 2.5em; diff --git a/internal/web/templates/readings-interlinear.html b/internal/web/templates/readings-interlinear.html index 0ba7c02..5b71318 100644 --- a/internal/web/templates/readings-interlinear.html +++ b/internal/web/templates/readings-interlinear.html @@ -10,7 +10,6 @@ {{range .}}

{{.Heading}}

- {{if .Subtitle}}

{{.Subtitle}}

{{end}} {{if .Note}}

{{.Note}}

{{else}} diff --git a/internal/web/templates/readings-vertical.html b/internal/web/templates/readings-vertical.html index 08f526d..d9896c1 100644 --- a/internal/web/templates/readings-vertical.html +++ b/internal/web/templates/readings-vertical.html @@ -8,7 +8,6 @@ {{range .}}

{{.Heading}}

- {{if .Subtitle}}

{{.Subtitle}}

{{end}}
{{range .Columns}}
@@ -17,7 +16,7 @@ {{if .Refrain}}

{{.Text}}

{{else if .VNum}} -

{{.VNum}} {{.Text}}

+

{{.VNum}}{{.Text}}

{{else}}

{{.Text}}

{{end}} diff --git a/internal/web/templates/readings.html b/internal/web/templates/readings.html index 0c57ddd..a1cbcdb 100644 --- a/internal/web/templates/readings.html +++ b/internal/web/templates/readings.html @@ -5,7 +5,6 @@ {{range .}}

{{.Heading}}

- {{if .Subtitle}}

{{.Subtitle}}

{{end}}
{{range .Columns}}
@@ -14,7 +13,7 @@ {{if .Refrain}}

{{.Text}}

{{else if .VNum}} -

{{.VNum}} {{.Text}}

+

{{.VNum}}{{.Text}}

{{else}}

{{.Text}}

{{end}} -- cgit v1.3