From 8d7e708d98a3a46ba3ba08f9c8deeb4838bb8ca5 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Fri, 17 May 2024 23:37:43 +0200 Subject: Render posts completely using common rendering methods The aim is to reduce reliance on custom logic in the handlers for every protocol, eventually outsourcing all of it into `render.Methods`, leaving each protocol to simply direct calls to the correct template. --- src/http/assets.go | 2 +- src/http/drafts.go | 2 +- src/http/http.go | 2 +- src/http/index.go | 2 +- src/http/posts.go | 129 +++---------------------------------------------- src/http/tpl.go | 84 ++++++++++++++++++-------------- src/http/tpl/post.html | 30 +++++++----- 7 files changed, 77 insertions(+), 174 deletions(-) (limited to 'src/http') diff --git a/src/http/assets.go b/src/http/assets.go index 79ab98c..5a47152 100644 --- a/src/http/assets.go +++ b/src/http/assets.go @@ -34,7 +34,7 @@ func (a *api) managePostAssetsHandler() http.Handler { IDs: ids, } - executeTemplate(rw, r, tpl, tplPayload) + a.executeTemplate(rw, r, tpl, tplPayload) }) } diff --git a/src/http/drafts.go b/src/http/drafts.go index f8e4c8a..b0550ce 100644 --- a/src/http/drafts.go +++ b/src/http/drafts.go @@ -48,7 +48,7 @@ func (a *api) manageDraftPostsHandler() http.Handler { tplPayload.NextPage = page + 1 } - executeTemplate(rw, r, tpl, tplPayload) + a.executeTemplate(rw, r, tpl, tplPayload) }) } diff --git a/src/http/http.go b/src/http/http.go index fd0ea16..9bfed59 100644 --- a/src/http/http.go +++ b/src/http/http.go @@ -146,7 +146,7 @@ func New(params Params) (API, error) { auther: NewAuther(params.AuthUsers, params.AuthRatelimit), } - a.redirectTpl = a.mustParseTpl("redirect.html") + a.redirectTpl = mustParseTpl(a.emptyTpl(), "redirect.html") a.srv = &http.Server{Handler: a.handler()} diff --git a/src/http/index.go b/src/http/index.go index 21c6c16..48d0d33 100644 --- a/src/http/index.go +++ b/src/http/index.go @@ -31,6 +31,6 @@ func (a *api) renderIndexHandler() http.Handler { return } - executeTemplate(rw, r, tpl, nil) + a.executeTemplate(rw, r, tpl, nil) }) } diff --git a/src/http/posts.go b/src/http/posts.go index ab3a18a..939b811 100644 --- a/src/http/posts.go +++ b/src/http/posts.go @@ -12,13 +12,10 @@ import ( txttpl "text/template" "time" - "github.com/gomarkdown/markdown" - "github.com/gomarkdown/markdown/html" - "github.com/gomarkdown/markdown/parser" - "dev.mediocregopher.com/mediocre-blog.git/src/gmi" "dev.mediocregopher.com/mediocre-blog.git/src/http/apiutil" "dev.mediocregopher.com/mediocre-blog.git/src/post" "dev.mediocregopher.com/mediocre-blog.git/src/post/asset" + "dev.mediocregopher.com/mediocre-blog.git/src/render" "dev.mediocregopher.com/mediocre-go-lib.git/mctx" ) @@ -92,80 +89,6 @@ func (a *api) postPreprocessFuncs() post.PreprocessFunctions { } } -func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) { - - preprocessFuncs := a.postPreprocessFuncs() - - bodyBuf := new(bytes.Buffer) - - if err := storedPost.PreprocessBody(bodyBuf, preprocessFuncs); err != nil { - return postTplPayload{}, fmt.Errorf("preprocessing post body: %w", err) - } - - if storedPost.Format == post.FormatGemtext { - - prevBodyBuf := bodyBuf - bodyBuf = new(bytes.Buffer) - - err := gmi.GemtextToMarkdown( - bodyBuf, prevBodyBuf, a.params.GeminiGatewayURL, - ) - - if err != nil { - return postTplPayload{}, fmt.Errorf("converting gemtext to markdown: %w", err) - } - } - - // this helps the markdown renderer properly parse pages which end in a - // `` tag... I don't know why. - _, _ = bodyBuf.WriteString("\n") - - parserExt := parser.CommonExtensions | parser.AutoHeadingIDs - parser := parser.NewWithExtensions(parserExt) - - htmlFlags := html.HrefTargetBlank - htmlRenderer := html.NewRenderer(html.RendererOptions{Flags: htmlFlags}) - - renderedBody := markdown.ToHTML(bodyBuf.Bytes(), parser, htmlRenderer) - - tplPayload := postTplPayload{ - StoredPost: storedPost, - Body: template.HTML(renderedBody), - } - - if series := storedPost.Series; series != "" { - - seriesPosts, err := a.params.PostStore.GetBySeries(series) - if err != nil { - return postTplPayload{}, fmt.Errorf( - "fetching posts for series %q: %w", series, err, - ) - } - - var foundThis bool - - for i := range seriesPosts { - - seriesPost := seriesPosts[i] - - if seriesPost.ID == storedPost.ID { - foundThis = true - continue - } - - if !foundThis { - tplPayload.SeriesNext = &seriesPost - continue - } - - tplPayload.SeriesPrevious = &seriesPost - break - } - } - - return tplPayload, nil -} - func (a *api) getPostsHandler() http.Handler { tpl := a.mustParseBasedTpl("posts.html") @@ -236,7 +159,7 @@ func (a *api) getPostsHandler() http.Handler { tplPayload.NextPage = page + 1 } - executeTemplate(rw, r, tpl, tplPayload) + a.executeTemplate(rw, r, tpl, tplPayload) }) } @@ -245,37 +168,7 @@ func (a *api) getPostHandler() http.Handler { tpl := a.mustParseBasedTpl("post.html") return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - - id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html") - - storedPost, err := a.params.PostStore.GetByID(id) - - if errors.Is(err, post.ErrPostNotFound) { - http.Error(rw, "Post not found", 404) - return - } else if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("fetching post with id %q: %w", id, err), - ) - return - } - - tplPayload, err := a.postToPostTplPayload(storedPost) - - if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf( - "generating template payload for post with id %q: %w", - id, err, - ), - ) - return - } - - executeTemplate( - rw, r, tpl, tplPayload, - executeTemplateWithTitlePrefix(storedPost.Title), - ) + a.executeTemplate(rw, r, tpl, nil) }) } @@ -319,7 +212,7 @@ func (a *api) managePostsHandler() http.Handler { tplPayload.NextPage = page + 1 } - executeTemplate(rw, r, tpl, tplPayload) + a.executeTemplate(rw, r, tpl, tplPayload) }) } @@ -378,7 +271,7 @@ func (a *api) editPostHandler(isDraft bool) http.Handler { Formats: post.Formats, } - executeTemplate(rw, r, tpl, tplPayload) + a.executeTemplate(rw, r, tpl, tplPayload) }) } @@ -516,15 +409,7 @@ func (a *api) previewPostHandler() http.Handler { PublishedAt: time.Now(), } - tplPayload, err := a.postToPostTplPayload(storedPost) - - if err != nil { - apiutil.InternalServerError( - rw, r, fmt.Errorf("generating template payload: %w", err), - ) - return - } - - executeTemplate(rw, r, tpl, tplPayload) + r = r.WithContext(render.WithPost(r.Context(), storedPost)) + a.executeTemplate(rw, r, tpl, nil) }) } diff --git a/src/http/tpl.go b/src/http/tpl.go index 24f2453..c623f2e 100644 --- a/src/http/tpl.go +++ b/src/http/tpl.go @@ -1,9 +1,12 @@ package http import ( + "bytes" "embed" + "errors" "fmt" "html/template" + "io" "io/fs" "net/http" "net/url" @@ -12,6 +15,8 @@ import ( "time" "dev.mediocregopher.com/mediocre-blog.git/src/http/apiutil" + "dev.mediocregopher.com/mediocre-blog.git/src/post" + "dev.mediocregopher.com/mediocre-blog.git/src/render" ) //go:embed tpl @@ -105,77 +110,84 @@ func (a *api) tplFuncs() template.FuncMap { } } -func (a *api) parseTpl(name, tplBody string) (*template.Template, error) { - - tpl := template.New(name) +func (a *api) emptyTpl() *template.Template { + tpl := template.New("") tpl = tpl.Funcs(a.tplFuncs()) tpl = tpl.Funcs(template.FuncMap(a.postPreprocessFuncs().ToFuncMap())) - - var err error - - if tpl, err = tpl.Parse(tplBody); err != nil { - return nil, err - } - - return tpl, nil + return tpl } -func (a *api) mustParseTpl(name string) *template.Template { - return template.Must(a.parseTpl(name, mustReadTplFile(name))) +func mustParseTpl(tpl *template.Template, name string) *template.Template { + return template.Must(tpl.New(name).Parse(mustReadTplFile(name))) } func (a *api) mustParseBasedTpl(name string) *template.Template { - tpl := a.mustParseTpl(name) - tpl = template.Must(tpl.New("gemini-cta.html").Parse(mustReadTplFile("gemini-cta.html"))) - tpl = template.Must(tpl.New("base.html").Parse(mustReadTplFile("base.html"))) + tpl := a.emptyTpl() + tpl = mustParseTpl(tpl, "gemini-cta.html") + tpl = mustParseTpl(tpl, "base.html") + tpl = mustParseTpl(tpl, name) return tpl } type tplData struct { + *render.Methods Payload interface{} Title string } -func newTPLData(r *http.Request, payload interface{}) tplData { +// WithTitlePrefix returns a copy of tplData but with the given string prefixed +// to the page title. This is intended for use within templates, when nesting +// the base template. +func (d tplData) WithTitlePrefix(prefix string) tplData { + d.Title = prefix + " - " + d.Title + return d +} + +func (a *api) newTPLData(r *http.Request, payload interface{}) tplData { return tplData{ + Methods: render.NewMethods( + r.Context(), + r.URL, + a.params.PublicURL, + a.params.GeminiGatewayURL, + a.params.PostStore, + a.postPreprocessFuncs(), + ), Payload: payload, Title: "mediocregopher's lil web corner", } } -type executeTemplateOpt func(*tplData) - -func executeTemplateWithTitlePrefix(prefix string) executeTemplateOpt { - return func(d *tplData) { - d.Title = prefix + " - " + d.Title - } -} - // executeTemplate expects to be the final action in an http.Handler -func executeTemplate( - rw http.ResponseWriter, r *http.Request, - tpl *template.Template, payload interface{}, - opts ...executeTemplateOpt, +func (a *api) executeTemplate( + rw http.ResponseWriter, + r *http.Request, + tpl *template.Template, + payload interface{}, ) { - tplData := newTPLData(r, payload) + tplData := a.newTPLData(r, payload) - for _, opt := range opts { - opt(&tplData) - } + buf := new(bytes.Buffer) - if err := tpl.Execute(rw, tplData); err != nil { + err := tpl.Execute(buf, tplData) + if errors.Is(err, post.ErrPostNotFound) { + http.Error(rw, "Post not found", 404) + return + } else if err != nil { apiutil.InternalServerError( rw, r, fmt.Errorf("rendering template: %w", err), ) return } + + io.Copy(rw, buf) } func (a *api) executeRedirectTpl( rw http.ResponseWriter, r *http.Request, url string, ) { - executeTemplate(rw, r, a.redirectTpl, struct { + a.executeTemplate(rw, r, a.redirectTpl, struct { URL string }{ URL: url, @@ -188,7 +200,7 @@ func (a *api) renderDumbTplHandler(tplName string) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - tplData := newTPLData(r, nil) + tplData := a.newTPLData(r, nil) if err := tpl.Execute(rw, tplData); err != nil { apiutil.InternalServerError( diff --git a/src/http/tpl/post.html b/src/http/tpl/post.html index db69302..0cf3622 100644 --- a/src/http/tpl/post.html +++ b/src/http/tpl/post.html @@ -1,45 +1,51 @@ {{ define "body" }} +{{ $post := .GetThisPost -}}

- {{ .Payload.Title }} + {{ $post.Title }}

-{{ if ne .Payload.Description "" }} +{{ if ne $post.Description "" }}

- - {{ .Payload.Description }} + - {{ $post.Description }}

{{ end }}
-{{ .Payload.Body }} +{{ .PostHTMLBody $post }}

- Published {{ DateTimeFormat .Payload.PublishedAt }} + Published {{ DateTimeFormat $post.PublishedAt }}

-{{ if (or .Payload.SeriesPrevious .Payload.SeriesNext) }} +{{- if $post.Series }} +{{ $seriesNextPrev := .GetPostSeriesNextPrevious $post -}} +{{ if or $seriesNextPrev.Next $seriesNextPrev.Previous }}

This post is part of a series.
- {{ if .Payload.SeriesPrevious }} - Previously: {{ .Payload.SeriesPrevious.Title }} + {{ if $seriesNextPrev.Next }} + Next: {{ $seriesNextPrev.Next.Title }} {{ end }} - {{ if (and .Payload.SeriesNext .Payload.SeriesPrevious) }} + {{ if and $seriesNextPrev.Next $seriesNextPrev.Previous }}
{{ end }} - {{ if .Payload.SeriesNext }} - Next: {{ .Payload.SeriesNext.Title }}
+ {{ if $seriesNextPrev.Previous }} + Previously: {{ $seriesNextPrev.Previous.Title }} +
{{ end }}

{{ end }} +{{ end }} {{ template "gemini-cta.html" . }} {{ end }} -{{ template "base.html" . }} +{{ $post := .GetThisPost -}} +{{ template "base.html" (.WithTitlePrefix $post.Title) }} -- cgit v1.2.3