@cenk1cenk2/md-printer 2.21.0 → 3.0.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 (80) hide show
  1. package/README.md +332 -48
  2. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs +1 -2
  3. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs +1 -2
  4. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs +8 -0
  5. package/dist/commands/index.mjs +133 -295
  6. package/dist/components/Date.svelte +1 -0
  7. package/dist/components/Markdown.svelte +20 -0
  8. package/dist/components/Nodes.svelte +32 -0
  9. package/dist/components/Page.svelte +20 -0
  10. package/dist/components/PageBreak.svelte +5 -0
  11. package/dist/components/PageNumber.svelte +1 -0
  12. package/dist/components/SignatureBlock.svelte +56 -0
  13. package/dist/components/Title.svelte +1 -0
  14. package/dist/components/TotalPages.svelte +1 -0
  15. package/dist/components/index.ts +8 -0
  16. package/dist/constants/file.constants.mjs +1 -8
  17. package/dist/constants/index.mjs +3 -4
  18. package/dist/constants/template.constants.mjs +48 -10
  19. package/dist/interfaces/commands/index.mjs +1 -1
  20. package/dist/interfaces/commands/run.interface.mjs +1 -1
  21. package/dist/interfaces/index.mjs +1 -1
  22. package/dist/interfaces/markdown.interface.mjs +1 -0
  23. package/dist/interfaces/renderer.interface.mjs +1 -0
  24. package/dist/interfaces/session.interface.mjs +1 -0
  25. package/dist/interfaces/signature.interface.mjs +1 -1
  26. package/dist/interfaces/template.interface.mjs +1 -0
  27. package/dist/services/i18n.service.mjs +4 -8
  28. package/dist/services/index.mjs +9 -2
  29. package/dist/services/loopback.mjs +18 -0
  30. package/dist/services/markdown.service.mjs +123 -0
  31. package/dist/services/printer.module.mjs +41 -0
  32. package/dist/services/printer.service.mjs +171 -0
  33. package/dist/services/renderer/browser.mjs +41 -0
  34. package/dist/services/renderer/browser.service.mjs +56 -0
  35. package/dist/services/renderer/renderer.service.mjs +198 -0
  36. package/dist/services/session/Preview.svelte +238 -0
  37. package/dist/services/session/preview.service.mjs +187 -0
  38. package/dist/services/session/session.service.mjs +115 -0
  39. package/dist/services/signature/adapters/a-trust.adapter.mjs +27 -37
  40. package/dist/services/signature/index.mjs +1 -2
  41. package/dist/services/signature/signature.adapter.mjs +1 -2
  42. package/dist/services/signature/signature.service.mjs +1 -3
  43. package/dist/services/template/Document.svelte +21 -0
  44. package/dist/services/template/Margin.svelte +15 -0
  45. package/dist/services/template/assets.mjs +127 -0
  46. package/dist/services/template/render.entry.ts +32 -0
  47. package/dist/services/template/svelte.plugin.mjs +66 -0
  48. package/dist/services/template/template.service.mjs +211 -0
  49. package/oclif.manifest.json +45 -34
  50. package/package.json +64 -31
  51. package/templates/default/Template.svelte +83 -0
  52. package/templates/default/settings.json +1 -3
  53. package/templates/invoice/Template.svelte +247 -0
  54. package/templates/invoice/components/Client.svelte +42 -0
  55. package/templates/invoice/components/Company.svelte +41 -0
  56. package/templates/invoice/components/Items.svelte +66 -0
  57. package/templates/invoice/components/Notes.svelte +21 -0
  58. package/templates/invoice/components/Payment.svelte +25 -0
  59. package/templates/invoice/components/Signature.svelte +34 -0
  60. package/templates/invoice/components/Totals.svelte +47 -0
  61. package/templates/invoice/round.js +2 -0
  62. package/templates/invoice/settings.json +1 -4
  63. package/templates/kilic.dev/Template.svelte +102 -0
  64. package/templates/kilic.dev/settings.json +1 -3
  65. package/templates/mail/Template.svelte +166 -0
  66. package/templates/mail/settings.json +1 -3
  67. package/dist/hooks/not-found.hook.mjs +0 -7
  68. package/dist/hooks/update-notifier.hook.mjs +0 -7
  69. package/dist/interfaces/pupeteer.mjs +0 -1
  70. package/templates/default/main.css +0 -73
  71. package/templates/invoice/.gitignore +0 -1
  72. package/templates/invoice/footer.html +0 -18
  73. package/templates/invoice/tailwind.css +0 -164
  74. package/templates/invoice/template.html.j2 +0 -226
  75. package/templates/kilic.dev/footer.html +0 -26
  76. package/templates/kilic.dev/header.html +0 -16
  77. package/templates/kilic.dev/main.css +0 -74
  78. package/templates/mail/.gitignore +0 -1
  79. package/templates/mail/tailwind.css +0 -145
  80. package/templates/mail/template.html.j2 +0 -14
