@cenk1cenk2/md-printer 2.21.0 → 3.1.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.
- package/README.md +333 -48
- package/bin/dev.js +0 -2
- package/bin/run.js +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs +8 -0
- package/dist/commands/index.mjs +32 -345
- package/dist/components/Date.svelte +1 -0
- package/dist/components/Markdown.svelte +20 -0
- package/dist/components/Nodes.svelte +32 -0
- package/dist/components/Page.svelte +20 -0
- package/dist/components/PageBreak.svelte +5 -0
- package/dist/components/PageNumber.svelte +1 -0
- package/dist/components/SignatureBlock.svelte +56 -0
- package/dist/components/Title.svelte +1 -0
- package/dist/components/TotalPages.svelte +1 -0
- package/dist/components/index.ts +8 -0
- package/dist/constants/file.constants.mjs +1 -8
- package/dist/constants/index.mjs +5 -4
- package/dist/constants/preview.constants.mjs +9 -0
- package/dist/constants/signature.constants.mjs +7 -0
- package/dist/constants/template.constants.mjs +48 -10
- package/dist/interfaces/commands/index.mjs +1 -1
- package/dist/interfaces/commands/run.interface.mjs +1 -1
- package/dist/interfaces/index.mjs +1 -1
- package/dist/interfaces/markdown.interface.mjs +1 -0
- package/dist/interfaces/renderer.interface.mjs +1 -0
- package/dist/interfaces/session.interface.mjs +1 -0
- package/dist/interfaces/signature.interface.mjs +1 -1
- package/dist/interfaces/template.interface.mjs +1 -0
- package/dist/modules/md-printer/md-printer.handler.mjs +113 -0
- package/dist/modules/md-printer/md-printer.module.mjs +21 -0
- package/dist/modules/md-printer/md-printer.schema.mjs +50 -0
- package/dist/services/i18n.service.mjs +12 -15
- package/dist/services/index.mjs +9 -2
- package/dist/services/loopback.mjs +18 -0
- package/dist/services/markdown.service.mjs +123 -0
- package/dist/services/printer.module.mjs +39 -0
- package/dist/services/printer.service.mjs +171 -0
- package/dist/services/renderer/browser.mjs +41 -0
- package/dist/services/renderer/browser.service.mjs +56 -0
- package/dist/services/renderer/renderer.service.mjs +198 -0
- package/dist/services/session/Preview.svelte +238 -0
- package/dist/services/session/preview.service.mjs +187 -0
- package/dist/services/session/session.service.mjs +115 -0
- package/dist/services/signature/adapters/a-trust.adapter.mjs +32 -36
- package/dist/services/signature/index.mjs +1 -2
- package/dist/services/signature/signature.adapter.mjs +1 -2
- package/dist/services/signature/signature.service.mjs +6 -10
- package/dist/services/template/Document.svelte +21 -0
- package/dist/services/template/Margin.svelte +15 -0
- package/dist/services/template/assets.mjs +127 -0
- package/dist/services/template/render.entry.ts +32 -0
- package/dist/services/template/svelte.plugin.mjs +66 -0
- package/dist/services/template/template.service.mjs +211 -0
- package/oclif.manifest.json +49 -36
- package/package.json +69 -35
- package/templates/default/Template.svelte +83 -0
- package/templates/default/settings.json +1 -3
- package/templates/invoice/Template.svelte +247 -0
- package/templates/invoice/components/Client.svelte +42 -0
- package/templates/invoice/components/Company.svelte +41 -0
- package/templates/invoice/components/Items.svelte +66 -0
- package/templates/invoice/components/Notes.svelte +21 -0
- package/templates/invoice/components/Payment.svelte +25 -0
- package/templates/invoice/components/Signature.svelte +34 -0
- package/templates/invoice/components/Totals.svelte +47 -0
- package/templates/invoice/round.js +2 -0
- package/templates/invoice/settings.json +1 -4
- package/templates/kilic.dev/Template.svelte +102 -0
- package/templates/kilic.dev/settings.json +1 -3
- package/templates/mail/Template.svelte +166 -0
- package/templates/mail/settings.json +1 -3
- package/dist/hooks/not-found.hook.mjs +0 -7
- package/dist/hooks/update-notifier.hook.mjs +0 -7
- package/dist/interfaces/pupeteer.mjs +0 -1
- package/templates/default/main.css +0 -73
- package/templates/invoice/.gitignore +0 -1
- package/templates/invoice/footer.html +0 -18
- package/templates/invoice/tailwind.css +0 -164
- package/templates/invoice/template.html.j2 +0 -226
- package/templates/kilic.dev/footer.html +0 -26
- package/templates/kilic.dev/header.html +0 -16
- package/templates/kilic.dev/main.css +0 -74
- package/templates/mail/.gitignore +0 -1
- package/templates/mail/tailwind.css +0 -145
- package/templates/mail/template.html.j2 +0 -14
|
@@ -1,25 +1,22 @@
|
|
|
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
3
|
import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
|
|
4
|
-
import { isAbsolute, join } from "path";
|
|
5
|
-
import { FileSystemService } from "@cenk1cenk2/oclif-common";
|
|
6
4
|
import { Injectable } from "@nestjs/common";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { isAbsolute, join } from "path";
|
|
7
7
|
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 {
|
|
15
14
|
static {
|
|
16
15
|
_SignatureService = this;
|
|
17
16
|
}
|
|
18
|
-
fs;
|
|
19
17
|
static LENGTH = 16384;
|
|
20
18
|
adapters;
|
|
21
|
-
constructor(
|
|
22
|
-
this.fs = fs;
|
|
19
|
+
constructor(aTrust) {
|
|
23
20
|
this.adapters = [aTrust];
|
|
24
21
|
}
|
|
25
22
|
get providers() {
|
|
@@ -27,7 +24,7 @@ let SignatureService = class SignatureService {
|
|
|
27
24
|
}
|
|
28
25
|
async certificate(file, date) {
|
|
29
26
|
const path = file.replace(/^~(?=\/|$)/, homedir());
|
|
30
|
-
const certificate = new X509Certificate(await
|
|
27
|
+
const certificate = new X509Certificate(await readFile(isAbsolute(path) ? path : join(process.cwd(), path)));
|
|
31
28
|
if (date < certificate.validFromDate || date > certificate.validToDate) throw new Error(`Signing certificate is not valid at ${date.toISOString()}, it is valid from ${certificate.validFrom} to ${certificate.validTo}.`);
|
|
32
29
|
return certificate;
|
|
33
30
|
}
|
|
@@ -91,7 +88,6 @@ let SignatureService = class SignatureService {
|
|
|
91
88
|
return name.split("\n").find((part) => part.startsWith("CN="))?.slice(3);
|
|
92
89
|
}
|
|
93
90
|
};
|
|
94
|
-
SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof
|
|
95
|
-
|
|
91
|
+
SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
|
|
96
92
|
//#endregion
|
|
97
|
-
export { SignatureService };
|
|
93
|
+
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 };
|
|
@@ -0,0 +1,211 @@
|
|
|
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 { inlineCss, inlineHtml } from "./assets.mjs";
|
|
5
|
+
import { sveltePlugin } from "./svelte.plugin.mjs";
|
|
6
|
+
import { Injectable } from "@nestjs/common";
|
|
7
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
8
|
+
import { FileSystemService } from "@cenk1cenk2/oclif-common";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { compile } from "@tailwindcss/node";
|
|
12
|
+
import { Scanner } from "@tailwindcss/oxide";
|
|
13
|
+
import { kebabCase } from "change-case";
|
|
14
|
+
import enhancedResolve from "enhanced-resolve";
|
|
15
|
+
import { decodeHTML } from "entities";
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
import { createRequire } from "module";
|
|
18
|
+
import { rolldown } from "rolldown";
|
|
19
|
+
import { render } from "svelte/server";
|
|
20
|
+
//#region src/services/template/template.service.ts
|
|
21
|
+
var _TemplateService;
|
|
22
|
+
let TemplateService = class TemplateService {
|
|
23
|
+
static {
|
|
24
|
+
_TemplateService = this;
|
|
25
|
+
}
|
|
26
|
+
fs;
|
|
27
|
+
static ROOT = fileURLToPath(new URL("../../../", import.meta.url));
|
|
28
|
+
static COMPONENTS = fileURLToPath(new URL(`../../components/index.ts`, import.meta.url));
|
|
29
|
+
static ENTRY = fileURLToPath(new URL("./render.entry.ts", import.meta.url));
|
|
30
|
+
require = createRequire(_TemplateService.ROOT);
|
|
31
|
+
stylesheets = enhancedResolve.create.sync({
|
|
32
|
+
extensions: [".css"],
|
|
33
|
+
mainFields: ["style"],
|
|
34
|
+
conditionNames: ["style"]
|
|
35
|
+
});
|
|
36
|
+
modules = enhancedResolve.create.sync({
|
|
37
|
+
extensions: [
|
|
38
|
+
".js",
|
|
39
|
+
".json",
|
|
40
|
+
".node",
|
|
41
|
+
".ts"
|
|
42
|
+
],
|
|
43
|
+
conditionNames: ["node", "import"]
|
|
44
|
+
});
|
|
45
|
+
constructor(fs) {
|
|
46
|
+
this.fs = fs;
|
|
47
|
+
}
|
|
48
|
+
resolve(template) {
|
|
49
|
+
const path = template.replace(/^~(?=\/|$)/, homedir());
|
|
50
|
+
const directory = isAbsolute(path) || path.startsWith(".") || /[\\/]/.test(path) ? resolve(path) : join(_TemplateService.ROOT, "templates", path);
|
|
51
|
+
if (!this.fs.exists(join(directory, "Template.svelte"))) throw new Error(`Template does not exist, expected Template.svelte in: ${directory}`);
|
|
52
|
+
return directory;
|
|
53
|
+
}
|
|
54
|
+
async settings(directory) {
|
|
55
|
+
const file = join(directory, "settings.json");
|
|
56
|
+
if (!this.fs.exists(file)) return {};
|
|
57
|
+
return JSON.parse(await this.fs.read(file));
|
|
58
|
+
}
|
|
59
|
+
async bundle(directory) {
|
|
60
|
+
const stylesheets = [];
|
|
61
|
+
const { module: entry, modules } = await this.compile(_TemplateService.ENTRY, join(directory, "Template.svelte"), stylesheets);
|
|
62
|
+
return {
|
|
63
|
+
entry,
|
|
64
|
+
modules,
|
|
65
|
+
stylesheets,
|
|
66
|
+
directives: new Map(Object.keys(entry.components).map((name) => [kebabCase(name), name]))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async page(file) {
|
|
70
|
+
const { module } = await this.compile(file, file, []);
|
|
71
|
+
return `<!doctype html>${render(module.default).body}`;
|
|
72
|
+
}
|
|
73
|
+
async render(directory, bundle, document, markdown, options) {
|
|
74
|
+
const context = [
|
|
75
|
+
["md-printer:document", document],
|
|
76
|
+
["md-printer:markdown", markdown],
|
|
77
|
+
["md-printer:components", bundle.entry.components]
|
|
78
|
+
];
|
|
79
|
+
const props = {
|
|
80
|
+
...document,
|
|
81
|
+
content: bundle.entry.content(markdown, context)
|
|
82
|
+
};
|
|
83
|
+
const [body, header, footer] = await Promise.all([
|
|
84
|
+
"body",
|
|
85
|
+
"header",
|
|
86
|
+
"footer"
|
|
87
|
+
].map((mode) => bundle.entry.part(props, [...context, ["md-printer:mode", mode]])));
|
|
88
|
+
const dependencies = [];
|
|
89
|
+
const cache = /* @__PURE__ */ new Map();
|
|
90
|
+
const css = `<style>${await this.css(directory, bundle, [
|
|
91
|
+
body,
|
|
92
|
+
header,
|
|
93
|
+
footer
|
|
94
|
+
].map((part) => part.body).join(""), options.highlight, dependencies)}</style>`;
|
|
95
|
+
const extracted = body.head.match(/<title>([\s\S]*?)<\/title>/)?.[1];
|
|
96
|
+
const title = options.title ?? (extracted && decodeHTML(extracted)) ?? options.fallbackTitle;
|
|
97
|
+
const head = body.head.replace(/<title>[\s\S]*?<\/title>/, "");
|
|
98
|
+
const styles = [...head.matchAll(/<style[^>]*>[\s\S]*?<\/style>/g)].map(([style]) => style).join("") + css;
|
|
99
|
+
return {
|
|
100
|
+
html: bundle.entry.document({
|
|
101
|
+
lang: options.language,
|
|
102
|
+
title,
|
|
103
|
+
head: head + css,
|
|
104
|
+
body: body.body
|
|
105
|
+
}),
|
|
106
|
+
header: await this.margin(bundle, header, styles, directory, cache, dependencies),
|
|
107
|
+
footer: await this.margin(bundle, footer, styles, directory, cache, dependencies),
|
|
108
|
+
dependencies
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async compile(input, template, stylesheets) {
|
|
112
|
+
const build = await rolldown({
|
|
113
|
+
input,
|
|
114
|
+
platform: "node",
|
|
115
|
+
logLevel: "silent",
|
|
116
|
+
plugins: [sveltePlugin({
|
|
117
|
+
template,
|
|
118
|
+
components: _TemplateService.COMPONENTS,
|
|
119
|
+
root: _TemplateService.ROOT,
|
|
120
|
+
stylesheets
|
|
121
|
+
})]
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
const { output: [chunk] } = await build.generate({
|
|
125
|
+
format: "esm",
|
|
126
|
+
sourcemap: "inline"
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
module: await import(`data:text/javascript;base64,${Buffer.from(chunk.code).toString("base64")}`),
|
|
130
|
+
modules: chunk.moduleIds.filter((id) => isAbsolute(id))
|
|
131
|
+
};
|
|
132
|
+
} finally {
|
|
133
|
+
await build.close();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async margin(bundle, part, styles, directory, cache, dependencies) {
|
|
137
|
+
if (!part.body.replace(/<!--[\s\S]*?-->/g, "").trim()) return;
|
|
138
|
+
const { content, assets } = await inlineHtml(bundle.entry.margin({
|
|
139
|
+
styles,
|
|
140
|
+
body: part.body
|
|
141
|
+
}), directory, {
|
|
142
|
+
remote: true,
|
|
143
|
+
cache
|
|
144
|
+
});
|
|
145
|
+
dependencies.push(...assets);
|
|
146
|
+
return content;
|
|
147
|
+
}
|
|
148
|
+
async css(directory, bundle, html, highlight, dependencies) {
|
|
149
|
+
const theme = `:root { ${readFileSync(this.highlightTheme(highlight), "utf8")} pre:has(> code.hljs) { padding: 0; background: transparent; } }`;
|
|
150
|
+
const compiler = await compile([theme, ...bundle.stylesheets.map(({ source }) => source)].join("\n"), {
|
|
151
|
+
base: directory,
|
|
152
|
+
from: bundle.stylesheets[0]?.file ?? join(directory, "Template.svelte"),
|
|
153
|
+
shouldRewriteUrls: true,
|
|
154
|
+
onDependency: (path) => dependencies.push(path),
|
|
155
|
+
customCssResolver: async (id, base) => this.resolveFrom(this.stylesheets, id, [
|
|
156
|
+
base,
|
|
157
|
+
directory,
|
|
158
|
+
_TemplateService.ROOT
|
|
159
|
+
]),
|
|
160
|
+
customJsResolver: async (id, base) => this.resolveFrom(this.modules, id, [
|
|
161
|
+
base,
|
|
162
|
+
directory,
|
|
163
|
+
_TemplateService.ROOT
|
|
164
|
+
])
|
|
165
|
+
});
|
|
166
|
+
const scanner = new Scanner({ sources: [
|
|
167
|
+
...compiler.sources.map(({ base, pattern, negated }) => ({
|
|
168
|
+
base,
|
|
169
|
+
pattern,
|
|
170
|
+
negated
|
|
171
|
+
})),
|
|
172
|
+
{
|
|
173
|
+
base: directory,
|
|
174
|
+
pattern: "**/*",
|
|
175
|
+
negated: false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
base: dirname(_TemplateService.COMPONENTS),
|
|
179
|
+
pattern: "**/*",
|
|
180
|
+
negated: false
|
|
181
|
+
}
|
|
182
|
+
] });
|
|
183
|
+
const { content, assets } = await inlineCss(compiler.build([...scanner.scan(), ...scanner.scanFiles([{
|
|
184
|
+
content: html,
|
|
185
|
+
extension: "html"
|
|
186
|
+
}])]), directory);
|
|
187
|
+
dependencies.push(...assets);
|
|
188
|
+
return content;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Tries every base in turn, since a template directory usually has no `node_modules` and falls back to md-printer's own dependencies.
|
|
192
|
+
*/
|
|
193
|
+
resolveFrom(resolver, id, bases) {
|
|
194
|
+
for (const base of bases) try {
|
|
195
|
+
const resolved = resolver(base, id);
|
|
196
|
+
if (resolved) return resolved;
|
|
197
|
+
} catch {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
highlightTheme(name) {
|
|
202
|
+
try {
|
|
203
|
+
return this.require.resolve(`highlight.js/styles/${name}.css`);
|
|
204
|
+
} catch {
|
|
205
|
+
throw new Error(`Unknown highlight style "${name}", see the files in ${dirname(this.require.resolve("highlight.js/styles/github.css"))}.`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
TemplateService = _TemplateService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService])], TemplateService);
|
|
210
|
+
//#endregion
|
|
211
|
+
export { TemplateService };
|