From 71bb52a64a14fa1812bb685f4380f11187ee5b99 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sun, 21 Oct 2012 12:42:54 -0700 Subject: [PATCH] Added partial templates mailbox/list now renders --- conf/inbucket.conf | 3 + config.go | 17 +++- themes/integral/public/main.css | 2 +- themes/integral/templates/_base.html | 82 ++++++++++--------- .../{Mailbox/List.html => mailbox/_list.html} | 0 .../{Mailbox/Html.html => mailbox/html.html} | 0 .../Index.html => mailbox/index.html} | 47 +++++------ .../{Mailbox/Show.html => mailbox/show.html} | 0 themes/integral/templates/menu.html | 10 +-- themes/integral/templates/root-index.html | 11 --- themes/integral/templates/root/index.html | 19 +++++ web/context.go | 12 ++- web/helpers.go | 32 ++++---- web/{mailbox.go => mailbox_controller.go} | 62 +++++++------- web/root_controller.go | 4 +- web/server.go | 8 +- web/template.go | 76 ++++++++++++++--- 17 files changed, 238 insertions(+), 147 deletions(-) rename themes/integral/templates/{Mailbox/List.html => mailbox/_list.html} (100%) rename themes/integral/templates/{Mailbox/Html.html => mailbox/html.html} (100%) rename themes/integral/templates/{Mailbox/Index.html => mailbox/index.html} (53%) rename themes/integral/templates/{Mailbox/Show.html => mailbox/show.html} (100%) delete mode 100644 themes/integral/templates/root-index.html create mode 100644 themes/integral/templates/root/index.html rename web/{mailbox.go => mailbox_controller.go} (69%) diff --git a/conf/inbucket.conf b/conf/inbucket.conf index a38e978..5c8494e 100644 --- a/conf/inbucket.conf +++ b/conf/inbucket.conf @@ -34,6 +34,9 @@ theme=integral # Path to the selected themes template files template.dir=%(install.dir)s/themes/%(theme)s/templates +# Should we cache parsed templates (set to false during theme dev) +template.cache=false + # Path to the selected themes public (static) files public.dir=%(install.dir)s/themes/%(theme)s/public diff --git a/config.go b/config.go index 4ae451b..c2aa58a 100644 --- a/config.go +++ b/config.go @@ -17,10 +17,11 @@ type SmtpConfig struct { } type WebConfig struct { - Ip4address net.IP - Ip4port int - TemplateDir string - PublicDir string + Ip4address net.IP + Ip4port int + TemplateDir string + TemplateCache bool + PublicDir string } var smtpConfig *SmtpConfig @@ -69,6 +70,7 @@ func LoadConfig(filename string) error { requireOption(messages, "web", "ip4.address") requireOption(messages, "web", "ip4.port") requireOption(messages, "web", "template.dir") + requireOption(messages, "web", "template.cache") requireOption(messages, "web", "public.dir") requireOption(messages, "datastore", "path") if messages.Len() > 0 { @@ -158,6 +160,13 @@ func parseWebConfig() error { } webConfig.TemplateDir = str + option = "[web]template.cache" + flag, err := Config.Bool("web", "template.cache") + if err != nil { + return fmt.Errorf("Failed to parse %v: %v", option, err) + } + webConfig.TemplateCache = flag + option = "[web]public.dir" str, err = Config.String("web", "public.dir") if err != nil { diff --git a/themes/integral/public/main.css b/themes/integral/public/main.css index 77b3505..81d9925 100644 --- a/themes/integral/public/main.css +++ b/themes/integral/public/main.css @@ -141,6 +141,7 @@ a:hover { #content { width: 928px; + min-height: 300px; margin: 0 auto; } @@ -266,7 +267,6 @@ a:hover { #emailContent { padding-bottom: 20px; - min-height: 300px; } #emailHeader { diff --git a/themes/integral/templates/_base.html b/themes/integral/templates/_base.html index f1d304a..4616c2f 100644 --- a/themes/integral/templates/_base.html +++ b/themes/integral/templates/_base.html @@ -4,42 +4,50 @@ Design by Free CSS Templates http://www.freecsstemplates.org Released for free under a Creative Commons Attribution 2.5 License --> -{{define "col1menu"}}{{/* Used inside #content div */}} -
- -
-{{end}} - - -{{template "title" .}} - - - - - - -
-{{template "content" .}} -
- - + + + {{template "title" .}} + + + + {{template "script" .}} + + + +
+
+ {{template "menu" .}} +
+
+ {{with .ctx.Session.Flashes "errors"}} +
+

