@cenk1cenk2/md-printer 3.2.24 → 3.4.0

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.
Files changed (54) hide show
  1. package/README.md +213 -57
  2. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.mjs +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateMetadata.mjs +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateParam.mjs +1 -1
  5. package/dist/commands/index.mjs +3 -1
  6. package/dist/components/Localized.svelte +18 -0
  7. package/dist/components/SignatureBlock.svelte +8 -46
  8. package/dist/components/Translate.svelte +15 -0
  9. package/dist/components/extension.ts +8 -0
  10. package/dist/components/index.ts +3 -0
  11. package/dist/constants/attachment.constants.mjs +7 -0
  12. package/dist/constants/index.mjs +3 -2
  13. package/dist/constants/template.constants.mjs +26 -2
  14. package/dist/interfaces/attachment.interface.mjs +1 -0
  15. package/dist/modules/md-printer/md-printer.handler.mjs +4 -2
  16. package/dist/modules/md-printer/md-printer.module.mjs +1 -1
  17. package/dist/modules/md-printer/md-printer.schema.mjs +12 -1
  18. package/dist/services/i18n.service.mjs +6 -4
  19. package/dist/services/index.mjs +2 -1
  20. package/dist/services/markdown.service.mjs +1 -1
  21. package/dist/services/pdf/pdf.service.mjs +124 -0
  22. package/dist/services/printer.module.mjs +3 -1
  23. package/dist/services/printer.service.mjs +55 -11
  24. package/dist/services/renderer/browser.service.mjs +3 -3
  25. package/dist/services/renderer/renderer.service.mjs +3 -3
  26. package/dist/services/session/preview.service.mjs +2 -2
  27. package/dist/services/session/session.service.mjs +11 -11
  28. package/dist/services/signature/adapters/a-trust.adapter.mjs +2 -2
  29. package/dist/services/signature/signature.service.mjs +2 -2
  30. package/dist/services/template/assets.mjs +1 -1
  31. package/dist/services/template/render.entry.ts +6 -2
  32. package/dist/services/template/svelte.plugin.mjs +12 -7
  33. package/dist/services/template/template.schema.mjs +27 -0
  34. package/dist/services/template/template.service.mjs +123 -26
  35. package/oclif.manifest.json +16 -2
  36. package/package.json +14 -24
  37. package/templates/default/Template.svelte +0 -83
  38. package/templates/default/settings.json +0 -7
  39. package/templates/invoice/Template.svelte +0 -247
  40. package/templates/invoice/components/Client.svelte +0 -42
  41. package/templates/invoice/components/Company.svelte +0 -41
  42. package/templates/invoice/components/Items.svelte +0 -66
  43. package/templates/invoice/components/Notes.svelte +0 -21
  44. package/templates/invoice/components/Payment.svelte +0 -25
  45. package/templates/invoice/components/Signature.svelte +0 -34
  46. package/templates/invoice/components/Totals.svelte +0 -47
  47. package/templates/invoice/i18n/de.yaml +0 -30
  48. package/templates/invoice/i18n/en.yaml +0 -30
  49. package/templates/invoice/round.js +0 -2
  50. package/templates/invoice/settings.json +0 -11
  51. package/templates/kilic.dev/Template.svelte +0 -102
  52. package/templates/kilic.dev/settings.json +0 -7
  53. package/templates/mail/Template.svelte +0 -166
  54. package/templates/mail/settings.json +0 -7
package/README.md CHANGED
@@ -92,7 +92,7 @@ Markdown files take YAML front-matter. For YAML input the same keys are top-leve
92
92
 
93
93
  | Key | Description |
94
94
  | ----------------- | --------------------------------------------------------------------------------------------- |
