@cenk1cenk2/md-printer 2.11.3 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/dev.js CHANGED
@@ -30,7 +30,7 @@ async function main() {
30
30
 
31
31
  const { execute } = await import('@oclif/core')
32
32
 
33
- await execute({ development: true, dir: import.meta.url })
33
+ return execute({ development: true, dir: import.meta.url })
34
34
  }
35
35
 
36
36
  await main()
package/bin/run.js CHANGED
@@ -5,6 +5,7 @@ async function main() {
5
5
 
6
6
  await import('source-map-support').then((lib) => lib.install())
7
7
  await import('@cenk1cenk2/oclif-common').then((lib) => lib.setup())
8
+
8
9
  await execute({ dir: import.meta.url })
9
10
  }
10
11
 
@@ -1,5 +1,5 @@
1
1
  import { RequiredTemplateFiles, TEMPLATE_DIRECTORY, TemplateFiles } from "../constants/template.constants.js";
2
- import { OUTPUT_FILE_ACCEPTED_TYPES } from "../constants/file.constants.js";
2
+ import { InputFileType, OutputFileType } from "../constants/file.constants.js";
3
3
  import "../constants/index.js";
4
4
  import tailwind from "@tailwindcss/postcss";
5
5
  import { watch } from "chokidar";
@@ -8,7 +8,7 @@ import { defaultConfig } from "md-to-pdf/dist/lib/config.js";
8
8
  import { convertMdToPdf } from "md-to-pdf/dist/lib/md-to-pdf.js";
9
9
  import { serveDirectory } from "md-to-pdf/dist/lib/serve-dir.js";
10
10
  import Nunjucks from "nunjucks";
11
- import { basename, dirname, extname, join } from "path";
11
+ import { basename, dirname, extname, isAbsolute, join } from "path";
12
12
  import postcss from "postcss";
13
13
  import puppeteer from "puppeteer";
14
14
  import showdown from "showdown";
