Browse Source
Use templates for issue e-mail subject and body (#8329)
Use templates for issue e-mail subject and body (#8329)
* Add template capability for issue mail subject * Remove test string * Fix trim subject length * Add comment to template and run make fmt * Add information for the template * Rename defaultMailSubject() to fallbackMailSubject() * General rewrite of the mail template code * Fix .Doer name * Use text/template for subject instead of html * Fix subject Re: prefix * Fix mail tests * Fix static templates * [skip ci] Updated translations via Crowdin * Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) * Expose db.SetMaxOpenConns and allow other dbs to set their connection params * Add note about port exhaustion Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Prevent .code-view from overriding font on icon fonts (#8614) * Correct some outdated statements in the contributing guidelines (#8612) * More information for drone-cli in CONTRIBUTING.md * Increases the version of drone-cli to 1.2.0 * Adds a note for the Docker Toolbox on Windows Signed-off-by: LukBukkit <luk.bukkit@gmail.com> * Fix the url for the blog repository (now on gitea.com) Signed-off-by: LukBukkit <luk.bukkit@gmail.com> * Remove TrN due to lack of lang context * Redo templates to match previous code * Fix extra character in template * Unify PR & Issue tempaltes, fix format * Remove default subject * Add template tests * Fix template * Remove replaced function * Provide User as models.User for better consistency * Add docs * Fix doc inaccuracies, improve examples * Change mail footer to math AppName * Add test for mail subject/body template separation * Add support for code review comments * Update docs/content/doc/advanced/mail-templates-us.md Co-Authored-By: 6543 <24977596+6543@users.noreply.github.com>lunny/display_deleted_branch2
committed by
Lunny Xiao
13 changed files with 780 additions and 161 deletions
-
272docs/content/doc/advanced/mail-templates-us.md
-
33modules/templates/dynamic.go
-
130modules/templates/helper.go
-
55modules/templates/helper_test.go
-
23modules/templates/static.go
-
173services/mailer/mail.go
-
20services/mailer/mail_comment.go
-
36services/mailer/mail_issue.go
-
131services/mailer/mail_test.go
-
4templates/mail/issue/assigned.tmpl
-
16templates/mail/issue/comment.tmpl
-
31templates/mail/issue/default.tmpl
-
17templates/mail/issue/mention.tmpl
@ -0,0 +1,272 @@ |
|||
--- |
|||
date: "2019-10-23T17:00:00-03:00" |
|||
title: "Mail templates" |
|||
slug: "mail-templates" |
|||
weight: 45 |
|||
toc: true |
|||
draft: false |
|||
menu: |
|||
sidebar: |
|||
parent: "advanced" |
|||
name: "Mail templates" |
|||
weight: 45 |
|||
identifier: "mail-templates" |
|||
--- |
|||
|
|||
# Mail templates |
|||
|
|||
To craft the e-mail subject and contents for certain operations, Gitea can be customized by using templates. The templates |
|||
for these functions are located under the [`custom` directory](https://docs.gitea.io/en-us/customizing-gitea/). |
|||
Gitea has an internal template that serves as default in case there's no custom alternative. |
|||
|
|||
Custom templates are loaded when Gitea starts. Changes made to them are not recognized until Gitea is restarted again. |
|||
|
|||
## Mail notifications supporting templates |
|||
|
|||
Currently, the following notification events make use of templates: |
|||
|
|||
| Action name | Usage | |
|||
|---------------|--------------------------------------------------------------------------------------------------------------| |
|||
| `new` | A new issue or pull request was created. | |
|||
| `comment` | A new comment was created in an existing issue or pull request. | |
|||
| `close` | An issue or pull request was closed. | |
|||
| `reopen` | An issue or pull request was reopened. | |
|||
| `review` | The head comment of a review in a pull request. | |
|||
| `code` | A single comment on the code of a pull request. | |
|||
| `assigned` | Used was assigned to an issue or pull request. | |
|||
| `default` | Any action not included in the above categories, or when the corresponding category template is not present. | |
|||
|
|||
The path for the template of a particular message type is: |
|||
|
|||
``` |
|||
custom/templates/mail/{action type}/{action name}.tmpl |
|||
``` |
|||
|
|||
Where `{action type}` is one of `issue` or `pull` (for pull requests), and `{action name}` is one of the names listed above. |
|||
|
|||
For example, the specific template for a mail regarding a comment in a pull request is: |
|||
``` |
|||
custom/templates/mail/pull/comment.tmpl |
|||
``` |
|||
|
|||
However, creating templates for each and every action type/name combination is not required. |
|||
A fallback system is used to choose the appropriate template for an event. The _first existing_ |
|||
template on this list is used: |
|||
|
|||
* The specific template for the desired **action type** and **action name**. |
|||
* The template for action type `issue` and the desired **action name**. |
|||
* The template for the desired **action type**, action name `default`. |
|||
* The template for action type `issue`, action name `default`. |
|||
|
|||
The only mandatory template is action type `issue`, action name `default`, which is already embedded in Gitea |
|||
unless it's overridden by the user in the `custom` directory. |
|||
|
|||
## Template syntax |
|||
|
|||
Mail templates are UTF-8 encoded text files that need to follow one of the following formats: |
|||
|
|||
``` |
|||
Text and macros for the subject line |
|||
------------ |
|||
Text and macros for the mail body |
|||
``` |
|||
|
|||
or |
|||
|
|||
``` |
|||
Text and macros for the mail body |
|||
``` |
|||
|
|||
Specifying a _subject_ section is optional (and therefore also the dash line separator). When used, the separator between |
|||
_subject_ and _mail body_ templates requires at least three dashes; no other characters are allowed in the separator line. |
|||
|
|||
|
|||
_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and |
|||
are provided with a _metadata context_ assembled for each notification. The context contains the following elements: |
|||
|
|||
| Name | Type | Available | Usage | |
|||
|--------------------|----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
|||
| `.FallbackSubject` | string | Always | A default subject line. See Below. | |
|||
| `.Subject` | string | Only in body | The _subject_, once resolved. | |
|||
| `.Body` | string | Always | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_ | |
|||
| `.Link` | string | Always | The address of the originating issue, pull request or comment. | |
|||
| `.Issue` | models.Issue | Always | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is *not* a pull request. | |
|||
| `.Comment` | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment. | |
|||
| `.IsPull` | bool | Always | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`). | |
|||
| `.Repo` | string | Always | Name of the repository, including owner name (e.g. `mike/stuff`) | |
|||
| `.User` | models.User | Always | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used. | |
|||
| `.Doer` | models.User | Always | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used. | |
|||
| `.IsMention` | bool | Always | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository. | |
|||
| `.SubjectPrefix` | string | Always | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string. | |
|||
| `.ActionType` | string | Always | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected. | |
|||
| `.ActionName` | string | Always | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected. | |
|||
|
|||
All names are case sensitive. |
|||
|
|||
### The _subject_ part of the template |
|||
|
|||
The template engine used for the mail _subject_ is golang's [`text/template`](https://golang.org/pkg/text/template/). |
|||
Please refer to the linked documentation for details about its syntax. |
|||
|
|||
The _subject_ is built using the following steps: |
|||
|
|||
* A template is selected according to the type of notification and to what templates are present. |
|||
* The template is parsed and resolved (e.g. `{{.Issue.Index}}` is converted to the number of the issue |
|||
or pull request). |
|||
* All space-like characters (e.g. `TAB`, `LF`, etc.) are converted to normal spaces. |
|||
* All leading, trailing and redundant spaces are removed. |
|||
* The string is truncated to its first 256 runes (characters). |
|||
|
|||
If the end result is an empty string, **or** no subject template was available (i.e. the selected template |
|||
did not include a subject part), Gitea's **internal default** will be used. |
|||
|
|||
The internal default (fallback) subject is the equivalent of: |
|||
|
|||
``` |
|||
{{.SubjectPrefix}}[{{.Repo}}] {{.Issue.Title}} (#.Issue.Index) |
|||
``` |
|||
|
|||
For example: `Re: [mike/stuff] New color palette (#38)` |
|||
|
|||
Gitea's default subject can also be found in the template _metadata_ as `.FallbackSubject` from any of |
|||
the two templates, even if a valid subject template is present. |
|||
|
|||
### The _mail body_ part of the template |
|||
|
|||
The template engine used for the _mail body_ is golang's [`html/template`](https://golang.org/pkg/html/template/). |
|||
Please refer to the linked documentation for details about its syntax. |
|||
|
|||
The _mail body_ is parsed after the mail subject, so there is an additional _metadata_ field which is |
|||
the actual rendered subject, after all considerations. |
|||
|
|||
The expected result is HTML (including structural elements like`<html>`, `<body>`, etc.). Styling |
|||
through `<style>` blocks, `class` and `style` attributes is possible. However, `html/template` |
|||
does some [automatic escaping](https://golang.org/pkg/html/template/#hdr-Contexts) that should be considered. |
|||
|
|||
Attachments (such as images or external style sheets) are not supported. However, other templates can |
|||
be referenced too, for example to provide the contents of a `<style>` element in a centralized fashion. |
|||
The external template must be placed under `custom/mail` and referenced relative to that directory. |
|||
For example, `custom/mail/styles/base.tmpl` can be included using `{{template styles/base}}`. |
|||
|
|||
The mail is sent with `Content-Type: multipart/alternative`, so the body is sent in both HTML |
|||
and text formats. The latter is obtained by stripping the HTML markup. |
|||
|
|||
## Troubleshooting |
|||
|
|||
How a mail is rendered is directly dependent on the capabilities of the mail application. Many mail |
|||
clients don't even support HTML, so they show the text version included in the generated mail. |
|||
|
|||
If the template fails to render, it will be noticed only at the moment the mail is sent. |
|||
A default subject is used if the subject template fails, and whatever was rendered successfully |
|||
from the the _mail body_ is used, disregarding the rest. |
|||
|
|||
Please check [Gitea's logs](https://docs.gitea.io/en-us/logging-configuration/) for error messages in case of trouble. |
|||
|
|||
## Example |
|||
|
|||
`custom/templates/mail/issue/default.tmpl`: |
|||
|
|||
``` |
|||
[{{.Repo}}] @{{.Doer.Name}} |
|||
{{if eq .ActionName "new"}} |
|||
created |
|||
{{else if eq .ActionName "comment"}} |
|||
commented on |
|||
{{else if eq .ActionName "close"}} |
|||
closed |
|||
{{else if eq .ActionName "reopen"}} |
|||
reopened |
|||
{{else}} |
|||
updated |
|||
{{end}} |
|||
{{if eq .ActionType "issue"}} |
|||
issue |
|||
{{else}} |
|||
pull request |
|||
{{end}} |
|||
#{{.Issue.Index}}: {{.Issue.Title}} |
|||
------------ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<title>{{.Subject}}</title> |
|||
</head> |
|||
|
|||
<body> |
|||
{{if .IsMention}} |
|||
<p> |
|||
You are receiving this because @{{.Doer.Name}} mentioned you. |
|||
</p> |
|||
{{end}} |
|||
<p> |
|||
<p> |
|||
<a href="{{AppURL}}/{{.Doer.LowerName}}">@{{.Doer.Name}}</a> |
|||
{{if not (eq .Doer.FullName "")}} |
|||
({{.Doer.FullName}}) |
|||
{{end}} |
|||
{{if eq .ActionName "new"}} |
|||
created |
|||
{{else if eq .ActionName "close"}} |
|||
closed |
|||
{{else if eq .ActionName "reopen"}} |
|||
reopened |
|||
{{else}} |
|||
updated |
|||
{{end}} |
|||
<a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>. |
|||
</p> |
|||
{{if not (eq .Body "")}} |
|||
<h3>Message content:</h3> |
|||
<hr> |
|||
{{.Body | Str2html}} |
|||
{{end}} |
|||
</p> |
|||
<hr> |
|||
<p> |
|||
<a href="{{.Link}}">View it on Gitea</a>. |
|||
</p> |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
This template produces something along these lines: |
|||
|
|||
#### Subject |
|||
|
|||
> [mike/stuff] @rhonda commented on pull request #38: New color palette |
|||
|
|||
#### Mail body |
|||
|
|||
> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#). |
|||
> |
|||
> #### Message content: |
|||
> |
|||
> \__________________________________________________________________ |
|||
> |
|||
> Mike, I think we should tone down the blues a little. |
|||
> \__________________________________________________________________ |
|||
> |
|||
> [View it on Gitea](#). |
|||
|
|||
## Advanced |
|||
|
|||
The template system contains several functions that can be used to further process and format |
|||
the messages. Here's a list of some of them: |
|||
|
|||
| Name | Parameters | Available | Usage | |
|||
|----------------------|-------------|-----------|---------------------------------------------------------------------| |
|||
| `AppUrl` | - | Any | Gitea's URL | |
|||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" | |
|||
| `AppDomain` | - | Any | Gitea's host name | |
|||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed | |
|||
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. | |
|||
|
|||
These are _functions_, not metadata, so they have to be used: |
|||
|
|||
``` |
|||
Like this: {{Str2html "Escape<my>text"}} |
|||
Or this: {{"Escape<my>text" | Str2html}} |
|||
Or this: {{AppUrl}} |
|||
But not like this: {{.AppUrl}} |
|||
``` |
@ -0,0 +1,55 @@ |
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package templates |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
func TestSubjectBodySeparator(t *testing.T) { |
|||
test := func(input, subject, body string) { |
|||
loc := mailSubjectSplit.FindIndex([]byte(input)) |
|||
if loc == nil { |
|||
assert.Empty(t, subject, "no subject found, but one expected") |
|||
assert.Equal(t, body, input) |
|||
} else { |
|||
assert.Equal(t, subject, string(input[0:loc[0]])) |
|||
assert.Equal(t, body, string(input[loc[1]:])) |
|||
} |
|||
} |
|||
|
|||
test("Simple\n---------------\nCase", |
|||
"Simple\n", |
|||
"\nCase") |
|||
test("Only\nBody", |
|||
"", |
|||
"Only\nBody") |
|||
test("Minimal\n---\nseparator", |
|||
"Minimal\n", |
|||
"\nseparator") |
|||
test("False --- separator", |
|||
"", |
|||
"False --- separator") |
|||
test("False\n--- separator", |
|||
"", |
|||
"False\n--- separator") |
|||
test("False ---\nseparator", |
|||
"", |
|||
"False ---\nseparator") |
|||
test("With extra spaces\n----- \t \nBody", |
|||
"With extra spaces\n", |
|||
"\nBody") |
|||
test("With leading spaces\n -------\nOnly body", |
|||
"", |
|||
"With leading spaces\n -------\nOnly body") |
|||
test("Multiple\n---\n-------\n---\nSeparators", |
|||
"Multiple\n", |
|||
"\n-------\n---\nSeparators") |
|||
test("Insuficient\n--\nSeparators", |
|||
"", |
|||
"Insuficient\n--\nSeparators") |
|||
} |
@ -1,16 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<title>{{.Subject}}</title> |
|||
</head> |
|||
|
|||
<body> |
|||
<p>{{.Body | Str2html}}</p> |
|||
<p> |
|||
--- |
|||
<br> |
|||
<a href="{{.Link}}">View it on Gitea</a>. |
|||
</p> |
|||
</body> |
|||
</html> |
@ -0,0 +1,31 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<title>{{.Subject}}</title> |
|||
</head> |
|||
|
|||
<body> |
|||
{{if .IsMention}}<p>@{{.Doer.Name}} mentioned you:</p>{{end}} |
|||
<p> |
|||
{{- if eq .Body ""}} |
|||
{{if eq .ActionName "new"}} |
|||
Created #{{.Issue.Index}}. |
|||
{{else if eq .ActionName "close"}} |
|||
Closed #{{.Issue.Index}}. |
|||
{{else if eq .ActionName "reopen"}} |
|||
Reopened #{{.Issue.Index}}. |
|||
{{else}} |
|||
Empty comment on #{{.Issue.Index}}. |
|||
{{end}} |
|||
{{else}} |
|||
{{.Body | Str2html}} |
|||
{{end -}} |
|||
</p> |
|||
<p> |
|||
--- |
|||
<br> |
|||
<a href="{{.Link}}">View it on {{AppName}}</a>. |
|||
</p> |
|||
</body> |
|||
</html> |
@ -1,17 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<title>{{.Subject}}</title> |
|||
</head> |
|||
|
|||
<body> |
|||
<p>@{{.Doer.Name}} mentioned you:</p> |
|||
<p>{{.Body | Str2html}}</p> |
|||
<p> |
|||
--- |
|||
<br> |
|||
<a href="{{.Link}}">View it on Gitea</a>. |
|||
</p> |
|||
</body> |
|||
</html> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue