Files
gitea-MIRROR/routers/web/devtest/mail_preview.go
wxiaoguang a165fdb63c refactor: lazy init global regexp variables (#40269)
Gitea binary is an all-in-one program.

It doesn't make sense to compile dozens or even hundreds of regexps for
git hook sub commands.
2026-11-07 11:56:56 +00:00

105 lines
3.3 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package devtest
import (
"errors"
"io/fs"
"net/http"
"regexp"
"strings"
"sync"
"gitea.dev/modules/setting"
"gitea.dev/modules/templates"
"gitea.dev/modules/util"
"gitea.dev/services/context"
"gitea.dev/services/mailer"
"go.yaml.in/yaml/v4"
)
var mailDarkSchemeQuery = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(`@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)`)
})
func mailPreviewMockData(tmplName string) (map[string]any, error) {
mockData := map[string]any{}
mockDataContent, err := templates.AssetFS().ReadFile(tmplName + ".devtest.yml")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return mockData, nil
}
return nil, err
}
return mockData, yaml.Unmarshal(mockDataContent, &mockData)
}
func MailPreviewRender(ctx *context.Context) {
tmplName := ctx.PathParam("*")
mockData, err := mailPreviewMockData(tmplName)
if err != nil {
http.Error(ctx.Resp, "Failed to parse mock data: "+err.Error(), http.StatusInternalServerError)
return
}
mockData["locale"] = ctx.Locale
var mailBody strings.Builder
if err := mailer.LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, tmplName, mockData); err != nil {
http.Error(ctx.Resp, err.Error(), http.StatusInternalServerError)
return
}
body := mailBody.String()
// emulate mail clients, which resolve "cid:" URIs to the mail's inline attachments
body = strings.ReplaceAll(body, `src="cid:`, `src="`+setting.AppSubURL+`/devtest/mail-preview-embed/`)
// a page can force "color-scheme" on an embedded document but never "prefers-color-scheme"
if scheme := ctx.FormString("scheme"); scheme == "light" || scheme == "dark" {
body = mailDarkSchemeQuery().ReplaceAllString(body, util.Iif(scheme == "dark", "@media all", "@media not all"))
body = strings.Replace(body, "</head>", "<style>:root {color-scheme: "+scheme+"}</style></head>", 1)
}
// fragment templates like "mail/base/head" would be sniffed as text/plain otherwise
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = ctx.Resp.Write([]byte(body))
}
func MailPreviewEmbed(ctx *context.Context) {
content, err := mailer.LoadMailIcon(ctx.PathParam("*"))
if err != nil {
ctx.NotFound(err)
return
}
ctx.Resp.Header().Set("Content-Type", "image/png")
_, _ = ctx.Resp.Write(content)
}
func prepareMailPreviewRender(ctx *context.Context, tmplName string) {
subject := "(default subject)"
if mockData, err := mailPreviewMockData(tmplName); err == nil {
if mockSubject, ok := mockData["Subject"].(string); ok {
subject = util.IfZero(mockSubject, subject)
}
}
tmplSubject := mailer.LoadedTemplates().SubjectTemplates.Lookup(tmplName)
// FIXME: MAIL-TEMPLATE-SUBJECT: only "issue" related messages support using subject from templates
if tmplSubject != nil {
var buf strings.Builder
err := tmplSubject.Execute(&buf, nil)
if err != nil {
subject = "ERROR: " + err.Error()
} else {
subject = util.IfZero(buf.String(), subject)
}
}
ctx.Data["RenderMailSubject"] = subject
ctx.Data["RenderMailTemplateName"] = tmplName
}
func MailPreview(ctx *context.Context) {
ctx.Data["MailTemplateNames"] = mailer.LoadedTemplates().TemplateNames
tmplName := ctx.FormString("tmpl")
if tmplName != "" {
prepareMailPreviewRender(ctx, tmplName)
}
ctx.HTML(http.StatusOK, "devtest/mail-preview")
}