@@ -0,0 +1,115 @@
1
+ import "../../constants/template.constants.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
4
+ import { PrinterService } from "../printer.service.mjs";
5
+ import { dirname, join, resolve } from "path";
6
+ import { LoggerService } from "@cenk1cenk2/oclif-common";
7
+ import { Injectable } from "@nestjs/common";
8
+ import { EventEmitter } from "events";
9
+ import { watch } from "chokidar";
10
+ import { debounce } from "perfect-debounce";
11
+ //#region src/services/session/session.service.ts
12
+ let SessionService = class SessionService extends EventEmitter {
13
+ logger;
14
+ printer;
15
+ watcher;
16
+ bundle;
17
+ stopped = false;
18
+ modules = /* @__PURE__ */ new Set();
19
+ watched = /* @__PURE__ */ new Set();
20
+ version = 0;
21
+ building;
22
+ flags;
23
+ ignored = /* @__PURE__ */ new Set();
24
+ build = debounce(() => this.stopped ? Promise.resolve() : this.building = this.run(), 50);
25
+ constructor(logger, printer) {
26
+ super();
27
+ this.logger = logger;
28
+ this.printer = printer;
29
+ this.logger.setup(this.constructor.name);
30
+ }
31
+ async start(flags) {
32
+ this.flags = flags;
33
+ this.watcher = watch([], {
34
+ ignoreInitial: true,
35
+ atomic: true
36
+ }).on("change", (path) => this.changed(path)).on("add", (path) => this.changed(path)).on("unlink", (path) => this.changed(path));
37
+ await this.build();
38
+ }
39
+ async onModuleDestroy() {
40
+ await this.stop();
41
+ }
42
+ ignore(path) {
43
+ this.ignored.add(resolve(path));
44
+ }
45
+ async stop() {
46
+ this.stopped = true;
47
+ this.build.cancel();
48
+ await this.building;
49
+ await this.watcher?.close();
50
+ this.watcher = void 0;
51
+ }
52
+ changed(path) {
53
+ const file = resolve(path);
54
+ if (this.ignored.has(file)) return;
55
+ this.logger.debug("Changed: %s", file);
56
+ if (this.modules.has(file) || this.bundle && dirname(file) === join(this.bundle.template, "components")) this.bundle = void 0;
57
+ this.build();
58
+ }
59
+ async run() {
60
+ const started = performance.now();
61
+ this.emit("building");
62
+ try {
63
+ const input = await this.printer.input(this.flags);
64
+ const options = await this.printer.options(this.flags, input);
65
+ const template = join(options.template, "Template.svelte");
66
+ this.modules.add(template);
67
+ this.watch([
68
+ input.file,
69
+ template,
70
+ join(options.template, "settings.json"),
71
+ join(options.template, "i18n"),
72
+ join(options.template, "components"),
73
+ options.sign.certificate && resolve(options.sign.certificate)
74
+ ]);
75
+ if (this.bundle?.template !== options.template) this.bundle = {
76
+ template: options.template,
77
+ bundle: this.printer.bundle(options)
78
+ };
79
+ const cached = this.bundle;
80
+ const bundle = await cached.bundle.catch((err) => {
81
+ if (this.bundle === cached) this.bundle = void 0;
82
+ throw err;
83
+ });
84
+ if (this.bundle !== cached) return;
85
+ this.modules = /* @__PURE__ */ new Set([template, ...bundle.modules]);
86
+ this.watch(bundle.modules);
87
+ const ctx = {
88
+ input,
89
+ options
90
+ };
91
+ await this.printer.prepare(ctx);
92
+ const document = await this.printer.document(ctx, bundle);
93
+ this.watch(document.dependencies);
94
+ const { content, assets } = await this.printer.output(ctx, document);
95
+ this.watch(assets);
96
+ this.emit("ready", {
97
+ version: ++this.version,
98
+ ms: Math.round(performance.now() - started),
99
+ ctx,
100
+ document,
101
+ output: content
102
+ });
103
+ } catch (err) {
104
+ this.emit("failed", err instanceof Error ? err : new Error(String(err)));
105
+ }
106
+ }
107
+ watch(paths) {
108
+ const fresh = paths.filter((path) => path && !this.watched.has(path));
109
+ for (const path of fresh) this.watched.add(path);
110
+ if (fresh.length) this.watcher.add(fresh);
111
+ }
112
+ };
113
+ SessionService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof PrinterService === "undefined" ? Object : PrinterService])], SessionService);
114
+ //#endregion
115
+ export { SessionService };
@@ -1,23 +1,17 @@
1
1
  import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
