Let me use interface{} for template vars

Since most of the `render.Vars{}` fields were hardcoded/not really
editable for the templates, apart from .Data, this struct is now locked
away in the render subpackage.

End http.HandlerFunc's can then make any arbitrary template data
structure they want to, available inside the templates as `.Data`.
This commit is contained in:
Noah 2018-02-10 14:05:41 -08:00
parent eab7dae75b
commit 6d3de7da69
21 changed files with 173 additions and 203 deletions

View File

@ -33,7 +33,7 @@ func (b *Blog) AdminRoutes(r *mux.Router) {
// AdminHandler is the admin landing page. // AdminHandler is the admin landing page.
func (b *Blog) AdminHandler(w http.ResponseWriter, r *http.Request) { func (b *Blog) AdminHandler(w http.ResponseWriter, r *http.Request) {
render.Template(w, r, "admin/index", NewVars()) render.Template(w, r, "admin/index", nil)
} }
// FileTree holds information about files in the document roots. // FileTree holds information about files in the document roots.
@ -106,13 +106,13 @@ func (b *Blog) EditorHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"File": file, "File": file,
"Path": fp, "Path": fp,
"Body": string(body), "Body": string(body),
"FromCore": fromCore, "FromCore": fromCore,
}) }
b.RenderTemplate(w, r, "admin/editor", v) render.Template(w, r, "admin/editor", v)
return return
} }
@ -165,19 +165,19 @@ func (b *Blog) editorFileList(w http.ResponseWriter, r *http.Request) {
trees = append(trees, tree) trees = append(trees, tree)
} }
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"FileTrees": trees, "FileTrees": trees,
}) }
b.RenderTemplate(w, r, "admin/filelist", v) render.Template(w, r, "admin/filelist", v)
} }
// SettingsHandler lets you configure the app from the frontend. // SettingsHandler lets you configure the app from the frontend.
func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) { func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
v := NewVars()
// Get the current settings. // Get the current settings.
settings, _ := settings.Load() settings, _ := settings.Load()
v.Data["s"] = settings v := map[string]interface{}{
"s": settings,
}
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
redisPort, _ := strconv.Atoi(r.FormValue("redis-port")) redisPort, _ := strconv.Atoi(r.FormValue("redis-port"))
@ -220,7 +220,7 @@ func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
settings.Mail.Password = form.MailPassword settings.Mail.Password = form.MailPassword
err := form.Validate() err := form.Validate()
if err != nil { if err != nil {
v.Error = err v["Error"] = err
} else { } else {
// Save the settings. // Save the settings.
settings.Save() settings.Save()
@ -230,5 +230,5 @@ func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
b.RenderTemplate(w, r, "admin/settings", v) render.Template(w, r, "admin/settings", v)
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/kirsle/blog/core/internal/log" "github.com/kirsle/blog/core/internal/log"
"github.com/kirsle/blog/core/internal/middleware/auth" "github.com/kirsle/blog/core/internal/middleware/auth"
"github.com/kirsle/blog/core/internal/models/users" "github.com/kirsle/blog/core/internal/models/users"
"github.com/kirsle/blog/core/internal/render"
"github.com/kirsle/blog/core/internal/responses" "github.com/kirsle/blog/core/internal/responses"
"github.com/kirsle/blog/core/internal/sessions" "github.com/kirsle/blog/core/internal/sessions"
) )
@ -40,8 +41,9 @@ func (b *Blog) Login(w http.ResponseWriter, r *http.Request, u *users.User) erro
// LoginHandler shows and handles the login page. // LoginHandler shows and handles the login page.
func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) { func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
vars := NewVars() vars := map[string]interface{}{
vars.Form = forms.Setup{} "Form": forms.Setup{},
}
var nextURL string var nextURL string
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
@ -49,22 +51,22 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
} else { } else {
nextURL = r.URL.Query().Get("next") nextURL = r.URL.Query().Get("next")
} }
vars.Data["NextURL"] = nextURL vars["NextURL"] = nextURL
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
form := &forms.Login{ form := &forms.Login{
Username: r.FormValue("username"), Username: r.FormValue("username"),
Password: r.FormValue("password"), Password: r.FormValue("password"),
} }
vars.Form = form vars["Form"] = form
err := form.Validate() err := form.Validate()
if err != nil { if err != nil {
vars.Error = err vars["Error"] = err
} else { } else {
// Test the login. // Test the login.
user, err := users.CheckAuth(form.Username, form.Password) user, err := users.CheckAuth(form.Username, form.Password)
if err != nil { if err != nil {
vars.Error = errors.New("bad username or password") vars["Error"] = errors.New("bad username or password")
} else { } else {
// Login OK! // Login OK!
responses.Flash(w, r, "Login OK!") responses.Flash(w, r, "Login OK!")
@ -82,7 +84,7 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
b.RenderTemplate(w, r, "login", vars) render.Template(w, r, "login", vars)
} }
// LogoutHandler logs the user out and redirects to the home page. // LogoutHandler logs the user out and redirects to the home page.
@ -113,13 +115,14 @@ func (b *Blog) AccountHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
v := NewVars()
form := &forms.Account{ form := &forms.Account{
Username: user.Username, Username: user.Username,
Email: user.Email, Email: user.Email,
Name: user.Name, Name: user.Name,
} }
v.Form = form v := map[string]interface{}{
"Form": form,
}
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
form.Username = users.Normalize(r.FormValue("username")) form.Username = users.Normalize(r.FormValue("username"))
@ -172,5 +175,5 @@ func (b *Blog) AccountHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
b.RenderTemplate(w, r, "account", v) render.Template(w, r, "account", v)
} }

