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>/gis, "") // Remove style blocks .replace(/]*>.*?<\/script>/gis, "") // Remove script blocks .replace(//gi, "\n") // Convert
to newlines .replace(/<\/p>/gi, "\n\n") // Convert

to double newlines .replace(/<\/div>/gi, "\n") // Convert to newlines .replace(/<\/h[1-6]>/gi, "\n\n") // Convert headings to double newlines .replace(/]*>/gi, "• ") // Convert
  • to bullet points .replace(/<\/li>/gi, "\n") // End list items with newlines .replace(/<[^>]*>/g, "") // Remove all other HTML tags .replace(/ /g, " ") // Convert   to spaces .replace(/&/g, "&") // Convert & to & .replace(/</g, "<") // Convert < to < .replace(/>/g, ">") // Convert > to > .replace(/"/g, '"') // Convert " to " .replace(/'/g, "'") // Convert ' 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}}
    {{gifAlt}}
    {{/if}} `; // Insert GIF after the header or at the beginning of content if (htmlContent.includes('
    ')) { return htmlContent.replace( '
    ', `
    ${gifHtml}` ); } else if (htmlContent.includes("")) { return htmlContent.replace("", `${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();