2023-06-19 14:42:47 -07:00
|
|
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2018-02-04 15:45:07 -08:00
|
|
|
|
2023-08-10 19:46:45 -07:00
|
|
|
package template // import "miniflux.app/v2/internal/template"
|
2018-02-04 15:45:07 -08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-02-18 21:33:29 -08:00
|
|
|
"embed"
|
2018-02-04 15:45:07 -08:00
|
|
|
"html/template"
|
2018-04-27 22:07:46 -07:00
|
|
|
"time"
|
2018-02-04 15:45:07 -08:00
|
|
|
|
2023-08-10 19:46:45 -07:00
|
|
|
"miniflux.app/v2/internal/locale"
|
2018-02-04 15:45:07 -08:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
)
|
|
|
|
|
2021-02-18 21:33:29 -08:00
|
|
|
//go:embed templates/common/*.html
|
|
|
|
var commonTemplateFiles embed.FS
|
|
|
|
|
|
|
|
//go:embed templates/views/*.html
|
|
|
|
var viewTemplateFiles embed.FS
|
|
|
|
|
2018-02-04 15:45:07 -08:00
|
|
|
// Engine handles the templating system.
|
|
|
|
type Engine struct {
|
2018-09-22 15:04:55 -07:00
|
|
|
templates map[string]*template.Template
|
|
|
|
funcMap *funcMap
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|
|
|
|
|
2021-02-18 21:33:29 -08:00
|
|
|
// NewEngine returns a new template engine.
|
|
|
|
func NewEngine(router *mux.Router) *Engine {
|
|
|
|
return &Engine{
|
|
|
|
templates: make(map[string]*template.Template),
|
|
|
|
funcMap: &funcMap{router},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-08 18:00:59 +02:00
|
|
|
func (e *Engine) ParseTemplates() {
|
2025-08-06 05:07:49 +02:00
|
|
|
funcMap := e.funcMap.Map()
|
2025-08-08 18:00:59 +02:00
|
|
|
templates := map[string][]string{ // this isn't a global variable so that it can be garbage-collected.
|
|
|
|
"about.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"add_subscription.html": {"feed_menu.html", "layout.html", "settings_menu.html"},
|
|
|
|
"api_keys.html": {"layout.html", "settings_menu.html"},
|
2025-08-20 16:00:11 -04:00
|
|
|
"starred_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
2025-08-08 18:00:59 +02:00
|
|
|
"categories.html": {"layout.html"},
|
|
|
|
"category_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"category_feeds.html": {"feed_list.html", "layout.html"},
|
|
|
|
"choose_subscription.html": {"feed_menu.html", "layout.html"},
|
|
|
|
"create_api_key.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"create_category.html": {"layout.html"},
|
|
|
|
"create_user.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"edit_category.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"edit_feed.html": {"layout.html"},
|
|
|
|
"edit_user.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"entry.html": {"layout.html"},
|
|
|
|
"feed_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"feeds.html": {"feed_list.html", "feed_menu.html", "item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"history_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"import.html": {"feed_menu.html", "layout.html"},
|
|
|
|
"integrations.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"login.html": {"layout.html"},
|
|
|
|
"offline.html": {},
|
|
|
|
"search.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"sessions.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"settings.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"shared_entries.html": {"layout.html", "pagination.html"},
|
|
|
|
"tag_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"unread_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
|
|
"users.html": {"layout.html", "settings_menu.html"},
|
|
|
|
"webauthn_rename.html": {"layout.html"},
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|
2025-08-08 18:00:59 +02:00
|
|
|
|
|
|
|
for name, dependencies := range templates {
|
|
|
|
tpl := template.New("").Funcs(funcMap)
|
|
|
|
for _, dependency := range dependencies {
|
|
|
|
template.Must(tpl.ParseFS(commonTemplateFiles, "templates/common/"+dependency))
|
2021-02-18 21:33:29 -08:00
|
|
|
}
|
2025-08-08 18:00:59 +02:00
|
|
|
e.templates[name] = template.Must(tpl.ParseFS(viewTemplateFiles, "templates/views/"+name))
|
2021-02-18 21:33:29 -08:00
|
|
|
}
|
|
|
|
|
2025-08-08 18:00:59 +02:00
|
|
|
// Sanity check to ensure that all templates are correctly declared in `templates`.
|
|
|
|
if entries, err := viewTemplateFiles.ReadDir("templates/views"); err == nil {
|
|
|
|
for _, entry := range entries {
|
|
|
|
if _, ok := e.templates[entry.Name()]; !ok {
|
|
|
|
panic("Template " + entry.Name() + " isn't declared in ParseTemplates")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic("Unable to read all embedded views templates")
|
2021-03-07 15:25:34 -08:00
|
|
|
}
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|
|
|
|
|
2018-09-09 14:25:56 -07:00
|
|
|
// Render process a template.
|
2025-07-16 15:45:43 +02:00
|
|
|
func (e *Engine) Render(name string, data map[string]any) []byte {
|
2018-02-04 15:45:07 -08:00
|
|
|
tpl, ok := e.templates[name]
|
|
|
|
if !ok {
|
2025-08-08 18:00:59 +02:00
|
|
|
panic("The template " + name + " does not exists.")
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|
|
|
|
|
2021-06-01 03:19:37 +07:00
|
|
|
printer := locale.NewPrinter(data["language"].(string))
|
2018-09-20 19:45:56 -07:00
|
|
|
|
|
|
|
// Functions that need to be declared at runtime.
|
2018-04-27 22:07:46 -07:00
|
|
|
tpl.Funcs(template.FuncMap{
|
|
|
|
"elapsed": func(timezone string, t time.Time) string {
|
2018-09-22 15:04:55 -07:00
|
|
|
return elapsedTime(printer, timezone, t)
|
2018-04-27 22:07:46 -07:00
|
|
|
},
|
2025-07-16 15:45:43 +02:00
|
|
|
"t": printer.Printf,
|
|
|
|
"plural": printer.Plural,
|
2018-04-27 22:07:46 -07:00
|
|
|
})
|
|
|
|
|
2018-02-04 15:45:07 -08:00
|
|
|
var b bytes.Buffer
|
2025-08-08 18:00:59 +02:00
|
|
|
if err := tpl.ExecuteTemplate(&b, "base", data); err != nil {
|
2023-09-24 16:32:09 -07:00
|
|
|
panic(err)
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|
|
|
|
|
2018-04-29 16:35:04 -07:00
|
|
|
return b.Bytes()
|
2018-02-04 15:45:07 -08:00
|
|
|
}
|