mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	Refactor mail template and support preview (#34990)
This commit is contained in:
		| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"html/template" | 	"html/template" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"sync/atomic" | ||||||
| 	texttmpl "text/template" | 	texttmpl "text/template" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| @@ -16,6 +17,12 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type MailTemplates struct { | ||||||
|  | 	TemplateNames    []string | ||||||
|  | 	BodyTemplates    *template.Template | ||||||
|  | 	SubjectTemplates *texttmpl.Template | ||||||
|  | } | ||||||
|  |  | ||||||
| var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`) | var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`) | ||||||
|  |  | ||||||
| // mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject | // mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject | ||||||
| @@ -52,16 +59,17 @@ func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Mailer provides the templates required for sending notification mails. | // LoadMailTemplates provides the templates required for sending notification mails. | ||||||
| func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { | func LoadMailTemplates(ctx context.Context, loadedTemplates *atomic.Pointer[MailTemplates]) { | ||||||
|  | 	assetFS := AssetFS() | ||||||
|  | 	refreshTemplates := func(firstRun bool) { | ||||||
|  | 		var templateNames []string | ||||||
| 		subjectTemplates := texttmpl.New("") | 		subjectTemplates := texttmpl.New("") | ||||||
| 		bodyTemplates := template.New("") | 		bodyTemplates := template.New("") | ||||||
|  |  | ||||||
| 		subjectTemplates.Funcs(mailSubjectTextFuncMap()) | 		subjectTemplates.Funcs(mailSubjectTextFuncMap()) | ||||||
| 		bodyTemplates.Funcs(NewFuncMap()) | 		bodyTemplates.Funcs(NewFuncMap()) | ||||||
|  |  | ||||||
| 	assetFS := AssetFS() |  | ||||||
| 	refreshTemplates := func(firstRun bool) { |  | ||||||
| 		if !firstRun { | 		if !firstRun { | ||||||
| 			log.Trace("Reloading mail templates") | 			log.Trace("Reloading mail templates") | ||||||
| 		} | 		} | ||||||
| @@ -81,6 +89,7 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { | |||||||
| 			if firstRun { | 			if firstRun { | ||||||
| 				log.Trace("Adding mail template %s: %s by %s", tmplName, assetPath, layerName) | 				log.Trace("Adding mail template %s: %s by %s", tmplName, assetPath, layerName) | ||||||
| 			} | 			} | ||||||
|  | 			templateNames = append(templateNames, tmplName) | ||||||
| 			if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { | 			if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { | ||||||
| 				if firstRun { | 				if firstRun { | ||||||
| 					log.Fatal("Failed to parse mail template, err: %v", err) | 					log.Fatal("Failed to parse mail template, err: %v", err) | ||||||
| @@ -88,6 +97,12 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { | |||||||
| 				log.Error("Failed to parse mail template, err: %v", err) | 				log.Error("Failed to parse mail template, err: %v", err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		loaded := &MailTemplates{ | ||||||
|  | 			TemplateNames:    templateNames, | ||||||
|  | 			BodyTemplates:    bodyTemplates, | ||||||
|  | 			SubjectTemplates: subjectTemplates, | ||||||
|  | 		} | ||||||
|  | 		loadedTemplates.Store(loaded) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	refreshTemplates(true) | 	refreshTemplates(true) | ||||||
| @@ -99,6 +114,4 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { | |||||||
| 			refreshTemplates(false) | 			refreshTemplates(false) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return subjectTemplates, bodyTemplates |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								routers/web/devtest/mail_preview.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								routers/web/devtest/mail_preview.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | // Copyright 2025 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package devtest | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/templates" | ||||||
|  | 	"code.gitea.io/gitea/services/context" | ||||||
|  | 	"code.gitea.io/gitea/services/mailer" | ||||||
|  |  | ||||||
|  | 	"gopkg.in/yaml.v3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func MailPreviewRender(ctx *context.Context) { | ||||||
|  | 	tmplName := ctx.PathParam("*") | ||||||
|  | 	mockDataContent, err := templates.AssetFS().ReadFile("mail/" + tmplName + ".mock.yml") | ||||||
|  | 	mockData := map[string]any{} | ||||||
|  | 	if err == nil { | ||||||
|  | 		err = yaml.Unmarshal(mockDataContent, &mockData) | ||||||
|  | 		if err != nil { | ||||||
|  | 			http.Error(ctx.Resp, "Failed to parse mock data: "+err.Error(), http.StatusInternalServerError) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	mockData["locale"] = ctx.Locale | ||||||
|  | 	err = mailer.LoadedTemplates().BodyTemplates.ExecuteTemplate(ctx.Resp, tmplName, mockData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		_, _ = ctx.Resp.Write([]byte(err.Error())) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func prepareMailPreviewRender(ctx *context.Context, tmplName string) { | ||||||
|  | 	tmplSubject := mailer.LoadedTemplates().SubjectTemplates.Lookup(tmplName) | ||||||
|  | 	if tmplSubject == nil { | ||||||
|  | 		ctx.Data["RenderMailSubject"] = "default subject" | ||||||
|  | 	} else { | ||||||
|  | 		var buf strings.Builder | ||||||
|  | 		err := tmplSubject.Execute(&buf, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.Data["RenderMailSubject"] = err.Error() | ||||||
|  | 		} else { | ||||||
|  | 			ctx.Data["RenderMailSubject"] = buf.String() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	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") | ||||||
|  | } | ||||||
| @@ -1659,6 +1659,8 @@ func registerWebRoutes(m *web.Router) { | |||||||
| 		m.Group("/devtest", func() { | 		m.Group("/devtest", func() { | ||||||
| 			m.Any("", devtest.List) | 			m.Any("", devtest.List) | ||||||
| 			m.Any("/fetch-action-test", devtest.FetchActionTest) | 			m.Any("/fetch-action-test", devtest.FetchActionTest) | ||||||
|  | 			m.Any("/mail-preview", devtest.MailPreview) | ||||||
|  | 			m.Any("/mail-preview/*", devtest.MailPreviewRender) | ||||||
| 			m.Any("/{sub}", devtest.TmplCommon) | 			m.Any("/{sub}", devtest.TmplCommon) | ||||||
| 			m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView) | 			m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView) | ||||||
| 			m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs) | 			m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs) | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import ( | |||||||
| 	"mime" | 	"mime" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	texttmpl "text/template" | 	"sync/atomic" | ||||||
|  |  | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| @@ -23,6 +23,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
|  | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/typesniffer" | 	"code.gitea.io/gitea/modules/typesniffer" | ||||||
| 	sender_service "code.gitea.io/gitea/services/mailer/sender" | 	sender_service "code.gitea.io/gitea/services/mailer/sender" | ||||||
|  |  | ||||||
| @@ -31,11 +32,13 @@ import ( | |||||||
|  |  | ||||||
| const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322 | const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322 | ||||||
|  |  | ||||||
| var ( | var loadedTemplates atomic.Pointer[templates.MailTemplates] | ||||||
| 	bodyTemplates       *template.Template |  | ||||||
| 	subjectTemplates    *texttmpl.Template | var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`) | ||||||
| 	subjectRemoveSpaces = regexp.MustCompile(`[\s]+`) |  | ||||||
| ) | func LoadedTemplates() *templates.MailTemplates { | ||||||
|  | 	return loadedTemplates.Load() | ||||||
|  | } | ||||||
|  |  | ||||||
| // SendTestMail sends a test mail | // SendTestMail sends a test mail | ||||||
| func SendTestMail(email string) error { | func SendTestMail(email string) error { | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var mailSubject bytes.Buffer | 	var mailSubject bytes.Buffer | ||||||
| 	if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { | 	if err := LoadedTemplates().SubjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { | ||||||
| 		subject = sanitizeSubject(mailSubject.String()) | 		subject = sanitizeSubject(mailSubject.String()) | ||||||
| 		if subject == "" { | 		if subject == "" { | ||||||
| 			subject = fallback | 			subject = fallback | ||||||
| @@ -134,7 +134,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang | |||||||
|  |  | ||||||
| 	var mailBody bytes.Buffer | 	var mailBody bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { | ||||||
| 		log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) | 		log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -260,14 +260,14 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	template = typeName + "/" + name | 	template = typeName + "/" + name | ||||||
| 	ok := bodyTemplates.Lookup(template) != nil | 	ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil | ||||||
| 	if !ok && typeName != "issue" { | 	if !ok && typeName != "issue" { | ||||||
| 		template = "issue/" + name | 		template = "issue/" + name | ||||||
| 		ok = bodyTemplates.Lookup(template) != nil | 		ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil | ||||||
| 	} | 	} | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		template = typeName + "/default" | 		template = typeName + "/default" | ||||||
| 		ok = bodyTemplates.Lookup(template) != nil | 		ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil | ||||||
| 	} | 	} | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		template = "issue/default" | 		template = "issue/default" | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re | |||||||
|  |  | ||||||
| 	var mailBody bytes.Buffer | 	var mailBody bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplNewReleaseMail), mailMeta); err != nil { | ||||||
| 		log.Error("ExecuteTemplate [%s]: %v", string(tplNewReleaseMail)+"/body", err) | 		log.Error("ExecuteTemplate [%s]: %v", string(tplNewReleaseMail)+"/body", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U | |||||||
| 		"Destination": destination, | 		"Destination": destination, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var mailBody bytes.Buffer | 	var mailBody bytes.Buffer | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplTeamInviteMail), mailMeta); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplTeamInviteMail), mailMeta); err != nil { | ||||||
| 		log.Error("ExecuteTemplate [%s]: %v", string(tplTeamInviteMail)+"/body", err) | 		log.Error("ExecuteTemplate [%s]: %v", string(tplTeamInviteMail)+"/body", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/markup" | 	"code.gitea.io/gitea/modules/markup" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
|  | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| 	"code.gitea.io/gitea/services/attachment" | 	"code.gitea.io/gitea/services/attachment" | ||||||
| 	sender_service "code.gitea.io/gitea/services/mailer/sender" | 	sender_service "code.gitea.io/gitea/services/mailer/sender" | ||||||
| @@ -95,6 +96,13 @@ func prepareMailerBase64Test(t *testing.T) (doer *user_model.User, repo *repo_mo | |||||||
| 	return user, repo, issue, att1, att2 | 	return user, repo, issue, att1, att2 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func prepareMailTemplates(name, subjectTmpl, bodyTmpl string) { | ||||||
|  | 	loadedTemplates.Store(&templates.MailTemplates{ | ||||||
|  | 		SubjectTemplates: texttmpl.Must(texttmpl.New(name).Parse(subjectTmpl)), | ||||||
|  | 		BodyTemplates:    template.Must(template.New(name).Parse(bodyTmpl)), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestComposeIssueComment(t *testing.T) { | func TestComposeIssueComment(t *testing.T) { | ||||||
| 	doer, _, issue, comment := prepareMailerTest(t) | 	doer, _, issue, comment := prepareMailerTest(t) | ||||||
|  |  | ||||||
| @@ -107,8 +115,7 @@ func TestComposeIssueComment(t *testing.T) { | |||||||
| 	setting.IncomingEmail.Enabled = true | 	setting.IncomingEmail.Enabled = true | ||||||
| 	defer func() { setting.IncomingEmail.Enabled = false }() | 	defer func() { setting.IncomingEmail.Enabled = false }() | ||||||
|  |  | ||||||
| 	subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl)) | 	prepareMailTemplates("issue/comment", subjectTpl, bodyTpl) | ||||||
| 	bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl)) |  | ||||||
|  |  | ||||||
| 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}} | 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}} | ||||||
| 	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ | 	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ | ||||||
| @@ -153,8 +160,7 @@ func TestComposeIssueComment(t *testing.T) { | |||||||
| func TestMailMentionsComment(t *testing.T) { | func TestMailMentionsComment(t *testing.T) { | ||||||
| 	doer, _, issue, comment := prepareMailerTest(t) | 	doer, _, issue, comment := prepareMailerTest(t) | ||||||
| 	comment.Poster = doer | 	comment.Poster = doer | ||||||
| 	subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl)) | 	prepareMailTemplates("issue/comment", subjectTpl, bodyTpl) | ||||||
| 	bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl)) |  | ||||||
| 	mails := 0 | 	mails := 0 | ||||||
|  |  | ||||||
| 	defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) { | 	defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) { | ||||||
| @@ -169,9 +175,7 @@ func TestMailMentionsComment(t *testing.T) { | |||||||
| func TestComposeIssueMessage(t *testing.T) { | func TestComposeIssueMessage(t *testing.T) { | ||||||
| 	doer, _, issue, _ := prepareMailerTest(t) | 	doer, _, issue, _ := prepareMailerTest(t) | ||||||
|  |  | ||||||
| 	subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl)) | 	prepareMailTemplates("issue/new", subjectTpl, bodyTpl) | ||||||
| 	bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl)) |  | ||||||
|  |  | ||||||
| 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}} | 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}} | ||||||
| 	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ | 	msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ | ||||||
| 		Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue, | 		Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue, | ||||||
| @@ -200,15 +204,14 @@ func TestTemplateSelection(t *testing.T) { | |||||||
| 	doer, repo, issue, comment := prepareMailerTest(t) | 	doer, repo, issue, comment := prepareMailerTest(t) | ||||||
| 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}} | 	recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}} | ||||||
|  |  | ||||||
| 	subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse("issue/default/subject")) | 	prepareMailTemplates("issue/default", "issue/default/subject", "issue/default/body") | ||||||
| 	texttmpl.Must(subjectTemplates.New("issue/new").Parse("issue/new/subject")) |  | ||||||
| 	texttmpl.Must(subjectTemplates.New("pull/comment").Parse("pull/comment/subject")) |  | ||||||
| 	texttmpl.Must(subjectTemplates.New("issue/close").Parse("")) // Must default to fallback subject |  | ||||||
|  |  | ||||||
| 	bodyTemplates = template.Must(template.New("issue/default").Parse("issue/default/body")) | 	texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/new").Parse("issue/new/subject")) | ||||||
| 	template.Must(bodyTemplates.New("issue/new").Parse("issue/new/body")) | 	texttmpl.Must(LoadedTemplates().SubjectTemplates.New("pull/comment").Parse("pull/comment/subject")) | ||||||
| 	template.Must(bodyTemplates.New("pull/comment").Parse("pull/comment/body")) | 	texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/close").Parse("")) // Must default to a fallback subject | ||||||
| 	template.Must(bodyTemplates.New("issue/close").Parse("issue/close/body")) | 	template.Must(LoadedTemplates().BodyTemplates.New("issue/new").Parse("issue/new/body")) | ||||||
|  | 	template.Must(LoadedTemplates().BodyTemplates.New("pull/comment").Parse("pull/comment/body")) | ||||||
|  | 	template.Must(LoadedTemplates().BodyTemplates.New("issue/close").Parse("issue/close/body")) | ||||||
|  |  | ||||||
| 	expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) { | 	expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) { | ||||||
| 		subject := msg.ToMessage().GetGenHeader("Subject") | 		subject := msg.ToMessage().GetGenHeader("Subject") | ||||||
| @@ -253,9 +256,7 @@ func TestTemplateServices(t *testing.T) { | |||||||
| 	expect := func(t *testing.T, issue *issues_model.Issue, comment *issues_model.Comment, doer *user_model.User, | 	expect := func(t *testing.T, issue *issues_model.Issue, comment *issues_model.Comment, doer *user_model.User, | ||||||
| 		actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string, | 		actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string, | ||||||
| 	) { | 	) { | ||||||
| 		subjectTemplates = texttmpl.Must(texttmpl.New("issue/default").Parse(tplSubject)) | 		prepareMailTemplates("issue/default", tplSubject, tplBody) | ||||||
| 		bodyTemplates = template.Must(template.New("issue/default").Parse(tplBody)) |  | ||||||
|  |  | ||||||
| 		recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}} | 		recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}} | ||||||
| 		msg := testComposeIssueCommentMessage(t, &mailComment{ | 		msg := testComposeIssueCommentMessage(t, &mailComment{ | ||||||
| 			Issue: issue, Doer: doer, ActionType: actionType, | 			Issue: issue, Doer: doer, ActionType: actionType, | ||||||
| @@ -512,8 +513,7 @@ func TestEmbedBase64Images(t *testing.T) { | |||||||
| 	att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64) | 	att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64) | ||||||
|  |  | ||||||
| 	t.Run("ComposeMessage", func(t *testing.T) { | 	t.Run("ComposeMessage", func(t *testing.T) { | ||||||
| 		subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl)) | 		prepareMailTemplates("issue/new", subjectTpl, bodyTpl) | ||||||
| 		bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl)) |  | ||||||
|  |  | ||||||
| 		issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID) | 		issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID) | ||||||
| 		require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content")) | 		require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content")) | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ func sendUserMail(language string, u *user_model.User, tpl templates.TplName, co | |||||||
|  |  | ||||||
| 	var content bytes.Buffer | 	var content bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil { | ||||||
| 		log.Error("Template: %v", err) | 		log.Error("Template: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -90,7 +90,7 @@ func SendActivateEmailMail(u *user_model.User, email string) { | |||||||
|  |  | ||||||
| 	var content bytes.Buffer | 	var content bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil { | ||||||
| 		log.Error("Template: %v", err) | 		log.Error("Template: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -118,7 +118,7 @@ func SendRegisterNotifyMail(u *user_model.User) { | |||||||
|  |  | ||||||
| 	var content bytes.Buffer | 	var content bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil { | ||||||
| 		log.Error("Template: %v", err) | 		log.Error("Template: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -149,7 +149,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) | |||||||
|  |  | ||||||
| 	var content bytes.Buffer | 	var content bytes.Buffer | ||||||
|  |  | ||||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil { | 	if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil { | ||||||
| 		log.Error("Template: %v", err) | 		log.Error("Template: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ func NewContext(ctx context.Context) { | |||||||
| 		sender = &sender_service.SMTPSender{} | 		sender = &sender_service.SMTPSender{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	subjectTemplates, bodyTemplates = templates.Mailer(ctx) | 	templates.LoadMailTemplates(ctx, &loadedTemplates) | ||||||
|  |  | ||||||
| 	mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message { | 	mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message { | ||||||
| 		for _, msg := range items { | 		for _, msg := range items { | ||||||
|   | |||||||
| @@ -1,2 +1,3 @@ | |||||||
| {{template "base/head" ctx.RootData}} | {{template "base/head" ctx.RootData}} | ||||||
| <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}"> | <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}"> | ||||||
|  | {{template "base/alert" .}} | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								templates/devtest/mail-preview.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								templates/devtest/mail-preview.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | {{template "devtest/devtest-header"}} | ||||||
|  | <div class="page-content devtest ui container"> | ||||||
|  | 	<div class="flex-text-block tw-flex-wrap"> | ||||||
|  | 		{{range $templateName := .MailTemplateNames}} | ||||||
|  | 			<a class="ui button" href="?tmpl={{$templateName}}">{{$templateName}}</a> | ||||||
|  | 		{{else}} | ||||||
|  | 			<p>Mailer service is not enabled or no template is found</p> | ||||||
|  | 		{{end}} | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
|  | 	{{if .RenderMailTemplateName}} | ||||||
|  | 	<div class="tw-my-2"> | ||||||
|  | 		<div>Preview of: {{.RenderMailTemplateName}}</div> | ||||||
|  | 		<div>Subject: {{.RenderMailSubject}}</div> | ||||||
|  | 		<iframe src="{{AppSubUrl}}/devtest/mail-preview/{{.RenderMailTemplateName}}" class="mail-preview-body"></iframe> | ||||||
|  | 		<style> | ||||||
|  | 			.mail-preview-body { | ||||||
|  | 				border: 1px solid #ccc; | ||||||
|  | 				width: 100%; | ||||||
|  | 				height: 400px; | ||||||
|  | 				overflow: auto; | ||||||
|  | 			} | ||||||
|  | 		</style> | ||||||
|  | 	</div> | ||||||
|  | 	{{end}} | ||||||
|  | </div> | ||||||
|  | {{template "devtest/devtest-footer"}} | ||||||
							
								
								
									
										3
									
								
								templates/mail/auth/activate.mock.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								templates/mail/auth/activate.mock.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | DisplayName: User Display Name | ||||||
|  | Code: The-Activation-Code | ||||||
|  | ActiveCodeLives: 24h | ||||||
		Reference in New Issue
	
	Block a user