UNPKG

n8n-nodes-json-html-to-pdf

Version:

Professional n8n node to convert JSON data or HTML content to PDF documents with Handlebars template support

461 lines 20.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonHtmlToPdf = void 0; const n8n_workflow_1 = require("n8n-workflow"); const puppeteer_1 = __importDefault(require("puppeteer")); const handlebars_1 = __importDefault(require("handlebars")); class JsonHtmlToPdf { constructor() { this.description = { displayName: 'JSON/HTML to PDF', name: 'jsonHtmlToPdf', icon: 'file:jsonHtmlToPdf.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["inputType"]}}', description: 'Convert JSON data or HTML content to PDF documents', defaults: { name: 'JSON/HTML to PDF', }, inputs: ["main"], outputs: ["main"], properties: [ { displayName: 'Input Type', name: 'inputType', type: 'options', options: [ { name: 'JSON with Template', value: 'jsonTemplate', description: 'Use JSON data with a Handlebars template', }, { name: 'Raw HTML', value: 'html', description: 'Use raw HTML directly', }, ], default: 'jsonTemplate', description: 'Choose the input format', }, { displayName: 'JSON Data', name: 'jsonData', type: 'json', default: '{}', displayOptions: { show: { inputType: ['jsonTemplate'], }, }, description: 'JSON data to be used with the template', }, { displayName: 'HTML Template', name: 'htmlTemplate', type: 'string', typeOptions: { alwaysOpenEditWindow: true, editor: 'htmlEditor', }, default: `<!DOCTYPE html> <html> <head> <style> body { font-family: Arial, sans-serif; padding: 20px; } h1 { color: #333; } .content { margin: 20px 0; } </style> </head> <body> <h1>{{title}}</h1> <div class="content"> {{#each items}} <p>{{this}}</p> {{/each}} </div> <footer>Generated on {{date}}</footer> </body> </html>`, displayOptions: { show: { inputType: ['jsonTemplate'], }, }, description: 'Handlebars HTML template', }, { displayName: 'HTML Content', name: 'htmlContent', type: 'string', typeOptions: { alwaysOpenEditWindow: true, editor: 'htmlEditor', }, default: '', displayOptions: { show: { inputType: ['html'], }, }, description: 'Raw HTML content to convert to PDF', }, { displayName: 'Output Format', name: 'outputFormat', type: 'options', options: [ { name: 'Binary', value: 'binary', description: 'Output as binary data', }, { name: 'Base64', value: 'base64', description: 'Output as base64 string', }, ], default: 'binary', description: 'Choose the output format', }, { displayName: 'Property Name', name: 'binaryPropertyName', type: 'string', default: 'data', displayOptions: { show: { outputFormat: ['binary'], }, }, description: 'Name of the binary property to add', }, { displayName: 'Filename', name: 'fileName', type: 'string', default: 'document.pdf', displayOptions: { show: { outputFormat: ['binary'], }, }, description: 'Filename for the generated PDF', }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add Option', default: {}, options: [ { displayName: 'Page Format', name: 'format', type: 'options', options: [ { name: 'A3', value: 'A3' }, { name: 'A4', value: 'A4' }, { name: 'A5', value: 'A5' }, { name: 'Legal', value: 'Legal' }, { name: 'Letter', value: 'Letter' }, { name: 'Tabloid', value: 'Tabloid' }, ], default: 'A4', description: 'Paper format for the PDF', }, { displayName: 'Landscape', name: 'landscape', type: 'boolean', default: false, description: 'Whether to use landscape orientation', }, { displayName: 'Scale', name: 'scale', type: 'number', typeOptions: { minValue: 0.1, maxValue: 2, numberStepSize: 0.1, }, default: 1, description: 'Scale of the webpage rendering', }, { displayName: 'Display Header Footer', name: 'displayHeaderFooter', type: 'boolean', default: false, description: 'Whether to display header and footer', }, { displayName: 'Header Template', name: 'headerTemplate', type: 'string', default: '<div style="font-size: 10px; text-align: center;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>', displayOptions: { show: { displayHeaderFooter: [true], }, }, description: 'HTML template for the header', }, { displayName: 'Footer Template', name: 'footerTemplate', type: 'string', default: '<div style="font-size: 10px; text-align: center;">© 2024 - Generated with n8n</div>', displayOptions: { show: { displayHeaderFooter: [true], }, }, description: 'HTML template for the footer', }, { displayName: 'Margin Top', name: 'marginTop', type: 'string', default: '20px', description: 'Top margin (e.g., "20px", "1cm")', }, { displayName: 'Margin Bottom', name: 'marginBottom', type: 'string', default: '20px', description: 'Bottom margin', }, { displayName: 'Margin Left', name: 'marginLeft', type: 'string', default: '20px', description: 'Left margin', }, { displayName: 'Margin Right', name: 'marginRight', type: 'string', default: '20px', description: 'Right margin', }, { displayName: 'Print Background', name: 'printBackground', type: 'boolean', default: true, description: 'Whether to print background graphics', }, { displayName: 'Custom CSS', name: 'customCss', type: 'string', typeOptions: { alwaysOpenEditWindow: true, editor: 'codeNodeEditor', editorLanguage: 'css', }, default: '', description: 'Additional CSS to inject into the page', }, { displayName: 'Wait Until', name: 'waitUntil', type: 'options', options: [ { name: 'Load', value: 'load', description: 'Wait for the load event', }, { name: 'DOM Content Loaded', value: 'domcontentloaded', description: 'Wait for DOMContentLoaded event', }, { name: 'Network Idle (0 connections)', value: 'networkidle0', description: 'Wait until there are no network connections for 500ms', }, { name: 'Network Idle (2 connections)', value: 'networkidle2', description: 'Wait until there are no more than 2 network connections for 500ms', }, ], default: 'networkidle0', description: 'When to consider the page loaded', }, { displayName: 'Wait For Selector', name: 'waitForSelector', type: 'string', default: '', description: 'CSS selector to wait for before generating PDF', }, { displayName: 'Emulate Media Type', name: 'emulateMediaType', type: 'options', options: [ { name: 'None', value: 'none', }, { name: 'Screen', value: 'screen', }, { name: 'Print', value: 'print', }, ], default: 'none', description: 'Changes the CSS media type of the page', }, ], }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; let browser; try { browser = await puppeteer_1.default.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--single-process', '--disable-gpu', ], }); for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { try { const inputType = this.getNodeParameter('inputType', itemIndex); const outputFormat = this.getNodeParameter('outputFormat', itemIndex); const options = this.getNodeParameter('options', itemIndex, {}); let htmlContent = ''; if (inputType === 'jsonTemplate') { const jsonData = this.getNodeParameter('jsonData', itemIndex, {}); const htmlTemplate = this.getNodeParameter('htmlTemplate', itemIndex); const template = handlebars_1.default.compile(htmlTemplate); const parsedData = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData; if (!parsedData.date) { parsedData.date = new Date().toLocaleDateString(); } htmlContent = template(parsedData); } else { htmlContent = this.getNodeParameter('htmlContent', itemIndex); } if (options.customCss) { const cssTag = `<style>${options.customCss}</style>`; if (htmlContent.includes('</head>')) { htmlContent = htmlContent.replace('</head>', `${cssTag}</head>`); } else if (htmlContent.includes('<body>')) { htmlContent = htmlContent.replace('<body>', `<head>${cssTag}</head><body>`); } else { htmlContent = `${cssTag}${htmlContent}`; } } const page = await browser.newPage(); if (options.emulateMediaType && options.emulateMediaType !== 'none') { await page.emulateMediaType(options.emulateMediaType); } await page.setContent(htmlContent, { waitUntil: options.waitUntil || 'networkidle0', }); if (options.waitForSelector) { await page.waitForSelector(options.waitForSelector, { timeout: 30000 }); } const pdfOptions = { format: options.format || 'A4', landscape: options.landscape || false, scale: options.scale || 1, displayHeaderFooter: options.displayHeaderFooter || false, printBackground: options.printBackground !== false, margin: { top: options.marginTop || '20px', bottom: options.marginBottom || '20px', left: options.marginLeft || '20px', right: options.marginRight || '20px', }, }; if (options.displayHeaderFooter) { pdfOptions.headerTemplate = options.headerTemplate || ''; pdfOptions.footerTemplate = options.footerTemplate || ''; } const pdfBuffer = await page.pdf(pdfOptions); await page.close(); if (outputFormat === 'binary') { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex); const fileName = this.getNodeParameter('fileName', itemIndex); const binaryData = await this.helpers.prepareBinaryData(pdfBuffer, fileName, 'application/pdf'); returnData.push({ json: items[itemIndex].json, binary: { [binaryPropertyName]: binaryData, }, pairedItem: { item: itemIndex, }, }); } else { returnData.push({ json: { ...items[itemIndex].json, pdf: pdfBuffer.toString('base64'), }, pairedItem: { item: itemIndex, }, }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (this.continueOnFail()) { returnData.push({ json: { error: errorMessage, }, pairedItem: { item: itemIndex, }, }); continue; } throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, { itemIndex }); } } } finally { if (browser) { await browser.close(); } } return [returnData]; } } exports.JsonHtmlToPdf = JsonHtmlToPdf; //# sourceMappingURL=JsonHtmlToPdf.node.js.map