diff options
Diffstat (limited to 'internal/web/server.go')
| -rw-r--r-- | internal/web/server.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/internal/web/server.go b/internal/web/server.go index d8b451a..667e986 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -15,9 +15,11 @@ import ( "os/exec" "regexp" "runtime" + "strconv" "strings" "time" + "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/config" "github.com/lukaszkasprzak/lectio/internal/i18n" "github.com/lukaszkasprzak/lectio/internal/liturgy" @@ -34,10 +36,13 @@ var bibleVersions = []string{"bt", "wuj", "vul", "grb", "drb"} // (lectionary, versions, theme, offline) that requests can override via // query parameters; it is never mutated. func NewServer(cfg config.Config) http.Handler { + tbl, _ := bible.LoadBookTable(config.UserBooksTOML()) + mux := http.NewServeMux() mux.HandleFunc("GET /{$}", indexHandler(cfg)) mux.HandleFunc("GET /readings", readingsHandler(cfg)) + mux.HandleFunc("GET /reader", readerHandler(cfg, tbl)) mux.HandleFunc("GET /theme.css", themeCSSHandler(cfg)) staticSub, err := fs.Sub(staticFS, "static") @@ -282,6 +287,169 @@ func renderOrError(secs []liturgy.Section, versions []string, lectionary, displa return RenderReadings(secs, versions, lectionary, display, lang, dayInfo) } +// requestReaderVersions returns the corpus versions the reader request asks for +// (repeated ?v=), dropping "bt" (no corpus) and anything invalid; falling back +// to the configured default (or "wuj" when that is bt/invalid) so a plain visit +// shows exactly one column. +func requestReaderVersions(cfg config.Config, r *http.Request) []string { + var out []string + for _, v := range r.URL.Query()["v"] { + if v != "bt" && config.ValidVersion(v) { + out = append(out, v) + } + } + if len(out) == 0 { + d := cfg.DefaultVersion + if d == "bt" || !config.ValidVersion(d) { + d = "wuj" + } + out = []string{d} + } + return out +} + +// findBook returns the BookInfo whose Canonical matches, and whether found. +func findBook(books []bible.BookInfo, canonical string) (bible.BookInfo, bool) { + for _, b := range books { + if b.Canonical == canonical { + return b, true + } + } + return bible.BookInfo{}, false +} + +// clampChap keeps chap within the book's available chapters (contiguous in +// practice); 0 when the book has none in any corpus version. +func clampChap(chap int, chaps []int) int { + if len(chaps) == 0 { + return 0 + } + if chap < chaps[0] { + return chaps[0] + } + last := chaps[len(chaps)-1] + if chap > last { + return last + } + return chap +} + +// atoiDefault parses s as an int, returning def when it is empty/unparsable. +func atoiDefault(s string, def int) int { + if n, err := strconv.Atoi(strings.TrimSpace(s)); err == nil { + return n + } + return def +} + +type readerData struct { + BookOpts []bookOpt + Chap int + PrevChap, NextChap int + ChapOpts []chapOpt + VersionOpts []versionOpt + Display string + ThemeOpts []themeOpt + Theme string + Mono bool + Reading template.HTML + L i18n.UI + Lang string +} + +type bookOpt struct { + Value string // canonical name (corpus/query value) + Label string // dialect display name + Selected bool +} + +type chapOpt struct { + N int + Selected bool +} + +// readerHandler serves GET /reader: a book picker (dialect names), chapter +// navigation and corpus-version compare, rendering the same reading-pane +// templates/themes as the daily view. The controls form re-fetches /reader and +// hx-selects #reader-root, so book/chapter/version selects stay in sync. +func readerHandler(cfg config.Config, tbl *bible.BookTable) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + books := tbl.Books(cfg.SiglaLang()) + if len(books) == 0 { // defensive: embedded table always has books + http.Error(w, "web: no books", http.StatusInternalServerError) + return + } + + info, ok := findBook(books, r.URL.Query().Get("book")) + if !ok { + info = books[0] + } + versions := requestReaderVersions(cfg, r) + chaps := UnionChapters(info.Canonical) + first := 1 + if len(chaps) > 0 { + first = chaps[0] + } + chap := clampChap(atoiDefault(r.URL.Query().Get("chap"), first), chaps) + display := requestDisplay(cfg, r) + theme := r.URL.Query().Get("theme") + if theme == "" { + theme = cfg.WebTheme + } + + reading := RenderPassage(info.Canonical, info.Name, chap, versions, display, cfg.UILanguage) + + bookOpts := make([]bookOpt, 0, len(books)) + for _, b := range books { + bookOpts = append(bookOpts, bookOpt{Value: b.Canonical, Label: b.Name, Selected: b.Canonical == info.Canonical}) + } + chapOpts := make([]chapOpt, 0, len(chaps)) + for _, c := range chaps { + chapOpts = append(chapOpts, chapOpt{N: c, Selected: c == chap}) + } + selected := map[string]bool{} + for _, v := range versions { + selected[v] = true + } + versionOpts := make([]versionOpt, 0, len(readerCorpusVersions)) + for _, v := range readerCorpusVersions { + versionOpts = append(versionOpts, versionOpt{Code: v, Checked: selected[v]}) + } + themes := Themes() + themeOpts := make([]themeOpt, 0, len(themes)) + for _, name := range themes { + themeOpts = append(themeOpts, themeOpt{Name: name, Selected: name == theme}) + } + + prev, next := chap-1, chap+1 + if len(chaps) > 0 { + prev = clampChap(chap-1, chaps) + next = clampChap(chap+1, chaps) + } + + data := readerData{ + BookOpts: bookOpts, + Chap: chap, + PrevChap: prev, + NextChap: next, + ChapOpts: chapOpts, + VersionOpts: versionOpts, + Display: display, + ThemeOpts: themeOpts, + Theme: theme, + Mono: queryBool(r, "mono", cfg.WebMono), + Reading: reading, + L: i18n.Get(cfg.UILanguage), + Lang: cfg.UILanguage, + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := tmpl.ExecuteTemplate(w, "reader.html", data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + // themeCSSHandler serves one theme's stylesheet: the requested name, or // (unknown/invalid) cfg.WebTheme, or (that also unknown) the built-in // default -- so an unrecognized ?name= degrades to a working theme instead |
