import mjml2html from 'mjml'
function getPath(data, path) {
return String(path || '').split('.').filter(Boolean).reduce((value, key) => value?.[key], data)
}
export function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''')
}
export function interpolate(input, data) {
return String(input ?? '').replace(/{{\s*([\w.[\]-]+)\s*}}/g, (_, path) => {
const normalized = path.replace(/\[(\d+)\]/g, '.$1')
const value = getPath(data, normalized)
return value == null ? '' : String(value)
})
}
function safeUrl(input, data) {
const url = interpolate(input, data).trim()
return /^(https?:|mailto:)/i.test(url) ? url : '#'
}
function renderTable(block, data) {
const rows = getPath(data, block.props?.rowsPath) || []
const columns = Array.isArray(block.props?.columns) ? block.props.columns : []
if (!Array.isArray(rows)) return ''
return `
${columns.map(c => `| ${escapeHtml(c.label)} | `).join('')}
${rows.map(row => `${columns.map(c => `| ${escapeHtml(getPath(row, c.key))} | `).join('')}
`).join('')}
`
}
function blockHtml(block, data) {
const p = block.props || {}
switch (block.type) {
case 'heading':
return `${escapeHtml(interpolate(p.text, data))}
`
case 'text':
return `${escapeHtml(interpolate(p.text, data))}
`
case 'kpi':
return `${escapeHtml(interpolate(p.label, data))}${escapeHtml(interpolate(p.value, data))}
`
case 'button':
return `${escapeHtml(interpolate(p.text, data))}
`
case 'image':
return `
`
case 'divider':
return '
'
case 'table':
return renderTable(block, data)
default:
return ''
}
}
export function renderReportHtml(template, data = {}) {
const blocks = template?.blocks || []
return `
${blocks.map(block => blockHtml(block, data)).join('\n')}`
}
function emailBlock(block, data) {
const p = block.props || {}
switch (block.type) {
case 'heading':
return `${escapeHtml(interpolate(p.text, data))}`
case 'text':
return `${escapeHtml(interpolate(p.text, data))}`
case 'kpi':
return `${escapeHtml(interpolate(p.label, data))}${escapeHtml(interpolate(p.value, data))}`
case 'button':
return `${escapeHtml(interpolate(p.text, data))}`
case 'image':
return ``
case 'divider':
return ''
default:
return 'This block is available in report output only.'
}
}
export function renderEmailHtml(template, data = {}) {
const body = (template?.blocks || []).map(block => emailBlock(block, data)).join('\n')
const mjml = `${body}`
const result = mjml2html(mjml, { validationLevel: 'soft' })
if (result.errors?.length) console.warn('MJML warnings', result.errors)
return result.html
}