@@ -18,6 +18,29 @@ import { Args, Command, ConfigService, FileSystemService, Flags, JsonParser, Mer
18
18
  var MDPrinter = class extends Command {
19
19
  static description = "Generates a PDF from the given markdown file with the selected HTML template.";
20
20
  static flags = {
21
+ stdin: Flags.string({
22
+ char: "I",
23
+ description: "Read the input from stdin.",
24
+ required: false,
25
+ exclusive: ["file"],
26
+ allowStdin: "only"
27
+ }),
28
+ ["input-filetype"]: Flags.string({
29
+ char: "f",
30
+ options: Object.values(InputFileType),
31
+ description: "File type to be processed. By default it is detected by the file extension.",
32
+ default: InputFileType.MARKDOWN
33
+ }),
34
+ ["output-filetype"]: Flags.string({
35
+ char: "F",
36
+ options: Object.values(OutputFileType),
37
+ description: "File type to be processed. By default it is detected by the file extension.",
38
+ default: OutputFileType.PDF
39
+ }),
40
+ stdout: Flags.boolean({
41
+ char: "O",
42
+ description: "Write to stdout instead of a file."
43
+ }),
21
44
  template: Flags.string({
22
45
  char: "t",
23
46
  default: "default",
@@ -42,10 +65,7 @@ var MDPrinter = class extends Command {
42
65
  })
43
66
  };
44
67
  static args = {
45
- file: Args.string({
46
- description: "File to be processed.",
47
- required: true
48
- }),
68
+ file: Args.string({ description: "File to be processed." }),
49
69
  output: Args.string({
50
70
  description: "Output file that will be generated. Overwrites the one define in front-matter.",
51
71
  required: false
@@ -71,20 +91,21 @@ var MDPrinter = class extends Command {
71
91
  }
72
92
  async run() {
73
93
  this.tasks.add([{ task: async (ctx) => {
74
- const file = join(process.cwd(), this.args.file);
75
- if (!this.fs.exists(file)) throw new Error(`File does not exists: ${file}`);
76
- this.logger.info("Loading file: %s", file);
77
- ctx.file = file;
78
- switch (extname(ctx.file)) {
79
- case ".md": {
80
- const data = graymatter.read(ctx.file);
81
- ctx.content = await this.fs.read(file);
94
+ if (this.args.file) {
95
+ ctx.file = isAbsolute(this.args.file) ? this.args.file : join(process.cwd(), this.args.file);
96
+ if (!this.fs.exists(ctx.file)) throw new Error(`File does not exists: ${ctx.file}`);
97
+ this.logger.info("Loading file: %s", ctx.file);
98
+ ctx.content = await this.fs.read(ctx.file);
99
+ } else ctx.content = this.flags.stdin;
100
+ switch (this.flags["input-filetype"] ? this.flags["input-filetype"] : extname(ctx.file).replace(/^\./, "")) {
101
+ case InputFileType.MARKDOWN: {
102
+ const data = graymatter(ctx.content);
82
103
  ctx.content = data.content;
83
104
  ctx.metadata = data.data;
84
105
  break;
85
106
  }
86
- case ".yml":
87
- ctx.content = await this.fs.read(file);
107
+ case InputFileType.YAML:
108
+ case InputFileType.YAML_SHORT:
88
109
  ctx.metadata = await this.app.get(ParserService).parse(ctx.file, ctx.content);
89
110
  break;
90
111
  default: throw new Error("File type is not accepted.");
@@ -106,10 +127,13 @@ var MDPrinter = class extends Command {
106
127
  [TemplateFiles.TEMPLATE]: join(ctx.templates, TemplateFiles.TEMPLATE)
107
128
  };
108
129
  ctx.options = await this.cs.extend([paths[TemplateFiles.SETTINGS], {
109
- dest: this.args?.output ?? ctx.metadata?.dest ?? `${basename(this.args.file, extname(this.args.file))}.pdf`,
130
+ dest: this.args?.output ?? ctx.metadata?.dest ?? `${basename(this.args.file, extname(this.args.file))}.${this.flags["output-filetype"]}`,
110
131
  document_title: ctx.metadata?.document_title ?? this.flags.title ?? this.args.file,
111
132
  launch_options: { executablePath: this.flags.browser }
112
133
  }]);
134
+ this.flags.stdout ??= ctx.metadata.stdout;
135
+ if (this.flags.stdout) ctx.options.dest = "stdout";
136
+ if (this.flags["output-filetype"] === OutputFileType.HTML || ctx.metadata["output-filetype"] === OutputFileType.HTML) ctx.options.as_html = true;
113
137
  this.logger.debug("Options: %o", ctx.options);
114
138
  if (this.fs.exists(paths[TemplateFiles.HEADER])) {
115
139
  this.logger.debug("Header exists for template.");
@@ -178,8 +202,11 @@ var MDPrinter = class extends Command {
178
202
  resolve(null);
179
203
  }));
180
204
  if (output) {
205
+ if (this.flags.stdout) {
206
+ process.stdout.write(output.content);
207
+ return;
208
+ }
181
209
  if (!output.filename) throw new Error("Output should either be defined with the variable or front-matter.");
182
- else if (!OUTPUT_FILE_ACCEPTED_TYPES.includes(extname(output.filename))) throw new Error(`Output file should be ending with the extension: ${OUTPUT_FILE_ACCEPTED_TYPES.join(", ")} -> current: ${extname(output.filename)}`);
183
210
  this.logger.info("Output file will be: %s", output.filename);
184
211
  await this.fs.mkdir(dirname(output.filename));
185
212
  this.logger.info("Writing file to output: %s", output.filename);
@@ -1,5 +1,21 @@
1
1
  //#region src/constants/file.constants.ts
2
2
  const OUTPUT_FILE_ACCEPTED_TYPES = [".pdf"];
3
+ const INPUT_FILE_ACCEPTED_TYPES = [
4
+ ".md",
5
+ ".yml",
6
+ ".yaml"
7
+ ];
8
+ let OutputFileType = /* @__PURE__ */ function(OutputFileType$1) {
9
+ OutputFileType$1["PDF"] = "pdf";
10
+ OutputFileType$1["HTML"] = "html";
11
+ return OutputFileType$1;
12
+ }({});
13
+ let InputFileType = /* @__PURE__ */ function(InputFileType$1) {
14
+ InputFileType$1["MARKDOWN"] = "md";
15
+ InputFileType$1["YAML"] = "yaml";
16
+ InputFileType$1["YAML_SHORT"] = "yml";
17
+ return InputFileType$1;
18
+ }({});
3
19
 
4
20
  //#endregion
5
- export { OUTPUT_FILE_ACCEPTED_TYPES };
21
+ export { INPUT_FILE_ACCEPTED_TYPES, InputFileType, OUTPUT_FILE_ACCEPTED_TYPES, OutputFileType };
@@ -1,4 +1,4 @@
1
1
  import { RequiredTemplateFiles, TEMPLATE_DIRECTORY, TemplateFiles } from "./template.constants.js";
2
- import { OUTPUT_FILE_ACCEPTED_TYPES } from "./file.constants.js";
2
+ import { INPUT_FILE_ACCEPTED_TYPES, InputFileType, OUTPUT_FILE_ACCEPTED_TYPES, OutputFileType } from "./file.constants.js";
3
3
 
4
- export { OUTPUT_FILE_ACCEPTED_TYPES, RequiredTemplateFiles, TEMPLATE_DIRECTORY, TemplateFiles };
4
+ export { INPUT_FILE_ACCEPTED_TYPES, InputFileType, OUTPUT_FILE_ACCEPTED_TYPES, OutputFileType, RequiredTemplateFiles, TEMPLATE_DIRECTORY, TemplateFiles };
@@ -5,8 +5,7 @@
5
5
  "args": {
6
6
  "file": {
7
7
  "description": "File to be processed.",
8
- "name": "file",
9
- "required": true
8
+ "name": "file"
10
9
  },
11
10
  "output": {
12
11
  "description": "Output file that will be generated. Overwrites the one define in front-matter.",
@@ -55,6 +54,52 @@
55
54
  "allowNo": false,
56
55
  "type": "boolean"
57
56
  },
57
+ "stdin": {
58
+ "char": "I",
59
+ "description": "Read the input from stdin.",
60
+ "exclusive": [
61
+ "file"
62
+ ],
63
+ "name": "stdin",
64
+ "required": false,
65
+ "hasDynamicHelp": false,
66
+ "multiple": false,
67
+ "type": "option"
68
+ },
69
+ "input-filetype": {
70
+ "char": "f",
71
+ "description": "File type to be processed. By default it is detected by the file extension.",
72
+ "name": "input-filetype",
73
+ "default": "md",
74
+ "hasDynamicHelp": false,
75
+ "multiple": false,
76
+ "options": [
77
+ "md",
78
+ "yaml",
79
+ "yml"
80
+ ],
81
+ "type": "option"
82
+ },
83
+ "output-filetype": {
84
+ "char": "F",
85
+ "description": "File type to be processed. By default it is detected by the file extension.",
86
+ "name": "output-filetype",
87
+ "default": "pdf",
88
+ "hasDynamicHelp": false,
89
+ "multiple": false,
90
+ "options": [
91
+ "pdf",
92
+ "html"
93
+ ],
94
+ "type": "option"
95
+ },
96
+ "stdout": {
97
+ "char": "O",
98
+ "description": "Write to stdout instead of a file.",
99
+ "name": "stdout",
100
+ "allowNo": false,
101
+ "type": "boolean"
102
+ },
58
103
  "template": {
59
104
  "char": "t",
60
105
  "description": "HTML template for the generated PDF file.",
@@ -106,5 +151,5 @@
106
151
  "enableJsonFlag": false
107
152
  }
108
153
  },
109
- "version": "2.11.2"
154
+ "version": "2.12.0"
110
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cenk1cenk2/md-printer",
3
- "version": "2.11.3",
3
+ "version": "2.12.1",
4
4
  "description": "A markdown printer.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -93,21 +93,23 @@
93
93
  "yaml": "^2.8.1"
94
94
  },
95
95
  "devDependencies": {
96
- "@cenk1cenk2/cz-cc": "^2.1.4",
97
- "@cenk1cenk2/eslint-config": "^3.1.65",
96
+ "@cenk1cenk2/cz-cc": "^2.1.5",
97
+ "@cenk1cenk2/eslint-config": "^3.1.67",
98
98
  "@types/config": "^3.3.5",
99
99
  "@types/fs-extra": "^11.0.4",
100
100
  "@types/node": "^24.3.1",
101
101
  "@types/nunjucks": "^3.2.6",
102
102
  "@types/showdown": "^2.0.6",
103
- "eslint": "^9.34.0",
103
+ "eslint": "^9.35.0",
104
104
  "execa": "^9.6.0",
105
105
  "globby": "^14.1.0",
106
106
  "lint-staged": "^16.1.6",
107
- "oclif": "^4.22.16",
107
+ "oclif": "^4.22.18",
108
108
  "prettier": "^3.6.2",
109
+ "prettier-plugin-jinja-template": "^2.1.0",
109
110
  "simple-git-hooks": "^2.13.1",
110
111
  "theme-colors": "^0.1.0",
112
+ "tpm": "^1.4.14",
111
113
  "ts-node": "^10.9.2",
112
114
  "tsconfig-paths": "^4.2.0",
113
115
  "tsdown": "^0.14.2",
@@ -1,3 +1,6 @@
1
+ <!--
2
+ vim: ft=htmldjango
3
+ -->
1
4
  <!DOCTYPE html>
2
5
  <html lang="en">
3
6
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "body_class": [],
3
+ "highlight_style": "atom-one-dark",
4
+ "pdf_options": {
5
+ "format": "A4",
6
+ "margin": "0",
7
+ "printBackground": true
8
+ }
9
+ }
@@ -0,0 +1,132 @@
1
+ @import 'tailwindcss';
2
+ @plugin '@tailwindcss/typography';
3
+
4
+ @theme {
5
+ --color-primary-500: #cd0043;
6
+ --color-gray-50: oklch(98.5% 0.002 247.839);
7
+ --color-gray-100: #abb2bf;
8
+ --color-gray-200: #979eab;
9
+ --color-gray-300: #7c8a9d;
10
+ --color-gray-400: #5c6370;
11
+ --color-gray-500: #4b5263;
12
+ --color-gray-600: #2c333d;
13
+ --color-gray-700: #22282f;
14
+ --color-gray-800: #1e2127;
15
+ --color-gray-900: #17191e;
16
+ --color-gray-950: oklch(13% 0.028 261.692);
17
+ }
18
+
19
+ *,
20
+ ::after,
21
+ ::before,
22
+ ::backdrop,
23
+ ::file-selector-button {
24
+ border-color: var(--color-gray-200, currentcolor);
25
+ }
26
+
27
+ address {
28
+ @apply text-sm;
29
+ }
30
+
31
+ /* Typography styles converted from config */
32
+ .prose a {
33
+ text-decoration: none;
34
+ }
35
+
36
+ .prose h1 {
37
+ font-weight: 700;
38
+ padding-top: 0.1875rem;
39
+ /* 0.75 * 0.25rem */
40
+ padding-bottom: 0.1875rem;
41
+ margin-bottom: 0;
42
+ margin-top: 0;
43
+ border-width: 0;
44
+ }
45
+
46
+ .prose h2 {
47
+ padding-top: 0.125rem;
48
+ /* 0.5 * 0.25rem */
49
+ padding-bottom: 0.125rem;
50
+ margin-bottom: 0;
51
+ margin-top: 0;
52
+ border-width: 0;
53
+ }
54
+
55
+ .prose h3 {
56
+ padding-top: 0.0625rem;
57
+ /* 0.25 * 0.25rem */
58
+ padding-bottom: 0.0625rem;
59
+ margin-bottom: 0;
60
+ margin-top: 0;
61
+ border-width: 0;
62
+ }
63
+
64
+ p {
65
+ margin: 0.1em 0em;
66
+ }
67
+
68
+ .prose blockquote {
69
+ font-weight: 400;
70
+ color: var(--color-gray-600);
71
+ font-style: normal;
72
+ quotes: '\201C' '\201D' '\2018' '\2019';
73
+ }
74
+
75
+ .prose blockquote p:first-of-type::before {
76
+ content: '';
77
+ }
78
+
79
+ .prose blockquote p:last-of-type::after {
80
+ content: '';
81
+ }
82
+
83
+ .prose ul > li {
84
+ padding-left: 1em;
85
+ text-align: left;
86
+ }
87
+
88
+ .prose ol > li {
89
+ padding-left: 1em;
90
+ text-align: left;
91
+ }
92
+
93
+ .prose ol > li::before {
94
+ top: calc(0.875em - 0.1em);
95
+ }
96
+
97
+ .prose ul > li::before {
98
+ top: calc(0.875em - 0.1em);
99
+ }
100
+
101
+ html {
102
+ font-size: 12px;
103
+ }
104
+
105
+ .page-break {
106
+ page-break-after: always;
107
+ }
108
+
109
+ .text-vertical {
110
+ text-orientation: upright;
111
+ writing-mode: vertical-lr;
112
+ display: flex;
113
+ flex-direction: column;
114
+ justify-content: center;
115
+ @apply p-4;
116
+ }
117
+
118
+ table th {
119
+ @apply bg-gray-700 text-white;
120
+ }
121
+
122
+ table tr {
123
+ border-top: 0;
124
+ @apply even:bg-gray-50;
125
+ }
126
+
127
+ table th,
128
+ table td,
129
+ table tr {
130
+ border: 0;
131
+ padding: 0.5em 0.5em;
132
+ }
@@ -0,0 +1,14 @@
1
+ <!--
2
+ vim: ft=htmldjango
3
+ -->
4
+ <!DOCTYPE html>
5
+ <html>
6
+ <head>
7
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
8
+ <meta charset="UTF-8" />
9
+ </head>
10
+
11
+ <body>
12
+ <div class="leading-tight prose">{{ content | safe }}</div>
13
+ </body>
14
+ </html>
@@ -1,30 +1,34 @@
1
+ <!--
2
+ vim: ft=htmldjango
3
+ -->
1
4
  <!DOCTYPE html>
2
5
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Document</title>
8
- </head>
9
- <body>
10
- <div class="max-w-full leading-tight prose">
11
- <h1 class="mb-2 text-center border-b-2">Privatrechnung</h1>
12
- <div class="text-center">
13
- <p class="m-0 font-bold">Rechnungsnummer</p>
14
- <p class="m-0 font-bold">{{ id }}</p>
15
- </div>
16
- <div class="grid grid-cols-2" style="margin-bottom: 1em !important;">
17
- <div class="grid-cols-1">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <title>Document</title>
11
+ </head>
12
+
13
+ <body>
14
+ <div class="max-w-full leading-tight prose">
15
+ <h1 class="mb-2 text-center border-b-2">Privatrechnung</h1>
16
+ <div class="text-center">
17
+ <p class="m-0 font-bold">Rechnungsnummer</p>
18
+ <p class="m-0 font-bold">{{ id }}</p>
19
+ </div>
20
+ <div class="grid grid-cols-2" style="margin-bottom: 1em !important;">
21
+ <div class="grid-cols-1">
18
22
  <div>
19
23
  <h4 class="">Absender</h4>
20
24
  </div>
21
25
  <div>
22
26
  <p class="my-2 font-semibold">{{ sender.name }}</p>
23
27
  {% if sender.email %}
24
- <p class="m-0 my-2">{{ sender.email }}</p>
28
+ <p class="m-0 my-2">{{ sender.email }}</p>
25
29
  {% endif %}
26
30
  {% if sender.phone %}
27
- <p class="m-0 my-2">{{ sender.phone }}</p>
31
+ <p class="m-0 my-2">{{ sender.phone }}</p>
28
32
  {% endif %}
29
33
  <div class="mt-4">
30
34
  <p class="m-0">{{ sender.address }}</p>
@@ -32,18 +36,18 @@
32
36
  <p class="m-0">{{ sender.location }}</p>
33
37
  </div>
34
38
  </div>
35
- </div>
36
- <div class="grid-cols-1 text-right">
39
+ </div>
40
+ <div class="grid-cols-1 text-right">
37
41
  <div>
38
42
  <h4 class="">Empfänger</h4>
39
43
  </div>
40
44
  <div class="leading-tight">
41
45
  <p class="my-2 font-semibold">{{ receiver.name }}</p>
42
46
  {% if receiver.email %}
43
- <p class="m-0 my-2">{{ receiver.email }}</p>
47
+ <p class="m-0 my-2">{{ receiver.email }}</p>
44
48
  {% endif %}
45
49
  {% if receiver.phone %}
46
- <p class="m-0 my-2">{{ receiver.phone }}</p>
50
+ <p class="m-0 my-2">{{ receiver.phone }}</p>
47
51
  {% endif %}
48
52
  <div class="mt-4">
49
53
  <p class="m-0">{{ receiver.address }}</p>
@@ -51,90 +55,74 @@
51
55
  <p class="m-0">{{ receiver.location }}</p>
52
56
  </div>
53
57
  </div>
54
- </div>
58
+ </div>
55
59
  </div>
56
- <div class="border-t-2">
60
+ <div class="border-t-2">
57
61
  <p class="font-bold">Dies ist eine Privatrechnung über eine nicht gewerbliche Tätigkeit. Umsatzsteuer wird daher nicht in Rechnung gestellt.</p>
58
- <table class="w-full rounded-lg border-gray-200 table-fixed">
59
- <thead class="border-b-2 border-primary-500">
60
- <tr class="bg-gray-200">
61
- <th class="px-2 w-full">
62
- Leistung
63
- </th>
64
- <th class="px-8">
65
- Menge
66
- </th>
67
- <th class="px-4">
68
- Einzelpreis
69
- </th>
70
- <th class="px-4">
71
- Gesamtpreis
72
- </th>
73
- </tr>
74
- </thead>
75
- <tbody>
76
- {% set sum = 0 %}
77
- {% for item in items %}
78
- <tr class="even:bg-gray-50">
79
- <td class="px-2">{{ item.description }}</td>
80
- <td class="text-center">{{ item.quantity }} {{ item.type }}</td>
81
- <td class="text-center">{{ item.price }}{{ currency }}</td>
82
- <td class="font-semibold text-center">{{ item.quantity * item.price }}{{ currency }}</td>
83
- </tr>
84
- {% set sum = sum + item.quantity * item.price %}
85
- {% endfor %}
86
- </tbody>
87
- <tfoot class="text-black border-t-2 border-primary-500">
88
- <tr class="border-0 bg-gray-200!">
89
- <td></td>
90
- <td></td>
91
- <th class="text-center">Gesamt</th>
92
- <td class="font-bold text-center text-primary-500">{{ sum }}{{ currency }}</td>
93
- </tr>
94
- </tfoot>
95
- </table>
96
- </div>
97
- <h3>Anmerkung</h3>
98
- <div class="py-2 px-4 mt-0 w-full rounded-lg border-2 border-gray-200">{{ content }}</div>
99
- <h3>Zahlungsdaten</h3>
100
- <div>
101
- <table class="mt-0 w-full rounded-lg border-gray-200 table-fixed">
102
- <thead class="border-b-2 border-gray-500">
103
- <tr class="text-center bg-gray-200">
104
- <th class="px-24">
105
- Name
106
- </th>
107
- <th class="px-24">
108
- Bank
109
- </th>
110
- <th class="px-12">
111
- BIC
112
- </th>
113
- <th class="w-full">
114
- IBAN
115
- </th>
116
- </tr>
117
- </thead>
118
- <tfoot class="text-black border-t-2 border-primary-500">
119
- <tr class="border-0 bg-gray-100! font-semibold text-center">
120
- <td>{{ payment.name }}</td>
121
- <td>{{ payment.bank }}</td>
122
- <td>{{ payment.bic }}</td>
123
- <td>{{ payment.iban }}</td>
124
- </tr>
125
- </tfoot>
126
- </table>
127
- </div>
128
- <hr class="border-t-2 mt-0! mb-2!" style="margin-top: 0 !important; margin-bottom: 1.5em !important;" />
129
- <div class="grid grid-cols-2 font-semibold">
130
- <div class="flex flex-row items-center text-center">
131
- <div class="w-full">
132
- <p class="my-4">{{ location }}</p>
133
- <p class="my-4">{{ date }}</p>
62
+ <table class="w-full rounded-lg border-gray-200 table-fixed">
63
+ <thead class="border-b-2 border-primary-500">
64
+ <tr class="bg-gray-200">
65
+ <th class="px-2 w-full">Leistung</th>
66
+ <th class="px-8">Menge</th>
67
+ <th class="px-4">Einzelpreis</th>
68
+ <th class="px-4">Gesamtpreis</th>
69
+ </tr>
70
+ </thead>
71
+ <tbody>
72
+ {% set sum = 0 %}
73
+ {% for item in items %}
74
+ <tr class="even:bg-gray-50">
75
+ <td class="px-2">{{ item.description }}</td>
76
+ <td class="text-center">{{ item.quantity }} {{ item.type }}</td>
77
+ <td class="text-center">{{ item.price }}{{ currency }}</td>
78
+ <td class="font-semibold text-center">{{ item.quantity * item.price }}{{ currency }}</td>
79
+ </tr>
80
+ {% set sum = sum + item.quantity * item.price %}
81
+ {% endfor %}
82
+ </tbody>
83
+ <tfoot class="text-black border-t-2 border-primary-500">
84
+ <tr class="border-0 bg-gray-200!">
85
+ <td></td>
86
+ <td></td>
87
+ <th class="text-center">Gesamt</th>
88
+ <td class="font-bold text-center text-primary-500">{{ sum }}{{ currency }}</td>
89
+ </tr>
90
+ </tfoot>
91
+ </table>
92
+ </div>
93
+ <h3>Anmerkung</h3>
94
+ <div class="py-2 px-4 mt-0 w-full rounded-lg border-2 border-gray-200">{{ content }}</div>
95
+ <h3>Zahlungsdaten</h3>
96
+ <div>
97
+ <table class="mt-0 w-full rounded-lg border-gray-200 table-fixed">
98
+ <thead class="border-b-2 border-gray-500">
99
+ <tr class="text-center bg-gray-200">
100
+ <th class="px-24">Name</th>
101
+ <th class="px-24">Bank</th>
102
+ <th class="px-12">BIC</th>
103
+ <th class="w-full">IBAN</th>
104
+ </tr>
105
+ </thead>
106
+ <tfoot class="text-black border-t-2 border-primary-500">
107
+ <tr class="border-0 bg-gray-100! font-semibold text-center">
108
+ <td>{{ payment.name }}</td>
109
+ <td>{{ payment.bank }}</td>
110
+ <td>{{ payment.bic }}</td>
111
+ <td>{{ payment.iban }}</td>
112
+ </tr>
113
+ </tfoot>
114
+ </table>
115
+ </div>
116
+ <hr class="border-t-2 mt-0! mb-2!" style="margin-top: 0 !important; margin-bottom: 1.5em !important;" />
117
+ <div class="grid grid-cols-2 font-semibold">
118
+ <div class="flex flex-row items-center text-center">
119
+ <div class="w-full">
120
+ <p class="my-4">{{ location }}</p>
121
+ <p class="my-4">{{ date }}</p>
122
+ </div>
134
123
  </div>
124
+ <div class="p-20 text-center rounded-lg border-2 border-gray-200">Unterschrift</div>
135
125
  </div>
136
- <div class="p-20 text-center rounded-lg border-2 border-gray-200">Unterschrift</div>
137
126
  </div>
138
- </div>
139
- </body>
127
+ </body>
140
128
  </html>
@@ -1,4 +0,0 @@
1
- /** @type {import("tailwindcss").Config} */
2
- module.exports = {
3
- content: ['./template.html.j2']
4
- }