2
2
  import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
3
+ import { isLoopbackRequest } from "../../loopback.mjs";
3
4
  import { SignatureAdapter } from "../signature.adapter.mjs";
5
+ import { readFile } from "fs/promises";
4
6
  import { LoggerService } from "@cenk1cenk2/oclif-common";
5
7
  import { Injectable } from "@nestjs/common";
6
8
  import { spawn } from "child_process";
7
- import { readFile } from "fs/promises";
9
+ import { once } from "events";
8
10
  import { createServer } from "http";
9
-
11
+ import getRawBody from "raw-body";
10
12
  //#region src/services/signature/adapters/a-trust.adapter.ts
11
- var _ATrustAdapter;
12
13
  let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
13
- static {
14
- _ATrustAdapter = this;
15
- }
16
14
  logger;
17
- static ENDPOINT = "https://service.a-trust.at/mobile/https-security-layer-request/default.aspx";
18
- static NAMESPACE = "http://www.buergerkarte.at/namespaces/securitylayer/1.2#";
19
- static TIMEOUT = 6e5;
20
- static PAGE = "./a-trust.html";
21
15
  name = "a-trust";
22
16
  constructor(logger) {
23
17
  super();
@@ -28,21 +22,20 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
28
22
  return this.collect(await this.session(this.request(request)));
29
23
  }
30
24
  request({ pdf, excluded: [from, to] }) {
31
- const data = Buffer.from(pdf).fill(0, from, to + 1);
32
25
  return [
33
26
  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
34
- `<sl:CreateCMSSignatureRequest xmlns:sl="${_ATrustAdapter.NAMESPACE}" Structure="detached" PAdESCompatibility="true">`,
27
+ "<sl:CreateCMSSignatureRequest xmlns:sl=\"http://www.buergerkarte.at/namespaces/securitylayer/1.2#\" Structure=\"detached\" PAdESCompatibility=\"true\">",
35
28
  "<sl:KeyboxIdentifier>SecureSignatureKeypair</sl:KeyboxIdentifier>",
36
29
  "<sl:DataObject>",
37
30
  "<sl:MetaInfo><sl:MimeType>application/pdf</sl:MimeType></sl:MetaInfo>",
38
- `<sl:Content><sl:Base64Content>${data.toString("base64")}</sl:Base64Content></sl:Content>`,
31
+ `<sl:Content><sl:Base64Content>${Buffer.from(pdf).fill(0, from, to + 1).toString("base64")}</sl:Base64Content></sl:Content>`,
39
32
  `<sl:ExcludedByteRange><sl:From>${from}</sl:From><sl:To>${to}</sl:To></sl:ExcludedByteRange>`,
40
33
  "</sl:DataObject>",
41
34
  "</sl:CreateCMSSignatureRequest>"
42
35
  ].join("");
43
36
  }
