135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package blog
|
|
|
|
import (
|
|
"embed"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
//go:embed templates/*.html
|
|
var templateFS embed.FS
|
|
|
|
type pageData struct {
|
|
Title string
|
|
Posts []Post
|
|
Post Post
|
|
PostList bool
|
|
ActiveSlug string
|
|
}
|
|
|
|
func safehtml(s string) template.HTML {
|
|
return template.HTML(s)
|
|
}
|
|
|
|
var tmpl *template.Template
|
|
|
|
func init() {
|
|
var err error
|
|
tmpl, err = template.New("").Funcs(template.FuncMap{"safehtml": safehtml}).ParseFS(templateFS, "templates/*.html")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func render(w http.ResponseWriter, r *http.Request, data pageData) {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
tmpl.ExecuteTemplate(w, "main-content", data)
|
|
return
|
|
}
|
|
tmpl.ExecuteTemplate(w, "base.html", data)
|
|
}
|
|
|
|
func RegisterRoutes(mux *http.ServeMux, contentDir string) error {
|
|
ogDir := filepath.Join(contentDir, "..", "og")
|
|
store := NewStore()
|
|
if err := store.Load(contentDir); err != nil {
|
|
log.Printf("blog: warning: could not load posts from %s: %v", contentDir, err)
|
|
}
|
|
store.GenerateOGImages(ogDir)
|
|
|
|
mux.Handle("/blog/og/", http.StripPrefix("/blog/og/", http.FileServer(http.Dir(ogDir))))
|
|
|
|
mux.HandleFunc("/blog/admin/reload", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := store.Load(contentDir); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
store.GenerateOGImages(ogDir)
|
|
w.Write([]byte("reloaded\n"))
|
|
})
|
|
|
|
mux.HandleFunc("/blog/posts/", func(w http.ResponseWriter, r *http.Request) {
|
|
slug := strings.TrimPrefix(r.URL.Path, "/blog/posts/")
|
|
if slug == "" || strings.Contains(slug, "/") {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
p, ok := store.Get(slug)
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
render(w, r, pageData{
|
|
Title: p.Title + " — Chaosmith Systems",
|
|
Posts: store.All(),
|
|
Post: p,
|
|
})
|
|
})
|
|
|
|
mux.HandleFunc("/blog/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/blog/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
render(w, r, pageData{
|
|
Title: "Chaosmith Systems — Field Notes",
|
|
Posts: store.All(),
|
|
PostList: true,
|
|
})
|
|
})
|
|
|
|
mux.HandleFunc("/blog/feed.xml", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
|
|
w.Write([]byte(generateRSS(store)))
|
|
})
|
|
|
|
mux.HandleFunc("/blog/feed.json", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/feed+json; charset=utf-8")
|
|
w.Write([]byte(generateJSONFeed(store)))
|
|
})
|
|
|
|
mux.HandleFunc("/sitemap.xml", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
|
w.Write([]byte(generateSitemap(store)))
|
|
})
|
|
|
|
mux.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.Write([]byte("User-agent: *\nAllow: /\n\nSitemap: https://chaosmith.systems/sitemap.xml\n"))
|
|
})
|
|
|
|
log.Printf("blog: loaded %d post(s) from %s, mounted at /blog/", store.Count(), contentDir)
|
|
return nil
|
|
}
|
|
|
|
func ServeLanding(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
data := pageData{Title: "Chaosmith Systems"}
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
tmpl.ExecuteTemplate(w, "main-content", data)
|
|
return
|
|
}
|
|
tmpl.ExecuteTemplate(w, "base.html", data)
|
|
}
|