Skip to content
quarry Artifact [ce7d8e86a7]
Login

Artifact [ce7d8e86a7]

Artifact ce7d8e86a7a138e17eaa271c36344a173ecc8f2885bdcdcb86561d52574ec450:


package markup

import (
	"bytes"
	"html/template"
)

// EmailDoc is everything the quarry email card shows. Body is trusted:
// it is EmailHTML's output, or markup the caller wrote and escaped.
type EmailDoc struct {
	Title string
	// Mark and Name are the header's left: what the email came from. Both
	// empty is quarry's own (account mail). Surface is the header's
	// muted right.
	Mark    template.HTML
	Name    string
	Surface string
	Context template.HTML // who did what, where
	Meta    string        // muted line under Context: time and reference
	Body    template.HTML

	Open    string // the button's label and target; none without both
	OpenURL string
	ReplyIn string // what a reply does ("answer in chat"); "" for no hint

	Why         string
	SettingsURL string // "" for mail that has no preference
	HomeURL     string
	HomeName    string

	// LogoURL is the forge's own logo, served publicly as a PNG. It is
	// quarry's mark in the footer, and in the header of mail that has no
	// other source (account mail).
	LogoURL string
	// WordmarkURL is the application's QUARRY wordmark as a PNG (120x29
	// shown): the header's name for mail about the application, and the
	// footer's signature on every card. Empty falls back to the word.
	WordmarkURL string

	// Brand replaces Name in the header with the wordmark image.
	Brand template.HTML
}

const mailFont = "'IBM Plex Sans',-apple-system,'Segoe UI',Helvetica,Arial,sans-serif"

var emailDocTmpl = template.Must(template.New("email").Parse(`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;600;700&family=IBM+Plex+Mono&display=swap">
<title>{{.Title}}</title>
<style>
  @media (prefers-color-scheme: dark) {
    .q-page { background:#141617 !important; }
    .q-card { background:#1d2021 !important; border-color:#504945 !important; }
    .q-text, .q-text td, .q-text li, .q-text p { color:#f0ece4 !important; }
    .q-muted { color:#bdb6ab !important; }
    .q-rule { border-color:#504945 !important; }
    .q-a { color:#d99058 !important; }
    .q-code { background:#32302f !important; color:#f0ece4 !important; }
    .q-th { background:#2a2826 !important; color:#f0ece4 !important; border-color:#504945 !important; }
    .q-tde { background:#232526 !important; }
    .q-tbl { border-color:#504945 !important; }
    .q-quote { border-color:#665c54 !important; color:#bdb6ab !important; }
    .q-btn { background:#d99058 !important; color:#1d2021 !important; }
    .q-mark { color:#d99058 !important; }
    .q-idnbg { background:#32302f !important; }
  }
</style>
</head>
<body style="margin:0;padding:0;">
<table role="presentation" class="q-page" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:24px 12px;">
<tr><td align="center">
  <table role="presentation" class="q-card" width="100%" cellpadding="0" cellspacing="0" style="max-width:600px;background:#fffcf5;border:1px solid #d5c4a1;border-radius:10px;">
    <tr><td class="q-rule" style="padding:18px 24px 14px;border-bottom:1px solid #e6dcc0;font-family:` + mailFont + `;">
      <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
        <td><table role="presentation" cellpadding="0" cellspacing="0"><tr>
          <td style="vertical-align:middle;padding-right:8px;line-height:0;font-size:0;">{{.Mark}}</td>
          <td class="q-text" style="vertical-align:middle;font-size:16px;font-weight:700;letter-spacing:-0.01em;color:#282828;">{{if .Brand}}{{.Brand}}{{else}}{{.Name}}{{end}}</td>
        </tr></table></td>
        <td align="right" class="q-muted" style="font-size:12px;color:#665c54;">{{.Surface}}</td>
      </tr></table>
    </td></tr>
    <tr><td style="padding:18px 24px 4px;font-family:` + mailFont + `;">
      <div class="q-text" style="font-size:15px;color:#282828;">{{.Context}}</div>{{if .Meta}}
      <div class="q-muted" style="font-size:12px;color:#665c54;margin-top:2px;">{{.Meta}}</div>{{end}}
    </td></tr>
{{if .Body}}    <tr><td class="q-text" style="padding:12px 24px 8px;font-family:` + mailFont + `;font-size:15px;line-height:1.55;color:#282828;">
{{.Body}}
    </td></tr>{{end}}{{if and .Open .OpenURL}}
    <tr><td style="padding:6px 24px 22px;font-family:` + mailFont + `;">
      <a class="q-btn" href="{{.OpenURL}}" style="display:inline-block;background:#a04412;color:#fffcf5;text-decoration:none;font-weight:600;font-size:14px;padding:9px 16px;border-radius:6px;">{{.Open}}</a>{{if .ReplyIn}}
      <span class="q-muted" style="font-size:13px;color:#665c54;">&nbsp; or reply to this email to {{.ReplyIn}}</span>{{end}}
    </td></tr>{{end}}
    <tr><td class="q-rule q-muted" style="padding:14px 24px 18px;border-top:1px solid #e6dcc0;font-family:` + mailFont + `;font-size:12px;line-height:1.5;color:#665c54;">
      {{.Why}}{{if .SettingsURL}}
      <a class="q-a" href="{{.SettingsURL}}" style="color:#a04412;">Notification settings</a>{{end}}
      <table role="presentation" cellpadding="0" cellspacing="0" style="margin-top:8px;"><tr>{{if .LogoURL}}
        <td style="vertical-align:middle;padding-right:6px;line-height:0;font-size:0;"><img src="{{.LogoURL}}" alt="" width="16" height="16" style="display:block;width:16px;height:16px;border:0;border-radius:4px;"></td>{{end}}
        <td style="vertical-align:middle;padding-right:5px;line-height:0;font-size:0;">{{if .WordmarkURL}}<img src="{{.WordmarkURL}}" alt="QUARRY" width="50" height="12" style="display:block;width:50px;height:12px;border:0;">{{else}}<span class="q-muted" style="font-size:11px;font-weight:700;letter-spacing:0.08em;color:#665c54;">QUARRY</span>{{end}}</td>{{if .HomeURL}}
        <td class="q-muted" style="vertical-align:middle;font-size:12px;line-height:1.5;color:#665c54;">&middot;
      <a class="q-a" href="{{.HomeURL}}" style="color:#a04412;">{{.HomeName}}</a></td>{{end}}
      </tr></table>
    </td></tr>
  </table>
</td></tr>
</table>
</body>
</html>
`))

// EmailDocument renders the card around d.Body. ok is false when the
// template fails or the document exceeds EmailHTMLMax; the caller then
// sends text only.
func EmailDocument(d EmailDoc) (string, bool) {
	if d.Mark == "" && d.Name == "" {
		// The application, not a repository: its logo and wordmark.
		d.Mark = `<span class="q-mark" style="font-size:16px;line-height:1;color:#a04412;">&#9670;</span>`
		if d.LogoURL != "" {
			d.Mark = AvatarMark(d.LogoURL, "")
		}
		d.Brand = template.HTML(`<span style="font-size:14px;font-weight:700;letter-spacing:0.08em;">QUARRY</span>`)
		if d.WordmarkURL != "" {
			d.Brand = WordmarkImg(d.WordmarkURL, 120, 29)
		}
	}
	var buf bytes.Buffer
	if err := emailDocTmpl.Execute(&buf, d); err != nil || buf.Len() > EmailHTMLMax {
		return "", false
	}
	return buf.String(), true
}