├── .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 | 
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 | 
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 |
69 | {{#each items}}
70 | - {{name}}: {{email}} {{#if (eq primary true)}}(primary){{/if}}
71 | {{/each}}
72 |
73 | ```
74 |
75 | And you will get the next output to send by email in HTML format:
76 | ```
77 |
78 | - Miquel: miquel@n8nhackers.com (primary)
79 | - Contact: support@n8nhackers.com
80 |
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 {{#each items}}\n - {{name}}: {{email}}
\n {{/each}}\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 |
--------------------------------------------------------------------------------