A webmail client. Forked from https://git.sr.ht/~migadu/alps
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

202 rindas
4.4 KiB

  1. package alps
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "io/ioutil"
  7. "net/url"
  8. "os"
  9. "strings"
  10. "github.com/labstack/echo/v4"
  11. )
  12. const themesDir = "themes"
  13. // GlobalRenderData contains data available in all templates.
  14. type GlobalRenderData struct {
  15. Path []string
  16. URL *url.URL
  17. LoggedIn bool
  18. // if logged in
  19. Username string
  20. Title string
  21. HavePlugin func(name string) bool
  22. // additional plugin-specific data
  23. Extra map[string]interface{}
  24. }
  25. // BaseRenderData is the base type for templates. It should be extended with
  26. // additional template-specific fields:
  27. //
  28. // type MyRenderData struct {
  29. // BaseRenderData
  30. // // add additional fields here
  31. // }
  32. type BaseRenderData struct {
  33. GlobalData GlobalRenderData
  34. // additional plugin-specific data
  35. Extra map[string]interface{}
  36. }
  37. // Global implements RenderData.
  38. func (brd *BaseRenderData) Global() *GlobalRenderData {
  39. return &brd.GlobalData
  40. }
  41. // RenderData is implemented by template data structs. It can be used to inject
  42. // additional data to all templates.
  43. type RenderData interface {
  44. // GlobalData returns a pointer to the global render data.
  45. Global() *GlobalRenderData
  46. }
  47. // NewBaseRenderData initializes a new BaseRenderData.
  48. //
  49. // It can be used by routes to pre-fill the base data:
  50. //
  51. // type MyRenderData struct {
  52. // BaseRenderData
  53. // // add additional fields here
  54. // }
  55. //
  56. // data := &MyRenderData{
  57. // BaseRenderData: *alps.NewBaseRenderData(ctx),
  58. // // other fields...
  59. // }
  60. func NewBaseRenderData(ctx *Context) *BaseRenderData {
  61. global := GlobalRenderData{
  62. Extra: make(map[string]interface{}),
  63. Path: strings.Split(ctx.Request().URL.Path, "/")[1:],
  64. Title: "Webmail",
  65. URL: ctx.Request().URL,
  66. HavePlugin: func(name string) bool {
  67. for _, plugin := range ctx.Server.plugins {
  68. if plugin.Name() == name {
  69. return true
  70. }
  71. }
  72. return false
  73. },
  74. }
  75. if ctx.Session != nil {
  76. global.LoggedIn = true
  77. global.Username = ctx.Session.username
  78. }
  79. return &BaseRenderData{
  80. GlobalData: global,
  81. Extra: make(map[string]interface{}),
  82. }
  83. }
  84. func (brd *BaseRenderData) WithTitle(title string) *BaseRenderData {
  85. brd.GlobalData.Title = title
  86. return brd
  87. }
  88. type renderer struct {
  89. logger echo.Logger
  90. defaultTheme string
  91. base *template.Template
  92. themes map[string]*template.Template
  93. }
  94. func (r *renderer) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error {
  95. // ectx is the raw *echo.context, not our own *Context
  96. ctx := ectx.Get("context").(*Context)
  97. var renderData RenderData
  98. if data == nil {
  99. renderData = &struct{ BaseRenderData }{*NewBaseRenderData(ctx)}
  100. } else {
  101. var ok bool
  102. renderData, ok = data.(RenderData)
  103. if !ok {
  104. return fmt.Errorf("data passed to template %q doesn't implement RenderData", name)
  105. }
  106. }
  107. for _, plugin := range ctx.Server.plugins {
  108. if err := plugin.Inject(ctx, name, renderData); err != nil {
  109. return fmt.Errorf("failed to run plugin %q: %v", plugin.Name(), err)
  110. }
  111. }
  112. // TODO: per-user theme selection
  113. t := r.base
  114. if r.defaultTheme != "" {
  115. t = r.themes[r.defaultTheme]
  116. }
  117. return t.ExecuteTemplate(w, name, data)
  118. }
  119. func loadTheme(name string, base *template.Template) (*template.Template, error) {
  120. theme, err := base.Clone()
  121. if err != nil {
  122. return nil, err
  123. }
  124. theme, err = theme.ParseGlob(themesDir + "/" + name + "/*.html")
  125. if err != nil {
  126. return nil, err
  127. }
  128. return theme, nil
  129. }
  130. func (r *renderer) Load(plugins []Plugin) error {
  131. base := template.New("")
  132. for _, p := range plugins {
  133. if err := p.LoadTemplate(base); err != nil {
  134. return fmt.Errorf("failed to load template for plugin %q: %v", p.Name(), err)
  135. }
  136. }
  137. themes := make(map[string]*template.Template)
  138. files, err := ioutil.ReadDir(themesDir)
  139. if err != nil && !os.IsNotExist(err) {
  140. return err
  141. }
  142. for _, fi := range files {
  143. if !fi.IsDir() {
  144. continue
  145. }
  146. r.logger.Printf("Loading theme %q", fi.Name())
  147. var err error
  148. if themes[fi.Name()], err = loadTheme(fi.Name(), base); err != nil {
  149. return fmt.Errorf("failed to load theme %q: %v", fi.Name(), err)
  150. }
  151. }
  152. if r.defaultTheme != "" {
  153. if _, ok := themes[r.defaultTheme]; !ok {
  154. return fmt.Errorf("failed to find default theme %q", r.defaultTheme)
  155. }
  156. }
  157. r.base = base
  158. r.themes = themes
  159. return nil
  160. }
  161. func newRenderer(logger echo.Logger, defaultTheme string) *renderer {
  162. return &renderer{
  163. logger: logger,
  164. defaultTheme: defaultTheme,
  165. }
  166. }