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.
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.
@ -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,
"Path": fp,
"Body": string(body),
"FromCore": fromCore,
})
b.RenderTemplate(w, r, "admin/editor", v)
}
render.Template(w, r, "admin/editor", v)
return
}
@ -165,19 +165,19 @@ func (b *Blog) editorFileList(w http.ResponseWriter, r *http.Request) {
trees = append(trees, tree)
}
v := NewVars(map[interface{}]interface{}{
v := map[string]interface{}{
"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.
func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
v := NewVars()
// Get the current settings.
settings, _ := settings.Load()
v.Data["s"] = settings
v := map[string]interface{}{
"s": settings,
}
if r.Method == http.MethodPost {
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
err := form.Validate()
if err != nil {
v.Error = err
v["Error"] = err
} else {
// Save the settings.
settings.Save()
@ -230,5 +230,5 @@ func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
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/middleware/auth"
"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/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.
func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
vars := NewVars()
vars.Form = forms.Setup{}
vars := map[string]interface{}{
"Form": forms.Setup{},
}
var nextURL string
if r.Method == http.MethodPost {
@ -49,22 +51,22 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
} else {
nextURL = r.URL.Query().Get("next")
}
vars.Data["NextURL"] = nextURL
vars["NextURL"] = nextURL
if r.Method == http.MethodPost {
form := &forms.Login{
Username: r.FormValue("username"),
Password: r.FormValue("password"),
}
vars.Form = form
vars["Form"] = form
err := form.Validate()
if err != nil {
vars.Error = err
vars["Error"] = err
} else {
// Test the login.
user, err := users.CheckAuth(form.Username, form.Password)
if err != nil {
vars.Error = errors.New("bad username or password")
vars["Error"] = errors.New("bad username or password")
} else {
// 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.
@ -113,13 +115,14 @@ func (b *Blog) AccountHandler(w http.ResponseWriter, r *http.Request) {
return
}
v := NewVars()
form := &forms.Account{
Username: user.Username,
Email: user.Email,
Name: user.Name,
}
v.Form = form
v := map[string]interface{}{
"Form": form,
}
if r.Method == http.MethodPost {
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"]
if !ok {
// They're listing all the tags.
b.RenderTemplate(w, r, "blog/tags.gohtml", NewVars())
render.Template(w, r, "blog/tags.gohtml", nil)
return
}
@ -182,11 +182,11 @@ func (b *Blog) CommonIndexHandler(w http.ResponseWriter, r *http.Request, tag, p
title = "Blog"
}
b.RenderTemplate(w, r, "blog/index", NewVars(map[interface{}]interface{}{
render.Template(w, r, "blog/index", map[string]interface{}{
"Title": title,
"Tag": tag,
"Privacy": privacy,
}))
})
}
// 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.
var output bytes.Buffer
v := render.Vars{
NoLayout: true,
Data: map[interface{}]interface{}{
v := map[string]interface{}{
"PreviousPage": previousPage,
"NextPage": nextPage,
"View": view,
},
}
b.RenderTemplate(&output, r, "blog/index.partial", v)
render.Template(&output, r, "blog/index.partial", v)
return template.HTML(output.String())
}
@ -331,14 +328,11 @@ func (b *Blog) RenderTags(r *http.Request, indexView bool) template.HTML {
}
var output bytes.Buffer
v := render.Vars{
NoLayout: true,
Data: map[interface{}]interface{}{
v := map[string]interface{}{
"IndexView": indexView,
"Tags": tags,
},
}
b.RenderTemplate(&output, r, "blog/tags.partial", v)
render.Template(&output, r, "blog/tags.partial", v)
return template.HTML(output.String())
}
@ -384,10 +378,10 @@ func (b *Blog) BlogArchive(w http.ResponseWriter, r *http.Request) {
result = append(result, byMonth[label])
}
v := NewVars(map[interface{}]interface{}{
v := map[string]interface{}{
"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
@ -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,
})
b.RenderTemplate(w, r, "blog/entry", v)
}
render.Template(w, r, "blog/entry", v)
return nil
}
@ -447,19 +441,16 @@ func (b *Blog) RenderPost(r *http.Request, p *posts.Post, indexView bool, numCom
rendered = template.HTML(p.Body)
}
meta := render.Vars{
NoLayout: true,
Data: map[interface{}]interface{}{
meta := map[string]interface{}{
"Post": p,
"Rendered": rendered,
"Author": author,
"IndexView": indexView,
"Snipped": snipped,
"NumComments": numComments,
},
}
output := bytes.Buffer{}
err = b.RenderTemplate(&output, r, "blog/entry.partial", meta)
err = render.Template(&output, r, "blog/entry.partial", meta)
if err != nil {
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.
func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
v := NewVars(map[interface{}]interface{}{
v := map[string]interface{}{
"preview": "",
})
}
var post *posts.Post
// Are we editing an existing post?
@ -480,7 +471,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
if err == nil {
post, err = posts.Load(id)
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()
}
}
@ -496,13 +487,13 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
switch r.FormValue("submit") {
case "preview":
if post.ContentType == string(MARKDOWN) {
v.Data["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body))
v["preview"] = template.HTML(markdown.RenderTrustedMarkdown(post.Body))
} else {
v.Data["preview"] = template.HTML(post.Body)
v["preview"] = template.HTML(post.Body)
}
case "post":
if err := post.Validate(); err != nil {
v.Error = err
v["Error"] = err
} else {
author, _ := auth.CurrentUser(r)
post.AuthorID = author.ID
@ -510,7 +501,7 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
post.Updated = time.Now().UTC()
err = post.Save()
if err != nil {
v.Error = err
v["Error"] = err
} else {
responses.Flash(w, r, "Post created!")
responses.Redirect(w, "/"+post.Fragment)
@ -519,16 +510,16 @@ func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
}
}
v.Data["post"] = post
b.RenderTemplate(w, r, "blog/edit", v)
v["post"] = post
render.Template(w, r, "blog/edit", v)
}
// DeletePost to delete a blog entry.
func (b *Blog) DeletePost(w http.ResponseWriter, r *http.Request) {
var post *posts.Post
v := NewVars(map[interface{}]interface{}{
v := map[string]interface{}{
"Post": nil,
})
}
var idStr string
if r.Method == http.MethodPost {
@ -557,6 +548,6 @@ func (b *Blog) DeletePost(w http.ResponseWriter, r *http.Request) {
return
}
v.Data["Post"] = post
b.RenderTemplate(w, r, "blog/delete", v)
v["Post"] = post
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.")
return
}
v := NewVars()
currentUser, _ := auth.CurrentUser(r)
editToken := b.GetEditToken(w, r)
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.Save(r, w)
v := map[string]interface{}{}
// Previewing, deleting, or posting?
switch submit {
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))
case ActionPost:
if err := c.Validate(); err != nil {
v.Error = err
v["Error"] = err
} else {
// Store our edit token, if we don't have one. For example, admins
// 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.Data["Comment"] = c
v.Data["Editing"] = c.Editing
v.Data["Deleting"] = submit == ActionDelete
v["Thread"] = t
v["Comment"] = c
v["Editing"] = c.Editing
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.
func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
v := NewVars()
var err error
// POST to unsubscribe from all threads.
if r.Method == http.MethodPost {
email := r.FormValue("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 {
v.Error = errors.New("invalid email address")
err = errors.New("invalid email address")
}
m := comments.LoadMailingList()
@ -294,7 +294,9 @@ func (b *Blog) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
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.

View File

@ -12,15 +12,17 @@ import (
"github.com/kirsle/blog/core/internal/forms"
"github.com/kirsle/blog/core/internal/markdown"
"github.com/kirsle/blog/core/internal/models/settings"
"github.com/kirsle/blog/core/internal/render"
"github.com/kirsle/blog/core/internal/responses"
)
// ContactRoutes attaches the contact URL to the app.
func (b *Blog) ContactRoutes(r *mux.Router) {
r.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) {
v := NewVars()
form := forms.Contact{}
v.Form = &form
form := &forms.Contact{}
v := map[string]interface{}{
"Form": form,
}
// If there is no site admin, show an error.
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)
err := b.RenderTemplate(w, r, ".errors/404", render.Vars{
Message: message,
err := render.Template(w, r, ".errors/404", map[string]string{
"Message": message,
})
if err != nil {
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.
func (b *Blog) Forbidden(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusForbidden)
err := b.RenderTemplate(w, r, ".errors/403", render.Vars{
Message: message,
err := render.Template(w, r, ".errors/403", map[string]string{
"Message": message,
})
if err != nil {
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.
func (b *Blog) Error(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusInternalServerError)
err := b.RenderTemplate(w, r, ".errors/500", render.Vars{
Message: message,
err := render.Template(w, r, ".errors/500", map[string]string{
"Message": message,
})
if err != nil {
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.
func (b *Blog) BadRequest(w http.ResponseWriter, r *http.Request, message string) {
w.WriteHeader(http.StatusBadRequest)
err := b.RenderTemplate(w, r, ".errors/400", render.Vars{
Message: message,
err := render.Template(w, r, ".errors/400", map[string]string{
"Message": message,
})
if err != nil {
log.Error(err.Error())

View File

@ -14,8 +14,9 @@ import (
// SetupHandler is the initial blog setup route.
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.
@ -26,15 +27,10 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
}
if r.Method == http.MethodPost {
form := forms.Setup{
Username: r.FormValue("username"),
Password: r.FormValue("password"),
Confirm: r.FormValue("confirm"),
}
vars.Form = form
form.ParseForm(r)
err := form.Validate()
if err != nil {
vars.Error = err
vars["Error"] = err
} else {
// Save the site config.
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)
if err != nil {
log.Error("Error: %v", err)
vars.Error = err
vars["Error"] = err
}
// 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
// Form is an interface for forms that can validate themselves.
type Form interface {
Validate() error
}
// type Form interface {
// Validate() error
// }

View File

@ -2,6 +2,7 @@ package forms
import (
"errors"
"net/http"
)
// Setup is for the initial blog setup page at /initial-setup.
@ -11,6 +12,13 @@ type Setup struct {
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.
func (f Setup) Validate() error {
if len(f.Username) == 0 {

View File

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

View File

@ -7,7 +7,6 @@ import (
"strings"
"time"
"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"
@ -20,12 +19,12 @@ import (
// 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 {
type vars struct {
// Global, "constant" template variables.
SetupNeeded bool
Title string
Path string
TemplatePath string
TemplatePath string // actual template file on disk
LoggedIn bool
CurrentUser *users.User
CSRF string
@ -34,36 +33,11 @@ type Vars struct {
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
}
// 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
Data interface{}
}
// 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
// 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 {
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.
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 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.Editable = !strings.HasPrefix(path, "admin/")
// v interface{}, withLayout bool, functions map[string]interface{}) error {
var (
layout Filepath
templateName string
err error
)
// 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)
return err
}
v.TemplatePath = filepath.URI
// Get the layout template.
if !v.NoLayout {
if !isPartial {
templateName = "layout"
layout, err = ResolvePath(".layout")
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
// and allows the filepath template to set the page title.
var templates []string
if !v.NoLayout {
if !isPartial {
templates = append(templates, layout.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?
if strings.HasSuffix(filepath.URI, ".gohtml") {
b.RenderTemplate(w, r, filepath.URI, NewVars())
render.Template(w, r, filepath.URI, nil)
return
}
@ -58,11 +58,11 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
html := markdown.RenderTrustedMarkdown(body)
title, _ := markdown.TitleFromMarkdown(body)
b.RenderTemplate(w, r, ".markdown", NewVars(map[interface{}]interface{}{
render.Template(w, r, ".markdown", map[string]interface{}{
"Title": title,
"HTML": template.HTML(html),
"MarkdownFile": filepath.URI,
}))
"MarkdownPath": filepath.URI,
})
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" }}
<h1>400 Bad Request</h1>
{{ .Message }}
{{ .Data.Message }}
{{ end }}

View File

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

View File

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

View File

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

View File

@ -79,9 +79,9 @@
{{ template "content" . }}
{{ if and .CurrentUser.Admin .Editable }}
{{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }}
<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>
{{ end }}
</div>

View File

@ -3,4 +3,10 @@
{{ .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 }}

View File

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

View File

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