aboutsummaryrefslogtreecommitdiff
path: root/internal/tui
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui')
-rw-r--r--internal/tui/reader.go48
-rw-r--r--internal/tui/reader_test.go30
2 files changed, 71 insertions, 7 deletions
diff --git a/internal/tui/reader.go b/internal/tui/reader.go
index de89a40..973161d 100644
--- a/internal/tui/reader.go
+++ b/internal/tui/reader.go
@@ -33,6 +33,9 @@ const (
// selStyle marks the picker's selected row (reverse video, legible on any theme).
var selStyle = lipgloss.NewStyle().Reverse(true)
+// markStyle renders the red "*" that flags a verse carrying a bookmark.
+var markStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Bold(true)
+
// modalStyle / modalTitleStyle render the bookmark note box and the delete
// confirmation as a prominent centered, bordered dialog (not a footer line).
var (
@@ -92,6 +95,7 @@ type ReaderModel struct {
markFilter string // active tag filter (substring, case-insensitive)
markFiltering bool // typing into the tag filter
confirmDelete bool // bookmarks list is awaiting delete confirmation
+ markedVerses map[int]bool // verse numbers in the current book+chapter that carry a bookmark
flash string // transient status line (e.g. "bookmarked ...")
width, height int
@@ -488,7 +492,7 @@ func (m ReaderModel) updateBookmarks(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
m.mode = modeRead
- return m, nil
+ return m.withMarks(), nil
case "/":
m.markFiltering = true
return m, nil
@@ -689,6 +693,30 @@ func (m ReaderModel) scrollToVerse(verse int) int {
// saveBookmark stores a bookmark of the current book+chapter at the chosen
// verse with an optional note, and sets a flash message.
+// withMarks recomputes the set of verse numbers in the current book+chapter that
+// carry a bookmark, so the reader can flag them with a red "*". One store read;
+// refreshed on chapter load, on save, and on returning from the bookmarks list.
+func (m ReaderModel) withMarks() ReaderModel {
+ m.markedVerses = nil
+ if m.store == nil || m.bookIdx < 0 || m.bookIdx >= len(m.books) {
+ return m
+ }
+ all, err := m.store.List("")
+ if err != nil {
+ return m
+ }
+ canon := m.books[m.bookIdx].Canonical
+ chap := m.currentChapter()
+ set := map[int]bool{}
+ for _, bm := range all {
+ if bm.Book == canon && bm.Chapter == chap {
+ set[bm.Verse] = true
+ }
+ }
+ m.markedVerses = set
+ return m
+}
+
func (m ReaderModel) saveBookmark(verse int, note, tags string) ReaderModel {
if m.store == nil || len(m.verses) == 0 {
return m
@@ -706,7 +734,7 @@ func (m ReaderModel) saveBookmark(verse int, note, tags string) ReaderModel {
Tags: bookmarks.ParseTags(tags),
})
m.flash = fmt.Sprintf("bookmarked %s %d:%d", book.Name, chap, verse)
- return m
+ return m.withMarks()
}
// savePlace persists the current reading position (book+chapter+top verse) so
@@ -752,7 +780,7 @@ func (m ReaderModel) openBook() ReaderModel {
func (m ReaderModel) loadVerses() ReaderModel {
if len(m.chapters) == 0 {
m.verses = nil
- return m
+ return m.withMarks()
}
if m.chapPos < 0 {
m.chapPos = 0
@@ -761,7 +789,7 @@ func (m ReaderModel) loadVerses() ReaderModel {
m.chapPos = len(m.chapters) - 1
}
m.verses = bible.Verses(m.version(), m.books[m.bookIdx].Canonical, m.chapters[m.chapPos])
- return m
+ return m.withMarks()
}
func (m ReaderModel) chapterStep(d int) ReaderModel {
@@ -850,13 +878,19 @@ func (m ReaderModel) readBodyHL(w, hl int) []string {
numW := maxNumWidth(blocks)
var lines []string
for i, b := range blocks {
+ var vlines []string
if i == hl {
for _, ln := range strings.Split(render.Wrap(b, w), "\n") {
- lines = append(lines, selStyle.Render(ln))
+ vlines = append(vlines, selStyle.Render(ln))
}
- continue
+ } else {
+ vlines = styleBlock(b, false, w, numW)
+ }
+ // Flag a bookmarked verse with a red "*" at the end of its last line.
+ if m.markedVerses[m.verses[i].Verse] && len(vlines) > 0 {
+ vlines[len(vlines)-1] += markStyle.Render("*")
}
- lines = append(lines, styleBlock(b, false, w, numW)...)
+ lines = append(lines, vlines...)
}
return lines
}
diff --git a/internal/tui/reader_test.go b/internal/tui/reader_test.go
index da8d4d4..b741732 100644
--- a/internal/tui/reader_test.go
+++ b/internal/tui/reader_test.go
@@ -98,6 +98,36 @@ func TestReaderBookmarkTagFilter(t *testing.T) {
}
}
+func TestReaderBookmarkMarker(t *testing.T) {
+ m := win(enReader(t), 80, 30)
+ // vul is always embedded, so verses load without -tags fullbible.
+ for i, v := range m.versions {
+ if v == "vul" {
+ m.verIdx = i
+ }
+ }
+ if _, err := m.store.Add(bookmarks.Bookmark{Book: "John", Chapter: 3, Verse: 16, Note: "x"}); err != nil {
+ t.Fatal(err)
+ }
+ m.bookIdx = m.bookIndex("John")
+ if m.bookIdx < 0 {
+ t.Fatal("book John not found")
+ }
+ m = m.openAt(3, 1) // loads John 3 and refreshes the marked-verse set
+
+ if !m.markedVerses[16] {
+ t.Fatalf("verse 16 not flagged; markedVerses=%v", m.markedVerses)
+ }
+ if m.markedVerses[15] {
+ t.Fatal("verse 15 should not be flagged")
+ }
+ // the rendered body carries the "*" marker (styles are stripped in tests, so
+ // the star is plain, but present).
+ if !strings.Contains(strings.Join(m.readBodyHL(m.innerW(), -1), "\n"), "*") {
+ t.Fatal("rendered body has no bookmark marker")
+ }
+}
+
func TestReaderInit(t *testing.T) {
m := enReader(t)
if m.mode != modePick {