render.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. package main
  2. import (
  3. "bytes"
  4. "embed"
  5. "html/template"
  6. "log"
  7. "net/http"
  8. "time"
  9. "github.com/yuin/goldmark"
  10. "github.com/yuin/goldmark/extension"
  11. "github.com/yuin/goldmark/parser"
  12. )
  13. var (
  14. //go:embed templates/*.html
  15. templateFS embed.FS
  16. //go:embed static
  17. staticFS embed.FS
  18. )
  19. // newMarkdown returns a CommonMark renderer with GitHub-style extensions.
  20. // Raw HTML in posts is omitted (goldmark's default).
  21. func newMarkdown() goldmark.Markdown {
  22. return goldmark.New(
  23. goldmark.WithExtensions(extension.GFM),
  24. goldmark.WithParserOptions(parser.WithAutoHeadingID()),
  25. )
  26. }
  27. // pageData is passed to every template.
  28. type pageData struct {
  29. Site string
  30. Posts []*Post
  31. Post *Post
  32. Newer, Older *Post
  33. Status int
  34. Message string
  35. }
  36. // loadTemplates parses each page template together with the shared layout.
  37. func loadTemplates() (map[string]*template.Template, error) {
  38. funcs := template.FuncMap{
  39. "date": func(t time.Time) string { return t.Format("2 January 2006") },
  40. "isodate": func(t time.Time) string { return t.Format(time.DateOnly) },
  41. }
  42. base, err := template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html")
  43. if err != nil {
  44. return nil, err
  45. }
  46. pages := map[string]*template.Template{}
  47. for _, name := range []string{"index", "post", "error"} {
  48. t, err := base.Clone()
  49. if err != nil {
  50. return nil, err
  51. }
  52. if pages[name], err = t.ParseFS(templateFS, "templates/"+name+".html"); err != nil {
  53. return nil, err
  54. }
  55. }
  56. return pages, nil
  57. }
  58. // render executes a page into a buffer first so a template error becomes a
  59. // clean 500 instead of a half-written page.
  60. func (a *app) render(w http.ResponseWriter, status int, page string, data pageData) {
  61. data.Site = a.siteName
  62. var buf bytes.Buffer
  63. if err := a.pages[page].ExecuteTemplate(&buf, "layout.html", data); err != nil {
  64. log.Printf("render %s: %v", page, err)
  65. http.Error(w, "internal server error", http.StatusInternalServerError)
  66. return
  67. }
  68. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  69. w.WriteHeader(status)
  70. buf.WriteTo(w)
  71. }