Further simplify template rendering
This commit is contained in:
parent
f0045ae2cf
commit
eab7dae75b
|
@ -33,7 +33,7 @@ func (b *Blog) AdminRoutes(r *mux.Router) {
|
|||
|
||||
// AdminHandler is the admin landing page.
|
||||
func (b *Blog) AdminHandler(w http.ResponseWriter, r *http.Request) {
|
||||
b.RenderTemplate(w, r, "admin/index", render.Vars{})
|
||||
render.Template(w, r, "admin/index", NewVars())
|
||||
}
|
||||
|
||||
// FileTree holds information about files in the document roots.
|
||||
|
|
|
@ -23,7 +23,6 @@ func (b *Blog) AuthRoutes(r *mux.Router) {
|
|||
// MustLogin handles errors from the LoginRequired middleware by redirecting
|
||||
// the user to the login page.
|
||||
func (b *Blog) MustLogin(w http.ResponseWriter, r *http.Request) {
|
||||
log.Info("MustLogin for %s", r.URL.Path)
|
||||
responses.Redirect(w, "/login?next="+r.URL.Path)
|
||||
}
|
||||
|
||||
|
|
26
core/blog.go
26
core/blog.go
|
@ -44,6 +44,10 @@ type Archive struct {
|
|||
|
||||
// BlogRoutes attaches the blog routes to the app.
|
||||
func (b *Blog) BlogRoutes(r *mux.Router) {
|
||||
render.Funcs["RenderIndex"] = b.RenderIndex
|
||||
render.Funcs["RenderPost"] = b.RenderPost
|
||||
render.Funcs["RenderTags"] = b.RenderTags
|
||||
|
||||
// Public routes
|
||||
r.HandleFunc("/blog", b.IndexHandler)
|
||||
r.HandleFunc("/blog.rss", b.RSSHandler)
|
||||
|
@ -302,19 +306,14 @@ func (b *Blog) RenderIndex(r *http.Request, tag, privacy string) template.HTML {
|
|||
// Render the blog index partial.
|
||||
var output bytes.Buffer
|
||||
v := render.Vars{
|
||||
NoLayout: true,
|
||||
Data: map[interface{}]interface{}{
|
||||
"PreviousPage": previousPage,
|
||||
"NextPage": nextPage,
|
||||
"View": view,
|
||||
},
|
||||
}
|
||||
v = b.LoadDefaults(v, r)
|
||||
render.PartialTemplate(&output, "blog/index.partial", render.Config{
|
||||
Request: r,
|
||||
Vars: &v,
|
||||
WithLayout: false,
|
||||
Functions: b.TemplateFuncs(nil, r, nil),
|
||||
})
|
||||
b.RenderTemplate(&output, r, "blog/index.partial", v)
|
||||
|
||||
return template.HTML(output.String())
|
||||
}
|
||||
|
@ -333,19 +332,13 @@ func (b *Blog) RenderTags(r *http.Request, indexView bool) template.HTML {
|
|||
|
||||
var output bytes.Buffer
|
||||
v := render.Vars{
|
||||
NoLayout: true,
|
||||
Data: map[interface{}]interface{}{
|
||||
"IndexView": indexView,
|
||||
"Tags": tags,
|
||||
},
|
||||
}
|
||||
v = b.LoadDefaults(v, r)
|
||||
render.PartialTemplate(&output, "blog/tags.partial", render.Config{
|
||||
Request: r,
|
||||
Vars: &v,
|
||||
WithLayout: false,
|
||||
Functions: b.TemplateFuncs(nil, nil, nil),
|
||||
})
|
||||
// b.RenderPartialTemplate(&output, r, "blog/tags.partial", v, false, nil)
|
||||
b.RenderTemplate(&output, r, "blog/tags.partial", v)
|
||||
|
||||
return template.HTML(output.String())
|
||||
}
|
||||
|
@ -455,6 +448,7 @@ func (b *Blog) RenderPost(r *http.Request, p *posts.Post, indexView bool, numCom
|
|||
}
|
||||
|
||||
meta := render.Vars{
|
||||
NoLayout: true,
|
||||
Data: map[interface{}]interface{}{
|
||||
"Post": p,
|
||||
"Rendered": rendered,
|
||||
|
@ -465,7 +459,7 @@ func (b *Blog) RenderPost(r *http.Request, p *posts.Post, indexView bool, numCom
|
|||
},
|
||||
}
|
||||
output := bytes.Buffer{}
|
||||
err = b.RenderPartialTemplate(&output, r, "blog/entry.partial", meta, false, nil)
|
||||
err = b.RenderTemplate(&output, r, "blog/entry.partial", meta)
|
||||
if err != nil {
|
||||
return template.HTML(fmt.Sprintf("[template error in blog/entry.partial: %s]", err.Error()))
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
gorilla "github.com/gorilla/sessions"
|
||||
"github.com/kirsle/blog/core/internal/log"
|
||||
"github.com/kirsle/blog/core/internal/markdown"
|
||||
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||
|
@ -23,6 +22,8 @@ import (
|
|||
|
||||
// CommentRoutes attaches the comment routes to the app.
|
||||
func (b *Blog) CommentRoutes(r *mux.Router) {
|
||||
render.Funcs["RenderComments"] = b.RenderComments
|
||||
|
||||
r.HandleFunc("/comments", b.CommentHandler)
|
||||
r.HandleFunc("/comments/subscription", b.SubscriptionHandler)
|
||||
r.HandleFunc("/comments/quick-delete", b.QuickDeleteHandler)
|
||||
|
@ -40,13 +41,16 @@ type CommentMeta struct {
|
|||
}
|
||||
|
||||
// RenderComments renders a comment form partial and returns the HTML.
|
||||
func (b *Blog) RenderComments(session *gorilla.Session, csrfToken, url, subject string, ids ...string) template.HTML {
|
||||
func (b *Blog) RenderComments(r *http.Request, subject string, ids ...string) template.HTML {
|
||||
id := strings.Join(ids, "-")
|
||||
session := sessions.Get(r)
|
||||
url := r.URL.Path
|
||||
|
||||
// Load their cached name and email if they posted a comment before.
|
||||
name, _ := session.Values["c.name"].(string)
|
||||
email, _ := session.Values["c.email"].(string)
|
||||
editToken, _ := session.Values["c.token"].(string)
|
||||
csrf, _ := session.Values["csrf"].(string)
|
||||
|
||||
// Check if the user is a logged-in admin, to make all comments editable.
|
||||
var isAdmin bool
|
||||
|
@ -71,7 +75,7 @@ func (b *Blog) RenderComments(session *gorilla.Session, csrfToken, url, subject
|
|||
c.HTML = template.HTML(markdown.RenderMarkdown(c.Body))
|
||||
c.ThreadID = thread.ID
|
||||
c.OriginURL = url
|
||||
c.CSRF = csrfToken
|
||||
c.CSRF = csrf
|
||||
|
||||
// Look up the author username.
|
||||
if c.UserID > 0 {
|
||||
|
@ -120,7 +124,7 @@ func (b *Blog) RenderComments(session *gorilla.Session, csrfToken, url, subject
|
|||
ID: thread.ID,
|
||||
OriginURL: url,
|
||||
Subject: subject,
|
||||
CSRF: csrfToken,
|
||||
CSRF: csrf,
|
||||
Thread: &thread,
|
||||
NewComment: comments.Comment{
|
||||
Name: name,
|
||||
|
|
1
core/internal/controllers/setup/setup.go
Normal file
1
core/internal/controllers/setup/setup.go
Normal file
|
@ -0,0 +1 @@
|
|||
package setup
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/kirsle/blog/core/internal/log"
|
||||
"github.com/kirsle/blog/core/internal/models/users"
|
||||
"github.com/kirsle/blog/core/internal/sessions"
|
||||
"github.com/kirsle/blog/core/internal/types"
|
||||
|
@ -46,8 +45,6 @@ func LoginRequired(onError http.HandlerFunc) negroni.HandlerFunc {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Redirect away!")
|
||||
onError(w, r)
|
||||
}
|
||||
|
||||
|
|
14
core/internal/render/functions.go
Normal file
14
core/internal/render/functions.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Funcs is a global funcmap that the blog can hook its internal
|
||||
// methods onto.
|
||||
var Funcs = template.FuncMap{
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
}
|
|
@ -9,23 +9,14 @@ import (
|
|||
|
||||
"github.com/kirsle/blog/core/internal/forms"
|
||||
"github.com/kirsle/blog/core/internal/log"
|
||||
"github.com/kirsle/blog/core/internal/middleware"
|
||||
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||
"github.com/kirsle/blog/core/internal/models/settings"
|
||||
"github.com/kirsle/blog/core/internal/models/users"
|
||||
"github.com/kirsle/blog/core/internal/sessions"
|
||||
"github.com/kirsle/blog/core/internal/types"
|
||||
)
|
||||
|
||||
// Config provides the settings and injectables for rendering templates.
|
||||
type Config struct {
|
||||
// Refined and raw variables for the templates.
|
||||
Vars *Vars // Normal RenderTemplate's
|
||||
|
||||
// Wrap the template with the `.layout.gohtml`
|
||||
WithLayout bool
|
||||
|
||||
// Inject your own functions for the Go templates.
|
||||
Functions map[string]interface{}
|
||||
|
||||
Request *http.Request
|
||||
}
|
||||
|
||||
// Vars is an interface to implement by the templates to pass their own custom
|
||||
// variables in. It auto-loads global template variables (site name, etc.)
|
||||
// when the template is rendered.
|
||||
|
@ -34,6 +25,7 @@ type Vars struct {
|
|||
SetupNeeded bool
|
||||
Title string
|
||||
Path string
|
||||
TemplatePath string
|
||||
LoggedIn bool
|
||||
CurrentUser *users.User
|
||||
CSRF string
|
||||
|
@ -53,18 +45,58 @@ type Vars struct {
|
|||
Form forms.Form
|
||||
}
|
||||
|
||||
// PartialTemplate handles rendering a Go template to a writer, without
|
||||
// doing anything extra to the vars or dealing with net/http. This is ideal for
|
||||
// rendering partials, such as comment partials.
|
||||
//
|
||||
// This will wrap the template in `.layout.gohtml` by default. To render just
|
||||
// a bare template on its own, i.e. for partial templates, create a Vars struct
|
||||
// with `Vars{NoIndex: true}`
|
||||
func PartialTemplate(w io.Writer, path string, C Config) error {
|
||||
if C.Request == nil {
|
||||
panic("render.RenderPartialTemplate(): The *http.Request is nil!?")
|
||||
// loadDefaults combines template variables with default, globally available vars.
|
||||
func (v *Vars) loadDefaults(r *http.Request) {
|
||||
// Get the site settings.
|
||||
s, err := settings.Load()
|
||||
if err != nil {
|
||||
s = settings.Defaults()
|
||||
}
|
||||
|
||||
if s.Initialized == false && !strings.HasPrefix(r.URL.Path, "/initial-setup") {
|
||||
v.SetupNeeded = true
|
||||
}
|
||||
v.Request = r
|
||||
v.RequestTime = r.Context().Value(types.StartTimeKey).(time.Time)
|
||||
v.Title = s.Site.Title
|
||||
v.Path = r.URL.Path
|
||||
|
||||
user, err := auth.CurrentUser(r)
|
||||
v.CurrentUser = user
|
||||
v.LoggedIn = err == nil
|
||||
}
|
||||
|
||||
// Template responds with an HTML template.
|
||||
//
|
||||
// The vars will be massaged a bit to load the global defaults (such as the
|
||||
// website title and user login status), the user's session may be updated with
|
||||
// new CSRF token, and other such things. If you just want to render a template
|
||||
// without all that nonsense, use RenderPartialTemplate.
|
||||
func Template(w io.Writer, r *http.Request, path string, v Vars) error {
|
||||
// Inject globally available variables.
|
||||
v.loadDefaults(r)
|
||||
|
||||
// If this is the HTTP response, handle session-related things.
|
||||
if rw, ok := w.(http.ResponseWriter); ok {
|
||||
rw.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
||||
session := sessions.Get(r)
|
||||
|
||||
// Flashed messages.
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
for _, flash := range flashes {
|
||||
_ = flash
|
||||
v.Flashes = append(v.Flashes, flash.(string))
|
||||
}
|
||||
session.Save(r, rw)
|
||||
}
|
||||
|
||||
// CSRF token for forms.
|
||||
v.CSRF = middleware.GenerateCSRFToken(rw, r, session)
|
||||
}
|
||||
|
||||
v.RequestDuration = time.Now().Sub(v.RequestTime)
|
||||
v.Editable = !strings.HasPrefix(path, "admin/")
|
||||
|
||||
// v interface{}, withLayout bool, functions map[string]interface{}) error {
|
||||
var (
|
||||
layout Filepath
|
||||
|
@ -80,7 +112,7 @@ func PartialTemplate(w io.Writer, path string, C Config) error {
|
|||
}
|
||||
|
||||
// Get the layout template.
|
||||
if C.WithLayout {
|
||||
if !v.NoLayout {
|
||||
templateName = "layout"
|
||||
layout, err = ResolvePath(".layout")
|
||||
if err != nil {
|
||||
|
@ -98,27 +130,12 @@ func PartialTemplate(w io.Writer, path string, C Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Template functions.
|
||||
funcmap := template.FuncMap{
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
"TemplateName": func() string {
|
||||
return filepath.URI
|
||||
},
|
||||
}
|
||||
if C.Functions != nil {
|
||||
for name, fn := range C.Functions {
|
||||
funcmap[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Useful template functions.
|
||||
t := template.New(filepath.Absolute).Funcs(funcmap)
|
||||
t := template.New(filepath.Absolute).Funcs(Funcs)
|
||||
|
||||
// Parse the template files. The layout comes first because it's the wrapper
|
||||
// and allows the filepath template to set the page title.
|
||||
var templates []string
|
||||
if C.WithLayout {
|
||||
if !v.NoLayout {
|
||||
templates = append(templates, layout.Absolute)
|
||||
}
|
||||
t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...)
|
||||
|
@ -127,7 +144,7 @@ func PartialTemplate(w io.Writer, path string, C Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(w, templateName, C.Vars)
|
||||
err = t.ExecuteTemplate(w, templateName, v)
|
||||
if err != nil {
|
||||
log.Error("Template parsing error: %s", err)
|
||||
return err
|
||||
|
@ -135,25 +152,3 @@ func PartialTemplate(w io.Writer, path string, C Config) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Template responds with an HTML template.
|
||||
//
|
||||
// The vars will be massaged a bit to load the global defaults (such as the
|
||||
// website title and user login status), the user's session may be updated with
|
||||
// new CSRF token, and other such things. If you just want to render a template
|
||||
// without all that nonsense, use RenderPartialTemplate.
|
||||
func Template(w http.ResponseWriter, path string, C Config) error {
|
||||
if C.Request == nil {
|
||||
panic("render.RenderTemplate(): The *http.Request is nil!?")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
||||
PartialTemplate(w, path, Config{
|
||||
Request: C.Request,
|
||||
Vars: C.Vars,
|
||||
WithLayout: true,
|
||||
Functions: C.Functions,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Is it a template file?
|
||||
if strings.HasSuffix(filepath.URI, ".gohtml") {
|
||||
b.RenderTemplate(w, r, filepath.URI, render.Vars{})
|
||||
b.RenderTemplate(w, r, filepath.URI, NewVars())
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,49 +1,12 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kirsle/blog/core/internal/forms"
|
||||
"github.com/kirsle/blog/core/internal/middleware"
|
||||
"github.com/kirsle/blog/core/internal/middleware/auth"
|
||||
"github.com/kirsle/blog/core/internal/models/settings"
|
||||
"github.com/kirsle/blog/core/internal/models/users"
|
||||
"github.com/kirsle/blog/core/internal/render"
|
||||
"github.com/kirsle/blog/core/internal/sessions"
|
||||
"github.com/kirsle/blog/core/internal/types"
|
||||
)
|
||||
|
||||
// Vars is an interface to implement by the templates to pass their own custom
|
||||
// variables in. It auto-loads global template variables (site name, etc.)
|
||||
// when the template is rendered.
|
||||
type Vars struct {
|
||||
// Global, "constant" template variables.
|
||||
SetupNeeded bool
|
||||
Title string
|
||||
Path string
|
||||
LoggedIn bool
|
||||
CurrentUser *users.User
|
||||
CSRF string
|
||||
Editable bool // page is editable
|
||||
Request *http.Request
|
||||
RequestTime time.Time
|
||||
RequestDuration time.Duration
|
||||
|
||||
// Configuration variables
|
||||
NoLayout bool // don't wrap in .layout.html, just render the template
|
||||
|
||||
// Common template variables.
|
||||
Message string
|
||||
Flashes []string
|
||||
Error error
|
||||
Data map[interface{}]interface{}
|
||||
Form forms.Form
|
||||
}
|
||||
|
||||
// NewVars initializes a Vars struct with the custom Data map initialized.
|
||||
// You may pass in an initial value for this map if you want.
|
||||
func NewVars(data ...map[interface{}]interface{}) render.Vars {
|
||||
|
@ -58,102 +21,20 @@ func NewVars(data ...map[interface{}]interface{}) render.Vars {
|
|||
}
|
||||
}
|
||||
|
||||
// LoadDefaults combines template variables with default, globally available vars.
|
||||
func (b *Blog) LoadDefaults(v render.Vars, r *http.Request) render.Vars {
|
||||
// Get the site settings.
|
||||
s, err := settings.Load()
|
||||
if err != nil {
|
||||
s = settings.Defaults()
|
||||
}
|
||||
|
||||
if s.Initialized == false && !strings.HasPrefix(r.URL.Path, "/initial-setup") {
|
||||
v.SetupNeeded = true
|
||||
}
|
||||
v.Request = r
|
||||
v.RequestTime = r.Context().Value(types.StartTimeKey).(time.Time)
|
||||
v.Title = s.Site.Title
|
||||
v.Path = r.URL.Path
|
||||
|
||||
user, err := auth.CurrentUser(r)
|
||||
v.CurrentUser = user
|
||||
v.LoggedIn = err == nil
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RenderPartialTemplate handles rendering a Go template to a writer, without
|
||||
// doing anything extra to the vars or dealing with net/http. This is ideal for
|
||||
// rendering partials, such as comment partials.
|
||||
//
|
||||
// This will wrap the template in `.layout.gohtml` by default. To render just
|
||||
// a bare template on its own, i.e. for partial templates, create a Vars struct
|
||||
// with `Vars{NoIndex: true}`
|
||||
func (b *Blog) RenderPartialTemplate(w io.Writer, r *http.Request, path string, v render.Vars, withLayout bool, functions map[string]interface{}) error {
|
||||
v = b.LoadDefaults(v, r)
|
||||
return render.PartialTemplate(w, path, render.Config{
|
||||
Request: r,
|
||||
Vars: &v,
|
||||
WithLayout: withLayout,
|
||||
Functions: b.TemplateFuncs(nil, nil, functions),
|
||||
})
|
||||
}
|
||||
|
||||
// RenderTemplate responds with an HTML template.
|
||||
//
|
||||
// The vars will be massaged a bit to load the global defaults (such as the
|
||||
// website title and user login status), the user's session may be updated with
|
||||
// new CSRF token, and other such things. If you just want to render a template
|
||||
// without all that nonsense, use RenderPartialTemplate.
|
||||
func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path string, vars render.Vars) error {
|
||||
// new CSRF token, and other such things.
|
||||
//
|
||||
// For server-rendered templates given directly to the user (i.e., in controllers),
|
||||
// give it the http.ResponseWriter; for partial templates you can give it a
|
||||
// bytes.Buffer to write to instead. The subtle difference is whether or not the
|
||||
// template will have access to the request's session.
|
||||
func (b *Blog) RenderTemplate(w io.Writer, r *http.Request, path string, vars render.Vars) error {
|
||||
if r == nil {
|
||||
panic("core.RenderTemplate(): the *http.Request is nil!?")
|
||||
}
|
||||
|
||||
// Inject globally available variables.
|
||||
vars = b.LoadDefaults(vars, r)
|
||||
|
||||
// Add any flashed messages from the endpoint controllers.
|
||||
session := sessions.Get(r)
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
for _, flash := range flashes {
|
||||
_ = flash
|
||||
vars.Flashes = append(vars.Flashes, flash.(string))
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
vars.RequestDuration = time.Now().Sub(vars.RequestTime)
|
||||
vars.CSRF = middleware.GenerateCSRFToken(w, r, session)
|
||||
vars.Editable = !strings.HasPrefix(path, "admin/")
|
||||
|
||||
return render.Template(w, path, render.Config{
|
||||
Request: r,
|
||||
Vars: &vars,
|
||||
Functions: b.TemplateFuncs(w, r, nil),
|
||||
})
|
||||
}
|
||||
|
||||
// TemplateFuncs returns the common template function map.
|
||||
func (b *Blog) TemplateFuncs(w http.ResponseWriter, r *http.Request, inject map[string]interface{}) map[string]interface{} {
|
||||
fn := map[string]interface{}{
|
||||
"RenderIndex": b.RenderIndex,
|
||||
"RenderPost": b.RenderPost,
|
||||
"RenderTags": b.RenderTags,
|
||||
"RenderComments": func(subject string, ids ...string) template.HTML {
|
||||
if w == nil || r == nil {
|
||||
return template.HTML("[RenderComments Error: need both http.ResponseWriter and http.Request]")
|
||||
}
|
||||
|
||||
session := sessions.Get(r)
|
||||
csrf := middleware.GenerateCSRFToken(w, r, session)
|
||||
return b.RenderComments(session, csrf, r.URL.Path, subject, ids...)
|
||||
},
|
||||
}
|
||||
|
||||
if inject != nil {
|
||||
for k, v := range inject {
|
||||
fn[k] = v
|
||||
}
|
||||
}
|
||||
return fn
|
||||
return render.Template(w, r, path, vars)
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
{{ if and .CurrentUser.Admin .Editable }}
|
||||
<p class="mt-4">
|
||||
<strong>Admin:</strong> [<a href="/admin/editor?file={{ or .Data.MarkdownFile TemplateName }}">edit this page</a>]
|
||||
<strong>Admin:</strong> [<a href="/admin/editor?file={{ or .Data.MarkdownFile .Path }}">edit this page</a>]
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<h2 id="comments" class="mt-4">Comments</h2>
|
||||
|
||||
{{ $idStr := printf "%d" $p.ID}}
|
||||
{{ RenderComments $p.Title "post" $idStr }}
|
||||
{{ RenderComments .Request $p.Title "post" $idStr }}
|
||||
{{ else }}
|
||||
<hr>
|
||||
<em>Comments are disabled on this post.</em>
|
||||
|
|
Loading…
Reference in New Issue
Block a user