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.
 
 
 
 

135 lines
2.8 KiB

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