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
JavaScript
;
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