diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 16:39:35 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 16:39:35 +0200 |
| commit | 102bf0c522c94d36f8363c38711621fe5ad18555 (patch) | |
| tree | c5be424abf0b2a2316e318502ff698f6029228a5 /internal/tui | |
| parent | 3eb99c1dd63565fbc1f5bf5af7fc8e835aad182e (diff) | |
| download | lectio-102bf0c522c94d36f8363c38711621fe5ad18555.tar.gz lectio-102bf0c522c94d36f8363c38711621fe5ad18555.zip | |
bookmarks: store as universal TSV (header row) with one-time JSON migration; tui note box wraps long notes with hanging indent + hard-break; v0.21.0
Diffstat (limited to 'internal/tui')
| -rw-r--r-- | internal/tui/reader.go | 83 |
1 files changed, 75 insertions, 8 deletions
diff --git a/internal/tui/reader.go b/internal/tui/reader.go index 72b7d0a..df92349 100644 --- a/internal/tui/reader.go +++ b/internal/tui/reader.go @@ -741,21 +741,88 @@ func (m ReaderModel) View() string { } // viewMark renders the bookmark note/tags box as a prominent centered dialog. +// The note and tags wrap (with a hanging indent) within a bounded width, so a +// long note flows down inside the box instead of overrunning the border. func (m ReaderModel) viewMark() string { ui := i18n.Get(m.cfg.UILanguage) b := m.books[m.bookIdx] - title := modalTitleStyle.Render(fmt.Sprintf("★ %s %d:%d", b.Name, m.currentChapter(), m.markVerse)) - note := " " + ui.ReaderMarkNote + ": " + m.markNote - tags := " " + ui.ReaderMarkTags + ": " + m.markTags - if m.markField == 0 { - note += "▏" - } else { - tags += "▏" + cw := m.width - 12 + if cw > 56 { + cw = 56 + } + if cw < 24 { + cw = 24 } - inner := title + "\n\n" + note + "\n" + tags + "\n\n" + citationStyle.Render(ui.ReaderMarkHelp) + title := modalTitleStyle.Render(fmt.Sprintf("★ %s %d:%d", b.Name, m.currentChapter(), m.markVerse)) + lines := wrapField(ui.ReaderMarkNote, m.markNote, cw, m.markField == 0) + lines = append(lines, wrapField(ui.ReaderMarkTags, m.markTags, cw, m.markField == 1)...) + inner := title + "\n\n" + strings.Join(lines, "\n") + "\n\n" + citationStyle.Render(ui.ReaderMarkHelp) return m.modal(inner) } +// wrapField renders "label: value" wrapping value to width with a hanging +// indent under the label; a cursor is appended when the field is active. +func wrapField(label, value string, width int, active bool) []string { + prefix := label + ": " + pw := len([]rune(prefix)) + text := value + if active { + text += "▏" + } + tw := width - pw + if tw < 8 { + tw = 8 + } + wrapped := hardWrap(text, tw) + indent := strings.Repeat(" ", pw) + out := make([]string, 0, len(wrapped)) + for i, ln := range wrapped { + if i == 0 { + out = append(out, prefix+ln) + } else { + out = append(out, indent+ln) + } + } + return out +} + +// hardWrap wraps on spaces but, unlike render.Wrap, also hard-breaks a single +// token longer than width (notes can contain arbitrary unbroken text). +func hardWrap(s string, width int) []string { + if width < 1 { + width = 1 + } + var lines []string + cur := "" + for _, word := range strings.Fields(s) { + for len([]rune(word)) > width { + if cur != "" { + lines = append(lines, cur) + cur = "" + } + r := []rune(word) + lines = append(lines, string(r[:width])) + word = string(r[width:]) + } + switch { + case cur == "": + cur = word + case len([]rune(cur))+1+len([]rune(word)) <= width: + cur += " " + word + default: + lines = append(lines, cur) + cur = word + } + } + if cur != "" { + lines = append(lines, cur) + } + if len(lines) == 0 { + return []string{""} + } + return lines +} + // viewBookmarks renders the saved-bookmarks list. func (m ReaderModel) viewBookmarks() string { w := m.width |
