Files
Gitea/modules/templates/page.go
2026-01-24 05:11:49 +00:00

99 lines
2.9 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"context"
"html/template"
"io"
"net/http"
"slices"
"strings"
"sync"
texttemplate "text/template"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
type pageRenderer struct {
tmplRenderer *tmplRender
}
func (r *pageRenderer) funcMap(ctx context.Context) template.FuncMap {
pageFuncMap := NewFuncMap()
pageFuncMap["ctx"] = func() any { return ctx }
return pageFuncMap
}
func (r *pageRenderer) funcMapDummy() template.FuncMap {
dummyFuncMap := NewFuncMap()
dummyFuncMap["ctx"] = func() any { return nil } // for template compilation only, no context available
return dummyFuncMap
}
func (r *pageRenderer) TemplateLookup(tmpl string, templateCtx context.Context) (TemplateExecutor, error) { //nolint:revive // we don't use ctx, only pass it to the template executor
return r.tmplRenderer.Templates().Executor(tmpl, r.funcMap(templateCtx))
}
func (r *pageRenderer) HTML(w io.Writer, status int, tplName TplName, data any, templateCtx context.Context) error { //nolint:revive // we don't use ctx, only pass it to the template executor
name := string(tplName)
if respWriter, ok := w.(http.ResponseWriter); ok {
if respWriter.Header().Get("Content-Type") == "" {
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
}
respWriter.WriteHeader(status)
}
t, err := r.TemplateLookup(name, templateCtx)
if err != nil {
return texttemplate.ExecError{Name: name, Err: err}
}
return t.Execute(w, data)
}
var PageRenderer = sync.OnceValue(func() *pageRenderer {
rendererType := util.Iif(setting.IsProd, "static", "auto-reloading")
log.Debug("Creating %s HTML Renderer", rendererType)
assetFS := AssetFS()
tr := &tmplRender{
collectTemplateNames: func() ([]string, error) {
names, err := assetFS.ListAllFiles(".", true)
if err != nil {
return nil, err
}
names = slices.DeleteFunc(names, func(file string) bool {
return strings.HasPrefix(file, "mail/") || !strings.HasSuffix(file, ".tmpl")
})
for i, file := range names {
names[i] = strings.TrimSuffix(file, ".tmpl")
}
return names, nil
},
readTemplateContent: func(name string) ([]byte, error) {
return assetFS.ReadFile(name + ".tmpl")
},
}
pr := &pageRenderer{tmplRenderer: tr}
if err := tr.recompileTemplates(pr.funcMapDummy()); err != nil {
processStartupTemplateError(err)
}
if !setting.IsProd {
go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() {
if err := tr.recompileTemplates(pr.funcMapDummy()); err != nil {
log.Error("Template error: %v\n%s", err, log.Stack(2))
}
})
}
return pr
})
func PageRendererReload() error {
return PageRenderer().tmplRenderer.recompileTemplates(PageRenderer().funcMapDummy())
}