A webmail client. Forked from https://git.sr.ht/~migadu/alps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

127 lines
2.8 KiB

  1. package koushin
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "github.com/labstack/echo/v4"
  9. )
  10. const themesDir = "themes"
  11. // GlobalRenderData contains data available in all templates.
  12. type GlobalRenderData struct {
  13. LoggedIn bool
  14. // if logged in
  15. Username string
  16. // TODO: list of mailboxes
  17. // Additional plugin-specific data
  18. Extra map[string]interface{}
  19. }
  20. // BaseRenderData is the base type for templates. It should be extended with
  21. // new template-specific fields.
  22. type BaseRenderData struct {
  23. Global GlobalRenderData
  24. // Additional plugin-specific data
  25. Extra map[string]interface{}
  26. }
  27. func NewBaseRenderData(ctx *Context) *BaseRenderData {
  28. global := GlobalRenderData{Extra: make(map[string]interface{})}
  29. if ctx.Session != nil {
  30. global.LoggedIn = true
  31. global.Username = ctx.Session.username
  32. }
  33. return &BaseRenderData{
  34. Global: global,
  35. Extra: make(map[string]interface{}),
  36. }
  37. }
  38. type renderer struct {
  39. base *template.Template
  40. themes map[string]*template.Template
  41. defaultTheme string
  42. }
  43. func (r *renderer) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error {
  44. // ectx is the raw *echo.context, not our own *Context
  45. ctx := ectx.Get("context").(*Context)
  46. for _, plugin := range ctx.Server.Plugins {
  47. if err := plugin.Inject(name, data); err != nil {
  48. return fmt.Errorf("failed to run plugin '%v': %v", plugin.Name(), err)
  49. }
  50. }
  51. // TODO: per-user theme selection
  52. t := r.base
  53. if r.defaultTheme != "" {
  54. t = r.themes[r.defaultTheme]
  55. }
  56. return t.ExecuteTemplate(w, name, data)
  57. }
  58. func loadTheme(name string, base *template.Template) (*template.Template, error) {
  59. theme, err := base.Clone()
  60. if err != nil {
  61. return nil, err
  62. }
  63. theme, err = theme.ParseGlob(themesDir + "/" + name + "/*.html")
  64. if err != nil {
  65. return nil, err
  66. }
  67. return theme, nil
  68. }
  69. func loadTemplates(logger echo.Logger, defaultTheme string, plugins []Plugin) (*renderer, error) {
  70. base := template.New("")
  71. for _, p := range plugins {
  72. if err := p.LoadTemplate(base); err != nil {
  73. return nil, fmt.Errorf("failed to load template for plugin '%v': %v", p.Name(), err)
  74. }
  75. }
  76. themes := make(map[string]*template.Template)
  77. files, err := ioutil.ReadDir(themesDir)
  78. if err != nil && !os.IsNotExist(err) {
  79. return nil, err
  80. }
  81. for _, fi := range files {
  82. if !fi.IsDir() {
  83. continue
  84. }
  85. logger.Printf("Loading theme '%v'", fi.Name())
  86. var err error
  87. if themes[fi.Name()], err = loadTheme(fi.Name(), base); err != nil {
  88. return nil, fmt.Errorf("failed to load theme '%v': %v", fi.Name(), err)
  89. }
  90. }
  91. if defaultTheme != "" {
  92. if _, ok := themes[defaultTheme]; !ok {
  93. return nil, fmt.Errorf("failed to find default theme '%v'", defaultTheme)
  94. }
  95. }
  96. return &renderer{
  97. base: base,
  98. themes: themes,
  99. defaultTheme: defaultTheme,
  100. }, nil
  101. }