95
- | `template` | Built-in template name or path to a template directory. |
95
+ | `template` | Built-in template name, path to a template directory or an object, see [Template sources](#template-sources). |
96
96
  | `dest` | Output file, relative to the current directory, not to the input file. |
97
97
  | `outputFiletype` | `pdf` (default) or `html`. |
98
98
  | `language` | Language for template translations. |
@@ -100,6 +100,8 @@ Markdown files take YAML front-matter. For YAML input the same keys are top-leve
100
100
  | `documentTitle` | Document title. |
101
101
  | `pdfOptions` | Puppeteer [PDF options](https://pptr.dev/api/puppeteer.pdfoptions), merged over the defaults. |
102
102
  | `sign` | Signing options, see [Signing](#signing). |
103
+ | `attachments` | PDFs merged into the output, see [Attachments and compression](#attachments-and-compression). |
104
+ | `compress` | `true` compresses the images of the whole output PDF, attachments included. |
103
105
 
104
106
  A template may carry a `settings.json` with defaults for its documents:
105
107
 
@@ -133,6 +135,29 @@ pdfOptions:
133
135
  - `continuous` is enabled when any of `--continuous`, front-matter or `settings.json` enables it.
134
136
  - The document title is `--title`, then `documentTitle`, then the `<title>` set by the template, then the input file name.
135
137
 
138
+ ## Attachments and compression
139
+
140
+ PDF output can carry other PDFs, such as certificates or reference letters, and can be compressed.
141
+
142
+ ```yaml
143
+ ---
144
+ compress: true
145
+ attachments:
146
+ - file: ./assets/references/diploma.pdf
147
+ position: before
148
+ - file: https://example.com/certificate.pdf
149
+ position: 2
150
+ compress: true
151
+ - file: ./assets/references/letter.pdf
152
+ ---
153
+ ```
154
+
155
+ - `file` is a path relative to the input file, confined to its directory like every other local asset, or an `http(s)` URL.
156
+ - `position` is `before` the document, `after` it (the default) or a page number: the attachment starts at that page of the rendered document, so `2` places it between the first and the second page, and the page after the last one appends it. Page numbers always count pages of the rendered document, not of other attachments, and attachments sharing a position keep the order they are listed in.
157
+ - `compress: true` on an attachment re-encodes its images, `compress: true` at the top level does the same for the whole output, attachments included. Images are scaled down to at most 2000px and stored as JPEG at quality 75 whenever that makes them smaller; masks and images with transparency keep their lossless encoding.
158
+ - Attachments and compression are applied before signing, so a signature covers them. They are only supported for PDF output.
159
+ - `--preview` rebuilds when a local attachment changes.
160
+
136
161
  ## Development
137
162
 
138
163
  ```sh
@@ -152,25 +177,93 @@ md-printer document.md out.pdf --preview --no-open
152
177
 
153
178
  ### Built-in templates
154
179
 
180
+ Every built-in template is a package of its own, `@cenk1cenk2/md-printer-template-<name>`. md-printer depends on all of them, so they install with it, and each one brings the fonts, icons and stylesheets it uses as its own dependencies. `default`, `kilic.dev` and `mail` are published from the `templates/` directory of this repository, `cv` from https://gitlab.kilic.dev/cenk/cv and `invoice` from https://gitlab.kilic.dev/cenk/invoices.
181
+
155
182
  | Name | Description |
156
183
  | ----------- | -------------------------------------------------------------------------------------------------------------- |
157
184
  | `default` | GitHub-flavoured Markdown styling with Montserrat. This is the default. |
158
185
  | `kilic.dev` | Same styling with a logo in the header and a footer with title and page numbers. |
159
186
  | `invoice` | Invoice from YAML data (`company`, `client`, `payment`, `items`, `currency`, `date`, `id`) and Markdown notes. Translations in `en` and `de`, a signature block when signing. See `test/invoice.md`. |
187
+ | `cv` | CV in the table layout of a classic Word CV, from front-matter (`name`, `suffix`, `title`, `tagline`, `photo`) and Markdown sections placed with the `::section`, `:::details` / `::detail` / `:::pair`, `:::entry`, `::::group` / `:::course`, `::skill`, `:::note` and `:::languages` / `::language` components. The PDF is tagged and has an outline, so screening tools read it as text. See `test/cv.md`. |
160
188
  | `mail` | Plain content with no margin and Tailwind typography, meant for HTML output. |
161
189
 
190
+ ### Template sources
191
+
192
+ `-t` and a string `template` in front-matter take a built-in name or a path. A value without a path separator is a built-in name, anything with `/` or starting with `.` or `~` is a path, relative to the current directory.
193
+
194
+ `template` in front-matter can also be an object selecting the source by `type`. This is the only way to use a template from an npm package.
195
+
196
+ ```yaml
197
+ ---
198
+ template:
199
+ type: package
200
+ name: '@cenk1cenk2/md-printer-template-cv'
201
+ ---
202
+ ```
203
+
204
+ | `type` | Keys | Description |
205
+ | ----------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
206
+ | `builtin` | `name` | The package `@cenk1cenk2/md-printer-template-<name>` from md-printer's own dependencies. |
207
+ | `package` | `name`, `directory?` | An npm package, resolved from the directory of the input file (its `node_modules`, walking up like Node does), then from the current directory, then from md-printer's own dependencies. The package directory is the template directory. `directory` selects a subdirectory of the package instead, and may not leave the package, symlinks included. |
208
+ | `directory` | `path` | A template directory, relative to the directory of the input file. |
209
+
210
+ The same on the command line, where flags win over front-matter. `--template-type` says how to read `-t`, and `--template-directory` is the `directory` of a package. A `--template-type directory` path is relative to the input file too. Without `--template-type`, `-t` is a built-in name or a path as above.
211
+
212
+ ```sh
213
+ md-printer document.md --template-type package -t @cenk1cenk2/md-printer-template-cv
214
+ md-printer document.md --template-type package -t my-templates --template-directory letters
215
+ md-printer document.md --template-type builtin -t cv
216
+ md-printer document.md --template-type directory -t ./template
217
+ ```
218
+
219
+ An unknown `type`, a missing or unknown key, or a `package` that is not installed fails the run. A package has to allow access to its `package.json`, so one with an `exports` map lists `./package.json` there.
220
+
162
221
  ### Authoring a template
163
222
 
164
- A template is a directory. Pass it with `-t ./path` or `template: ./path`. A value without a path separator is a built-in name, anything with `/` or starting with `.` or `~` is a path.
223
+ A template is a directory. Pass it with `-t ./path`, `template: ./path` or as one of the other [template sources](#template-sources). Its Svelte files and its TypeScript are kept apart, so ESLint, Prettier and tsconfig can target each group:
165
224
 
166
225
  ```
167
226
  my-template/
168
- Template.svelte required
169
- settings.json optional
170
- i18n/ optional, en.yaml, de.json, ...
171
- components/ optional, *.svelte placed from markdown, see Markdown components
227
+ package.json optional, see below
228
+ settings.json optional, always at the root
229
+ i18n/ optional, en.yaml, de.json, ...
230
+ template/
231
+ Template.svelte required
232
+ components/*.svelte optional, placed from markdown, see Markdown components
233
+ src/
234
+ extension.ts optional, see Extension module
235
+ *.ts optional, plain modules
172
236
  ```
173
237
 
238
+ `files` in `settings.json` moves any of these. Every key is optional and defaults to the layout above:
239
+
240
+ ```json
241
+ {
242
+ "files": {
243
+ "template": "template/Template.svelte",
244
+ "components": "template/components",
245
+ "extension": "src/extension.ts",
246
+ "i18n": "i18n"
247
+ }
248
+ }
249
+ ```
250
+
251
+ - `components`, `extension` and `i18n` also take an array, read in order: a component of a later folder replaces one of the same name, an export of a later extension module replaces one of the same name, and a key of a later i18n folder replaces the same key while nested keys merge. Use this to share components or translations between templates:
252
+
253
+ ```json
254
+ {
255
+ "files": {
256
+ "components": ["template/shared", "template/components"],
257
+ "extension": ["src/format.ts", "src/extension.ts"],
258
+ "i18n": ["i18n/shared", "i18n"]
259
+ }
260
+ }
261
+ ```
262
+
263
+ - Paths are relative to the template directory. A path leaving it, a path that does not exist and an unknown key each fail the build.
264
+ - A template written for 3.x keeps working: for each key `files` does not set, when the default location is missing, the root location is used if it exists, that is `Template.svelte`, `components/` and `extension.*`. A path set in `files` never falls back.
265
+ - A package keeps what it does not publish out with an `.npmignore`, as the built-in templates do, for example `release.config.cjs` and `CHANGELOG.md`.
266
+
174
267
  `Template.svelte` is a Svelte 5 component receiving these props:
175
268
 
176
269
  | Prop | Description |
@@ -180,10 +273,15 @@ my-template/
180
273
  | `t` | i18next translate function, loaded from `i18n/<language>.(yaml\|yml\|json)`. |
181
274
  | `language` | The resolved language. |
182
275
  | `signature` | `{ date, name?, issuer?, serial? }` while signing is enabled, otherwise `undefined`. |
276
+ | `extension` | The named exports of the [extension module](#extension-module), empty without one. |
183
277
 
184
278
  Set the document title with `<svelte:head><title>...</title></svelte:head>`.
185
279
 
186
- Styles that need Tailwind CSS v4 or package imports go into a `<style lang="postcss">` block in `Template.svelte` or any component it imports. These blocks are collected and compiled together, so they can `@import 'tailwindcss'` and use `@plugin`, `@theme` and utility classes. Plain `<style>` blocks stay scoped Svelte styles. `@import` of packages (for example `@fontsource/*`, `github-markdown-css`) resolves from the template directory first and also from md-printer's own dependencies, so the template does not need a `node_modules`. The selected highlight.js theme is always included. Local `url()` and image references are inlined as data URIs.
280
+ Components may be written in TypeScript with `<script lang="ts">`, and `.ts` modules can be imported, as `./round.js` or `./round.ts`. Svelte strips the types of components, so only type annotations are allowed there, no enums or other TypeScript that emits code; `.ts` modules take full TypeScript. Types are not checked while rendering. The built-in templates are written this way.
281
+
282
+ A template can bring its own dependencies: give the template directory a `package.json` and install it there, as the built-in templates do. Imports in components, `.ts` modules, `@import` in `<style lang="postcss">` and imported images such as `@fortawesome/fontawesome-free/svgs/solid/user.svg` resolve from the template's `node_modules` first and fall back to md-printer's own dependencies. md-printer does not install them for you; a missing one fails the build with the directory to install it in.
283
+
284
+ Styles that need Tailwind CSS v4 or package imports go into a `<style lang="postcss">` block in `Template.svelte` or any component it imports. These blocks are collected and compiled together, so they can `@import 'tailwindcss'` and use `@plugin`, `@theme` and utility classes. Plain `<style>` blocks stay scoped Svelte styles. `@import` of packages (for example `@fontsource/*`, `github-markdown-css`) resolves from the template directory first and then from md-printer's own dependencies, which cover `tailwindcss` and the highlight.js themes. The selected highlight.js theme is always included. Local `url()` and image references are inlined as data URIs.
187
285
 
188
286
  Images are imported, which inlines them as data URIs:
189
287
 
@@ -195,6 +293,31 @@ Images are imported, which inlines them as data URIs:
195
293
  <img src={logo} alt="logo" />
196
294
  ```
197
295
 
296
+ #### Extension module
297
+
298
+ A template may contain one extension module, `src/extension.ts`, `.mts`, `.cts`, `.js`, `.mjs` or `.cjs`, so the template can bring its own functions. More than one at the default location fails the build, naming the files; to combine several modules, list them in `files.extension`.
299
+
300
+ ```ts
301
+ // src/extension.ts
302
+ export function money(value: number): string {
303
+ return `${value.toFixed(2)} EUR`
304
+ }
305
+ ```
306
+
307
+ It is compiled in the same bundle as the template, so TypeScript works and its imports resolve like those of the template's own modules, from its `node_modules` first. Its named exports are available as the `extension` prop of `Template.svelte`, through the `md-printer:extension` context with `getContext`, and in any component with the `getExtension` helper:
308
+
309
+ ```svelte
310
+ <script lang="ts">
311
+ import { getExtension } from '@cenk1cenk2/md-printer/components'
312
+
313
+ const { money } = getExtension<typeof import('../../src/extension.js')>()
314
+ </script>
315
+
316
+ <span>{money(12)}</span>
317
+ ```
318
+
319
+ The default export is left out. An extension only exposes its exports, md-printer calls none of them by itself. `--preview` rebuilds when the module changes, is added or is removed.
320
+
198
321
  #### Shared components
199
322
 
200
323
  Import from `@cenk1cenk2/md-printer/components`:
@@ -208,7 +331,9 @@ Import from `@cenk1cenk2/md-printer/components`:
208
331
  | `TotalPages` | Total page count, filled by Chrome. Header and footer only. |
209
332
  | `Title` | Document title, filled by Chrome. Header and footer only. |
210
333
  | `Date` | Print date, filled by Chrome. Header and footer only. |
211
- | `SignatureBlock` | Visible signature block for the `signature` prop. Needs Tailwind in a `<style lang="postcss">` block. Optional `labels` prop overrides its texts. |
334
+ | `Localized` | Text for the language of the document from props named after languages, `<Localized en="Invoice" de="Rechnung" />` or `:localized{en="Invoice" de="Rechnung"}`, for words that need no entry in `i18n/`. Takes the language, then its base language (`de` for `de-AT`), then `fallback`, otherwise nothing. In a template a prop can also be a snippet for markup. Plain text without a wrapper element. |
335
+ | `Translate` | Text translated with the `i18n/` directory of the template, `<Translate key="present" />` or `::translate{key="present"}`, inline as `:translate{key="present"}`. Plain text without a wrapper element. Optional `fallback` for a missing key, otherwise the key itself is printed. |
336
+ | `SignatureBlock` | Hands the signature, `{ date, name?, issuer?, serial? }`, to its `children` snippet, so the template draws the visible block with its own markup and styles. Takes the `signature` prop, or as `::signature-block` the signature of the document. Renders nothing without a signature or without `children`, so a template gives `::signature-block` its markup with its own `template/components/SignatureBlock.svelte`, see below. |
212
337
 
213
338
  ```svelte
214
339
  <script>
@@ -234,10 +359,32 @@ Import from `@cenk1cenk2/md-printer/components`:
234
359
 
235
360
  #### Markdown components
236
361
 
237
- Every `*.svelte` file in the template's `components/` directory can be placed from markdown, see [Components](#components). The shared components can be placed the same way, for example `::page-break` or `::signature-block`, except `Markdown`. A template component with the same name as a shared one replaces it.
362
+ Every `*.svelte` file in the template's `template/components/` directory can be placed from markdown, see [Components](#components). The shared components can be placed the same way, for example `::page-break` or `::signature-block`, except `Markdown`.
363
+
364
+ A template replaces or extends any shared component, `SignatureBlock` and `Translate` included, with a file of the same name in `template/components/`:
365
+
366
+ - **Replace:** `template/components/SignatureBlock.svelte` is what `::signature-block` renders from then on. This is about directives: a component that imports `SignatureBlock` from `@cenk1cenk2/md-printer/components` keeps getting the original, so import `./components/SignatureBlock.svelte` to use the replacement in the template itself.
367
+ - **Extend:** that import always reaches md-printer's original, also from a file with the same name, so a component can wrap it:
368
+
369
+ ```svelte
370
+ <!-- template/components/SignatureBlock.svelte -->
371
+ <script>
372
+ import { SignatureBlock } from '@cenk1cenk2/md-printer/components'
373
+
374
+ let props = $props()
375
+ </script>
376
+
377
+ <SignatureBlock {...props}>
378
+ {#snippet children(signature)}
379
+ <section class="my-8">
380
+ <strong>Digitally signed</strong> by {signature.name} on {signature.date}
381
+ </section>
382
+ {/snippet}
383
+ </SignatureBlock>
384
+ ```
238
385
 
239
386
  ```svelte
240
- <!-- components/Callout.svelte -->
387
+ <!-- template/components/Callout.svelte -->
241
388
  <script>
242
389
  import { getContext } from 'svelte'
243
390
 
@@ -277,7 +424,7 @@ md-printer invoice.yaml -t invoice --sign --sign-certificate ~/id-austria.pem
277
424
  - Signing only works for PDF output and is skipped with `--preview`.
278
425
  - The signing session has to run in your browser. md-printer opens a temporary local page in an app window of the discovered browser, using your normal profile (or with `xdg-open` when none is found), you log in with ID Austria in the A-Trust page, and paste the resulting signature (starts with `MII`) back into the local page. A paste that is cut short is refused on the page, so you can copy it again. It waits until the signature arrives, press Ctrl+C to abort.
279
426
  - The returned signature is always verified before it is embedded: its digest must match this document and it must be cryptographically valid. The result is printed with the signer name, serial and issuer. A failed verification fails the run, and with `--log-level debug` the returned signature is written next to the output as `<output>.cms.der`.
280
- - With a certificate, the signer name, issuer and serial are passed to the template as `signature`, so `SignatureBlock` can show who signed and when. The certificate must be valid at signing time, and the returned signature is rejected if it was not made with it. Without a certificate only the date is available.
427
+ - With a certificate, the signer name, issuer and serial are passed to the template as `signature`, so the template can show who signed and when through `SignatureBlock`. The certificate must be valid at signing time, and the returned signature is rejected if it was not made with it. Without a certificate only the date is available.
281
428
  - Verify the result at https://www.signaturpruefung.gv.at.
282
429
 
283
430
  ## Migrating from 2.x
@@ -302,58 +449,67 @@ Renders a markdown or yaml file through a Svelte template into a PDF or HTML doc
302
449
  USAGE
303
450
  $ md-printer [FILE] [OUTPUT] [--log-level
304
451
  silent|direct|fatal|error|warn|info|verbose|debug|trace] [-I] [-f
305
- md|yaml|yml] [-F pdf|html] [-O] [-t <value>] [-T <value>] [-l <value>] [-b
306
- <value>] [--continuous] [-p] [--host 127.0.0.1|localhost|::1] [--port
307
- <value>] [--open] [--sign] [--sign-provider a-trust] [--sign-certificate
308
- <value>]
452
+ md|yaml|yml] [-F pdf|html] [-O] [-t <value>] [--template-type <value>]
453
+ [--template-directory <value>] [-T <value>] [-l <value>] [-b <value>]
454
+ [--continuous] [-p] [--host 127.0.0.1|localhost|::1] [--port <value>]
455
+ [--open] [--sign] [--sign-provider a-trust] [--sign-certificate <value>]
309
456
 
310
457
  ARGUMENTS
311
458
  [FILE] Markdown or yaml file to render.
312
459
  [OUTPUT] Output file. Overwrites the one defined in front-matter.
313
460
 
314
461
  FLAGS
315
- -F, --output-filetype=<option> Output file type, defaults to pdf. Overwrites
316
- the one defined in front-matter.
317
- <options: pdf|html>
318
- -I, --stdin Read the input from stdin.
319
- -O, --stdout Write the output to stdout instead of a file.
320
- -T, --title=<value> Document title. Overwrites the one defined in
321
- front-matter or the template.
322
- -b, --browser=<value> Browser executable used for printing.
323
- Discovered automatically when omitted (Brave,
324
- Chrome, Chromium, Edge).
325
- -f, --input-filetype=<option> Input file type. Detected from the file
326
- extension, markdown for stdin and unknown
327
- extensions.
328
- <options: md|yaml|yml>
329
- -l, --language=<value> Language for template translations. Overwrites
330
- the one defined in front-matter and template
331
- settings.
332
- -p, --preview Rebuild whenever the input, the template or
333
- its assets change, show the HTML and the PDF
334
- in a live preview and rewrite the output file
335
- when there is one.
336
- -t, --template=<value> Built-in template name or path to a template
337
- directory, defaults to "default". Overwrites
338
- the one defined in front-matter.
339
- --continuous Render the PDF as a single continuous page
340
- without page breaks.
341
- --host=<option> [default: 127.0.0.1] Loopback host for the
342
- preview server, it serves the files next to
343
- the input and is never exposed to the network.
344
- <options: 127.0.0.1|localhost|::1>
345
- --[no-]open Open the preview window, --no-open keeps
346
- rebuilding and writing without one.
347
- --port=<value> Port for the preview server, random when 0.
348
- --sign Sign the PDF with a qualified electronic
349
- signature through ID Austria (A-Trust).
350
- --sign-certificate=<value> Signer certificate (DER or PEM) used for the
351
- visible signature block and to verify the
352
- returned signature.
353
- --sign-provider=<option> Signature provider, defaults to a-trust.
354
- Overwrites the one defined in front-matter and
355
- template settings.
356
- <options: a-trust>
462
+ -F, --output-filetype=<option> Output file type, defaults to pdf.
463
+ Overwrites the one defined in front-matter.
464
+ <options: pdf|html>
465
+ -I, --stdin Read the input from stdin.
466
+ -O, --stdout Write the output to stdout instead of a
467
+ file.
468
+ -T, --title=<value> Document title. Overwrites the one defined
469
+ in front-matter or the template.
470
+ -b, --browser=<value> Browser executable used for printing.
471
+ Discovered automatically when omitted
472
+ (Brave, Chrome, Chromium, Edge).
473
+ -f, --input-filetype=<option> Input file type. Detected from the file
474
+ extension, markdown for stdin and unknown
475
+ extensions.
476
+ <options: md|yaml|yml>
477
+ -l, --language=<value> Language for template translations.
478
+ Overwrites the one defined in front-matter
479
+ and template settings.
480
+ -p, --preview Rebuild whenever the input, the template or
481
+ its assets change, show the HTML and the PDF
482
+ in a live preview and rewrite the output
483
+ file when there is one.
484
+ -t, --template=<value> Built-in template name or path to a template
485
+ directory, defaults to "default", read as
486
+ --template-type when given. Overwrites the
487
+ one defined in front-matter.
488
+ --continuous Render the PDF as a single continuous page
489
+ without page breaks.
490
+ --host=<option> [default: 127.0.0.1] Loopback host for the
491
+ preview server, it serves the files next to
492
+ the input and is never exposed to the
493
+ network.
494
+ <options: 127.0.0.1|localhost|::1>
495
+ --[no-]open Open the preview window, --no-open keeps
496
+ rebuilding and writing without one.
497
+ --port=<value> Port for the preview server, random when 0.
498
+ --sign Sign the PDF with a qualified electronic
499
+ signature through ID Austria (A-Trust).
500
+ --sign-certificate=<value> Signer certificate (DER or PEM) used for the
501
+ visible signature block and to verify the
502
+ returned signature.
503
+ --sign-provider=<option> Signature provider, defaults to a-trust.
504
+ Overwrites the one defined in front-matter
505
+ and template settings.
506
+ <options: a-trust>
507
+ --template-directory=<value> Directory inside the package to use as the
508
+ template, only valid with --template-type
509
+ package.
510
+ --template-type=<value> How to read --template, one of builtin,
511
+ package or directory, like the type of the
512
+ object form in front-matter.
357
513
 
358
514
  CLI FLAGS
359
515
  --log-level=<option> [default: INFO, env: LOG_LEVEL] Set the log level of the
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateMetadata.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateParam.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
@@ -1,7 +1,7 @@
1
1
  import { MdPrinterHandler } from "../modules/md-printer/md-printer.handler.mjs";
2
2
  import { MdPrinterModule } from "../modules/md-printer/md-printer.module.mjs";
3
3
  import { MdPrinterSchema } from "../modules/md-printer/md-printer.schema.mjs";
4
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Args, Command, Flags, UseHandler } from "@cenk1cenk2/oclif-common";
6
6
  // src/commands/index.ts
7
7
  let MDPrinter = class MDPrinter extends Command {
@@ -12,6 +12,8 @@ let MDPrinter = class MDPrinter extends Command {
12
12
  ["output-filetype"]: Flags.string({ char: "F" }),
13
13
  stdout: Flags.boolean({ char: "O" }),
14
14
  template: Flags.string({ char: "t" }),
15
+ ["template-type"]: Flags.string(),
16
+ ["template-directory"]: Flags.string(),
15
17
  title: Flags.string({ char: "T" }),
16
18
  language: Flags.string({ char: "l" }),
17
19
  browser: Flags.string({ char: "b" }),
@@ -0,0 +1,18 @@
1
+ <!--
2
+ @component
3
+ Text picked by the language of the document from props named after languages, such as `en` and `de`, for words that need no entry in `i18n/`. Takes the
4
+ language as it is, then its base language, then `fallback`. In a template a prop can also be a snippet. Plain text without a wrapper element, like Translate.
5
+ -->
6
+ <script>
7
+ import { getContext } from 'svelte'
8
+
9
+ /** @type {{ fallback?: string | import('svelte').Snippet } & Record<string, string | import('svelte').Snippet | undefined>} */
10
+ let { fallback, ...languages } = $props()
11
+
12
+ /** @type {import('../interfaces/template.interface.js').DocumentContext} */
13
+ const { language } = getContext('md-printer:document')
14
+
15
+ const value = $derived(languages[language] ?? languages[language.split('-')[0]] ?? fallback)
16
+ </script>
17
+
18
+ {#if typeof value === 'function'}{@render value()}{:else}{value ?? ''}{/if}
@@ -1,56 +1,18 @@
1
1
  <!--
2
2
  @component
3
- Visible block for a digitally signed document, filled from the `signature` prop md-printer passes to templates when signing is enabled.
4
- Styled with Tailwind classes, so the template needs a `<style lang="postcss">` block importing Tailwind.
3
+ Passes the signature of a digitally signed document to the `children` snippet, which draws the block, so the template owns its markup and styles. The
4
+ signature comes from the `signature` prop or, as a directive, from the document context. Renders nothing without a signature or without `children`.
5
5
  -->
6
6
  <script>
7
+ import { getContext } from 'svelte'
8
+
7
9
  /**
8
10
  * @type {{
9
- * signature: { date: string, name?: string, issuer?: string, serial?: string },
10
- * labels?: { title?: string, by?: string, date?: string, issuer?: string, verify?: string },
11
- * class?: string
11
+ * signature?: import('../interfaces/signature.interface.js').MdPrinterSignature,
12
+ * children?: import('svelte').Snippet<[import('../interfaces/signature.interface.js').MdPrinterSignature]>
12
13
  * }}
13
14
  */
14
- let { signature, labels = {}, class: className = '' } = $props()
15
-
16
- const text = $derived({
17
- title: 'Digitally signed',
18
- by: 'Signed by',
19
- date: 'Date',
20
- issuer: 'Certificate',
21
- verify: 'This document carries a qualified electronic signature. Verify it at',
22
- ...labels
23
- })
15
+ let { signature = getContext('md-printer:document').signature, children } = $props()
24
16
  </script>
25
17
 
26
- <div class="w-full overflow-hidden rounded-xl border-y-2 shadow-md shadow-gray-300 border-gray-900! text-xs {className}">
27
- <div class="flex items-center gap-3 px-5 py-3 bg-gray-900 text-white">
28
- <svg class="w-5 h-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
29
- <path d="M12 3l7 3v6c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z" />
30
- <path d="M9 12l2 2 4-4" />
31
- </svg>
32
- <span class="font-extrabold uppercase tracking-wider">{text.title}</span>
33
- </div>
34
- <div class="flex flex-col px-5 py-2">
35
- {#if signature.name}
36
- <div class="flex justify-between gap-4 py-2">
37
- <span class="font-semibold text-gray-400">{text.by}</span>
38
- <span class="font-semibold text-right">{signature.name}</span>
39
- </div>
40
- {/if}
41
- <div class="flex justify-between gap-4 py-2 border-t border-t-gray-100/30">
42
- <span class="font-semibold text-gray-400">{text.date}</span>
43
- <span class="text-right">{signature.date}</span>
44
- </div>
45
- {#if signature.issuer}
46
- <div class="flex justify-between gap-4 py-2 border-t border-t-gray-100/30">
47
- <span class="font-semibold text-gray-400">{text.issuer}</span>
48
- <span class="text-right text-[0.6rem]">{signature.issuer} · {signature.serial}</span>
49
- </div>
50
- {/if}
51
- </div>
52
- <p class="px-5 py-3 bg-gray-50 text-[0.6rem] text-gray-400 leading-snug">
53
- {text.verify}
54
- <a href="https://www.signaturpruefung.gv.at" class="font-semibold text-gray-900 underline">signaturpruefung.gv.at</a>
55
- </p>
56
- </div>
18
+ {#if signature && children}{@render children(signature)}{/if}
@@ -0,0 +1,15 @@
1
+ <!--
2
+ @component
3
+ Text translated with the `i18n/` directory of the template. Plain text without a wrapper element, so it flows inside paragraphs and table cells.
4
+ -->
5
+ <script>
6
+ import { getContext } from 'svelte'
7
+
8
+ /** @type {{ key: string, fallback?: string }} */
9
+ let { key, fallback } = $props()
10
+
11
+ /** @type {import('../interfaces/template.interface.js').DocumentContext} */
12
+ const { t } = getContext('md-printer:document')
13
+ </script>
14
+
15
+ {t(key, { defaultValue: fallback ?? key })}
@@ -0,0 +1,8 @@
1
+ import { getContext } from 'svelte'
2
+
3
+ import type { TemplateExtension } from '../interfaces/template.interface.js'
4
+
5
+ /** The named exports of the extension module of the template, empty without one. */
6
+ export function getExtension<T extends TemplateExtension = TemplateExtension>(): T {
7
+ return getContext('md-printer:extension')
8
+ }
@@ -1,4 +1,6 @@
1
+ export { getExtension } from './extension.js'
1
2
  export { default as Date } from './Date.svelte'
3
+ export { default as Localized } from './Localized.svelte'
2
4
  export { default as Markdown } from './Markdown.svelte'
3
5
  export { default as Page } from './Page.svelte'
4
6
  export { default as PageBreak } from './PageBreak.svelte'
@@ -6,3 +8,4 @@ export { default as PageNumber } from './PageNumber.svelte'
6
8
  export { default as SignatureBlock } from './SignatureBlock.svelte'
7
9
  export { default as Title } from './Title.svelte'
8
10
  export { default as TotalPages } from './TotalPages.svelte'
11
+ export { default as Translate } from './Translate.svelte'
@@ -0,0 +1,7 @@
1
+ // src/constants/attachment.constants.ts
2
+ let AttachmentPosition = /* @__PURE__ */ function(AttachmentPosition) {
3
+ AttachmentPosition["BEFORE"] = "before";
4
+ AttachmentPosition["AFTER"] = "after";
5
+ return AttachmentPosition;
6
+ }({});
7
+ export { AttachmentPosition };
@@ -1,5 +1,6 @@
1
- import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TemplateFiles } from "./template.constants.mjs";
1
+ import { AttachmentPosition } from "./attachment.constants.mjs";
2
+ import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType } from "./template.constants.mjs";
2
3
  import { InputFileType, OutputFileType } from "./file.constants.mjs";
3
4
  import { PreviewHost } from "./preview.constants.mjs";
4
5
  import { SignatureProvider } from "./signature.constants.mjs";
5
- export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TemplateFiles };
6
+ export { AttachmentPosition, ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType };
@@ -4,6 +4,21 @@ let TemplateFiles = /* @__PURE__ */ function(TemplateFiles) {
4
4
  TemplateFiles["SETTINGS"] = "settings.json";
5
5
  return TemplateFiles;
6
6
  }({});
7
+ /** Where a template keeps its files unless `files` in its settings says otherwise, the root locations of 3.x come last. */
8
+ const TEMPLATE_LAYOUT = {
9
+ template: ["template/Template.svelte", "Template.svelte"],
10
+ components: ["template/components", "components"],
11
+ extension: ["src", "."],
12
+ i18n: ["i18n"]
13
+ };
14
+ const TEMPLATE_EXTENSION_FILES = [
15
+ "extension.ts",
16
+ "extension.mts",
17
+ "extension.cts",
18
+ "extension.js",
19
+ "extension.mjs",
20
+ "extension.cjs"
21
+ ];
7
22
  let RenderMode = /* @__PURE__ */ function(RenderMode) {
8
23
  RenderMode["BODY"] = "body";
9
24
  RenderMode["HEADER"] = "header";
@@ -15,8 +30,15 @@ let MediaType = /* @__PURE__ */ function(MediaType) {
15
30
  MediaType["PRINT"] = "print";
16
31
  return MediaType;
17
32
  }({});
33
+ /** Built-in templates are packages named with this prefix, which md-printer depends on. */
34
+ const TEMPLATE_PACKAGE_PREFIX = "@cenk1cenk2/md-printer-template-";
35
+ let TemplateSourceType = /* @__PURE__ */ function(TemplateSourceType) {
36
+ TemplateSourceType["BUILTIN"] = "builtin";
37
+ TemplateSourceType["PACKAGE"] = "package";
38
+ TemplateSourceType["DIRECTORY"] = "directory";
39
+ return TemplateSourceType;
40
+ }({});
18
41
  let Directory = /* @__PURE__ */ function(Directory) {
19
- Directory["TEMPLATES"] = "templates";
20
42
  Directory["COMPONENTS"] = "components";
21
43
  Directory["I18N"] = "i18n";
22
44
  return Directory;
@@ -25,6 +47,7 @@ let ModuleSpecifier = /* @__PURE__ */ function(ModuleSpecifier) {
25
47
  ModuleSpecifier["TEMPLATE"] = "md-printer:template";
26
48
  ModuleSpecifier["COMPONENTS"] = "@cenk1cenk2/md-printer/components";
27
49
  ModuleSpecifier["TEMPLATE_COMPONENTS"] = "md-printer:template-components";
50
+ ModuleSpecifier["EXTENSION"] = "md-printer:extension";
28
51
  return ModuleSpecifier;
29
52
  }({});
30
53
  let ContextKey = /* @__PURE__ */ function(ContextKey) {
@@ -32,6 +55,7 @@ let ContextKey = /* @__PURE__ */ function(ContextKey) {
32
55
  ContextKey["DOCUMENT"] = "md-printer:document";
33
56
  ContextKey["MARKDOWN"] = "md-printer:markdown";
34
57
  ContextKey["COMPONENTS"] = "md-printer:components";
58
+ ContextKey["EXTENSION"] = "md-printer:extension";
35
59
  return ContextKey;
36
60
  }({});
37
61
  const TAILWIND_STYLE_LANG = "postcss";
@@ -56,4 +80,4 @@ const DEFAULT_PDF_OPTIONS = {
56
80
  left: "20mm"
57
81
  }
58
82
  };
59
- export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TemplateFiles };
83
+ export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType };
@@ -0,0 +1 @@
1
+ export {};