View File

@ -151,7 +151,7 @@ func (b *Blog) Tagged(w http.ResponseWriter, r *http.Request) {
tag, ok := params["tag"] tag, ok := params["tag"]
if !ok { if !ok {
// They're listing all the tags. // They're listing all the tags.
b.RenderTemplate(w, r, "blog/tags.gohtml", NewVars()) render.Template(w, r, "blog/tags.gohtml", nil)
return return
} }
@ -182,11 +182,11 @@ func (b *Blog) CommonIndexHandler(w http.ResponseWriter, r *http.Request, tag, p
title = "Blog" title = "Blog"
} }
b.RenderTemplate(w, r, "blog/index", NewVars(map[interface{}]interface{}{ render.Template(w, r, "blog/index", map[string]interface{}{
"Title": title, "Title": title,
"Tag": tag, "Tag": tag,
"Privacy": privacy, "Privacy": privacy,
})) })
} }
// RecentPosts gets and filters the blog entries and orders them by most recent. // RecentPosts gets and filters the blog entries and orders them by most recent.
@ -305,15 +305,12 @@ func (b *Blog) RenderIndex(r *http.Request, tag, privacy string) template.HTML {
// Render the blog index partial. // Render the blog index partial.
var output bytes.Buffer var output bytes.Buffer
v := render.Vars{ v := map[string]interface{}{
NoLayout: true,
Data: map[interface{}]interface{}{
"PreviousPage": previousPage, "PreviousPage": previousPage,
"NextPage": nextPage, "NextPage": nextPage,
"View": view, "View": view,
},
} }
b.RenderTemplate(&output, r, "blog/index.partial", v) render.Template(&output, r, "blog/index.partial", v)
return template.HTML(output.String()) return template.HTML(output.String())
} }
@ -331,14 +328,11 @@ func (b *Blog) RenderTags(r *http.Request, indexView bool) template.HTML {
} }
var output bytes.Buffer var output bytes.Buffer
v := render.Vars{ v := map[string]interface{}{
NoLayout: true,
Data: map[interface{}]interface{}{
"IndexView": indexView, "IndexView": indexView,
"Tags": tags, "Tags": tags,
},
} }
b.RenderTemplate(&output, r, "blog/tags.partial", v) render.Template(&output, r, "blog/tags.partial", v)
return template.HTML(output.String()) return template.HTML(output.String())
} }
@ -384,10 +378,10 @@ func (b *Blog) BlogArchive(w http.ResponseWriter, r *http.Request) {
result = append(result, byMonth[label]) result = append(result, byMonth[label])
} }
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"Archive": result, "Archive": result,
}) }
b.RenderTemplate(w, r, "blog/archive", v) render.Template(w, r, "blog/archive", v)
} }
// viewPost is the underlying implementation of the handler to view a blog // viewPost is the underlying implementation of the handler to view a blog
@ -408,10 +402,10 @@ func (b *Blog) viewPost(w http.ResponseWriter, r *http.Request, fragment string)
} }
} }
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"Post": post, "Post": post,
}) }
b.RenderTemplate(w, r, "blog/entry", v) render.Template(w, r, "blog/entry", v)
return nil return nil
} }
@ -447,19 +441,16 @@ func (b *Blog) RenderPost(r *http.Request, p *posts.Post, indexView bool, numCom
rendered = template.HTML(p.Body) rendered = template.HTML(p.Body)
} }
meta := render.Vars{ meta := map[string]interface{}{
NoLayout: true,
Data: map[interface{}]interface{}{
"Post": p, "Post": p,
"Rendered": rendered, "Rendered": rendered,
"Author": author, "Author": author,
"IndexView": indexView, "IndexView": indexView,
"Snipped": snipped, "Snipped": snipped,
"NumComments": numComments, "NumComments": numComments,
},
} }
output := bytes.Buffer{} output := bytes.Buffer{}
err = b.RenderTemplate(&output, r, "blog/entry.partial", meta) err = render.Template(&output, r, "blog/entry.partial", meta)
if err != nil { if err != nil {
return template.HTML(fmt.Sprintf("[template error in blog/entry.partial: %s]", err.Error())) return template.HTML(fmt.Sprintf("[template error in blog/entry.partial: %s]", err.Error()))
} }
@ -469,9 +460,9 @@ func (b *Blog) RenderPost(r *http.Request, p *posts.Post, indexView bool, numCom
// EditBlog is the blog writing and editing page. // EditBlog is the blog writing and editing page.
func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) { func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"preview": "", "preview": "",
}) }
var post *posts.Post var post *posts.Post
// Are we editing an existing post? // Are we editing an existing post?
@ -480,7 +471,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
if err == nil { if err == nil {
post, err = posts.Load(id) post, err = posts.Load(id)
if err != nil { if err != nil {
v.Error = errors.New("that post ID was not found") v["Error"] = errors.New("that post ID was not found")
post = posts.New() post = posts.New()
} }
} }
@ -496,13 +487,13 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
switch r.FormValue("submit") { switch r.FormValue("submit") {
case "preview": case "preview":
if post.ContentType == string(MARKDOWN) { if post.ContentType == string(MARKDOWN) {
v.Data["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body)) v["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body))
} else { } else {
v.Data["preview"] = template.HTML(post.Body) v["preview"] = template.HTML(post.Body)
} }
case "post": case "post":
if err := post.Validate(); err != nil { if err := post.Validate(); err != nil {
v.Error = err v["Error"] = err
} else { } else {
author, _ := auth.CurrentUser(r) author, _ := auth.CurrentUser(r)
post.AuthorID = author.ID post.AuthorID = author.ID
@ -510,7 +501,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
post.Updated = time.Now().UTC() post.Updated = time.Now().UTC()
err = post.Save() err = post.Save()
if err != nil { if err != nil {
v.Error = err v["Error"] = err
} else { } else {
responses.Flash(w, r, "Post created!") responses.Flash(w, r, "Post created!")
responses.Redirect(w, "/"+post.Fragment) responses.Redirect(w, "/"+post.Fragment)
@ -519,16 +510,16 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
} }
} }
v.Data["post"] = post v["post"] = post
b.RenderTemplate(w, r, "blog/edit", v) render.Template(w, r, "blog/edit", v)
} }
// DeletePost to delete a blog entry. // DeletePost to delete a blog entry.
func (b *Blog) DeletePost(w http.ResponseWriter, r *http.Request) { func (b *Blog) DeletePost(w http.ResponseWriter, r *http.Request) {
var post *posts.Post var post *posts.Post
v := NewVars(map[interface{}]interface{}{ v := map[string]interface{}{
"Post": nil, "Post": nil,
}) }
var idStr string var idStr string
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
@ -557,6 +548,6 @@ func (b *Blog) DeletePost(w http.ResponseWriter, r *http.Request) {
return return
} }
v.Data["Post"] = post v["Post"] = post
b.RenderTemplate(w, r, "blog/delete", v) render.Template(w, r, "blog/delete", v)
} }

