package views
import (
"bytes"
"fmt"
"html/template"
"io"
"net/http"
"path/filepath"
"strings"
"github.com/dnote/dnote/pkg/server/context"
"github.com/dnote/dnote/pkg/server/log"
"github.com/gorilla/csrf"
"github.com/pkg/errors"
)
const (
// templateExt is the template extension
templateExt string = ".gohtml"
)
const (
siteTitle = "Dnote"
)
// Config is a view config
type Config struct {
Title string
Layout string
HeaderTemplate string
}
func (c Config) getLayout() string {
if c.Layout == "" {
return "base"
}
return c.Layout
}
// NewView returns a new view by parsing the given layout and files
func NewView(baseDir string, c Config, files ...string) *View {
addTemplatePath(baseDir, files)
addTemplateExt(files)
files = append(files, layoutFiles(baseDir)...)
t, err := template.New(c.Title).Funcs(template.FuncMap{
"csrfField": func() (template.HTML, error) {
return "", errors.New("csrfField is not implemented")
},
"css": func() []string {
return strings.Split("", ",")
},
"title": func() string {
if c.Title != "" {
return fmt.Sprintf("%s | %s", c.Title, siteTitle)
}
return siteTitle
},
"headerTemplate": func() string {
return c.HeaderTemplate
},
}).ParseFiles(files...)
if err != nil {
panic(errors.Wrap(err, "instantiating view."))
}
return &View{
Template: t,
Layout: c.getLayout(),
}
}
// View holds the information about a view
type View struct {
Template *template.Template
Layout string
}
func (v *View) ServeHTTP(w http.ResponseWriter, r *http.Request) {
v.Render(w, r, nil)
}
// Render is used to render the view with the predefined layout.
func (v *View) Render(w http.ResponseWriter, r *http.Request, data interface{}) {
w.Header().Set("Content-Type", "text/html")
var vd Data
switch d := data.(type) {
case Data:
vd = d
// do nothing
default:
vd = Data{
Yield: data,
}
}
if alert := getAlert(r); alert != nil {
vd.Alert = alert
clearAlert(w)
}
vd.User = context.User(r.Context())
var buf bytes.Buffer
csrfField := csrf.TemplateField(r)
tpl := v.Template.Funcs(template.FuncMap{
"csrfField": func() template.HTML {
return csrfField
},
})
if err := tpl.ExecuteTemplate(&buf, v.Layout, vd); err != nil {
log.ErrorWrap(err, fmt.Sprintf("executing a template '%s'", v.Template.Name()))
http.Error(w, AlertMsgGeneric, http.StatusInternalServerError)
return
}
io.Copy(w, &buf)
}
// layoutFiles returns a slice of strings representing
// the layout files used in our application.
func layoutFiles(baseDir string) []string {
pattern := fmt.Sprintf("%s/layouts/*%s", baseDir, templateExt)
files, err := filepath.Glob(pattern)
if err != nil {
panic(err)
}
return files
}
// addTemplatePath takes in a slice of strings
// representing file paths for templates.
func addTemplatePath(baseDir string, files []string) {
for i, f := range files {
files[i] = fmt.Sprintf("%s/%s", baseDir, f)
}
}
// addTemplateExt takes in a slice of strings
// representing file paths for templates and it appends
// the templateExt extension to each string in the slice
//
// Eg the input {"home"} would result in the output
// {"home.gohtml"} if templateExt == ".gohtml"
func addTemplateExt(files []string) {
for i, f := range files {
files[i] = f + templateExt
}
}