themes-terminal/email_wrapper.templ
Alex Dunmow 0a9b177f7c initial: theme plugin terminal
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/terminal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 14:11:44 +08:00

142 lines
6.1 KiB
Plaintext

package main
import (
"bytes"
"context"
"fmt"
"strings"
"git.dev.alexdunmow.com/block/core/templates"
)
const terminalEmailRule = "================================================================================"
const terminalSigDelim = "-- \n"
// TerminalEmailWrapper produces a plain-text-first, monospace, 80-col centred
// email body. The first part is the plain text fallback; the HTML version
// follows it as a comment-delimited block. Inline styles only — no <style>
// tags, no external stylesheets.
//
// Many CMS pipelines accept a single string body and MIME-multipart it
// themselves; we emit the plain text up front via an HTML comment block so
// the assembled message contains both representations in a deterministic
// order regardless of the host's MIME assembly.
func TerminalEmailWrapper(body string, emailCtx templates.EmailContext) string {
plain := buildTerminalPlainText(body, emailCtx)
var buf bytes.Buffer
terminalEmailHTML(emailCtx, body, plain).Render(context.Background(), &buf)
return buf.String()
}
// buildTerminalPlainText assembles the text/plain payload: ASCII rule above
// and below, the body, then a `-- \n` signature delimiter, then site info
// and unsubscribe URL.
//
// The body is passed through as-is. Senders that want pristine plain text
// should compose the text version themselves; we do not attempt to strip
// HTML markup here.
func buildTerminalPlainText(body string, emailCtx templates.EmailContext) string {
var b strings.Builder
b.WriteString(terminalEmailRule + "\n")
b.WriteString(strings.TrimSpace(body) + "\n")
b.WriteString(terminalEmailRule + "\n")
b.WriteString(terminalSigDelim)
if emailCtx.SiteSettings.SiteName != "" {
b.WriteString(emailCtx.SiteSettings.SiteName + "\n")
}
if emailCtx.SiteSettings.SiteURL != "" {
b.WriteString(emailCtx.SiteSettings.SiteURL + "\n")
}
if emailCtx.UnsubscribeURL != "" {
b.WriteString("unsubscribe: " + emailCtx.UnsubscribeURL + "\n")
}
return b.String()
}
func terminalEmailBgColor(c templates.EmailContext) string {
if c.Colors.Background != "" {
return c.Colors.Background
}
return "#0a0a0a"
}
func terminalEmailFgColor(c templates.EmailContext) string {
if c.Colors.Foreground != "" {
return c.Colors.Foreground
}
return "#00ff00"
}
func terminalEmailBorderColor(c templates.EmailContext) string {
if c.Colors.Border != "" {
return c.Colors.Border
}
return "#1a4a1a"
}
func terminalEmailMutedFgColor(c templates.EmailContext) string {
if c.Colors.MutedForeground != "" {
return c.Colors.MutedForeground
}
return "#5a8c5a"
}
// terminalEmailHTML emits an HTML document with inline styles only. The
// plain text representation is included in an HTML comment so callers and
// hand-inspection both see both parts.
templ terminalEmailHTML(emailCtx templates.EmailContext, body, plain string) {
<!DOCTYPE html>
@templ.Raw(fmt.Sprintf("<!-- text/plain alternative\n%s\n-->\n", plain))
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
</head>
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;", terminalEmailBgColor(emailCtx)) }>
if emailCtx.PreviewText != "" {
<div style="display:none; max-height:0; overflow:hidden;">{ emailCtx.PreviewText }</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("background-color: %s;", terminalEmailBgColor(emailCtx)) }>
<tr>
<td align="center" style={ fmt.Sprintf("padding: 32px 12px; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s;", terminalEmailFgColor(emailCtx)) }>
<table role="presentation" width="640" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 640px; width: 100%%; border: 1px solid %s; background-color: %s;", terminalEmailBorderColor(emailCtx), terminalEmailBgColor(emailCtx)) }>
<tr>
<td style={ fmt.Sprintf("padding: 16px 24px 0; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s; letter-spacing: 0.05em;", terminalEmailFgColor(emailCtx)) }>
{ terminalEmailRule }
</td>
</tr>
<tr>
<td style={ fmt.Sprintf("padding: 16px 24px; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s; font-size: 14px; line-height: 1.6;", terminalEmailFgColor(emailCtx)) }>
@templ.Raw(body)
</td>
</tr>
<tr>
<td style={ fmt.Sprintf("padding: 0 24px; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s; letter-spacing: 0.05em;", terminalEmailFgColor(emailCtx)) }>
{ terminalEmailRule }
</td>
</tr>
<tr>
<td style={ fmt.Sprintf("padding: 16px 24px; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s; font-size: 12px;", terminalEmailMutedFgColor(emailCtx)) }>
<pre style={ fmt.Sprintf("margin: 0; font-family: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: %s; white-space: pre-wrap;", terminalEmailMutedFgColor(emailCtx)) }>{ terminalSigDelim }</pre>
if emailCtx.SiteSettings.SiteName != "" {
<div>{ emailCtx.SiteSettings.SiteName }</div>
}
if emailCtx.SiteSettings.SiteURL != "" {
<div><a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", terminalEmailFgColor(emailCtx)) }>{ emailCtx.SiteSettings.SiteURL }</a></div>
}
if emailCtx.UnsubscribeURL != "" {
<div><a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", terminalEmailMutedFgColor(emailCtx)) }>unsubscribe</a></div>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}