View File

@ -148,7 +148,6 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
b.BadRequest(w, r, "That method is not allowed.") b.BadRequest(w, r, "That method is not allowed.")
return return
} }
v := NewVars()
currentUser, _ := auth.CurrentUser(r) currentUser, _ := auth.CurrentUser(r)
editToken := b.GetEditToken(w, r) editToken := b.GetEditToken(w, r)
submit := r.FormValue("submit") submit := r.FormValue("submit")
@ -205,6 +204,8 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
session.Values["c.email"] = c.Email session.Values["c.email"] = c.Email
session.Save(r, w) session.Save(r, w)
v := map[string]interface{}{}
// Previewing, deleting, or posting? // Previewing, deleting, or posting?
switch submit { switch submit {
case ActionPreview, ActionDelete: case ActionPreview, ActionDelete:
@ -216,7 +217,7 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
c.HTML = template.HTML(markdown.RenderMarkdown(c.Body)) c.HTML = template.HTML(markdown.RenderMarkdown(c.Body))
case ActionPost: case ActionPost:
if err := c.Validate(); err != nil { if err := c.Validate(); err != nil {
v.Error = err v["Error"] = err
} else { } else {
// Store our edit token, if we don't have one. For example, admins // Store our edit token, if we don't have one. For example, admins
// can edit others' comments but should not replace their edit token. // can edit others' comments but should not replace their edit token.
@ -255,25 +256,24 @@ func (b *Blog) CommentHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
v.Data["Thread"] = t v["Thread"] = t
v.Data["Comment"] = c v["Comment"] = c
v.Data["Editing"] = c.Editing v["Editing"] = c.Editing
v.Data["Deleting"] = submit == ActionDelete v["Deleting"] = submit == ActionDelete
b.RenderTemplate(w, r, "comments/index.gohtml", v) render.Template(w, r, "comments/index.gohtml", v)
} }
// SubscriptionHandler to opt out of subscriptions. // SubscriptionHandler to opt out of subscriptions.
func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) { func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
v := NewVars() var err error
// POST to unsubscribe from all threads. // POST to unsubscribe from all threads.
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
email := r.FormValue("email") email := r.FormValue("email")
if email == "" { if email == "" {
v.Error = errors.New("email address is required to unsubscribe from comment threads") err = errors.New("email address is required to unsubscribe from comment threads")
} else if _, err := mail.ParseAddress(email); err != nil { } else if _, err := mail.ParseAddress(email); err != nil {
v.Error = errors.New("invalid email address") err = errors.New("invalid email address")
} }
m := comments.LoadMailingList() m := comments.LoadMailingList()
@ -294,7 +294,9 @@ func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
b.RenderTemplate(w, r, "comments/subscription.gohtml", v) render.Template(w, r, "comments/subscription.gohtml", map[string]error{
"Error": err,
})
} }
// QuickDeleteHandler allows the admin to quickly delete spam without logging in. // QuickDeleteHandler allows the admin to quickly delete spam without logging in.

