150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
package email
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// TemplateData carries the context required to render any notification email.
|
|
// Fields are optional per template; callers should populate what they have.
|
|
type TemplateData struct {
|
|
ActorName string
|
|
ActorEmail string
|
|
DocumentPath string
|
|
DocumentTitle string
|
|
CommentBody string
|
|
MentionText string
|
|
ConflictDesc string
|
|
ViewURL string
|
|
UnsubURL string
|
|
InstanceName string
|
|
}
|
|
|
|
// Render returns a plain-text subject and body for the given notification
|
|
// type. Subjects are short and prefixed with the document path so email
|
|
// clients thread per-document.
|
|
func Render(kind string, data TemplateData) (subject, body string, err error) {
|
|
if data.InstanceName == "" {
|
|
data.InstanceName = "Cairnquire"
|
|
}
|
|
switch kind {
|
|
case "file_changed":
|
|
return renderFileChanged(data)
|
|
case "comment":
|
|
return renderComment(data)
|
|
case "mention":
|
|
return renderMention(data)
|
|
case "conflict":
|
|
return renderConflict(data)
|
|
default:
|
|
return "", "", fmt.Errorf("unknown email template kind %q", kind)
|
|
}
|
|
}
|
|
|
|
func subjectPrefix(data TemplateData) string {
|
|
if data.DocumentPath != "" {
|
|
return "[" + data.DocumentPath + "]"
|
|
}
|
|
return "[" + data.InstanceName + "]"
|
|
}
|
|
|
|
func footer(data TemplateData) string {
|
|
var b strings.Builder
|
|
b.WriteString("\n--\n")
|
|
if data.ViewURL != "" {
|
|
fmt.Fprintf(&b, "View online: %s\n", data.ViewURL)
|
|
}
|
|
if data.UnsubURL != "" {
|
|
fmt.Fprintf(&b, "Unsubscribe: %s\n", data.UnsubURL)
|
|
}
|
|
fmt.Fprintf(&b, "You receive this because you watch this document on %s.\n", data.InstanceName)
|
|
return b.String()
|
|
}
|
|
|
|
func renderFileChanged(data TemplateData) (string, string, error) {
|
|
title := data.DocumentTitle
|
|
if title == "" {
|
|
title = data.DocumentPath
|
|
}
|
|
actor := data.ActorName
|
|
if actor == "" {
|
|
actor = "Someone"
|
|
}
|
|
subject := fmt.Sprintf("%s Updated by %s", subjectPrefix(data), actor)
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "%s updated %q at %s UTC.\n", actor, title, time.Now().UTC().Format("2006-01-02 15:04"))
|
|
if data.DocumentPath != "" {
|
|
fmt.Fprintf(&b, "Path: %s\n", data.DocumentPath)
|
|
}
|
|
b.WriteString("\nView the changes online to see what is new.\n")
|
|
b.WriteString(footer(data))
|
|
return subject, b.String(), nil
|
|
}
|
|
|
|
func renderComment(data TemplateData) (string, string, error) {
|
|
actor := data.ActorName
|
|
if actor == "" {
|
|
actor = "Someone"
|
|
}
|
|
subject := fmt.Sprintf("%s Comment from %s", subjectPrefix(data), actor)
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "%s commented on %q:\n", actor, docLabel(data))
|
|
b.WriteString("\n")
|
|
if data.CommentBody != "" {
|
|
// Quote the comment body, line by line, as plain text.
|
|
for _, line := range strings.Split(data.CommentBody, "\n") {
|
|
fmt.Fprintf(&b, "> %s\n", line)
|
|
}
|
|
} else {
|
|
b.WriteString("> (no body)\n")
|
|
}
|
|
b.WriteString("\nReply to this email to respond.\n")
|
|
b.WriteString(footer(data))
|
|
return subject, b.String(), nil
|
|
}
|
|
|
|
func renderMention(data TemplateData) (string, string, error) {
|
|
actor := data.ActorName
|
|
if actor == "" {
|
|
actor = "Someone"
|
|
}
|
|
subject := fmt.Sprintf("%s Mention from %s", subjectPrefix(data), actor)
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "%s mentioned you on %q.\n", actor, docLabel(data))
|
|
if data.MentionText != "" {
|
|
b.WriteString("\n")
|
|
for _, line := range strings.Split(data.MentionText, "\n") {
|
|
fmt.Fprintf(&b, "> %s\n", line)
|
|
}
|
|
}
|
|
b.WriteString(footer(data))
|
|
return subject, b.String(), nil
|
|
}
|
|
|
|
func renderConflict(data TemplateData) (string, string, error) {
|
|
subject := fmt.Sprintf("%s Sync conflict", subjectPrefix(data))
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "A sync conflict was detected on %q.\n", docLabel(data))
|
|
if data.ConflictDesc != "" {
|
|
fmt.Fprintf(&b, "\nDetails: %s\n", data.ConflictDesc)
|
|
}
|
|
b.WriteString("\nOpen the document to review and resolve the conflict.\n")
|
|
b.WriteString(footer(data))
|
|
return subject, b.String(), nil
|
|
}
|
|
|
|
func docLabel(data TemplateData) string {
|
|
if data.DocumentTitle != "" {
|
|
return data.DocumentTitle
|
|
}
|
|
if data.DocumentPath != "" {
|
|
return data.DocumentPath
|
|
}
|
|
return "a document"
|
|
}
|