aboutsummaryrefslogtreecommitdiff
path: root/renderer.go
diff options
context:
space:
mode:
authorSimon Ser <contact@emersion.fr>2020-01-20 22:05:42 +0100
committerSimon Ser <contact@emersion.fr>2020-01-20 22:05:42 +0100
commit9fdccc3a4beaf36195a14d7dd17801681f93d45b (patch)
tree687af54fa7596d869ed79bdf4f2f035edb511010 /renderer.go
parentb58c15d121095ef58f8fb1ac5d3a456e89baee2a (diff)
downloadalps-9fdccc3a4beaf36195a14d7dd17801681f93d45b.tar.gz
alps-9fdccc3a4beaf36195a14d7dd17801681f93d45b.zip
Rename template.go to renderer.go
Diffstat (limited to 'renderer.go')
-rw-r--r--renderer.go178
1 files changed, 178 insertions, 0 deletions
diff --git a/renderer.go b/renderer.go
new file mode 100644
index 0000000..b229d34
--- /dev/null
+++ b/renderer.go
@@ -0,0 +1,178 @@
+package koushin
+
+import (
+ "fmt"
+ "html/template"
+ "io"
+ "io/ioutil"
+ "os"
+
+ "github.com/labstack/echo/v4"
+)
+
+const themesDir = "themes"
+
+// GlobalRenderData contains data available in all templates.
+type GlobalRenderData struct {
+ Path string
+
+ LoggedIn bool
+
+ // if logged in
+ Username string
+ // TODO: list of mailboxes
+
+ // additional plugin-specific data
+ Extra map[string]interface{}
+}
+
+// BaseRenderData is the base type for templates. It should be extended with
+// additional template-specific fields:
+//
+// type MyRenderData struct {
+// BaseRenderData
+// // add additional fields here
+// }
+type BaseRenderData struct {
+ GlobalData GlobalRenderData
+ // additional plugin-specific data
+ Extra map[string]interface{}
+}
+
+// Global implements RenderData.
+func (brd *BaseRenderData) Global() *GlobalRenderData {
+ return &brd.GlobalData
+}
+
+// RenderData is implemented by template data structs. It can be used to inject
+// additional data to all templates.
+type RenderData interface {
+ // GlobalData returns a pointer to the global render data.
+ Global() *GlobalRenderData
+}
+
+// NewBaseRenderData initializes a new BaseRenderData.
+//
+// It can be used by routes to pre-fill the base data:
+//
+// type MyRenderData struct {
+// BaseRenderData
+// // add additional fields here
+// }
+//
+// data := &MyRenderData{
+// BaseRenderData: *koushin.NewBaseRenderData(ctx),
+// // other fields...
+// }
+func NewBaseRenderData(ctx *Context) *BaseRenderData {
+ global := GlobalRenderData{Extra: make(map[string]interface{})}
+
+ if ctx.Session != nil {
+ global.LoggedIn = true
+ global.Username = ctx.Session.username
+ }
+
+ global.Path = ctx.Request().URL.String()
+
+ return &BaseRenderData{
+ GlobalData: global,
+ Extra: make(map[string]interface{}),
+ }
+}
+
+type renderer struct {
+ logger echo.Logger
+ defaultTheme string
+
+ base *template.Template
+ themes map[string]*template.Template
+}
+
+func (r *renderer) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error {
+ // ectx is the raw *echo.context, not our own *Context
+ ctx := ectx.Get("context").(*Context)
+
+ var renderData RenderData
+ if data == nil {
+ renderData = &struct{ BaseRenderData }{*NewBaseRenderData(ctx)}
+ } else {
+ var ok bool
+ renderData, ok = data.(RenderData)
+ if !ok {
+ return fmt.Errorf("data passed to template '%v' doesn't implement RenderData", name)
+ }
+ }
+
+ for _, plugin := range ctx.Server.plugins {
+ if err := plugin.Inject(ctx, name, renderData); err != nil {
+ return fmt.Errorf("failed to run plugin '%v': %v", plugin.Name(), err)
+ }
+ }
+
+ // TODO: per-user theme selection
+ t := r.base
+ if r.defaultTheme != "" {
+ t = r.themes[r.defaultTheme]
+ }
+ return t.ExecuteTemplate(w, name, data)
+}
+
+func loadTheme(name string, base *template.Template) (*template.Template, error) {
+ theme, err := base.Clone()
+ if err != nil {
+ return nil, err
+ }
+
+ theme, err = theme.ParseGlob(themesDir + "/" + name + "/*.html")
+ if err != nil {
+ return nil, err
+ }
+
+ return theme, nil
+}
+
+func (r *renderer) Load(plugins []Plugin) error {
+ base := template.New("")
+
+ for _, p := range plugins {
+ if err := p.LoadTemplate(base); err != nil {
+ return fmt.Errorf("failed to load template for plugin '%v': %v", p.Name(), err)
+ }
+ }
+
+ themes := make(map[string]*template.Template)
+
+ files, err := ioutil.ReadDir(themesDir)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ for _, fi := range files {
+ if !fi.IsDir() {
+ continue
+ }
+
+ r.logger.Printf("Loading theme '%v'", fi.Name())
+ var err error
+ if themes[fi.Name()], err = loadTheme(fi.Name(), base); err != nil {
+ return fmt.Errorf("failed to load theme '%v': %v", fi.Name(), err)
+ }
+ }
+
+ if r.defaultTheme != "" {
+ if _, ok := themes[r.defaultTheme]; !ok {
+ return fmt.Errorf("failed to find default theme '%v'", r.defaultTheme)
+ }
+ }
+
+ r.base = base
+ r.themes = themes
+ return nil
+}
+
+func newRenderer(logger echo.Logger, defaultTheme string) *renderer {
+ return &renderer{
+ logger: logger,
+ defaultTheme: defaultTheme,
+ }
+}