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>
142 lines
6.1 KiB
Plaintext
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>
|
|
}
|