summaryrefslogtreecommitdiff
path: root/internal/bookmarks
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 16:07:14 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 16:07:14 +0200
commit0321ea57f5c561215a8dea111ed7f3ea7b05594e (patch)
tree75ce332f8985907fcd0118aa2c3f4aafb8587ad7 /internal/bookmarks
parentf48b79d8860e4c4616821ceeeea091ab4b838197 (diff)
downloadlectio-0321ea57f5c561215a8dea111ed7f3ea7b05594e.tar.gz
lectio-0321ea57f5c561215a8dea111ed7f3ea7b05594e.zip
tui reader: bookmarks (m=mark w/ note, b=list/jump/delete, per-verse via top-visible verse) + remember last place; v0.18.0
Diffstat (limited to 'internal/bookmarks')
-rw-r--r--internal/bookmarks/bookmarks.go10
-rw-r--r--internal/bookmarks/place.go66
-rw-r--r--internal/bookmarks/place_test.go17
3 files changed, 84 insertions, 9 deletions
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/<name> -- 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)
+ }
+}