├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── node.js.yml │ └── npm-publish.yml ├── .gitignore ├── .nvmrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── build_version.sh ├── credentials └── README.md ├── gulpfile.js ├── images ├── n8n-and-n8nhackers.png └── workflow-sample.jpeg ├── index.js ├── nodes ├── DocumentGenerator │ ├── DocumentGenerator.node.json │ ├── DocumentGenerator.node.ts │ └── DocumentGenerator.svg └── README.md ├── package.json ├── sample_workflow.json ├── start_n8n.sh ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | env: { 5 | browser: true, 6 | es6: true, 7 | node: true, 8 | }, 9 | 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | project: ['./tsconfig.json'], 13 | sourceType: 'module', 14 | extraFileExtensions: ['.json'], 15 | }, 16 | ignorePatterns: [ 17 | '.eslintrc.js', 18 | '**/*.js', 19 | '**/node_modules/**', 20 | '**/dist/**', 21 | ], 22 | 23 | overrides: [ 24 | { 25 | files: ['package.json'], 26 | plugins: ['eslint-plugin-n8n-nodes-base'], 27 | extends: ['plugin:n8n-nodes-base/community'], 28 | rules: { 29 | 'n8n-nodes-base/community-package-json-name-still-default': 'off', 30 | } 31 | }, 32 | { 33 | files: ['./credentials/**/*.ts'], 34 | plugins: ['eslint-plugin-n8n-nodes-base'], 35 | extends: ['plugin:n8n-nodes-base/credentials'], 36 | rules: { 37 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off', 38 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off', 39 | }, 40 | }, 41 | { 42 | files: ['./nodes/**/*.ts'], 43 | plugins: ['eslint-plugin-n8n-nodes-base'], 44 | extends: ['plugin:n8n-nodes-base/nodes'], 45 | rules: { 46 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off', 47 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off', 48 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off', 49 | 'n8n-nodes-base/node-execute-block-operation-missing-singular-pairing': 'off', 50 | 'n8n-nodes-base/node-execute-block-operation-missing-plural-pairing': 'off', 51 | }, 52 | }, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: [bug] 4 | assignees: 5 | - mcolomer 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | Please always be sure to use the latest compatible version. 12 | - type: textarea 13 | id: bug-description 14 | attributes: 15 | label: Describe the bug 16 | description: A clear and concise description of what the bug is. 17 | placeholder: The description of the bug. 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: expected-behavior 22 | attributes: 23 | label: Describe the expected behavior 24 | description: A clear and concise description of what you expected to happen. 25 | placeholder: The expected behavior. 26 | validations: 27 | required: true 28 | - type: input 29 | attributes: 30 | label: What is your Node.js version? 31 | placeholder: 14.X.X 32 | validations: 33 | required: true 34 | - type: input 35 | attributes: 36 | label: What is your n8n version? 37 | placeholder: 0.189.0 38 | validations: 39 | required: true 40 | - type: input 41 | attributes: 42 | label: What is your n8n-nodes-text-manipulation version? 43 | placeholder: 0.189.0 44 | validations: 45 | required: true 46 | - type: dropdown 47 | id: os 48 | attributes: 49 | label: What operating system are you seeing the problem on? 50 | multiple: true 51 | options: 52 | - Linux 53 | - Windows 54 | - MacOS 55 | - Other (enter below with the version) 56 | - type: input 57 | attributes: 58 | label: Operating system version (or if other, then please fill in complete name and version) 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: logs 63 | attributes: 64 | label: Relevant log output 65 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 66 | render: shell 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | labels: [enhancement] 4 | assignees: 5 | - mcolomer 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this feature request! 11 | - type: textarea 12 | id: feature-description 13 | attributes: 14 | label: Describe the function you would like to have 15 | description: A clear and concise description of what you want to happen. 16 | placeholder: The description of the function. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: alternative-solution 21 | attributes: 22 | label: Describe your current alternative solution. 23 | description: Your current solution that you use, if there is one. 24 | placeholder: Your alternative solution. 25 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [18.x, 20.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Install dependencies 26 | run: npm ci 27 | - name: Lint 28 | run: npm run lint 29 | - name: Build 30 | run: npm run build --if-present 31 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: 16 16 | - run: npm ci 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v2 24 | with: 25 | node-version: 16 26 | registry-url: https://registry.npmjs.org/ 27 | - run: npm ci 28 | - run: npm publish --access public 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 31 | if: ${{ !github.event.release.prerelease }} 32 | - run: npm publish --access public --tag beta 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 35 | if: ${{ github.event.release.prerelease }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .tmp 4 | tmp 5 | dist 6 | npm-debug.log* 7 | yarn.lock 8 | .vscode/launch.json 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/iron 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "yzhang.markdown-all-in-one" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 n8n hackers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner image](images/n8n-and-n8nhackers.png) 2 | 3 | # n8n-nodes-document-generator 4 | 5 | This is an n8n community node. It helps you to create: 6 | * One dynamic content per all input items. 7 | * One dynamic content per input item. 8 | 9 | If you have any questions or remarks please [contact me](mailto:support@n8nhackers.com). 10 | 11 | [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform. 12 | 13 | ## TL;DR 14 | Don't want to read? Import the sample workflow [Generate dynamic contents for EMAILS or HTML pages](https://n8n.io/workflows/1790-generate-dynamic-contents-for-emails-or-html-pages/) to test this node with random samples. 15 | ![Generate dynamic contents for EMAILS or HTML pages](images/workflow-sample.jpeg?raw=true "Generate dynamic contents for EMAILS or HTML pages") 16 | 17 | ## Installation 18 | 19 | Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation. 20 | 21 | ## Compatibility 22 | 23 | This node was developed and tested with version 0.193.5 of n8n. 24 | 25 | ## Dependencies 26 | If you install this node, n8n will install automatically the next extra npm packages: 27 | * [@jaredwray/fumanchu](https://www.npmjs.com/package/@jaredwray/fumanchu): this new Handlebars replacement provides Handlebars + Helpers Together. 28 | 29 | ## Usage 30 | The node can solve multiple use cases when creating content like: 31 | * Email generation (HTML or TEXT) 32 | * Static HTML pages 33 | * WordPress posts 34 | * Telegram/Slack messages 35 | * Use helpers to filter templates 36 | 37 | The sky is your limit! 38 | 39 | Just follow the next samples to create your dynamic content and forget to use SET, CODE nodes. 40 | 41 | ### All input items in one template 42 | 43 | #### Cases 44 | * You want to send a list of recent news about n8n. 45 | * You want to send the list of the customers created in the last hour in your database. 46 | 47 | #### Template 48 | Supposing that you have a customer list in JSON: 49 | ```json 50 | [ 51 | { 52 | "email": "miquel@n8nhackers.com", 53 | "name": "Miquel", 54 | "primary": true 55 | }, 56 | { 57 | "email": "support@n8nhackers.com", 58 | "name": "Contact", 59 | "primary": false 60 | } 61 | ] 62 | ``` 63 | 64 | We will try a Handlebars helper #if to show if the contact is the primary email or not. 65 | 66 | If you use the next template: 67 | ``` 68 | 73 | ``` 74 | 75 | And you will get the next output to send by email in HTML format: 76 | ``` 77 | 81 | ``` 82 | 83 | Property **items** is always mandatory to iterate over all items. 84 | 85 | ### One input item per template 86 | 87 | #### Cases 88 | * You have an invoice with header and lines and you want to send it by email. 89 | 90 | #### Template 91 | If you have one item/invoice with this JSON: 92 | ```json 93 | { 94 | "date": "2022-01-12", 95 | "to": "N8n hackers", 96 | "address": "Granollers, Spain", 97 | "total": 133.10, 98 | "lines": [ 99 | { 100 | "description": "Create a node to render items in handlebar templates", 101 | "quantity": 1, 102 | "amount": 100, 103 | "vat": 21, 104 | "total": 121 105 | }, 106 | { 107 | "description": "Test a node to render items in handlebar templates", 108 | "quantity": 1, 109 | "amount": 10, 110 | "vat": 2.10, 111 | "total": 12.1 112 | } 113 | ] 114 | } 115 | ``` 116 | 117 | You need to use this template: 118 | ``` 119 | Date: {{date}} 120 | To: {{to}} 121 | Address: {{address}} 122 | Details: 123 | {{#each lines}} 124 | - "{{description}}" x {{quantity}} = {{amount}}€ + {{vat}}€ = {{total}}€ 125 | {{/each}} 126 | Total invoice: {{total}}€ 127 | ``` 128 | 129 | And you will get the next output to send by email in TEXT format: 130 | ``` 131 | Date: 2022-01-12 132 | To: N8n hackers 133 | Address: Granollers, Spain 134 | Details: 135 | - "Create a node to render items in handlebar templates" x 1 = 100€ + 21€ = 121€ 136 | - "Test a node to render items in handlebar templates" x 1 = 10€ + 2.10€ = 12.10€ 137 | Total invoice: 133.10€ 138 | ``` 139 | 140 | I recommend using this method if you want to send multiple invoices. 141 | 142 | ## Helpers 143 | Now the node supports helpers thanks to the [@jaredwray/fumanchu](https://www.npmjs.com/package/@jaredwray/fumanchu#helpers) package. 144 | 145 | We recommend checking 146 | 147 | ## Doubts about templates syntax 148 | Please, check the [official page](https://handlebarsjs.com/guide/expressions.html#basic-usage) to review all the existing expressions in Handlebars. 149 | 150 | ## Another way to try it out 151 | 152 | [N8N documentation on custom nodes](https://docs.n8n.io/nodes/creating-nodes/create-n8n-nodes-module.html) 153 | 154 | Clone the n8n-nodes-document-generator repository and execute: 155 | ``` 156 | # Use v20.12.2 = lts/iron 157 | nvm use lts/iron 158 | 159 | # Install dependencies 160 | npm install 161 | 162 | # Build the code 163 | npm run build 164 | 165 | # "Publish" the package locally 166 | npm link 167 | ``` 168 | 169 | Create an N8N installation and add the n8n-nodes-document-generator to it: 170 | ``` 171 | # Ensure that custom nodes directory exists in your .n8n 172 | mkdir ~/.n8n/custom 173 | 174 | # Init npm packages (intro to all questions) 175 | cd ~/.n8n/custom && npm init 176 | 177 | # "Install" the locally published module 178 | npm link n8n-nodes-document-generator 179 | 180 | # Start n8n 181 | n8n start 182 | ``` 183 | 184 | # Contribution 185 | 186 | To make this node even better, please let us know, [how you use it](mailto:support@n8nhackers.com). Commits are always welcome. 187 | 188 | # Issues 189 | 190 | If you have any issues, please [let us know on GitHub](https://github.com/n8nhackers/n8n-nodes-document-generator/issues). 191 | 192 | # About 193 | 194 | Nodes by [n8nhackers.com](https://n8nhackers.com). For productive use and consulting on this, [contact us please](mailto:support@n8nhackers.com). 195 | 196 | Special thanks to [N8n nodemation](https://n8n.io) workflow automation by Jan Oberhauser. 197 | 198 | # License 199 | 200 | [MIT](https://github.com/n8n-io/n8n-nodes-starter/blob/master/LICENSE.md) 201 | -------------------------------------------------------------------------------- /build_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #check if lint is ok 4 | yarn run lint 5 | if [ $? -ne 0 ]; then 6 | echo "Linting failed. Please fix the issues before proceeding." 7 | exit 1 8 | fi 9 | 10 | #check if test is ok 11 | yarn run test 12 | if [ $? -ne 0 ]; then 13 | echo "Tests failed. Please fix the issues before proceeding." 14 | exit 1 15 | fi 16 | 17 | # build 18 | yarn run build 19 | if [ $? -ne 0 ]; then 20 | echo "Build failed. Please fix the issues before proceeding." 21 | exit 1 22 | fi 23 | 24 | npm version patch 25 | #check if version is ok 26 | if [ $? -ne 0 ]; then 27 | echo "Versioning failed. Please fix the issues before proceeding." 28 | exit 1 29 | fi 30 | 31 | git push --tags && git push 32 | if [ $? -ne 0 ]; then 33 | echo "Git push failed. Please fix the issues before proceeding." 34 | exit 1 35 | fi 36 | 37 | #publish to npm 38 | npm publish --access public 39 | if [ $? -ne 0 ]; then 40 | echo "Publishing failed. Please fix the issues before proceeding." 41 | exit 1 42 | fi 43 | echo "Build and publish completed successfully." 44 | -------------------------------------------------------------------------------- /credentials/README.md: -------------------------------------------------------------------------------- 1 | # credentials folder 2 | 3 | put your credentials in this folder 4 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { task, src, dest } = require('gulp'); 3 | 4 | task('build:icons', copyIcons); 5 | 6 | function copyIcons() { 7 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg}'); 8 | const nodeDestination = path.resolve('dist', 'nodes'); 9 | 10 | src(nodeSource).pipe(dest(nodeDestination)); 11 | 12 | const credSource = path.resolve('credentials', '**', '*.{png,svg}'); 13 | const credDestination = path.resolve('dist', 'credentials'); 14 | 15 | return src(credSource).pipe(dest(credDestination)); 16 | } 17 | -------------------------------------------------------------------------------- /images/n8n-and-n8nhackers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8nhackers/n8n-nodes-document-generator/1b13653ca58ff372fb792e97e26db2eca674bb36/images/n8n-and-n8nhackers.png -------------------------------------------------------------------------------- /images/workflow-sample.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8nhackers/n8n-nodes-document-generator/1b13653ca58ff372fb792e97e26db2eca674bb36/images/workflow-sample.jpeg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8nhackers/n8n-nodes-document-generator/1b13653ca58ff372fb792e97e26db2eca674bb36/index.js -------------------------------------------------------------------------------- /nodes/DocumentGenerator/DocumentGenerator.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "node": "n8n-nodes-document-generator.DocumentGenerator", 3 | "nodeVersion": "1.1", 4 | "codexVersion": "1.0", 5 | "categories": ["Marketing"], 6 | "resources": { 7 | "primaryDocumentation": [ 8 | { 9 | "url": "https://github.com/n8nhackers/n8n-nodes-document-generator" 10 | } 11 | ] 12 | }, 13 | "alias": [ 14 | "Generate" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /nodes/DocumentGenerator/DocumentGenerator.node.ts: -------------------------------------------------------------------------------- 1 | import { handlebars, helpers } from '@jaredwray/fumanchu'; 2 | import { IExecuteFunctions } from 'n8n-core'; 3 | import { 4 | IBinaryKeyData, 5 | IDataObject, 6 | INodeExecutionData, 7 | INodeType, 8 | INodeTypeDescription, 9 | } from 'n8n-workflow'; 10 | 11 | /** 12 | * A node which allows you to generate documents by templates. 13 | */ 14 | export class DocumentGenerator implements INodeType { 15 | description: INodeTypeDescription = { 16 | displayName: 'DocumentGenerator', 17 | name: 'documentGenerator', 18 | icon: 'file:DocumentGenerator.svg', 19 | group: ['transform'], 20 | version: 1, 21 | subtitle: '', 22 | //description: 'Render data using templates and save to TEXT, HTML, PDF or PNG files', 23 | description: 'Render data using a Handlebars template', 24 | defaults: { 25 | name: 'DocumentGenerator', 26 | }, 27 | inputs: ['main'], 28 | outputs: ['main'], 29 | credentials: [], 30 | // Basic node details will go here 31 | properties: [ 32 | // Resources and operations will go here 33 | { 34 | displayName: 'Operation', 35 | name: 'operation', 36 | type: 'options', 37 | options: [ 38 | { 39 | name: 'Render Template', 40 | value: 'render', 41 | description: 'Render a text template', 42 | action: 'Render a text template', 43 | }, 44 | ], 45 | default: 'render', 46 | noDataExpression: true, 47 | required: true, 48 | }, 49 | { 50 | displayName: 'Render All Items with One Template', 51 | name: 'oneTemplate', 52 | type: 'boolean', 53 | default: false, // Initial state of the toggle 54 | description: 55 | 'Whether to render all input items using the sample template.\nSyntax: {{#each items}}{{columnname}}{{/each}}.\nOtherwise, every item has its own template', 56 | displayOptions: { 57 | // the resources and operations to display this element with 58 | show: { 59 | operation: ['render'], 60 | }, 61 | }, 62 | }, 63 | { 64 | displayName: 'Use a Template String', 65 | name: 'useTemplateString', 66 | type: 'boolean', 67 | default: true, // Initial state of the toggle 68 | description: 69 | 'Whether to render all input items using a template String or a template URL', 70 | displayOptions: { 71 | // the resources and operations to display this element with 72 | show: { 73 | operation: ['render'], 74 | }, 75 | }, 76 | }, 77 | { 78 | displayName: 'Template String', 79 | name: 'template', 80 | type: 'string', 81 | required: true, 82 | typeOptions: { 83 | rows: 5, 84 | alwaysOpenEditWindow: true, 85 | }, 86 | displayOptions: { 87 | show: { 88 | useTemplateString: [true], 89 | }, 90 | }, 91 | default: '', 92 | placeholder: '{{handlebars template}}', 93 | description: 94 | 'The template string to use for rendering. Please check the official page for Handlebars syntax.', 95 | }, 96 | { 97 | displayName: 'Template URL', 98 | name: 'templateURL', 99 | type: 'string', 100 | required: true, 101 | displayOptions: { 102 | show: { 103 | useTemplateString: [false], 104 | }, 105 | }, 106 | default: '', 107 | placeholder: 'https://mydomain.com/emails/template.html', 108 | description: 109 | 'The template URL to use for rendering. Please check the official page for Handlebars syntax.', 110 | }, 111 | { 112 | displayName: 'Define a Custom Output Key', 113 | name: 'customOutputKey', 114 | type: 'boolean', 115 | default: false, // Initial state of the toggle 116 | description: 117 | 'Whether to define a custom output key instead of the default "text" property', 118 | displayOptions: { 119 | // the resources and operations to display this element with 120 | show: { 121 | operation: ['render'], 122 | }, 123 | }, 124 | }, 125 | { 126 | displayName: 'Output Key', 127 | name: 'outputKey', 128 | type: 'string', 129 | required: true, 130 | displayOptions: { 131 | show: { 132 | customOutputKey: [true], 133 | }, 134 | }, 135 | default: '', 136 | placeholder: 'text', 137 | description: 'The output property name where we save rendered text', 138 | } 139 | ], 140 | }; 141 | // The execute method will go here 142 | async execute(this: IExecuteFunctions): Promise { 143 | const items = this.getInputData(); 144 | const returnData = []; 145 | 146 | const newItemBinary: IBinaryKeyData = {}; 147 | 148 | const operation = this.getNodeParameter('operation', 0) as string; 149 | const oneTemplate = this.getNodeParameter('oneTemplate', 0) as boolean; 150 | const customOutputKey = this.getNodeParameter('customOutputKey', 0) as boolean; 151 | const useTemplateString = this.getNodeParameter('useTemplateString', 0) as boolean; 152 | //const binary = false; //this.getNodeParameter('binary', 0) as boolean; 153 | //const fileType = ''; //this.getNodeParameter('fileType', 0) as string; 154 | 155 | let template = ''; 156 | if (useTemplateString) { 157 | template = this.getNodeParameter('template', 0) as string; 158 | } else { 159 | const templateURL = this.getNodeParameter('templateURL', 0) as string; 160 | template = await this.helpers.request(templateURL); 161 | } 162 | helpers({ handlebars }, {}); 163 | 164 | const templateHelper = handlebars.compile(template); 165 | 166 | let key = 'text'; 167 | if (customOutputKey) { 168 | key = this.getNodeParameter('outputKey', 0) as string; 169 | } 170 | 171 | if (oneTemplate) { 172 | var cleanedItems = items.map(function (item) { 173 | return item.json; 174 | }); 175 | let newItemJson: IDataObject = {}; 176 | newItemJson[key] = templateHelper({ items: cleanedItems }); 177 | returnData.push({ json: newItemJson }); 178 | } else { 179 | for (let i = 0; i < items.length; i++) { 180 | let item = items[i]; 181 | if (operation === 'render') { 182 | let newItemJson: IDataObject = {}; 183 | // Get email input 184 | // Get additional fields input 185 | var rendered = templateHelper(item.json); 186 | newItemJson[key] = rendered; 187 | returnData.push({ 188 | json: newItemJson, 189 | pairedItem: { 190 | item: i, 191 | }, 192 | binary: Object.keys(newItemBinary).length === 0 ? undefined : newItemBinary, 193 | }); 194 | } 195 | } 196 | } 197 | 198 | // Map data to n8n data structure 199 | return this.prepareOutputData(returnData); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /nodes/DocumentGenerator/DocumentGenerator.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nodes/README.md: -------------------------------------------------------------------------------- 1 | # credentials folder 2 | 3 | put your nodes in this folder 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n8n-nodes-document-generator", 3 | "version": "1.0.10", 4 | "author": { 5 | "name": "Miquel Colomer", 6 | "email": "miquel@n8nhackers.com" 7 | }, 8 | "description": "This node creates dynamic content for documents or emails with Handlebars templates", 9 | "keywords": [ 10 | "n8n", 11 | "workflow", 12 | "n8n-community-node-package", 13 | "document generator", 14 | "handlebars", 15 | "n8n hackers", 16 | "html generator", 17 | "pdf generator", 18 | "email generator" 19 | ], 20 | "main": "index.js", 21 | "scripts": { 22 | "lint": "eslint nodes credentials package.json --no-error-on-unmatched-pattern", 23 | "lint:fix": "eslint nodes credentials package.json --no-error-on-unmatched-pattern --fix", 24 | "link": "npm link", 25 | "link:n8n": "cd ~/.n8n/nodes/node_modules && npm link n8n-nodes-document-generator", 26 | "unlink:n8n": "cd ~/.n8n/nodes/node_modules && npm unlink n8n-nodes-document-generator", 27 | "build": "tsc && gulp build:icons", 28 | "prepare": "npm run build", 29 | "dev": "tsc --watch", 30 | "format": "prettier nodes credentials --write", 31 | "tsc": "tsc", 32 | "n8n:start": "n8n start --tunnel" 33 | }, 34 | "prettier": "eslint-config-n8n-nodes-base/prettierrc", 35 | "files": [ 36 | "dist" 37 | ], 38 | "n8n": { 39 | "n8nNodesApiVersion": 1, 40 | "credentials": [], 41 | "nodes": [ 42 | "dist/nodes/DocumentGenerator/DocumentGenerator.node.js" 43 | ] 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/n8nhackers/n8n-nodes-document-generator.git" 48 | }, 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/n8nhackers/n8n-nodes-document-generator/issues" 52 | }, 53 | "homepage": "https://github.com/n8nhackers/n8n-nodes-document-generator#readme", 54 | "devDependencies": { 55 | "@types/express": "^4.17.13", 56 | "@types/lodash": "^4.14.182", 57 | "@types/node": "^18.6.3", 58 | "@types/request-promise-native": "^1.0.18", 59 | "@typescript-eslint/eslint-plugin": "^5.32.0", 60 | "@typescript-eslint/parser": "^5.32.0", 61 | "eslint": "^8.22.0", 62 | "eslint-plugin-deprecation": "^1.3.2", 63 | "eslint-plugin-eslint-plugin": "^5.0.2", 64 | "eslint-plugin-import": "^2.26.0", 65 | "eslint-plugin-n8n-nodes-base": "^1.5.5", 66 | "eslint-plugin-prettier": "^4.2.1", 67 | "eslint-plugin-promise": "^6.0.0", 68 | "eslint-plugin-simple-import-sort": "^7.0.0", 69 | "gulp": "^4.0.2", 70 | "n8n-core": "^0.128.0", 71 | "n8n-workflow": "^0.110.0", 72 | "prettier": "^2.7.1", 73 | "prettier-plugin-jsdoc": "^0.3.38", 74 | "typescript": "^4.7.4" 75 | }, 76 | "dependencies": { 77 | "@jaredwray/fumanchu": "^3.1.1" 78 | }, 79 | "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" 80 | } 81 | -------------------------------------------------------------------------------- /sample_workflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Generate dynamic contents for EMAILS or HTML pages", 3 | "nodes": [ 4 | { 5 | "parameters": {}, 6 | "id": "880dcc94-8b97-44f0-bc7d-06663cab81a8", 7 | "name": "On clicking 'execute'", 8 | "type": "n8n-nodes-base.manualTrigger", 9 | "position": [ 10 | 580, 11 | 440 12 | ], 13 | "typeVersion": 1 14 | }, 15 | { 16 | "parameters": { 17 | "operation": "getAllPeople", 18 | "returnAll": true 19 | }, 20 | "id": "1aeed2e9-c464-4f7b-a5dd-ef8d0b3f8884", 21 | "name": "Customer Datastore", 22 | "type": "n8n-nodes-base.n8nTrainingCustomerDatastore", 23 | "position": [ 24 | 900, 25 | 440 26 | ], 27 | "typeVersion": 1 28 | }, 29 | { 30 | "parameters": { 31 | "operation": "sort", 32 | "sortFieldsUi": { 33 | "sortField": [ 34 | { 35 | "fieldName": "name" 36 | } 37 | ] 38 | }, 39 | "options": {} 40 | }, 41 | "id": "18c1bc4c-5330-41ea-8acb-2e2b197bd0fa", 42 | "name": "Item Lists", 43 | "type": "n8n-nodes-base.itemLists", 44 | "position": [ 45 | 1120, 46 | 440 47 | ], 48 | "typeVersion": 1 49 | }, 50 | { 51 | "parameters": { 52 | "functionCode": "item.lines = [\n {\n concept: \"Service\",\n description: \"Design of HTML banners\",\n quantity: 1,\n amount: 22,\n vat: 22 * 0.21,\n total: 22 * 1.21\n },\n {\n concept: \"Service\",\n description: \"Design of PNG banners\",\n quantity: 1,\n amount: 33,\n vat: 33 * 0.21,\n total: 33 * 1.21\n }\n]\n\nitem.date = \"2022-01-12\";\nitem.total = 133.10;\n\nreturn item;" 53 | }, 54 | "id": "81c7d115-303f-45f8-92c9-a3b8aa352e1b", 55 | "name": "Add lines", 56 | "type": "n8n-nodes-base.functionItem", 57 | "position": [ 58 | 1340, 59 | 300 60 | ], 61 | "typeVersion": 1 62 | }, 63 | { 64 | "parameters": { 65 | "fromEmail": "mcolomer@n8nhackers.com", 66 | "toEmail": "mcolomer@n8nhackers.com", 67 | "subject": "=Invoice for {{ $node[\"Add lines\"].json[\"name\"] }}", 68 | "html": "={{ $json[\"text\"] }}", 69 | "options": {} 70 | }, 71 | "id": "910485cc-22e4-444b-bc0f-082ea02c1d73", 72 | "name": "Send one TEXT email per item", 73 | "type": "n8n-nodes-base.emailSend", 74 | "position": [ 75 | 1780, 76 | 300 77 | ], 78 | "typeVersion": 1, 79 | "disabled": true 80 | }, 81 | { 82 | "parameters": { 83 | "fromEmail": "mcolomer@n8nhackers.com", 84 | "toEmail": "mcolomer@n8nhackers.com", 85 | "subject": "New customers", 86 | "html": "={{ $json[\"text\"] }}", 87 | "options": {} 88 | }, 89 | "id": "f3550ed4-d044-4c0a-a9ba-fb384565a21f", 90 | "name": "Send one HTML Email per list", 91 | "type": "n8n-nodes-base.emailSend", 92 | "position": [ 93 | 1780, 94 | 540 95 | ], 96 | "typeVersion": 1, 97 | "disabled": true 98 | }, 99 | { 100 | "parameters": { 101 | "template": "Date: {{created}}\nTo: {{name}} <{{email}}>\nAddress: {{country}}\nDetails:\n{{#each lines}}\n- \\\"{{description}}\\\" x {{quantity}} = {{amount}}€ + {{vat}}€ = {{total}}€\n{{/each}}\nTotal invoice: {{total}}€" 102 | }, 103 | "id": "d5274ba2-5910-4630-a8c2-441dbcaa9f94", 104 | "name": "One item per template", 105 | "type": "n8n-nodes-document-generator.documentGenerator", 106 | "typeVersion": 1, 107 | "position": [ 108 | 1560, 109 | 300 110 | ] 111 | }, 112 | { 113 | "parameters": { 114 | "oneTemplate": true, 115 | "template": "\n \n \n\n\n New customers in last 24h:\n \n\n" 116 | }, 117 | "id": "92501623-5eb2-4eb5-9e92-063b0cb6d330", 118 | "name": "All items, one template", 119 | "type": "n8n-nodes-document-generator.documentGenerator", 120 | "typeVersion": 1, 121 | "position": [ 122 | 1560, 123 | 540 124 | ] 125 | } 126 | ], 127 | "pinData": {}, 128 | "connections": { 129 | "On clicking 'execute'": { 130 | "main": [ 131 | [ 132 | { 133 | "node": "Customer Datastore", 134 | "type": "main", 135 | "index": 0 136 | } 137 | ] 138 | ] 139 | }, 140 | "Item Lists": { 141 | "main": [ 142 | [ 143 | { 144 | "node": "Add lines", 145 | "type": "main", 146 | "index": 0 147 | }, 148 | { 149 | "node": "All items, one template", 150 | "type": "main", 151 | "index": 0 152 | } 153 | ] 154 | ] 155 | }, 156 | "Customer Datastore": { 157 | "main": [ 158 | [ 159 | { 160 | "node": "Item Lists", 161 | "type": "main", 162 | "index": 0 163 | } 164 | ] 165 | ] 166 | }, 167 | "Add lines": { 168 | "main": [ 169 | [ 170 | { 171 | "node": "One item per template", 172 | "type": "main", 173 | "index": 0 174 | } 175 | ] 176 | ] 177 | }, 178 | "One item per template": { 179 | "main": [ 180 | [ 181 | { 182 | "node": "Send one TEXT email per item", 183 | "type": "main", 184 | "index": 0 185 | } 186 | ] 187 | ] 188 | }, 189 | "All items, one template": { 190 | "main": [ 191 | [ 192 | { 193 | "node": "Send one HTML Email per list", 194 | "type": "main", 195 | "index": 0 196 | } 197 | ] 198 | ] 199 | } 200 | }, 201 | "active": false, 202 | "settings": { 203 | "executionOrder": "v1" 204 | }, 205 | "versionId": "4c00b440-aa3a-4544-a139-d892c5293b8e", 206 | "meta": { 207 | "templateCredsSetupCompleted": true, 208 | "instanceId": "5616c2c57283090978141726380fa7ea68024a414bc9c9120f860835cc0e7dc2" 209 | }, 210 | "id": "uzNw8qOk8HybeIUw", 211 | "tags": [] 212 | } -------------------------------------------------------------------------------- /start_n8n.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export N8N_LOG_LEVEL=debug 4 | export N8N_LOG_FILE_LOCATION=n8n.log 5 | 6 | yarn install 7 | 8 | # # Load nvm if available. 9 | # export NVM_DIR="$HOME/.nvm" 10 | # [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 11 | 12 | # # ./NVM_DIR/nvm.sh use 13 | 14 | if [ ! -d "$HOME/.n8n/custom" ]; then 15 | mkdir -p "$HOME/.n8n/custom" 16 | fi 17 | 18 | if [ ! -d "$HOME/.n8n/custom/node_modules" ]; then 19 | mkdir -p "$HOME/.n8n/custom/node_modules" 20 | fi 21 | 22 | if [ ! -d "$HOME/.n8n/custom/node_modules/n8n-nodes-document-generator" ]; then 23 | yarn build && npm link 24 | cd "$HOME/.n8n/custom/node_modules" || exit 25 | npm link n8n-nodes-document-generator 26 | fi 27 | 28 | #exists n8n binary? 29 | if ! command -v n8n &> /dev/null; then 30 | echo "n8n command not found. Please install n8n globally using 'npm install -g n8n'." 31 | exit 1 32 | fi 33 | 34 | #n8n start --tunnel 35 | n8n start 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es2017", 5 | "es2019" 6 | ], 7 | "types": [ 8 | "node", 9 | ], 10 | "module": "commonjs", 11 | "noImplicitAny": true, 12 | "removeComments": true, 13 | "strictNullChecks": true, 14 | "strict": true, 15 | "preserveConstEnums": true, 16 | "resolveJsonModule": true, 17 | "declaration": true, 18 | "outDir": "./dist/", 19 | "target": "es2019", 20 | "sourceMap": true, 21 | "esModuleInterop": true, 22 | "useUnknownInCatchVariables": false, 23 | }, 24 | "include": [ 25 | "credentials/**/*", 26 | "nodes/**/*", 27 | "nodes/**/*.json", 28 | "package.json" 29 | ], 30 | "exclude": [ 31 | "node_modules", 32 | "**/*.spec.ts" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------