2018-02-10 18:08:45 +00:00
|
|
|
package render
|
|
|
|
|
|
|
|
import (
|
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2018-02-12 00:24:43 +00:00
|
|
|
"github.com/kirsle/blog/internal/log"
|
|
|
|
"github.com/kirsle/blog/internal/middleware"
|
|
|
|
"github.com/kirsle/blog/internal/middleware/auth"
|
|
|
|
"github.com/kirsle/blog/internal/sessions"
|
|
|
|
"github.com/kirsle/blog/internal/types"
|
2018-04-12 01:59:30 +00:00
|
|
|
"github.com/kirsle/blog/models/settings"
|
|
|
|
"github.com/kirsle/blog/models/users"
|
2018-02-10 18:08:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2018-02-10 22:05:41 +00:00
|
|
|
type vars struct {
|
2018-02-10 18:08:45 +00:00
|
|
|
// Global, "constant" template variables.
|
|
|
|
SetupNeeded bool
|
|
|
|
Title string
|
2018-04-12 01:59:30 +00:00
|
|
|
Description string
|
2018-02-10 18:08:45 +00:00
|
|
|
Path string
|
2018-02-10 22:05:41 +00:00
|
|
|
TemplatePath string // actual template file on disk
|
2018-02-10 18:08:45 +00:00
|
|
|
LoggedIn bool
|
|
|
|
CurrentUser *users.User
|
|
|
|
CSRF string
|
|
|
|
Editable bool // page is editable
|
|
|
|
Request *http.Request
|
|
|
|
RequestTime time.Time
|
|
|
|
RequestDuration time.Duration
|
|
|
|
|
|
|
|
// Common template variables.
|
|
|
|
Message string
|
|
|
|
Flashes []string
|
|
|
|
Error error
|
2018-02-10 22:05:41 +00:00
|
|
|
Data interface{}
|
2018-02-10 18:08:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 22:05:41 +00:00
|
|
|
// Template 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. If you just want to render a template
|
|
|
|
// without all that nonsense, use RenderPartialTemplate.
|
|
|
|
func Template(w io.Writer, r *http.Request, path string, data interface{}) error {
|
|
|
|
isPartial := strings.Contains(path, ".partial")
|
|
|
|
|
2018-02-10 21:16:20 +00:00
|
|
|
// Get the site settings.
|
|
|
|
s, err := settings.Load()
|
|
|
|
if err != nil {
|
|
|
|
s = settings.Defaults()
|
|
|
|
}
|
|
|
|
|
2018-02-10 22:05:41 +00:00
|
|
|
// Inject globally available variables.
|
|
|
|
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,
|
2018-04-12 01:59:30 +00:00
|
|
|
Description: s.Site.Description,
|
2018-02-10 22:05:41 +00:00
|
|
|
Path: r.URL.Path,
|
|
|
|
|
|
|
|
Data: data,
|
2018-02-10 21:16:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
rw.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
|
|
|
session := sessions.Get(r)
|
|
|
|
|
|
|
|
// Flashed messages.
|
|
|
|
if flashes := session.Flashes(); len(flashes) > 0 {
|
|
|
|
for _, flash := range flashes {
|
|
|
|
_ = flash
|
|
|
|
v.Flashes = append(v.Flashes, flash.(string))
|
|
|
|
}
|
|
|
|
session.Save(r, rw)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CSRF token for forms.
|
|
|
|
v.CSRF = middleware.GenerateCSRFToken(rw, r, session)
|
2018-02-10 18:08:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 21:16:20 +00:00
|
|
|
v.RequestDuration = time.Now().Sub(v.RequestTime)
|
|
|
|
v.Editable = !strings.HasPrefix(path, "admin/")
|
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
var (
|
|
|
|
layout Filepath
|
|
|
|
templateName string
|
|
|
|
)
|
|
|
|
|
|
|
|
// Find the file path to the template.
|
|
|
|
filepath, err := ResolvePath(path)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("RenderTemplate(%s): file not found", path)
|
|
|
|
return err
|
|
|
|
}
|
2018-02-10 22:05:41 +00:00
|
|
|
v.TemplatePath = filepath.URI
|
2018-02-10 18:08:45 +00:00
|
|
|
|
|
|
|
// Get the layout template.
|
2018-02-10 22:05:41 +00:00
|
|
|
if !isPartial {
|
2018-02-10 18:08:45 +00:00
|
|
|
templateName = "layout"
|
|
|
|
layout, err = ResolvePath(".layout")
|
|
|
|
if err != nil {
|
|
|
|
log.Error("RenderTemplate(%s): layout template not found", path)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
templateName = filepath.Basename
|
|
|
|
}
|
|
|
|
|
|
|
|
// The comment entry partial.
|
|
|
|
commentEntry, err := ResolvePath("comments/entry.partial")
|
|
|
|
if err != nil {
|
|
|
|
log.Error("RenderTemplate(%s): comments/entry.partial not found")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-10 21:16:20 +00:00
|
|
|
t := template.New(filepath.Absolute).Funcs(Funcs)
|
2018-02-10 18:08:45 +00:00
|
|
|
|
|
|
|
// 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
|
2018-02-10 22:05:41 +00:00
|
|
|
if !isPartial {
|
2018-02-10 18:08:45 +00:00
|
|
|
templates = append(templates, layout.Absolute)
|
|
|
|
}
|
|
|
|
t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-10 21:16:20 +00:00
|
|
|
err = t.ExecuteTemplate(w, templateName, v)
|
2018-02-10 18:08:45 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Template parsing error: %s", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|