Noah
6c91c67c97
* Users who set their Profile Picture to "friends only" or "private" can have their avatar be private all over the website to users who are not their friends or not granted access. * Users who are not your friends see a yellow placeholder avatar, and users not granted access to a private Profile Pic sees a purple avatar. * Admin users see these same placeholder avatars most places too (on search, forums, comments, etc.) if the user did not friend or grant the admin. But admins ALWAYS see it on their Profile Page directly, for ability to moderate. * Fix marking Notifications as read: clicking the link in an unread notification now will wait on the ajax request to finish before allowing the redirect. * Update the FAQ
153 lines
3.8 KiB
Go
153 lines
3.8 KiB
Go
package templates
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"code.nonshy.com/nonshy/website/pkg/session"
|
|
)
|
|
|
|
// Template is a logical HTML template for the app with ability to wrap around an html/template
|
|
// and provide middlewares, hooks or live reloading capability in debug mode.
|
|
type Template struct {
|
|
filename string // Filename on disk (index.html)
|
|
filepath string // Full path on disk (./web/templates/index.html)
|
|
modified time.Time // Modification date of the file at init time
|
|
tmpl *template.Template
|
|
}
|
|
|
|
// LoadTemplate processes and returns a template. Filename is relative
|
|
// to the template directory, e.g. "index.html". Call this at the initialization
|
|
// of your endpoint controller; in debug mode the template HTML from disk may be
|
|
// reloaded if modified after initial load.
|
|
func LoadTemplate(filename string) (*Template, error) {
|
|
filepath := config.TemplatePath + "/" + filename
|
|
stat, err := os.Stat(filepath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LoadTemplate(%s): %s", filename, err)
|
|
}
|
|
|
|
files := templates(config.TemplatePath + "/" + filename)
|
|
tmpl := template.New("page")
|
|
tmpl.Funcs(TemplateFuncs(nil))
|
|
tmpl.ParseFiles(files...)
|
|
|
|
return &Template{
|
|
filename: filename,
|
|
filepath: filepath,
|
|
modified: stat.ModTime(),
|
|
tmpl: tmpl,
|
|
}, nil
|
|
}
|
|
|
|
// Must LoadTemplate or panic.
|
|
func Must(filename string) *Template {
|
|
tmpl, err := LoadTemplate(filename)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return tmpl
|
|
}
|
|
|
|
// Execute a loaded template. In debug mode, the template file may be reloaded
|
|
// from disk if the file on disk has been modified.
|
|
func (t *Template) Execute(w http.ResponseWriter, r *http.Request, vars map[string]interface{}) error {
|
|
if vars == nil {
|
|
vars = map[string]interface{}{}
|
|
}
|
|
|
|
// Merge in global variables.
|
|
MergeVars(r, vars)
|
|
MergeUserVars(r, vars)
|
|
|
|
// Merge the flashed messsage variables in.
|
|
if r != nil {
|
|
sess := session.Get(r)
|
|
flashes, errors := sess.ReadFlashes(w)
|
|
vars["Flashes"] = flashes
|
|
vars["Errors"] = errors
|
|
}
|
|
|
|
// Reload the template from disk?
|
|
if stat, err := os.Stat(t.filepath); err == nil {
|
|
if stat.ModTime().After(t.modified) {
|
|
log.Info("Template(%s).Execute: file updated on disk, reloading", t.filename)
|
|
err = t.Reload()
|
|
if err != nil {
|
|
log.Error("Reloading error: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install the function map.
|
|
tmpl := t.tmpl
|
|
if r != nil {
|
|
tmpl = t.tmpl.Funcs(TemplateFuncs(r))
|
|
}
|
|
|
|
if err := tmpl.ExecuteTemplate(w, "base", vars); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reload the template from disk.
|
|
func (t *Template) Reload() error {
|
|
stat, err := os.Stat(t.filepath)
|
|
if err != nil {
|
|
return fmt.Errorf("Reload(%s): %s", t.filename, err)
|
|
}
|
|
|
|
files := templates(t.filepath)
|
|
tmpl := template.New("page")
|
|
tmpl.Funcs(TemplateFuncs(nil))
|
|
tmpl.ParseFiles(files...)
|
|
|
|
t.tmpl = tmpl
|
|
t.modified = stat.ModTime()
|
|
return nil
|
|
}
|
|
|
|
// Base template layout.
|
|
var baseTemplates = []string{
|
|
config.TemplatePath + "/base.html",
|
|
config.TemplatePath + "/partials/user_avatar.html",
|
|
}
|
|
|
|
// templates returns a template chain with the base templates preceding yours.
|
|
// Files given are expected to be full paths (config.TemplatePath + file)
|
|
func templates(files ...string) []string {
|
|
return append(baseTemplates, files...)
|
|
}
|
|
|
|
// RenderTemplate executes a template. Filename is relative to the templates
|
|
// root, e.g. "index.html"
|
|
func RenderTemplate(w io.Writer, r *http.Request, filename string, vars map[string]interface{}) error {
|
|
if vars == nil {
|
|
vars = map[string]interface{}{}
|
|
}
|
|
|
|
// Merge in user vars.
|
|
MergeVars(r, vars)
|
|
MergeUserVars(r, vars)
|
|
|
|
files := templates(config.TemplatePath + "/" + filename)
|
|
tmpl := template.Must(
|
|
template.New("index").ParseFiles(files...),
|
|
)
|
|
|
|
err := tmpl.ExecuteTemplate(w, "base", vars)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|