'use strict'
const BRAND = 'RunForge'
/** 所有经 renderEmail 输出的邮件页脚均附带此说明 */
const AUTO_REPLY_NOTICE = '本邮件由系统自动发送,请勿直接回复'
/**
* Escape text for safe insertion into HTML email bodies.
* @param {unknown} text
* @returns {string}
*/
function escapeHtml(text) {
if (text === undefined || text === null) return ''
return String(text)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
/**
* Preserve line breaks as
after escaping (for plaintext工单内容等).
* @param {unknown} text
* @returns {string}
*/
function escapeNl2br(text) {
return escapeHtml(text).replace(/\r\n|\r|\n/g, '
')
}
const ACCENTS = {
default: '#1e40af',
success: '#047857',
warning: '#b45309',
danger: '#b91c1c'
}
/**
* Wrap transactional email body in shared layout (single stylesheet + structure).
* @param {object} opts
* @param {string} opts.pageTitle
* @param {string} opts.headline
* @param {'default'|'success'|'warning'|'danger'} [opts.variant]
* @param {string} opts.bodyHtml — trusted HTML fragments assembled by callers (dynamic strings must be escaped first).
* @param {string} [opts.footerExtra] — shortPlaintext shown above copyright (will be escaped).
* @param {string} [opts.headlineSuffixHtml] — small trusted HTML after headline inside H1 (e.g. badge span).
*/
function renderEmail(opts) {
const {
pageTitle,
headline,
variant = 'default',
bodyHtml,
footerExtra = '',
headlineSuffixHtml = ''
} = opts
const accent = ACCENTS[variant] || ACCENTS.default
const year = new Date().getFullYear()
const footerNote = footerExtra
? `
${escapeHtml(footerExtra)}
` : '' const autoReplyLine = `${escapeHtml(AUTO_REPLY_NOTICE)}
` return `${BRAND} · 系统通知
${html}
` } /** Paragraph; escapes plain text */ function pEsc(text, options = {}) { return p(escapeHtml(text), options) } /** Key-value row inside .panel */ function kv(label, value) { const v = value === undefined || value === null || value === '' ? '—' : escapeHtml(value) return `${escapeHtml(label)}: ${v}
` } function panel(innerHtml) { return `