44
37
  async session(request) {
45
- let url = _ATrustAdapter.ENDPOINT;
38
+ let url = "https://service.a-trust.at/mobile/https-security-layer-request/default.aspx";
46
39
  let response = await fetch(url, {
47
40
  method: "POST",
48
41
  body: new URLSearchParams([["XMLRequest", request]]),
@@ -58,13 +51,15 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
58
51
  }
59
52
  async collect(login) {
60
53
  let resolve;
61
- let reject;
62
- const result = new Promise((done, fail) => {
54
+ const result = new Promise((done) => {
63
55
  resolve = done;
64
- reject = fail;
65
56
  });
66
- const page = await readFile(new URL(_ATrustAdapter.PAGE, import.meta.url));
57
+ const page = await readFile(new URL("./a-trust.html", import.meta.url));
67
58
  const server = createServer(async (req, res) => {
59
+ if (!isLoopbackRequest(req, req.method === "POST" ? `http://127.0.0.1:${server.address().port}` : void 0)) {
60
+ res.writeHead(403);
61
+ return res.end();
62
+ }
68
63
  if (req.method === "GET" && req.url === "/session") {
69
64
  res.setHeader("content-type", "application/json");
70
65
  res.writeHead(200);
@@ -75,8 +70,14 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
75
70
  res.writeHead(200);
76
71
  return res.end(page);
77
72
  }
78
- let body = "";
79
- for await (const chunk of req) body += chunk;
73
+ const body = await getRawBody(req, {
74
+ limit: "1mb",
75
+ encoding: true
76
+ }).catch((err) => {
77
+ res.writeHead(err.status ?? 400);
78
+ res.end();
79
+ });
80
+ if (body === void 0) return;
80
81
  const cms = (body.match(/<(?:\w+:)?CMSSignature>([^<]+)</)?.[1] ?? body).replace(/\s+/g, "");
81
82
  if (!/^MII[A-Za-z0-9+/=]+$/.test(cms)) {
82
83
  res.writeHead(400);
@@ -86,31 +87,20 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
86
87
  res.end("Signature received, you can close this tab.");
87
88
  resolve(Buffer.from(cms, "base64"));
88
89
  });
89
- await new Promise((done) => server.listen(0, "127.0.0.1", done));
90
+ server.listen(0, "127.0.0.1");
91
+ await once(server, "listening");
90
92
  const url = `http://127.0.0.1:${server.address().port}/`;
91
93
  this.logger.info("Sign the document in the browser, then paste the signature back: %s", url);
92
94
  this.open(url);
93
- const timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Timed out waiting for the signature.")), _ATrustAdapter.TIMEOUT);
94
- return result.finally(() => {
95
- clearTimeout(timeout);
96
- server.close();
97
- });
95
+ return result.finally(() => server.close());
98
96
  }
99
97
  open(url) {
100
- const [command, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? [
101
- "cmd",
102
- "/c",
103
- "start",
104
- "",
105
- url
106
- ] : ["xdg-open", url];
107
- spawn(command, args, {
98
+ spawn("xdg-open", [url], {
108
99
  stdio: "ignore",
109
100
  detached: true
110
101
  }).on("error", (err) => this.logger.warn("Can not open the browser, open the link manually: %s", err.message)).unref();
111
102
  }
112
103
  };
113
- ATrustAdapter = _ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService])], ATrustAdapter);
114
-
104
+ ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService])], ATrustAdapter);
115
105
  //#endregion
116
- export { ATrustAdapter };
106
+ export { ATrustAdapter };
@@ -1,5 +1,4 @@
1
1
  import { SignatureAdapter } from "./signature.adapter.mjs";
2
2
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
3
3
  import { SignatureService } from "./signature.service.mjs";
4
-
5
- export { ATrustAdapter, SignatureAdapter, SignatureService };
4
+ export { ATrustAdapter, SignatureAdapter, SignatureService };
@@ -1,5 +1,4 @@
1
1
  //#region src/services/signature/signature.adapter.ts
2
2
  var SignatureAdapter = class {};
3
-
4
3
  //#endregion
5
- export { SignatureAdapter };
4
+ export { SignatureAdapter };
@@ -8,7 +8,6 @@ import { plainAddPlaceholder } from "@signpdf/placeholder-plain";
8
8
  import { SUBFILTER_ETSI_CADES_DETACHED, findByteRange, removeTrailingNewLine } from "@signpdf/utils";
9
9
  import { X509Certificate } from "crypto";
10
10
  import { homedir } from "os";
11
-
12
11
  //#region src/services/signature/signature.service.ts
