From 6d3de7da691f1170adf7993e8596a6b904b92ff3 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sat, 10 Feb 2018 14:05:41 -0800 Subject: [PATCH] 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`. --- core/admin.go | 24 ++++----- core/auth.go | 23 ++++---- core/blog.go | 87 ++++++++++++++---------------- core/comments.go | 26 ++++----- core/contact.go | 10 ++-- core/errors.go | 16 +++--- core/initial-setup.go | 18 +++---- core/internal/forms/forms.go | 6 +-- core/internal/forms/setup.go | 8 +++ core/internal/markdown/markdown.go | 14 +++-- core/internal/render/templates.go | 64 ++++++++++------------ core/pages.go | 8 +-- core/templates.go | 40 -------------- root/.errors/400.gohtml | 2 +- root/.errors/403.gohtml | 2 +- root/.errors/404.gohtml | 2 +- root/.errors/500.gohtml | 2 +- root/.layout.gohtml | 4 +- root/.markdown.gohtml | 6 +++ root/contact.gohtml | 11 ++-- root/initial-setup.gohtml | 3 +- 21 files changed, 173 insertions(+), 203 deletions(-) delete mode 100644 core/templates.go diff --git a/core/admin.go b/core/admin.go index b910951..8af40ef 100644 --- a/core/admin.go +++ b/core/admin.go @@ -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) } diff --git a/core/auth.go b/core/auth.go index 29efd4b..ba59417 100644 --- a/core/auth.go +++ b/core/auth.go @@ -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) } diff --git a/core/blog.go b/core/blog.go index de2e5d3..1955e8d 100644 --- a/core/blog.go +++ b/core/blog.go @@ -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{}{ - "PreviousPage": previousPage, - "NextPage": nextPage, - "View": view, - }, + 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{}{ - "IndexView": indexView, - "Tags": tags, - }, + 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{}{ - "Post": p, - "Rendered": rendered, - "Author": author, - "IndexView": indexView, - "Snipped": snipped, - "NumComments": numComments, - }, + 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) } diff --git a/core/comments.go b/core/comments.go index 9d10114..f6a985b 100644 --- a/core/comments.go +++ b/core/comments.go @@ -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. diff --git a/core/contact.go b/core/contact.go index 11a8dcc..8c97717 100644 --- a/core/contact.go +++ b/core/contact.go @@ -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) }) } diff --git a/core/errors.go b/core/errors.go index 3219985..8984360 100644 --- a/core/errors.go +++ b/core/errors.go @@ -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()) diff --git a/core/initial-setup.go b/core/initial-setup.go index 3ad754b..d0063f5 100644 --- a/core/initial-setup.go +++ b/core/initial-setup.go @@ -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) } diff --git a/core/internal/forms/forms.go b/core/internal/forms/forms.go index a261e60..2e7b9ef 100644 --- a/core/internal/forms/forms.go +++ b/core/internal/forms/forms.go @@ -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 +// } diff --git a/core/internal/forms/setup.go b/core/internal/forms/setup.go index dd76a08..18afbd1 100644 --- a/core/internal/forms/setup.go +++ b/core/internal/forms/setup.go @@ -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 { diff --git a/core/internal/markdown/markdown.go b/core/internal/markdown/markdown.go index aa148b9..6bb1b47 100644 --- a/core/internal/markdown/markdown.go +++ b/core/internal/markdown/markdown.go @@ -125,8 +125,10 @@ func Pygmentize(language, source string) (string, error) { cacheKey := "pygmentize:" + hash // Do we have it cached? - if cached, err := Cache.Get(cacheKey); err == nil && len(cached) > 0 { - return string(cached), nil + if Cache != nil { + if cached, err := Cache.Get(cacheKey); err == nil && len(cached) > 0 { + return string(cached), nil + } } // Defer to the `pygmentize` command @@ -150,9 +152,11 @@ func Pygmentize(language, source string) (string, error) { } result = out.String() - 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) + 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 diff --git a/core/internal/render/templates.go b/core/internal/render/templates.go index 0b80619..0b7affe 100644 --- a/core/internal/render/templates.go +++ b/core/internal/render/templates.go @@ -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)...) diff --git a/core/pages.go b/core/pages.go index 7bea655..7e0bbbd 100644 --- a/core/pages.go +++ b/core/pages.go @@ -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 } diff --git a/core/templates.go b/core/templates.go deleted file mode 100644 index 07d45bf..0000000 --- a/core/templates.go +++ /dev/null @@ -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) -} diff --git a/root/.errors/400.gohtml b/root/.errors/400.gohtml index 25842a0..34ad409 100644 --- a/root/.errors/400.gohtml +++ b/root/.errors/400.gohtml @@ -2,5 +2,5 @@ {{ define "content" }}

400 Bad Request

-{{ .Message }} +{{ .Data.Message }} {{ end }} diff --git a/root/.errors/403.gohtml b/root/.errors/403.gohtml index b9832e9..61d91f8 100644 --- a/root/.errors/403.gohtml +++ b/root/.errors/403.gohtml @@ -2,5 +2,5 @@ {{ define "content" }}

403 Forbidden

-{{ .Message }} +{{ .Data.Message }} {{ end }} diff --git a/root/.errors/404.gohtml b/root/.errors/404.gohtml index aab335c..76ad398 100644 --- a/root/.errors/404.gohtml +++ b/root/.errors/404.gohtml @@ -2,7 +2,7 @@ {{ define "content" }}

404 Not Found

-{{ .Message }} +{{ .Data.Message }} {{ if .CurrentUser.Admin }}

diff --git a/root/.errors/500.gohtml b/root/.errors/500.gohtml index 2ca412f..9dcea92 100644 --- a/root/.errors/500.gohtml +++ b/root/.errors/500.gohtml @@ -2,5 +2,5 @@ {{ define "content" }}

500 Internal Server Error

-{{ .Message }} +{{ .Data.Message }} {{ end }} diff --git a/root/.layout.gohtml b/root/.layout.gohtml index 84d1eee..4060f56 100644 --- a/root/.layout.gohtml +++ b/root/.layout.gohtml @@ -79,9 +79,9 @@ {{ template "content" . }} - {{ if and .CurrentUser.Admin .Editable }} + {{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }}

- Admin: [edit this page] + Admin: [edit this page]

{{ end }} diff --git a/root/.markdown.gohtml b/root/.markdown.gohtml index 43a7881..99c8972 100644 --- a/root/.markdown.gohtml +++ b/root/.markdown.gohtml @@ -3,4 +3,10 @@ {{ .Data.HTML }} +{{ if and .CurrentUser.Admin .Editable }} +

+ Admin: [edit this page] +

+{{ end }} + {{ end }} diff --git a/root/contact.gohtml b/root/contact.gohtml index a1af15b..66f0d99 100644 --- a/root/contact.gohtml +++ b/root/contact.gohtml @@ -7,6 +7,9 @@ administrator.

+data={{ .Data }} + +{{ $form := .Data.Form }}
@@ -19,7 +22,7 @@ class="form-control" id="name" placeholder="Anonymous" - value="{{ .Form.Name }}"> + value="{{ $form.Name }}">
@@ -28,7 +31,7 @@ class="form-control" id="email" placeholder="(if you want a response)" - value="{{ .Form.Email }}"> + value="{{ $form.Email }}">
@@ -50,7 +53,7 @@ name="message" id="message" placeholder="Message" - required>{{ .Form.Message }} + required>{{ $form.Message }}
diff --git a/root/initial-setup.gohtml b/root/initial-setup.gohtml index 27aee3e..e2bbb5e 100644 --- a/root/initial-setup.gohtml +++ b/root/initial-setup.gohtml @@ -13,6 +13,7 @@ predictable for an attacker to guess.

+{{ $form := .Data.Form }}
@@ -22,7 +23,7 @@ class="form-control" id="setup-admin-username" placeholder="Enter username" - value="{{ .Form.Username }}"> + value="{{ $form.Username }}">