Please fix the following errors and resubmit:

+

    + {{range .}} +
  • {{.}}
  • + {{end}} +
+
+ {{end}} + {{template "content" .}} +
+
+ + diff --git a/themes/integral/templates/Mailbox/List.html b/themes/integral/templates/mailbox/_list.html similarity index 100% rename from themes/integral/templates/Mailbox/List.html rename to themes/integral/templates/mailbox/_list.html diff --git a/themes/integral/templates/Mailbox/Html.html b/themes/integral/templates/mailbox/html.html similarity index 100% rename from themes/integral/templates/Mailbox/Html.html rename to themes/integral/templates/mailbox/html.html diff --git a/themes/integral/templates/Mailbox/Index.html b/themes/integral/templates/mailbox/index.html similarity index 53% rename from themes/integral/templates/Mailbox/Index.html rename to themes/integral/templates/mailbox/index.html index cb98a0c..66e078e 100644 --- a/themes/integral/templates/Mailbox/Index.html +++ b/themes/integral/templates/mailbox/index.html @@ -1,7 +1,7 @@ -{{$title := printf "Inbucket for %v" .name}} -{{set "title" $title .}} -{{template "header.html" .}} +{{define "title"}}{{printf "Inbucket for %v" .name}}{{end}} {{$name := .name}} + +{{define "script"}} +{{end}} -
- - -
+{{define "menu"}} + - -
- {{template "errors.html" .}} - -
-

Select a message at left, or enter a different username into the box on upper right.

-
+ +
+{{end}} -{{template "footer.html" .}} +{{define "content"}} +
+

Select a message at left, or enter a different username into the box on upper right.

+
+{{end}} diff --git a/themes/integral/templates/Mailbox/Show.html b/themes/integral/templates/mailbox/show.html similarity index 100% rename from themes/integral/templates/Mailbox/Show.html rename to themes/integral/templates/mailbox/show.html diff --git a/themes/integral/templates/menu.html b/themes/integral/templates/menu.html index a322788..906b49f 100644 --- a/themes/integral/templates/menu.html +++ b/themes/integral/templates/menu.html @@ -1,6 +1,4 @@ -
- -
+ diff --git a/themes/integral/templates/root-index.html b/themes/integral/templates/root-index.html deleted file mode 100644 index f809a4a..0000000 --- a/themes/integral/templates/root-index.html +++ /dev/null @@ -1,11 +0,0 @@ -{{define "title"}}Inbucket{{end}} -{{define "content"}} -{{template "col1menu" .}} -
-

Inbucket is an email testing service; it will accept email for any email - address and make it available to view without a password.

-

To view email for a particular address, enter the username portion - of the address into the box on the upper right and click go.

-
-{{end}} - diff --git a/themes/integral/templates/root/index.html b/themes/integral/templates/root/index.html new file mode 100644 index 0000000..a8404c1 --- /dev/null +++ b/themes/integral/templates/root/index.html @@ -0,0 +1,19 @@ +{{define "title"}}Inbucket{{end}} + +{{define "script"}}{{end}} + +{{define "menu"}} + +{{end}} + +{{define "content"}} +

Inbucket is an email testing service; it will accept email for any email +address and make it available to view without a password.

+ +

To view email for a particular address, enter the username portion +of the address into the box on the upper right and click go.

