engine.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package template // import "miniflux.app/v2/internal/template"
  4. import (
  5. "bytes"
  6. "embed"
  7. "html/template"
  8. "time"
  9. "miniflux.app/v2/internal/locale"
  10. )
  11. //go:embed templates/common/*.html
  12. var commonTemplateFiles embed.FS
  13. //go:embed templates/views/*.html
  14. var viewTemplateFiles embed.FS
  15. // Engine handles the templating system.
  16. type Engine struct {
  17. templates map[string]*template.Template
  18. funcMap *funcMap
  19. }
  20. // NewEngine returns a new template engine.
  21. func NewEngine(basePath string) *Engine {
  22. return &Engine{
  23. templates: make(map[string]*template.Template),
  24. funcMap: &funcMap{basePath},
  25. }
  26. }
  27. func (e *Engine) ParseTemplates() {
  28. funcMap := e.funcMap.Map()
  29. templates := map[string][]string{ // this isn't a global variable so that it can be garbage-collected.
  30. "about.html": {"layout.html", "settings_menu.html"},
  31. "add_subscription.html": {"feed_menu.html", "layout.html", "settings_menu.html"},
  32. "api_keys.html": {"layout.html", "settings_menu.html"},
  33. "starred_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  34. "categories.html": {"layout.html"},
  35. "category_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  36. "category_feeds.html": {"feed_list.html", "layout.html"},
  37. "choose_subscription.html": {"feed_menu.html", "layout.html"},
  38. "create_api_key.html": {"layout.html", "settings_menu.html"},
  39. "create_category.html": {"layout.html"},
  40. "create_user.html": {"layout.html", "settings_menu.html"},
  41. "edit_category.html": {"layout.html", "settings_menu.html"},
  42. "edit_feed.html": {"layout.html"},
  43. "edit_user.html": {"layout.html", "settings_menu.html"},
  44. "entry.html": {"layout.html"},
  45. "feed_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  46. "feeds.html": {"feed_list.html", "feed_menu.html", "item_meta.html", "layout.html", "pagination.html"},
  47. "history_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  48. "import.html": {"feed_menu.html", "layout.html"},
  49. "integrations.html": {"layout.html", "settings_menu.html"},
  50. "login.html": {"layout.html"},
  51. "offline.html": {},
  52. "search.html": {"item_meta.html", "layout.html", "pagination.html"},
  53. "sessions.html": {"layout.html", "settings_menu.html"},
  54. "settings.html": {"layout.html", "settings_menu.html"},
  55. "shared_entries.html": {"layout.html", "pagination.html"},
  56. "tag_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  57. "unread_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
  58. "users.html": {"layout.html", "settings_menu.html"},
  59. "webauthn_rename.html": {"layout.html"},
  60. }
  61. for name, dependencies := range templates {
  62. tpl := template.New("").Funcs(funcMap)
  63. for _, dependency := range dependencies {
  64. template.Must(tpl.ParseFS(commonTemplateFiles, "templates/common/"+dependency))
  65. }
  66. e.templates[name] = template.Must(tpl.ParseFS(viewTemplateFiles, "templates/views/"+name))
  67. }
  68. // Sanity check to ensure that all templates are correctly declared in `templates`.
  69. if entries, err := viewTemplateFiles.ReadDir("templates/views"); err == nil {
  70. for _, entry := range entries {
  71. if _, ok := e.templates[entry.Name()]; !ok {
  72. panic("Template " + entry.Name() + " isn't declared in ParseTemplates")
  73. }
  74. }
  75. } else {
  76. panic("Unable to read all embedded views templates")
  77. }
  78. }
  79. // Render process a template.
  80. func (e *Engine) Render(name string, data map[string]any) []byte {
  81. tpl, ok := e.templates[name]
  82. if !ok {
  83. panic("The template " + name + " does not exists.")
  84. }
  85. printer := locale.NewPrinter(data["language"].(string))
  86. // Functions that need to be declared at runtime.
  87. tpl.Funcs(template.FuncMap{
  88. "elapsed": func(timezone string, t time.Time) string {
  89. return elapsedTime(printer, timezone, t)
  90. },
  91. "t": printer.Printf,
  92. "plural": printer.Plural,
  93. })
  94. var b bytes.Buffer
  95. if err := tpl.ExecuteTemplate(&b, "base", data); err != nil {
  96. panic(err)
  97. }
  98. return b.Bytes()
  99. }