feat(scripts): add mustache render-template via Responses pages
Add HTML template kind to Responses with a seeded system email-default template, render-template.js using Mustache, and html body support in send-email. Templates are editable but system pages cannot be deleted. Co-authored-by: Nasyarobby Putra <nasyarobby@gmail.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import Mustache from "mustache";
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
function htmlToText(html) {
|
||||
return html
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
||||
.replace(/<br\s*\/?>/gi, "\n")
|
||||
.replace(/<\/(p|div|h[1-6]|li|tr)>/gi, "\n")
|
||||
.replace(/<li[^>]*>/gi, "- ")
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} partialsConfig
|
||||
*/
|
||||
async function loadPartials(partialsConfig) {
|
||||
/** @type {Record<string, string>} */
|
||||
const partials = {};
|
||||
if (partialsConfig == null) return partials;
|
||||
if (typeof partialsConfig !== "object" || Array.isArray(partialsConfig)) {
|
||||
throw new Error("config.partials must be an object");
|
||||
}
|
||||
|
||||
for (const [partialName, pageName] of Object.entries(
|
||||
/** @type {Record<string, unknown>} */ (partialsConfig),
|
||||
)) {
|
||||
if (typeof pageName !== "string" || pageName.length === 0) {
|
||||
throw new Error(`config.partials.${partialName} must be a template page name`);
|
||||
}
|
||||
partials[partialName] = await $responses.getTemplate(pageName);
|
||||
}
|
||||
return partials;
|
||||
}
|
||||
|
||||
async function renderTemplate(ctx) {
|
||||
const templateName = ctx.config?.template;
|
||||
if (typeof templateName !== "string" || templateName.length === 0) {
|
||||
throw new Error("config.template is required");
|
||||
}
|
||||
|
||||
const vars = ctx.data?.vars ?? ctx.data;
|
||||
if (vars == null || typeof vars !== "object" || Array.isArray(vars)) {
|
||||
throw new Error("data.vars must be an object");
|
||||
}
|
||||
|
||||
const template = await $responses.getTemplate(templateName);
|
||||
const partials = await loadPartials(ctx.config?.partials);
|
||||
|
||||
log.info(
|
||||
{
|
||||
template: templateName,
|
||||
partials: Object.keys(partials),
|
||||
varKeys: Object.keys(vars),
|
||||
},
|
||||
"render-template: rendering mustache template",
|
||||
);
|
||||
|
||||
const html = Mustache.render(template, vars, partials);
|
||||
const text = htmlToText(html);
|
||||
|
||||
return {
|
||||
html,
|
||||
text,
|
||||
template: templateName,
|
||||
};
|
||||
}
|
||||
|
||||
renderTemplate.meta = {
|
||||
description:
|
||||
"Render an HTML template from Responses (kind: template) with Mustache and return html + plain-text fallback",
|
||||
config: {
|
||||
template: {
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "Responses page name with kind=template",
|
||||
},
|
||||
partials: {
|
||||
type: "object",
|
||||
required: false,
|
||||
description: "Map of partial name to template page name (e.g. { item: email-item })",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
vars: {
|
||||
type: "object",
|
||||
required: true,
|
||||
description: "Mustache view data (title, items, etc.)",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
html: { type: "string", description: "Rendered HTML" },
|
||||
text: { type: "string", description: "Plain-text fallback stripped from HTML" },
|
||||
template: { type: "string", description: "Template page name used" },
|
||||
},
|
||||
example: {
|
||||
data: {
|
||||
vars: {
|
||||
title: "Daily digest",
|
||||
message: "Latest items from your feed.",
|
||||
items: [
|
||||
{
|
||||
title: "Example post",
|
||||
link: "https://example.com/post",
|
||||
summary: "A short summary.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
config: {
|
||||
template: "email-default",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default renderTemplate;
|
||||
Reference in New Issue
Block a user