View File

@ -12,15 +12,17 @@ import (
"github.com/kirsle/blog/core/internal/forms" "github.com/kirsle/blog/core/internal/forms"
"github.com/kirsle/blog/core/internal/markdown" "github.com/kirsle/blog/core/internal/markdown"
"github.com/kirsle/blog/core/internal/models/settings" "github.com/kirsle/blog/core/internal/models/settings"
"github.com/kirsle/blog/core/internal/render"
"github.com/kirsle/blog/core/internal/responses" "github.com/kirsle/blog/core/internal/responses"
) )
// ContactRoutes attaches the contact URL to the app. // ContactRoutes attaches the contact URL to the app.
func (b *Blog) ContactRoutes(r *mux.Router) { func (b *Blog) ContactRoutes(r *mux.Router) {
r.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) { r.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) {
v := NewVars() form := &forms.Contact{}
form := forms.Contact{} v := map[string]interface{}{
v.Form = &form "Form": form,
}
// If there is no site admin, show an error. // If there is no site admin, show an error.
cfg, err := settings.Load() cfg, err := settings.Load()
@ -73,6 +75,6 @@ func (b *Blog) ContactRoutes(r *mux.Router) {
} }
} }
b.RenderTemplate(w, r, "contact", v) render.Template(w, r, "contact", v)
}) })
} }

