import ejs from 'ejs'; import log4js from 'log4js'; import posthtml from 'posthtml'; import { documentTemplateModel } from '../database/schemas/management/documenttemplate.schema.js'; import '../database/schemas/management/documentsize.schema.js'; // Load configuration import { loadConfig } from '../config.js'; import fs from 'fs'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import timezone from 'dayjs/plugin/timezone.js'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { getObject } from '../database/database.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Extend plugins dayjs.extend(utc); dayjs.extend(timezone); const config = loadConfig(); const logger = log4js.getLogger('Template Manager'); logger.level = config.server.logLevel; let baseTemplate; let baseCSS; let previewTemplate; let contentPlaceholder; async function loadTemplates() { // Synchronously load files baseTemplate = fs.readFileSync( join(__dirname, '/assets/basetemplate.ejs'), 'utf8' ); baseCSS = fs.readFileSync(join(__dirname, '/assets/styles.css'), 'utf8'); previewTemplate = fs.readFileSync( join(__dirname, '/assets/previewtemplate.ejs'), 'utf8' ); contentPlaceholder = fs.readFileSync( join(__dirname, '/assets/contentplaceholder.ejs'), 'utf8' ); } loadTemplates(); function getNodeStyles(attributes) { var styles = ''; if (attributes?.padding) { styles += `padding: ${attributes.padding};`; } if (attributes?.width) { styles += `width: ${attributes.width};`; } if (attributes?.height) { styles += `height: ${attributes.height};`; } if (attributes?.gap && attributes?.vertical != 'true') { styles += `column-gap: ${attributes.gap};`; } if (attributes?.gap && attributes?.vertical == 'true') { styles += `row-gap: ${attributes.gap};`; } if (attributes?.justify) { styles += `justify-content: ${attributes.justify};`; } if (attributes?.align) { styles += `align-items: ${attributes.align};`; } if (attributes?.border) { styles += `border: ${attributes.border};`; } if (attributes?.borderRadius) { styles += `border-radius: ${attributes.borderRadius};`; } if (attributes?.vertical == 'true') { styles += `flex-direction: column;`; } if (attributes?.grow) { styles += `flex-grow: ${attributes.grow};`; } if (attributes?.shrink) { styles += `flex-shrink: ${attributes.shrink};`; } return styles; } async function transformCustomElements(content) { const result = await posthtml([ tree => tree.match({ tag: 'Title1' }, node => ({ ...node, tag: 'h1', attrs: { class: 'documentTitle' } })), tree => tree.match({ tag: 'Title2' }, node => ({ ...node, tag: 'h2', attrs: { class: 'documentTitle' } })), tree => tree.match({ tag: 'Title3' }, node => ({ ...node, tag: 'h3', attrs: { class: 'documentText' } })), tree => tree.match({ tag: 'Title4' }, node => ({ ...node, tag: 'h4', attrs: { class: 'documentText' } })), tree => tree.match({ tag: 'Text' }, node => ({ ...node, tag: 'p', attrs: { class: 'documentText' } })), tree => tree.match({ tag: 'Bold' }, node => ({ ...node, tag: 'strong', attrs: { style: 'font-weight: bold;', class: 'documentBoldText' } })), tree => tree.match({ tag: 'Barcode' }, node => { return { tag: 'svg', attrs: { class: 'documentBarcode', 'jsbarcode-width': node.attrs?.width, 'jsbarcode-height': node.attrs?.height, 'jsbarcode-value': node.content[0], 'jsbarcode-format': node.attrs.format } }; }), tree => tree.match({ tag: 'Container' }, node => ({ ...node, tag: 'div', attrs: { class: 'documentContainer', style: getNodeStyles(node.attrs) } })), tree => tree.match({ tag: 'Flex' }, node => { return { ...node, tag: 'div', attrs: { class: 'documentFlex', style: getNodeStyles(node.attrs) } }; }), tree => tree.match({ tag: 'Divider' }, node => { return { ...node, tag: 'hr', attrs: { class: 'documentDivider', style: getNodeStyles(node.attrs) } }; }), tree => tree.match({ tag: 'DateTime' }, node => { const dateTime = dayjs.utc(node.content[0]); return { content: [dateTime.format('YYYY-MM-DD hh:mm:ss')], tag: 'span', attrs: { class: 'documentDateTime', style: getNodeStyles(node.attrs) } }; }) ]).process(content); return result.html; } export class TemplateManager { /** * Previews an EJS template by rendering it with provided data * @param {string} templateString - The EJS template as a string * @param {Object} data - Data object to pass to the template * @param {Object} options - EJS rendering options * @returns {Promise} The rendered HTML string */ async renderTemplate(id, content, data = {}, scale, options = {}) { try { // Set default options for EJS rendering const defaultOptions = { async: true, ...options }; const documentTemplate = await getObject({ model: documentTemplateModel, id, populate: [ { path: 'documentSize' }, { path: 'parent', strictPopulate: false } ], cached: true }); if (documentTemplate == null) { return { error: 'Document template not found.' }; } const documentSize = documentTemplate.documentSize; var templateData = data; if (documentTemplate.global == true) { templateData = { content: contentPlaceholder }; } // Render the template const templateContent = await ejs.render( content, templateData, defaultOptions ); var templateWithParentContent; if (documentTemplate.parent != undefined) { templateWithParentContent = await ejs.render( documentTemplate.parent.content, { content: templateContent }, defaultOptions ); } else { templateWithParentContent = templateContent; } const templateHtml = await transformCustomElements( templateWithParentContent ); const previewHtml = await ejs.render( previewTemplate, { content: templateHtml }, defaultOptions ); const baseHtml = await ejs.render( baseTemplate, { content: previewHtml, width: `${documentSize.width}mm`, height: `${documentSize.height}mm`, scale: `${scale}`, baseCSS: baseCSS }, defaultOptions ); const previewObject = { html: baseHtml }; return previewObject; } catch (error) { logger.warn('Error whilst previewing template:', error.message); return { error: error.message }; } } /** * Validates if a template string is valid EJS syntax * @param {string} templateString - The EJS template as a string * @returns {boolean} True if template is valid, false otherwise */ validateTemplate(templateString) { try { // Try to compile the template to check for syntax errors ejs.compile(templateString); return true; } catch (error) { return false; } } }