'use strict' const BRAND = 'RunForge' /** 页脚固定说明(分段展示,避免单行过长) */ const FOOTER_DISCLAIMER = { label: '本邮件由系统自动发送,请勿直接回复本邮件。', helpPrefix: '如需帮助,请访问 RunForge 网站提交工单,或发送邮件至', helpEmail: 'service@xxoo365.top' } /** 合并后的纯文本说明(供外部引用) */ const AUTO_REPLY_NOTICE = [ FOOTER_DISCLAIMER.label, `${FOOTER_DISCLAIMER.helpPrefix} ${FOOTER_DISCLAIMER.helpEmail}` ].join(' ') /** * 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' } /** * 页脚:可选补充说明 + 固定分段提示(左对齐,避免长句居中拥挤) * @param {string} footerExtra */ function buildFooterHtml(footerExtra) { const ctx = footerExtra ? `` : '' const mail = escapeHtml(FOOTER_DISCLAIMER.helpEmail) return `${ctx} ${escapeHtml(FOOTER_DISCLAIMER.label)} ` } /** * 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 footerHtml = buildFooterHtml(footerExtra) return ` ${escapeHtml(pageTitle)}

${BRAND} · 系统通知

${escapeHtml(headline)}${headlineSuffixHtml}

${bodyHtml}
` } /** Paragraph with pre-escaped or trusted HTML */ function p(html, options = {}) { const cls = options.last ? 'body-text last' : 'body-text' return `

${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 `
${innerHtml}
` } /** * @param {string} text — escaped or plain (will escape) * @param {'warning'|'danger'|'neutral'} [kind] */ function notice(text, kind = 'warning') { const cls = kind === 'danger' ? 'notice danger' : kind === 'neutral' ? 'notice neutral' : 'notice' return `
${escapeHtml(text)}
` } function codeBox(code) { return `
${escapeHtml(code)}
` } module.exports = { BRAND, AUTO_REPLY_NOTICE, escapeHtml, escapeNl2br, renderEmail, p, pEsc, kv, panel, notice, codeBox }