View File

@ -14,8 +14,8 @@ func (b *Blog) NotFound(w http.ResponseWriter, r *http.Request, message string)
} }
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
err := b.RenderTemplate(w, r, ".errors/404", render.Vars{ err := render.Template(w, r, ".errors/404", map[string]string{
Message: message, "Message": message,
}) })
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
@ -26,8 +26,8 @@ func (b *Blog) NotFound(w http.ResponseWriter, r *http.Request, message string)
// Forbidden sends an HTTP 403 Forbidden response. // Forbidden sends an HTTP 403 Forbidden response.
func (b *Blog) Forbidden(w http.ResponseWriter, r *http.Request, message string) { func (b *Blog) Forbidden(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
err := b.RenderTemplate(w, r, ".errors/403", render.Vars{ err := render.Template(w, r, ".errors/403", map[string]string{
Message: message, "Message": message,
}) })
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
@ -38,8 +38,8 @@ func (b *Blog) Forbidden(w http.ResponseWriter, r *http.Request, message string)
// Error sends an HTTP 500 Internal Server Error response. // Error sends an HTTP 500 Internal Server Error response.
func (b *Blog) Error(w http.ResponseWriter, r *http.Request, message string) { func (b *Blog) Error(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
err := b.RenderTemplate(w, r, ".errors/500", render.Vars{ err := render.Template(w, r, ".errors/500", map[string]string{
Message: message, "Message": message,
}) })
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
@ -50,8 +50,8 @@ func (b *Blog) Error(w http.ResponseWriter, r *http.Request, message string) {
// BadRequest sends an HTTP 400 Bad Request. // BadRequest sends an HTTP 400 Bad Request.
func (b *Blog) BadRequest(w http.ResponseWriter, r *http.Request, message string) { func (b *Blog) BadRequest(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
err := b.RenderTemplate(w, r, ".errors/400", render.Vars{ err := render.Template(w, r, ".errors/400", map[string]string{
Message: message, "Message": message,
}) })
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())

View File

@ -14,8 +14,9 @@ import (
// SetupHandler is the initial blog setup route. // SetupHandler is the initial blog setup route.
func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) { func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
vars := render.Vars{ form := &forms.Setup{}
Form: forms.Setup{}, vars := map[string]interface{}{
"Form": form,
} }
// Reject if we're already set up. // Reject if we're already set up.
@ -26,15 +27,10 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
} }
if r.Method == http.MethodPost { if r.Method == http.MethodPost {
form := forms.Setup{ form.ParseForm(r)
Username: r.FormValue("username"),
Password: r.FormValue("password"),
Confirm: r.FormValue("confirm"),
}
vars.Form = form
err := form.Validate() err := form.Validate()
if err != nil { if err != nil {
vars.Error = err vars["Error"] = err
} else { } else {
// Save the site config. // Save the site config.
log.Info("Creating default website config file") log.Info("Creating default website config file")
@ -54,7 +50,7 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
err := users.Create(user) err := users.Create(user)
if err != nil { if err != nil {
log.Error("Error: %v", err) log.Error("Error: %v", err)
vars.Error = err vars["Error"] = err
} }
// All set! // All set!
@ -64,5 +60,5 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
b.RenderTemplate(w, r, "initial-setup", vars) render.Template(w, r, "initial-setup", vars)
} }

View File

@ -1,6 +1,6 @@
package forms package forms
// Form is an interface for forms that can validate themselves. // Form is an interface for forms that can validate themselves.
type Form interface { // type Form interface {
Validate() error // Validate() error
} // }

View File

@ -2,6 +2,7 @@ package forms
import ( import (
"errors" "errors"
"net/http"
) )
// Setup is for the initial blog setup page at /initial-setup. // Setup is for the initial blog setup page at /initial-setup.
@ -11,6 +12,13 @@ type Setup struct {
Confirm string Confirm string
} }
// Parse form values.
func (f *Setup) ParseForm(r *http.Request) {
f.Username = r.FormValue("username")
f.Password = r.FormValue("password")
f.Confirm = r.FormValue("confirm")
}
// Validate the form. // Validate the form.
func (f Setup) Validate() error { func (f Setup) Validate() error {
if len(f.Username) == 0 { if len(f.Username) == 0 {

View File

@ -125,9 +125,11 @@ func Pygmentize(language, source string) (string, error) {
cacheKey := "pygmentize:" + hash cacheKey := "pygmentize:" + hash
// Do we have it cached? // Do we have it cached?
if Cache != nil {
if cached, err := Cache.Get(cacheKey); err == nil && len(cached) > 0 { if cached, err := Cache.Get(cacheKey); err == nil && len(cached) > 0 {
return string(cached), nil return string(cached), nil
} }
}
// Defer to the `pygmentize` command // Defer to the `pygmentize` command
bin := "pygmentize" bin := "pygmentize"
@ -150,10 +152,12 @@ func Pygmentize(language, source string) (string, error) {
} }
result = out.String() result = out.String()
if Cache != nil {
err := Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change err := Cache.Set(cacheKey, []byte(result), 60*60*24) // cool md5's don't change
if err != nil { if err != nil {
log.Error("Couldn't cache Pygmentize output: %s", err) log.Error("Couldn't cache Pygmentize output: %s", err)
} }
}
return result, nil return result, nil
} }

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/kirsle/blog/core/internal/forms"
"github.com/kirsle/blog/core/internal/log" "github.com/kirsle/blog/core/internal/log"
"github.com/kirsle/blog/core/internal/middleware" "github.com/kirsle/blog/core/internal/middleware"
"github.com/kirsle/blog/core/internal/middleware/auth" "github.com/kirsle/blog/core/internal/middleware/auth"
@ -20,12 +19,12 @@ import (
// Vars is an interface to implement by the templates to pass their own custom // 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.) // variables in. It auto-loads global template variables (site name, etc.)
// when the template is rendered. // when the template is rendered.
type Vars struct { type vars struct {
// Global, "constant" template variables. // Global, "constant" template variables.
SetupNeeded bool SetupNeeded bool
Title string Title string
Path string Path string
TemplatePath string TemplatePath string // actual template file on disk
LoggedIn bool LoggedIn bool
CurrentUser *users.User CurrentUser *users.User
CSRF string CSRF string
@ -34,36 +33,11 @@ type Vars struct {
RequestTime time.Time RequestTime time.Time
RequestDuration time.Duration RequestDuration time.Duration
// Configuration variables
NoLayout bool // don't wrap in .layout.html, just render the template
// Common template variables. // Common template variables.
Message string Message string
Flashes []string Flashes []string
Error error Error error
Data map[interface{}]interface{} Data interface{}
Form forms.Form
}
// 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. // Template responds with an HTML template.
@ -72,9 +46,30 @@ func (v *Vars) loadDefaults(r *http.Request) {
// website title and user login status), the user's session may be updated with // 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 // new CSRF token, and other such things. If you just want to render a template
// without all that nonsense, use RenderPartialTemplate. // without all that nonsense, use RenderPartialTemplate.
func Template(w io.Writer, r *http.Request, path string, v Vars) error { func Template(w io.Writer, r *http.Request, path string, data interface{}) error {
isPartial := strings.Contains(path, ".partial")
// Get the site settings.
s, err := settings.Load()
if err != nil {
s = settings.Defaults()
}
// Inject globally available variables. // Inject globally available variables.
v.loadDefaults(r) v := vars{
SetupNeeded: s.Initialized == false && !strings.HasPrefix(r.URL.Path, "/initial-setup"),
Request: r,
RequestTime: r.Context().Value(types.StartTimeKey).(time.Time),
Title: s.Site.Title,
Path: r.URL.Path,
Data: data,
}
user, err := auth.CurrentUser(r)
v.CurrentUser = user
v.LoggedIn = err == nil
// If this is the HTTP response, handle session-related things. // If this is the HTTP response, handle session-related things.
if rw, ok := w.(http.ResponseWriter); ok { if rw, ok := w.(http.ResponseWriter); ok {
@ -97,11 +92,9 @@ func Template(w io.Writer, r *http.Request, path string, v Vars) error {
v.RequestDuration = time.Now().Sub(v.RequestTime) v.RequestDuration = time.Now().Sub(v.RequestTime)
v.Editable = !strings.HasPrefix(path, "admin/") v.Editable = !strings.HasPrefix(path, "admin/")
// v interface{}, withLayout bool, functions map[string]interface{}) error {
var ( var (
layout Filepath layout Filepath
templateName string templateName string
err error
) )
// Find the file path to the template. // Find the file path to the template.
@ -110,9 +103,10 @@ func Template(w io.Writer, r *http.Request, path string, v Vars) error {
log.Error("RenderTemplate(%s): file not found", path) log.Error("RenderTemplate(%s): file not found", path)
return err return err
} }
v.TemplatePath = filepath.URI
// Get the layout template. // Get the layout template.
if !v.NoLayout { if !isPartial {
templateName = "layout" templateName = "layout"
layout, err = ResolvePath(".layout") layout, err = ResolvePath(".layout")
if err != nil { if err != nil {
@ -135,7 +129,7 @@ func Template(w io.Writer, r *http.Request, path string, v Vars) error {
// Parse the template files. The layout comes first because it's the wrapper // Parse the template files. The layout comes first because it's the wrapper
// and allows the filepath template to set the page title. // and allows the filepath template to set the page title.
var templates []string var templates []string
if !v.NoLayout { if !isPartial {
templates = append(templates, layout.Absolute) templates = append(templates, layout.Absolute)
} }
t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...) t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...)

View File

@ -41,7 +41,7 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
// Is it a template file? // Is it a template file?
if strings.HasSuffix(filepath.URI, ".gohtml") { if strings.HasSuffix(filepath.URI, ".gohtml") {
b.RenderTemplate(w, r, filepath.URI, NewVars()) render.Template(w, r, filepath.URI, nil)
return return
} }
@ -58,11 +58,11 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
html := markdown.RenderTrustedMarkdown(body) html := markdown.RenderTrustedMarkdown(body)
title, _ := markdown.TitleFromMarkdown(body) title, _ := markdown.TitleFromMarkdown(body)
b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{ render.Template(w, r, ".markdown", map[string]interface{}{
"Title": title, "Title": title,
"HTML": template.HTML(html), "HTML": template.HTML(html),
"MarkdownFile": filepath.URI, "MarkdownPath": filepath.URI,
})) })
return return
} }

View File

@ -1,40 +0,0 @@
package core
import (
"io"
"net/http"
"github.com/kirsle/blog/core/internal/render"
)
// 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 {
var value map[interface{}]interface{}
if len(data) > 0 {
value = data[0]
} else {
value = make(map[interface{}]interface{})
}
return render.Vars{
Data: value,
}
}
// 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.
//
// 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!?")
}
return render.Template(w, r, path, vars)
}

View File

@ -2,5 +2,5 @@
{{ define "content" }} {{ define "content" }}
<h1>400 Bad Request</h1> <h1>400 Bad Request</h1>
{{ .Message }} {{ .Data.Message }}
{{ end }} {{ end }}

View File

@ -2,5 +2,5 @@
{{ define "content" }} {{ define "content" }}
<h1>403 Forbidden</h1> <h1>403 Forbidden</h1>
{{ .Message }} {{ .Data.Message }}
{{ end }} {{ end }}

View File

@ -2,7 +2,7 @@
{{ define "content" }} {{ define "content" }}
<h1>404 Not Found</h1> <h1>404 Not Found</h1>
{{ .Message }} {{ .Data.Message }}
{{ if .CurrentUser.Admin }} {{ if .CurrentUser.Admin }}
<p> <p>

View File

@ -2,5 +2,5 @@
{{ define "content" }} {{ define "content" }}
<h1>500 Internal Server Error</h1> <h1>500 Internal Server Error</h1>
{{ .Message }} {{ .Data.Message }}
{{ end }} {{ end }}

View File

@ -79,9 +79,9 @@
{{ template "content" . }} {{ template "content" . }}
{{ if and .CurrentUser.Admin .Editable }} {{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }}
<p class="mt-4"> <p class="mt-4">
<strong>Admin:</strong> [<a href="/admin/editor?file={{ or .Data.MarkdownFile .Path }}">edit this page</a>] <strong>Admin:</strong> [<a href="/admin/editor?file={{ .TemplatePath }}">edit this page</a>]
</p> </p>
{{ end }} {{ end }}
</div> </div>

View File

@ -3,4 +3,10 @@
{{ .Data.HTML }} {{ .Data.HTML }}
{{ if and .CurrentUser.Admin .Editable }}
<p class="mt-4">
<strong>Admin:</strong> [<a href="/admin/editor?file={{ .Data.MarkdownPath }}">edit this page</a>]
</p>
{{ end }}
{{ end }} {{ end }}

View File

@ -7,6 +7,9 @@
administrator. administrator.
</p> </p>
data={{ .Data }}
{{ $form := .Data.Form }}
<form method="POST" action="/contact"> <form method="POST" action="/contact">
<input type="hidden" name="_csrf" value="{{ .CSRF }}"> <input type="hidden" name="_csrf" value="{{ .CSRF }}">
<div class="form-group"> <div class="form-group">
@ -19,7 +22,7 @@
class="form-control" class="form-control"
id="name" id="name"
placeholder="Anonymous" placeholder="Anonymous"
value="{{ .Form.Name }}"> value="{{ $form.Name }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Your email:</label> <label for="email">Your email:</label>
@ -28,7 +31,7 @@
class="form-control" class="form-control"
id="email" id="email"
placeholder="(if you want a response)" placeholder="(if you want a response)"
value="{{ .Form.Email }}"> value="{{ $form.Email }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="subject"> <label for="subject">
@ -40,7 +43,7 @@
class="form-control" class="form-control"
id="subject" id="subject"
placeholder="No Subject" placeholder="No Subject"
value="{{ .Form.Subject }}"> value="{{ $form.Subject }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="message">Message:</label> <label for="message">Message:</label>
@ -50,7 +53,7 @@
name="message" name="message"
id="message" id="message"
placeholder="Message" placeholder="Message"
required>{{ .Form.Message }}</textarea> required>{{ $form.Message }}</textarea>
</div> </div>
<button type="submit" class="btn btn-primary">Send Message</button> <button type="submit" class="btn btn-primary">Send Message</button>

View File

@ -13,6 +13,7 @@
predictable for an attacker to guess. predictable for an attacker to guess.
</p> </p>
{{ $form := .Data.Form }}
<form method="POST" action="/initial-setup"> <form method="POST" action="/initial-setup">
<input type="hidden" name="_csrf" value="{{ .CSRF }}"> <input type="hidden" name="_csrf" value="{{ .CSRF }}">
<div class="form-group"> <div class="form-group">
@ -22,7 +23,7 @@
class="form-control" class="form-control"
id="setup-admin-username" id="setup-admin-username"
placeholder="Enter username" placeholder="Enter username"
value="{{ .Form.Username }}"> value="{{ $form.Username }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="setup-admin-password1">Passphrase:</label> <label for="setup-admin-password1">Passphrase:</label>