engine.go 4.2 KB

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