diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 16:56:07 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 16:56:07 +0200 |
| commit | 378926240f34759116b19e078d2b2504965f5329 (patch) | |
| tree | c8e8f2288869882ccc01845eaead8f3c11f067dd /internal/web | |
| parent | 29e244c48222278c2f255ff2adf45e3af7c9f895 (diff) | |
| download | lectio-378926240f34759116b19e078d2b2504965f5329.tar.gz lectio-378926240f34759116b19e078d2b2504965f5329.zip | |
export: month calendar PDF (A4 landscape, Monday-first grid, feast name + gospel ref + liturgical colour per day); cli --calendar YYYY-MM; web /calendar + link; readings.GospelCitation; v0.23.0
Diffstat (limited to 'internal/web')
| -rw-r--r-- | internal/web/server.go | 36 | ||||
| -rw-r--r-- | internal/web/server_test.go | 18 | ||||
| -rw-r--r-- | internal/web/templates/index.html | 1 |
3 files changed, 55 insertions, 0 deletions
diff --git a/internal/web/server.go b/internal/web/server.go index 45ab06f..5ebed38 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -69,6 +69,7 @@ func NewServer(cfg config.Config) http.Handler { mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { indexHandler(s.get())(w, r) }) mux.HandleFunc("GET /readings", func(w http.ResponseWriter, r *http.Request) { readingsHandler(s.get())(w, r) }) mux.HandleFunc("GET /export", func(w http.ResponseWriter, r *http.Request) { exportHandler(s.get())(w, r) }) + mux.HandleFunc("GET /calendar", func(w http.ResponseWriter, r *http.Request) { calendarHandler(s.get())(w, r) }) mux.HandleFunc("GET /reader", func(w http.ResponseWriter, r *http.Request) { readerHandler(s.get(), s.table())(w, r) }) mux.HandleFunc("GET /theme.css", func(w http.ResponseWriter, r *http.Request) { themeCSSHandler(s.get())(w, r) }) mux.HandleFunc("GET /settings", settingsGet(s)) @@ -381,6 +382,41 @@ func exportHandler(cfg config.Config) http.HandlerFunc { } } +// calendarHandler serves GET /calendar?month=YYYY-MM&lectionary=: a printable +// A4 PDF month calendar of the celebration names + gospel references. Missing/ +// bad month defaults to the current month. +func calendarHandler(cfg config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + t, err := time.Parse("2006-01", r.URL.Query().Get("month")) + if err != nil { + t = time.Now() + } + year, month := t.Year(), int(t.Month()) + cfg.Lectionary = requestLectionary(cfg, r) + first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) + + var days []export.CalendarDay + for d := first; int(d.Month()) == month; d = d.AddDate(0, 0, 1) { + cd := export.CalendarDay{Day: d.Day()} + if secs, info, lerr := readings.Load(cfg, readings.Options{Date: d.Format("2006-01-02"), Offline: cfg.Offline, All: false}); lerr == nil { + cd.Name = info.Name + cd.Colour = info.Colour + cd.Citation = readings.GospelCitation(secs) + } + days = append(days, cd) + } + + data, perr := export.CalendarPDF(year, month, days, cfg.Lectionary, cfg.UILanguage) + if perr != nil { + http.Error(w, perr.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/pdf") + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="lectio-%04d-%02d-calendar.pdf"`, year, month)) + w.Write(data) + } +} + // renderOrError returns RenderReadings' fragment, or (on a readings.Load // error, or no sections at all for the date) a small escaped error // paragraph, localised via lang -- readings.Load errors are expected in diff --git a/internal/web/server_test.go b/internal/web/server_test.go index 43508ba..284a454 100644 --- a/internal/web/server_test.go +++ b/internal/web/server_test.go @@ -645,3 +645,21 @@ func TestExportNoReadings(t *testing.T) { t.Errorf("export with no readings status=%d (want 404/500)", rec.Code) } } + +func TestCalendarPDF(t *testing.T) { + t.Setenv("XDG_CACHE_HOME", t.TempDir()) + cfg := config.Default() + cfg.Offline = true // no network; empty cells are fine, PDF still renders + srv := NewServer(cfg) + rec := httptest.NewRecorder() + srv.ServeHTTP(rec, httptest.NewRequest("GET", "/calendar?month=2026-07", nil)) + if rec.Code != 200 { + t.Fatalf("calendar status %d", rec.Code) + } + if ct := rec.Header().Get("Content-Type"); ct != "application/pdf" { + t.Errorf("content-type %q", ct) + } + if b := rec.Body.Bytes(); len(b) < 1000 || string(b[:4]) != "%PDF" { + t.Errorf("not a pdf (len=%d)", len(b)) + } +} diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index cd30208..1e43df4 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -84,6 +84,7 @@ <a href="#" onclick="location='/export?fmt=txt&'+new URLSearchParams(new FormData(document.getElementById('controls'))).toString();return false;">txt</a> <a href="#" onclick="location='/export?fmt=md&'+new URLSearchParams(new FormData(document.getElementById('controls'))).toString();return false;">md</a> <a href="#" onclick="location='/export?fmt=pdf&'+new URLSearchParams(new FormData(document.getElementById('controls'))).toString();return false;">pdf</a> + ยท <a href="#" onclick="var d=(document.querySelector('#controls [name=date]').value||'').slice(0,7);location='/calendar?month='+d+'&lectionary='+document.querySelector('#controls [name=lectionary]').value;return false;">{{.L.Calendar}}</a> </span> <div id="pane">{{.Reading}}</div> |