13
12
  var _SignatureService;
14
13
  let SignatureService = class SignatureService {
@@ -92,6 +91,5 @@ let SignatureService = class SignatureService {
92
91
  }
93
92
  };
94
93
  SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService, typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
95
-
96
94
  //#endregion
97
- export { SignatureService };
95
+ export { SignatureService };
@@ -0,0 +1,21 @@
1
+ <!--
2
+ @component
3
+ HTML document around the rendered template body.
4
+ -->
5
+ <script>
6
+ /** @type {import('@interfaces').DocumentShellProps} */
7
+ let { lang, title, head, body } = $props()
8
+ </script>
9
+
10
+ <html {lang}>
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ {#if title !== undefined}
14
+ <title>{title}</title>
15
+ {/if}
16
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- styles and head elements rendered by md-printer -->
17
+ {@html head}
18
+ </head>
19
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- the template body rendered by md-printer -->
20
+ <body>{@html body}</body>
21
+ </html>
@@ -0,0 +1,15 @@
1
+ <!--
2
+ @component
3
+ Wrapper for a header or footer snippet printed into Chrome's isolated page margin, which ignores the page stylesheet and defaults to a tiny font.
4
+ -->
5
+ <script>
6
+ /** @type {import('@interfaces').MarginShellProps} */
7
+ let { styles, body } = $props()
8
+ </script>
9
+
10
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- styles collected by md-printer for the isolated margin document -->
11
+ {@html styles}
12
+ <div style="font-size: 10px; width: 100%; box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact">
13
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- the header or footer snippet rendered by md-printer -->
14
+ {@html body}
15
+ </div>
@@ -0,0 +1,127 @@
1
+ import { readFile, realpath } from "fs/promises";
2
+ import { isAbsolute, relative, sep } from "path";
3
+ import { toHtml } from "hast-util-to-html";
4
+ import { visit } from "unist-util-visit";
5
+ import { fileURLToPath, pathToFileURL } from "url";
6
+ import { fromHtml } from "hast-util-from-html";
7
+ import { transform, transformStyleAttribute } from "lightningcss";
8
+ import mime from "mime";
9
+ import { parseSrcset, stringifySrcset } from "srcset";
10
+ //#region src/services/template/assets.ts
11
+ const REFERENCES = {
12
+ img: ["src", "srcSet"],
13
+ source: ["src", "srcSet"],
14
+ input: ["src"],
15
+ video: ["poster"],
16
+ image: ["href", "xLinkHref"]
17
+ };
18
+ function mimeType(path) {
19
+ return mime.getType(path) ?? "application/octet-stream";
20
+ }
21
+ /**
22
+ * Images and fonts, the assets that can be embedded into a page as data URIs.
23
+ */
24
+ function isInlinableAsset(path) {
25
+ return /^(image|font)\//.test(mime.getType(path) ?? "");
26
+ }
27
+ function dataUri(path, content) {
28
+ return `data:${mimeType(path)};base64,${content.toString("base64")}`;
29
+ }
30
+ /**
31
+ * Resolves symlinks first, so a link inside the directory can not reach a file outside of it.
32
+ */
33
+ async function resolveConfined(path, base) {
34
+ const [real, root] = await Promise.all([realpath(path), realpath(base)]).catch(() => []);
35
+ if (!real) return;
36
+ const rel = relative(root, real);
37
+ return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? void 0 : real;
38
+ }
39
+ /**
40
+ * Replaces local image references in HTML with data URIs: the image attributes, `style` attributes and `<style>` elements.
41
+ * Remote references are fetched as well when `remote` is set, which Chrome's isolated header/footer templates need.
42
+ * Document content is `confined` to its directory, the same rule the PDF renderer applies to the assets a page loads.
43
+ */
44
+ async function inlineHtml(markup, base, options = {}) {
45
+ const assets = /* @__PURE__ */ new Set();
46
+ const inline = inliner(base, options, assets);
47
+ const tree = fromHtml(markup, { fragment: !/^<!doctype/i.test(markup) });
48
+ const pending = [];
49
+ visit(tree, "element", (node) => {
50
+ const { properties } = node;
51
+ for (const name of REFERENCES[node.tagName] ?? []) {
52
+ const value = properties[name];
53
+ if (typeof value !== "string") continue;
54
+ pending.push((async () => {
55
+ properties[name] = name === "srcSet" ? stringifySrcset(await Promise.all(parseSrcset(value).map(async (candidate) => ({
56
+ ...candidate,
57
+ url: await inline(candidate.url) ?? candidate.url
58
+ })))) : await inline(value) ?? value;
59
+ })());
60
+ }
61
+ if (typeof properties.style === "string") {
62
+ const style = properties.style;
63
+ pending.push(stylesheet(style, inline, true).then((css) => {
64
+ properties.style = css;
65
+ }));
66
+ }
67
+ if (node.tagName === "style") {
68
+ for (const child of node.children) if (child.type === "text") pending.push(stylesheet(child.value, inline).then((css) => {
69
+ child.value = css;
70
+ }));
71
+ }
72
+ });
73
+ await Promise.all(pending);
74
+ return {
75
+ content: toHtml(tree, { characterReferences: { useNamedReferences: true } }),
76
+ assets: [...assets]
77
+ };
78
+ }
79
+ /**
80
+ * Replaces local `url()` references in CSS with data URIs, with the same options as `inlineHtml`.
81
+ */
82
+ async function inlineCss(css, base, options = {}) {
83
+ const assets = /* @__PURE__ */ new Set();
84
+ return {
85
+ content: await stylesheet(css, inliner(base, options, assets)),
86
+ assets: [...assets]
87
+ };
88
+ }
89
+ function inliner(base, { remote = false, confined = false, cache = /* @__PURE__ */ new Map() }, assets) {
90
+ return async (reference) => {
91
+ const asset = await (cache.get(reference) ?? cache.set(reference, load(reference, base, remote, confined)).get(reference));
92
+ if (asset?.path) assets.add(asset.path);
93
+ return asset?.uri;
94
+ };
95
+ }
96
+ async function stylesheet(css, inline, attribute = false) {
97
+ const options = {
98
+ filename: "style.css",
99
+ code: Buffer.from(css),
100
+ analyzeDependencies: { preserveImports: true },
101
+ errorRecovery: true
102
+ };
103
+ const { code, dependencies } = attribute ? transformStyleAttribute({
104
+ ...options,
105
+ analyzeDependencies: true
106
+ }) : transform(options);
107
+ return (await Promise.all(dependencies.map(async (dependency) => [`"${dependency.placeholder}"`, JSON.stringify(dependency.type === "url" && await inline(dependency.url) || dependency.url)]))).reduce((result, [placeholder, value]) => result.replace(placeholder, () => value), code.toString());
108
+ }
109
+ async function load(reference, base, remote, confined) {
110
+ const url = reference.startsWith("#") ? void 0 : URL.parse(reference, pathToFileURL(`${base}${sep}`));
111
+ if (url?.protocol === "http:" || url?.protocol === "https:") {
112
+ if (!remote) return;
113
+ const response = await fetch(url).catch(() => void 0);
114
+ if (!response?.ok) return;
115
+ return { uri: `data:${response.headers.get("content-type") ?? mimeType(url.pathname)};base64,${Buffer.from(await response.arrayBuffer()).toString("base64")}` };
116
+ }
117
+ if (url?.protocol !== "file:" || url.host) return;
118
+ const path = fileURLToPath(url);
119
+ const file = confined ? await resolveConfined(path, base) : path;
120
+ if (!file || !isInlinableAsset(path)) return;
121
+ return readFile(file).then((content) => ({
122
+ uri: dataUri(path, content),
123
+ path: file
124
+ }), () => void 0);
125
+ }
126
+ //#endregion
127
+ export { dataUri, inlineCss, inlineHtml, isInlinableAsset, mimeType, resolveConfined };
@@ -0,0 +1,32 @@
1
+ import Template from 'md-printer:template'
2
+ import components from 'md-printer:template-components'
3
+ import { render } from 'svelte/server'
4
+
5
+ import Document from './Document.svelte'
6
+ import Margin from './Margin.svelte'
7
+ import Nodes from '../../components/Nodes.svelte'
8
+ import type { DocumentShellProps, MarginShellProps, MarkdownNode, RenderedPart, TemplateProps } from '@interfaces'
9
+
10
+ export { components }
11
+
12
+ /**
13
+ * Entry of every template bundle, compiled at runtime together with the template it renders.
14
+ */
15
+ export function part(props: TemplateProps, context: [string, unknown][]): Promise<RenderedPart> {
16
+ return Promise.resolve(render(Template, { props, context: new Map(context) }))
17
+ }
18
+
19
+ /**
20
+ * The markdown as one HTML string, for templates that place it with `{@html content}`. Plain HTML is kept as is, without Svelte's hydration markers.
21
+ */
22
+ export function content(nodes: MarkdownNode[], context: [string, unknown][]): string {
23
+ return nodes.map((node) => (node.type === 'html' ? node.html : render(Nodes, { props: { nodes: [node] }, context: new Map(context) }).body)).join('')
24
+ }
25
+
26
+ export function document(props: DocumentShellProps): string {
27
+ return `<!doctype html>${render(Document, { props }).body}`
28
+ }
29
+
30
+ export function margin(props: MarginShellProps): string {
31
+ return render(Margin, { props }).body
32
+ }
@@ -0,0 +1,66 @@
1
+ import "../../constants/template.constants.mjs";
2
+ import { dataUri, isInlinableAsset } from "./assets.mjs";
3
+ import { readFile, readdir } from "fs/promises";
4
+ import { basename, dirname, extname, join } from "path";
5
+ import { pathToFileURL } from "url";
6
+ import { existsSync } from "fs";
7
+ import { createRequire } from "module";
8
+ import { compile, preprocess } from "svelte/compiler";
9
+ //#region src/services/template/svelte.plugin.ts
10
+ /**
11
+ * `svelte` resolves to md-printer's copy, since templates can live outside any `node_modules`. `<style lang="postcss">` blocks are collected
12
+ * instead of compiled per component, so the template's stylesheet goes through Tailwind once. `md-printer:template-components` maps the shared
13
+ * components and the template's `components/*.svelte` by name, the template's winning.
14
+ */
15
+ function sveltePlugin({ template, components, root, stylesheets }) {
16
+ const require = createRequire(root);
17
+ return {
18
+ name: "md-printer:svelte",
19
+ resolveId(id) {
20
+ if (id === "md-printer:template") return template;
21
+ if (id === "@cenk1cenk2/md-printer/components") return components;
22
+ if (id === "md-printer:template-components") return `\0${id}`;
23
+ if (id === "svelte" || id.startsWith("svelte/")) return {
24
+ id: pathToFileURL(require.resolve(id)).href,
25
+ external: true
26
+ };
27
+ return null;
28
+ },
29
+ async load(id) {
30
+ if (id === `\0md-printer:template-components`) {
31
+ const directory = join(dirname(template), "components");
32
+ const files = existsSync(directory) ? (await readdir(directory)).filter((file) => extname(file) === ".svelte").sort() : [];
33
+ return [
34
+ `import * as shared from ${JSON.stringify("@cenk1cenk2/md-printer/components")}`,
35
+ ...files.map((file, index) => `import $${index} from ${JSON.stringify(join(directory, file))}`),
36
+ "const { Markdown, ...builtins } = shared",
37
+ `export default { ...builtins, ${files.map((file, index) => `${JSON.stringify(basename(file, ".svelte"))}: $${index}`).join(", ")} }`
38
+ ].join("\n");
39
+ }
40
+ if (!isInlinableAsset(id)) return null;
41
+ return `export default ${JSON.stringify(dataUri(id, await readFile(id)))}`;
42
+ },
43
+ async transform(code, id) {
44
+ if (extname(id) !== ".svelte") return null;
45
+ const { code: source } = await preprocess(code, { style: ({ content, attributes }) => {
46
+ if (attributes.lang !== "postcss") return;
47
+ stylesheets.push({
48
+ file: id,
49
+ source: content
50
+ });
51
+ return { code: "" };
52
+ } }, { filename: id });
53
+ const { js } = compile(source, {
54
+ filename: id,
55
+ generate: "server",
56
+ css: "injected"
57
+ });
58
+ return {
59
+ code: js.code,
60
+ map: js.map
61
+ };
62
+ }
63
+ };
64
+ }
65
+ //#endregion
66
+ export { sveltePlugin };