summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2024-05-17 23:37:43 +0200
committerBrian Picciano <mediocregopher@gmail.com>2024-05-18 14:47:09 +0200
commit8d7e708d98a3a46ba3ba08f9c8deeb4838bb8ca5 (patch)
tree6662c3e4c6c3baaea058a3deaba0d9cfc8e9cc40 /src/render
parentfac06df97a47cda6e8989bfc5f40f2a627279b92 (diff)
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.
Diffstat (limited to 'src/render')
-rw-r--r--src/render/methods.go226
-rw-r--r--src/render/render.go2
2 files changed, 228 insertions, 0 deletions
diff --git a/src/render/methods.go b/src/render/methods.go
new file mode 100644
index 0000000..6dd9332
--- /dev/null
+++ b/src/render/methods.go
@@ -0,0 +1,226 @@
+package render
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "html/template"
+ "net/url"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "dev.mediocregopher.com/mediocre-blog.git/src/gmi/gemtext"
+ "dev.mediocregopher.com/mediocre-blog.git/src/post"
+ "github.com/gomarkdown/markdown"
+ "github.com/gomarkdown/markdown/html"
+ "github.com/gomarkdown/markdown/parser"
+ gmnhg "github.com/tdemin/gmnhg"
+)
+
+type ctxKey string
+
+const (
+ ctxKeyPost ctxKey = "post"
+)
+
+// WithPost sets the post on a Context, such that it can be retrieved using
+// the GetThisPost method.
+func WithPost(ctx context.Context, p post.StoredPost) context.Context {
+ return context.WithValue(ctx, ctxKeyPost, p)
+}
+
+// GetPostsRes are the fields returned from the GetPosts method.
+type GetPostsRes struct {
+ Posts []post.StoredPost
+ HasMore bool
+}
+
+// GetPostSeriesNextPreviousRes are the fields returned from the
+// GetPostSeriesNextPreviousRes method.
+type GetPostSeriesNextPreviousRes struct {
+ Next *post.StoredPost
+ Previous *post.StoredPost
+}
+
+// Methods carries methods which are designed to be accessible from a template.
+type Methods struct {
+ ctx context.Context
+ url *url.URL
+ publicURL *url.URL
+ geminiGatewayURL *url.URL
+ postStore post.Store
+ preprocessFuncs post.PreprocessFunctions
+
+ thisPost *post.StoredPost // cache
+}
+
+// NewMethods initializes a Methods using its required dependencies.
+func NewMethods(
+ ctx context.Context,
+ url *url.URL,
+ publicURL *url.URL,
+ geminiGatewayURL *url.URL,
+ postStore post.Store,
+ preprocessFuncs post.PreprocessFunctions,
+) *Methods {
+ return &Methods{
+ ctx,
+ url,
+ publicURL,
+ geminiGatewayURL,
+ postStore,
+ preprocessFuncs,
+ nil, // thisPost
+ }
+}
+
+func (m *Methods) GetPosts(page, count int) (GetPostsRes, error) {
+ posts, hasMore, err := m.postStore.Get(page, count)
+ return GetPostsRes{posts, hasMore}, err
+}
+
+func (m *Methods) GetThisPost() (p post.StoredPost, err error) {
+ if m.thisPost != nil {
+ return *m.thisPost, nil
+ }
+
+ defer func() {
+ m.thisPost = &p
+ }()
+
+ if p, ok := m.ctx.Value(ctxKeyPost).(post.StoredPost); ok {
+ return p, nil
+ }
+
+ id := path.Base(m.url.Path)
+ id = strings.TrimSuffix(id, path.Ext(id))
+
+ return m.postStore.GetByID(id)
+}
+
+func (m *Methods) GetPostByID(id string) (post.StoredPost, error) {
+ p, err := m.postStore.GetByID(id)
+ if err != nil {
+ return post.StoredPost{}, fmt.Errorf("fetching post %q: %w", id, err)
+ }
+ return p, nil
+}
+
+func (m *Methods) GetPostSeriesNextPrevious(
+ p post.StoredPost,
+) (
+ GetPostSeriesNextPreviousRes, error,
+) {
+
+ seriesPosts, err := m.postStore.GetBySeries(p.Series)
+ if err != nil {
+ return GetPostSeriesNextPreviousRes{}, fmt.Errorf(
+ "fetching posts for series %q: %w", p.Series, err,
+ )
+ }
+
+ var (
+ res GetPostSeriesNextPreviousRes
+ foundThis bool
+ )
+
+ for i := range seriesPosts {
+
+ seriesPost := seriesPosts[i]
+
+ if seriesPost.ID == p.ID {
+ foundThis = true
+ continue
+ }
+
+ if !foundThis {
+ res.Next = &seriesPost
+ continue
+ }
+
+ res.Previous = &seriesPost
+ break
+ }
+
+ return res, nil
+}
+
+func (m *Methods) PostGemtextBody(p post.StoredPost) (string, error) {
+
+ buf := new(bytes.Buffer)
+
+ if err := p.PreprocessBody(buf, m.preprocessFuncs); err != nil {
+ return "", fmt.Errorf("preprocessing post body: %w", err)
+ }
+
+ bodyBytes := buf.Bytes()
+
+ if p.Format == post.FormatMarkdown {
+
+ gemtextBodyBytes, err := gmnhg.RenderMarkdown(bodyBytes, 0)
+ if err != nil {
+ return "", fmt.Errorf("converting from markdown: %w", err)
+ }
+
+ bodyBytes = gemtextBodyBytes
+ }
+
+ return string(bodyBytes), nil
+}
+
+func (m *Methods) PostHTMLBody(p post.StoredPost) (template.HTML, error) {
+ bodyBuf := new(bytes.Buffer)
+
+ if err := p.PreprocessBody(bodyBuf, m.preprocessFuncs); err != nil {
+ return "", fmt.Errorf("preprocessing post body: %w", err)
+ }
+
+ if p.Format == post.FormatGemtext {
+
+ prevBodyBuf := bodyBuf
+ bodyBuf = new(bytes.Buffer)
+
+ err := gemtext.ToMarkdown(
+ bodyBuf, prevBodyBuf, m.geminiGatewayURL,
+ )
+
+ if err != nil {
+ return "", fmt.Errorf("converting gemtext to markdown: %w", err)
+ }
+ }
+
+ // this helps the markdown renderer properly parse pages which end in a
+ // `</script>` 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)
+ return template.HTML(renderedBody), nil
+}
+
+func (m *Methods) GetQueryValue(key, def string) string {
+ v := m.url.Query().Get(key)
+ if v == "" {
+ v = def
+ }
+ return v
+}
+
+func (m *Methods) GetQueryIntValue(key string, def int) (int, error) {
+ vStr := m.GetQueryValue(key, strconv.Itoa(def))
+ return strconv.Atoi(vStr)
+}
+
+func (m *Methods) GetPath() (string, error) {
+ basePath := filepath.Join("/", m.publicURL.Path) // in case it's empty
+ return filepath.Rel(basePath, m.url.Path)
+}
+
+func (m *Methods) Add(a, b int) int { return a + b }
diff --git a/src/render/render.go b/src/render/render.go
new file mode 100644
index 0000000..28e60df
--- /dev/null
+++ b/src/render/render.go
@@ -0,0 +1,2 @@
+// Package render implements shared utilities used when rendering templates.
+package render