render.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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. //go:embed about.md
  19. aboutMarkdown []byte
  20. )
  21. // newMarkdown returns a CommonMark renderer with GitHub-style extensions.
  22. // Raw HTML in posts is omitted (goldmark's default).
  23. func newMarkdown() goldmark.Markdown {
  24. return goldmark.New(
  25. goldmark.WithExtensions(extension.GFM),
  26. goldmark.WithParserOptions(parser.WithAutoHeadingID()),
  27. )
  28. }
  29. // pageData is passed to every template.
  30. type pageData struct {
  31. Site string
  32. Config siteConfig
  33. Posts []*Post
  34. Post *Post
  35. Newer, Older *Post
  36. Status int
  37. Message string
  38. // About page only.
  39. Version, Commit string
  40. About template.HTML
  41. }
  42. // loadTemplates parses each page template together with the shared layout.
  43. func loadTemplates() (map[string]*template.Template, error) {
  44. funcs := template.FuncMap{
  45. "date": func(t time.Time) string { return t.Format("2 January 2006") },
  46. "isodate": func(t time.Time) string { return t.Format(time.DateOnly) },
  47. }
  48. base, err := template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html")
  49. if err != nil {
  50. return nil, err
  51. }
  52. pages := map[string]*template.Template{}
  53. for _, name := range []string{"index", "post", "about", "error"} {
  54. t, err := base.Clone()
  55. if err != nil {
  56. return nil, err
  57. }
  58. if pages[name], err = t.ParseFS(templateFS, "templates/"+name+".html"); err != nil {
  59. return nil, err
  60. }
  61. }
  62. return pages, nil
  63. }
  64. // render executes a page into a buffer first so a template error becomes a
  65. // clean 500 instead of a half-written page.
  66. func (a *app) render(w http.ResponseWriter, status int, page string, data pageData) {
  67. data.Site = a.siteName
  68. data.Config = a.config.Get()
  69. var buf bytes.Buffer
  70. if err := a.pages[page].ExecuteTemplate(&buf, "layout.html", data); err != nil {
  71. log.Printf("render %s: %v", page, err)
  72. http.Error(w, "internal server error", http.StatusInternalServerError)
  73. return
  74. }
  75. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  76. w.WriteHeader(status)
  77. buf.WriteTo(w)
  78. }