From 0321ea57f5c561215a8dea111ed7f3ea7b05594e Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Fri, 24 Jul 2026 16:07:14 +0200 Subject: tui reader: bookmarks (m=mark w/ note, b=list/jump/delete, per-verse via top-visible verse) + remember last place; v0.18.0 --- internal/bookmarks/bookmarks.go | 10 +----- internal/bookmarks/place.go | 66 ++++++++++++++++++++++++++++++++++++++++ internal/bookmarks/place_test.go | 17 +++++++++++ 3 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 internal/bookmarks/place.go create mode 100644 internal/bookmarks/place_test.go (limited to 'internal/bookmarks') diff --git a/internal/bookmarks/bookmarks.go b/internal/bookmarks/bookmarks.go index ac8926d..55a9e55 100644 --- a/internal/bookmarks/bookmarks.go +++ b/internal/bookmarks/bookmarks.go @@ -35,15 +35,7 @@ type Store struct { // bookmarks.json, mirroring the sigla store) and returns a ready Store. It never // fails: a missing file simply reads as an empty list. func Open() *Store { - base := os.Getenv("XDG_DATA_HOME") - if base == "" { - home, err := os.UserHomeDir() - if err != nil { - home = "." - } - base = filepath.Join(home, ".local", "share") - } - return &Store{path: filepath.Join(base, "lectio", "bookmarks.json")} + return &Store{path: dataFile("bookmarks.json")} } // ParseTags splits a comma-separated tag string into trimmed, non-empty tags. diff --git a/internal/bookmarks/place.go b/internal/bookmarks/place.go new file mode 100644 index 0000000..2aae058 --- /dev/null +++ b/internal/bookmarks/place.go @@ -0,0 +1,66 @@ +package bookmarks + +import ( + "encoding/json" + "os" + "path/filepath" +) + +// dataFile resolves ${XDG_DATA_HOME:-~/.local/share}/lectio/ -- the same +// data-dir rule as the sigla store and the bookmarks file. +func dataFile(name string) string { + base := os.Getenv("XDG_DATA_HOME") + if base == "" { + home, err := os.UserHomeDir() + if err != nil { + home = "." + } + base = filepath.Join(home, ".local", "share") + } + return filepath.Join(base, "lectio", name) +} + +// Place is the reader's last position, so the TUI reader can reopen where it +// left off (book + chapter + the top-visible verse). +type Place struct { + Book string `json:"book"` // canonical English name (Bible-corpus key) + Chapter int `json:"chapter"` + Verse int `json:"verse"` +} + +// SavePlace persists the reader's last position (atomic temp+rename). +func SavePlace(p Place) error { + path := dataFile("reader-place.json") + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + data, err := json.MarshalIndent(p, "", " ") + if err != nil { + return err + } + tmp := path + ".tmp" + if err := os.WriteFile(tmp, data, 0o644); err != nil { + return err + } + return os.Rename(tmp, path) +} + +// LoadPlace reads the reader's last position; ok is false when none is saved +// (or the file names no book). +func LoadPlace() (Place, bool, error) { + data, err := os.ReadFile(dataFile("reader-place.json")) + if err != nil { + if os.IsNotExist(err) { + return Place{}, false, nil + } + return Place{}, false, err + } + var p Place + if err := json.Unmarshal(data, &p); err != nil { + return Place{}, false, err + } + if p.Book == "" { + return Place{}, false, nil + } + return p, true, nil +} diff --git a/internal/bookmarks/place_test.go b/internal/bookmarks/place_test.go new file mode 100644 index 0000000..420db0e --- /dev/null +++ b/internal/bookmarks/place_test.go @@ -0,0 +1,17 @@ +package bookmarks + +import "testing" + +func TestPlaceRoundTrip(t *testing.T) { + t.Setenv("XDG_DATA_HOME", t.TempDir()) + if _, ok, _ := LoadPlace(); ok { + t.Fatal("empty data dir should have no saved place") + } + if err := SavePlace(Place{Book: "John", Chapter: 3, Verse: 16}); err != nil { + t.Fatal(err) + } + p, ok, err := LoadPlace() + if err != nil || !ok || p.Book != "John" || p.Chapter != 3 || p.Verse != 16 { + t.Errorf("place = %+v ok=%v err=%v", p, ok, err) + } +} -- cgit v1.3