+{{end}} + diff --git a/web/context.go b/web/context.go index 73aaaf7..271dc8c 100644 --- a/web/context.go +++ b/web/context.go @@ -1,12 +1,16 @@ package web import ( + "github.com/gorilla/mux" "github.com/gorilla/sessions" + "github.com/jhillyerd/inbucket" "net/http" ) type Context struct { - Session *sessions.Session + Vars map[string]string + Session *sessions.Session + DataStore *inbucket.DataStore } func (c *Context) Close() { @@ -14,9 +18,13 @@ func (c *Context) Close() { } func NewContext(req *http.Request) (*Context, error) { + vars := mux.Vars(req) sess, err := sessionStore.Get(req, "inbucket") + ds := inbucket.NewDataStore() ctx := &Context{ - Session: sess, + Vars: vars, + Session: sess, + DataStore: ds, } if err != nil { return ctx, err diff --git a/web/helpers.go b/web/helpers.go index ed18d2f..27abe9a 100644 --- a/web/helpers.go +++ b/web/helpers.go @@ -8,21 +8,8 @@ import ( ) var TemplateFuncs = template.FuncMap{ - // Reversable routing function - "reverse": func(name string, things ...interface{}) string { - // Convert the things to strings - strs := make([]string, len(things)) - for i, th := range things { - strs[i] = fmt.Sprint(th) - } - // Grab the route - u, err := Router.Get(name).URL(strs...) - if err != nil { - inbucket.Error("Failed to reverse route: %v", err) - return "/ROUTE-ERROR" - } - return u.Path - }, + // Reversable routing function (shared with templates) + "reverse": reverse, // Friendly date & time rendering "friendlyTime": func(t time.Time) template.HTML { ty, tm, td := t.Date() @@ -33,3 +20,18 @@ var TemplateFuncs = template.FuncMap{ return template.HTML(t.Format("Mon Jan 2, 2006")) }, } + +func reverse(name string, things ...interface{}) string { + // Convert the things to strings + strs := make([]string, len(things)) + for i, th := range things { + strs[i] = fmt.Sprint(th) + } + // Grab the route + u, err := Router.Get(name).URL(strs...) + if err != nil { + inbucket.Error("Failed to reverse route: %v", err) + return "/ROUTE-ERROR" + } + return u.Path +} diff --git a/web/mailbox.go b/web/mailbox_controller.go similarity index 69% rename from web/mailbox.go rename to web/mailbox_controller.go index cc6550e..043fd54 100644 --- a/web/mailbox.go +++ b/web/mailbox_controller.go @@ -1,52 +1,52 @@ package web import ( - "github.com/jhillyerd/inbucket/app/inbucket" - "github.com/robfig/revel" - "html/template" + "github.com/jhillyerd/inbucket" + "net/http" ) -type Mailbox struct { - *rev.Controller -} - -func (c Mailbox) Index(name string) rev.Result { - c.Validation.Required(name).Message("Account name is required") - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(Application.Index) +func MailboxIndex(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { + name := req.FormValue("name") + if len(name) == 0 { + ctx.Session.AddFlash("Account name is required", "errors") + http.Redirect(w, req, reverse("RootIndex"), http.StatusSeeOther) + return nil } - return c.Render(name) + return RenderTemplate("mailbox/index.html", w, map[string]interface{}{ + "ctx": ctx, + "name": name, + }) } -func (c Mailbox) List(name string) rev.Result { - c.Validation.Required(name).Message("Account name is required") - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(Application.Index) +func MailboxList(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { + name := ctx.Vars["name"] + if len(name) == 0 { + ctx.Session.AddFlash("Account name is required", "errors") + http.Redirect(w, req, reverse("RootIndex"), http.StatusSeeOther) + return nil } - ds := inbucket.NewDataStore() - mb, err := ds.MailboxFor(name) + mb, err := ctx.DataStore.MailboxFor(name) if err != nil { - return c.RenderError(err) + return err } messages, err := mb.GetMessages() if err != nil { - return c.RenderError(err) + return err } - rev.INFO.Printf("Got %v messsages", len(messages)) + inbucket.Trace("Got %v messsages", len(messages)) - c.Response.Out.Header().Set("Expires", "-1") - return c.Render(name, messages) + return RenderPartial("mailbox/_list.html", w, map[string]interface{}{ + "ctx": ctx, + "name": name, + "messages": messages, + }) } +/* func (c Mailbox) Show(name string, id string) rev.Result { +func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { c.Validation.Required(name).Message("Account name is required") c.Validation.Required(id).Message("Message ID is required") @@ -77,6 +77,7 @@ func (c Mailbox) Show(name string, id string) rev.Result { } func (c Mailbox) Delete(name string, id string) rev.Result { +func MailboxDelete(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { c.Validation.Required(name).Message("Account name is required") c.Validation.Required(id).Message("Message ID is required") @@ -104,6 +105,7 @@ func (c Mailbox) Delete(name string, id string) rev.Result { } func (c Mailbox) Html(name string, id string) rev.Result { +func MailboxHtml(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { c.Validation.Required(name).Message("Account name is required") c.Validation.Required(id).Message("Message ID is required") @@ -135,6 +137,7 @@ func (c Mailbox) Html(name string, id string) rev.Result { } func (c Mailbox) Source(name string, id string) rev.Result { +func MailboxSource(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { c.Validation.Required(name).Message("Account name is required") c.Validation.Required(id).Message("Message ID is required") @@ -161,3 +164,4 @@ func (c Mailbox) Source(name string, id string) rev.Result { c.Response.Out.Header().Set("Expires", "-1") return c.RenderText(*raw) } +*/ diff --git a/web/root_controller.go b/web/root_controller.go index 7205fdf..c1600f9 100644 --- a/web/root_controller.go +++ b/web/root_controller.go @@ -5,5 +5,7 @@ import ( ) func RootIndex(w http.ResponseWriter, req *http.Request, ctx *Context) (err error) { - return T("root-index.html").Execute(w, nil) + return RenderTemplate("root/index.html", w, map[string]interface{}{ + "ctx": ctx, + }) } diff --git a/web/server.go b/web/server.go index c4ac8bf..7effd92 100644 --- a/web/server.go +++ b/web/server.go @@ -31,17 +31,19 @@ var Router *mux.Router var sessionStore sessions.Store func setupRoutes(cfg inbucket.WebConfig) { - r := mux.NewRouter() - Router = r + Router = mux.NewRouter() inbucket.Info("Theme templates mapped to '%v'", cfg.TemplateDir) inbucket.Info("Theme static content mapped to '%v'", cfg.PublicDir) + r := Router // Static content r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(cfg.PublicDir)))) // Root - r.Path("/").Handler(handler(RootIndex)) + r.Path("/").Handler(handler(RootIndex)).Name("RootIndex").Methods("GET") + r.Path("/mailbox").Handler(handler(MailboxIndex)).Name("MailboxIndex").Methods("GET") + r.Path("/mailbox/list/{name}").Handler(handler(MailboxList)).Name("MailboxList").Methods("GET") } // Start() the web server diff --git a/web/template.go b/web/template.go index 121f3c9..74ff02b 100644 --- a/web/template.go +++ b/web/template.go @@ -3,31 +3,81 @@ package web import ( "github.com/jhillyerd/inbucket" "html/template" + "net/http" + "path" "path/filepath" + "strings" "sync" ) -var cachedTemplates = map[string]*template.Template{} var cachedMutex sync.Mutex +var cachedTemplates = map[string]*template.Template{} +var cachedPartials = map[string]*template.Template{} -func T(name string) *template.Template { +// RenderTemplate fetches the named template and renders it to the provided +// ResponseWriter. +func RenderTemplate(name string, w http.ResponseWriter, data interface{}) error { + t, err := ParseTemplate(name, false) + if err != nil { + inbucket.Error("Error in template '%v': %v", name, err) + return err + } + w.Header().Set("Expires", "-1") + return t.Execute(w, data) +} + +// RenderPartial fetches the named template and renders it to the provided +// ResponseWriter. +func RenderPartial(name string, w http.ResponseWriter, data interface{}) error { + t, err := ParseTemplate(name, true) + if err != nil { + inbucket.Error("Error in template '%v': %v", name, err) + return err + } + w.Header().Set("Expires", "-1") + return t.Execute(w, data) +} + +// ParseTemplate loads the requested template along with _base.html, caching +// the result (if configured to do so) +func ParseTemplate(name string, partial bool) (*template.Template, error) { cachedMutex.Lock() defer cachedMutex.Unlock() if t, ok := cachedTemplates[name]; ok { - return t + return t, nil } - templateDir := inbucket.GetWebConfig().TemplateDir - templateFile := filepath.Join(templateDir, name) - inbucket.Trace("Parsing template %v", templateFile) + cfg := inbucket.GetWebConfig() + tempPath := strings.Replace(name, "/", string(filepath.Separator), -1) + tempFile := filepath.Join(cfg.TemplateDir, tempPath) + inbucket.Trace("Parsing template %v", tempFile) - t := template.New("_base.html").Funcs(TemplateFuncs) - t = template.Must(t.ParseFiles( - filepath.Join(templateDir, "_base.html"), - templateFile, - )) - cachedTemplates[name] = t + var err error + var t *template.Template + if partial { + // Need to get basename of file to make it root template w/ funcs + base := path.Base(name) + t = template.New(base).Funcs(TemplateFuncs) + t, err = t.ParseFiles(tempFile) + } else { + t = template.New("_base.html").Funcs(TemplateFuncs) + t, err = t.ParseFiles(filepath.Join(cfg.TemplateDir, "_base.html"), tempFile) + } + if err != nil { + return nil, err + } - return t + // Allows us to disable caching for theme development + if cfg.TemplateCache { + if partial { + inbucket.Trace("Caching partial %v", name) + cachedTemplates[name] = t + } else { + inbucket.Trace("Caching template %v", name) + cachedTemplates[name] = t + } + } + + return t, nil }