outreach/lib/templateEngine.js
2025-08-15 01:03:38 -08:00

131 lines
4.4 KiB
JavaScript

const fs = require("fs").promises;
const path = require("path");
const Handlebars = require("handlebars");
const config = require("../config");
class TemplateEngine {
constructor() {
this.templatesDir = path.join(__dirname, "..", "templates");
this.compiledTemplates = new Map();
}
// Convert HTML to plain text by removing tags and formatting
htmlToText(html) {
return html
.replace(/<style[^>]*>.*?<\/style>/gis, "") // Remove style blocks
.replace(/<script[^>]*>.*?<\/script>/gis, "") // Remove script blocks
.replace(/<br\s*\/?>/gi, "\n") // Convert <br> to newlines
.replace(/<\/p>/gi, "\n\n") // Convert </p> to double newlines
.replace(/<\/div>/gi, "\n") // Convert </div> to newlines
.replace(/<\/h[1-6]>/gi, "\n\n") // Convert headings to double newlines
.replace(/<li[^>]*>/gi, "• ") // Convert <li> to bullet points
.replace(/<\/li>/gi, "\n") // End list items with newlines
.replace(/<[^>]*>/g, "") // Remove all other HTML tags
.replace(/&nbsp;/g, " ") // Convert &nbsp; to spaces
.replace(/&amp;/g, "&") // Convert &amp; to &
.replace(/&lt;/g, "<") // Convert &lt; to <
.replace(/&gt;/g, ">") // Convert &gt; to >
.replace(/&quot;/g, '"') // Convert &quot; to "
.replace(/&#39;/g, "'") // Convert &#39; to '
.replace(/\n\s*\n\s*\n/g, "\n\n") // Reduce multiple newlines to double
.replace(/^\s+|\s+$/gm, "") // Trim whitespace from lines
.trim();
}
async loadTemplate(templateName) {
const cacheKey = templateName;
if (this.compiledTemplates.has(cacheKey)) {
return this.compiledTemplates.get(cacheKey);
}
try {
const htmlPath = path.join(this.templatesDir, `${templateName}.html`);
const htmlContent = await fs.readFile(htmlPath, "utf-8");
// Automatically inject GIF if enabled
let finalHtmlContent = htmlContent;
if (config.gif.enabled && templateName === "outreach") {
finalHtmlContent = this.injectGifIntoHtml(htmlContent);
}
const htmlTemplate = Handlebars.compile(finalHtmlContent);
// Generate text version from HTML
const textTemplate = Handlebars.compile(
this.htmlToText(finalHtmlContent)
);
const compiledTemplate = {
html: htmlTemplate,
text: textTemplate,
};
this.compiledTemplates.set(cacheKey, compiledTemplate);
return compiledTemplate;
} catch (error) {
throw new Error(
`Failed to load template ${templateName}: ${error.message}`
);
}
}
// Inject GIF into HTML template automatically
injectGifIntoHtml(htmlContent) {
const gifHtml = `
{{#if gifUrl}}
<div style="text-align: center; margin: 20px 0;">
<img src="{{gifUrl}}" alt="{{gifAlt}}" style="max-width: 100%; height: auto; border-radius: 5px;" />
</div>
{{/if}}
`;
// Insert GIF after the header or at the beginning of content
if (htmlContent.includes('<div class="content">')) {
return htmlContent.replace(
'<div class="content">',
`<div class="content">${gifHtml}`
);
} else if (htmlContent.includes("<body>")) {
return htmlContent.replace("<body>", `<body>${gifHtml}`);
} else {
// Fallback: add at the beginning
return gifHtml + htmlContent;
}
}
async render(templateName, data) {
const template = await this.loadTemplate(templateName);
// Add default sender information from config
const defaultData = {
senderName: "John Smith",
senderTitle: "Business Development Manager",
senderCompany: "Legal Solutions Inc.",
fromEmail: config.email.user,
gifUrl: config.gif.enabled ? config.gif.url : null,
gifAlt: config.gif.enabled ? config.gif.alt : null,
...data,
};
return {
html: template.html(defaultData),
text: template.text(defaultData),
};
}
// Helper to format firm data for template
formatFirmData(firm) {
return {
firmName: firm.firmName || "your firm",
location: firm.location,
website: firm.website,
email: firm.contactEmail || firm.email,
greeting: firm.name || "Legal Professional",
// Additional fields can be mapped here
};
}
}
module.exports = new TemplateEngine();