package blog import ( "html/template" "log" "net/http" "path/filepath" "strings" ) const pageTpl = ` {{.Title}} {{if .Post.HTML}} {{else}} {{end}}
RSS
{{if .Post.HTML}}

{{.Post.Title}}

{{.Post.HTML | safehtml}}
{{else}}

Field Notes

{{if .Posts}} {{else}}

No posts yet.

{{end}} {{end}}
` type pageData struct { Title string Posts []Post Post Post ActiveSlug string } var tmpl *template.Template func safehtml(s string) template.HTML { return template.HTML(s) } func init() { var err error tmpl, err = template.New("blog").Funcs(template.FuncMap{"safehtml": safehtml}).Parse(pageTpl) if err != nil { panic(err) } } func render(w http.ResponseWriter, data pageData) { w.Header().Set("Content-Type", "text/html; charset=utf-8") tmpl.Execute(w, 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, 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, pageData{ Title: "Chaosmith Systems — Field Notes", Posts: store.All(), }) }) // RSS feed 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))) }) // JSON feed 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))) }) // Sitemap 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))) }) // robots.txt mux.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.Write([]byte(`User-agent: * Allow: / Sitemap: https://chaosmith.systems/sitemap.xml +---------------------+ | | | | | | | _ | | .' '. | | .' '. | | .' '. | | .' '. | |.' ,-------. '.| | ,' . '. | | ( ( ) ) | | '. ' ,' | | '-------' | | | | +---------+ | | | AGIO | | | | RPC | | |\ +---------+ /| | \ .' | | '. / | | \ / | | \ / | | \ .' | | '. / | | \ / | | - | | | | | +---------------------+ `)) }) log.Printf("blog: loaded %d post(s) from %s, mounted at /blog/", store.Count(), contentDir) return nil }