pdf-smith 0.2.0 → 0.3.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/dist/preview-plugin-26BUIGCE.js +199 -0
- package/dist/preview-plugin-26BUIGCE.js.map +1 -0
- package/dist/server.cjs +104 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/src/preview/app.tsx +262 -34
- package/src/preview/design-tokens.ts +42 -0
- package/src/preview/home.tsx +533 -78
- package/src/preview/icons.tsx +250 -0
- package/src/preview/navigation.tsx +325 -92
- package/src/preview/preview-plugin.ts +122 -2
- package/dist/preview-plugin-QDLEMAEE.js +0 -104
- package/dist/preview-plugin-QDLEMAEE.js.map +0 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import type { AddressInfo } from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
1
4
|
import type { Plugin } from 'vite';
|
|
5
|
+
import type { DocumentConfig } from '../config';
|
|
2
6
|
|
|
3
7
|
const VIRTUAL_DOCUMENTS_ID = 'virtual:pdf-smith-documents';
|
|
4
8
|
const RESOLVED_VIRTUAL_DOCUMENTS_ID = `\0${VIRTUAL_DOCUMENTS_ID}`;
|
|
@@ -7,11 +11,110 @@ interface PreviewPluginOptions {
|
|
|
7
11
|
pkgSrcDir: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
async function handleExportRequest(
|
|
15
|
+
server: import('vite').ViteDevServer,
|
|
16
|
+
slug: string,
|
|
17
|
+
res: import('node:http').ServerResponse,
|
|
18
|
+
) {
|
|
19
|
+
const root = server.config.root;
|
|
20
|
+
const pagesDir = path.join(root, 'pdfs', slug, 'pages');
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(pagesDir)) {
|
|
23
|
+
res.statusCode = 404;
|
|
24
|
+
res.setHeader('Content-Type', 'application/json');
|
|
25
|
+
res.end(JSON.stringify({ error: `Document "${slug}" not found` }));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let config: DocumentConfig | undefined;
|
|
30
|
+
const configPath = path.join(root, 'pdfs', slug, 'config.ts');
|
|
31
|
+
if (fs.existsSync(configPath)) {
|
|
32
|
+
try {
|
|
33
|
+
const configModule = await server.ssrLoadModule(configPath);
|
|
34
|
+
config = configModule.default;
|
|
35
|
+
} catch {
|
|
36
|
+
// Config loading failed, proceed without it
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let playwright: typeof import('playwright');
|
|
41
|
+
try {
|
|
42
|
+
playwright = await import('playwright');
|
|
43
|
+
} catch {
|
|
44
|
+
res.statusCode = 500;
|
|
45
|
+
res.setHeader('Content-Type', 'application/json');
|
|
46
|
+
res.end(
|
|
47
|
+
JSON.stringify({
|
|
48
|
+
error:
|
|
49
|
+
'Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium',
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let browser: import('playwright').Browser | undefined;
|
|
56
|
+
try {
|
|
57
|
+
const address = server.httpServer?.address() as AddressInfo | null;
|
|
58
|
+
const port = address?.port;
|
|
59
|
+
if (!port) throw new Error('Could not determine server port');
|
|
60
|
+
|
|
61
|
+
browser = await playwright.chromium.launch({ headless: true });
|
|
62
|
+
const page = await browser.newPage();
|
|
63
|
+
await page.goto(`http://localhost:${port}/${slug}`, { waitUntil: 'networkidle' });
|
|
64
|
+
await page.waitForSelector('[data-pdf-smith-page]', { timeout: 30_000 });
|
|
65
|
+
|
|
66
|
+
const margin = { top: '0', bottom: '0', left: '0', right: '0' };
|
|
67
|
+
const pdfOptions: Parameters<typeof page.pdf>[0] = {
|
|
68
|
+
preferCSSPageSize: true,
|
|
69
|
+
printBackground: true,
|
|
70
|
+
margin,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (config?.pageNumbers?.enabled) {
|
|
74
|
+
pdfOptions.displayHeaderFooter = true;
|
|
75
|
+
pdfOptions.headerTemplate = '<span></span>';
|
|
76
|
+
pdfOptions.footerTemplate =
|
|
77
|
+
config.pageNumbers.template ??
|
|
78
|
+
'<div style="font-size:10px;text-align:center;width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>';
|
|
79
|
+
margin.bottom = '40px';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const buffer = await page.pdf(pdfOptions);
|
|
83
|
+
await page.close();
|
|
84
|
+
|
|
85
|
+
const filename = config?.output ?? `${slug}.pdf`;
|
|
86
|
+
res.statusCode = 200;
|
|
87
|
+
res.setHeader('Content-Type', 'application/pdf');
|
|
88
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
89
|
+
res.end(buffer);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (!res.headersSent) {
|
|
92
|
+
res.statusCode = 500;
|
|
93
|
+
res.setHeader('Content-Type', 'application/json');
|
|
94
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : 'PDF export failed' }));
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
await browser?.close().catch(() => {});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
10
101
|
export function pdfSmithPreviewPlugin({ pkgSrcDir }: PreviewPluginOptions): Plugin {
|
|
11
102
|
return {
|
|
12
103
|
name: 'pdf-smith-preview',
|
|
13
104
|
|
|
14
105
|
configureServer(server) {
|
|
106
|
+
// Pre-middleware: Export API runs before Vite internals.
|
|
107
|
+
// Must be a sync function — async middleware returns a Promise that
|
|
108
|
+
// Connect ignores, so the pipeline can advance to the catch-all
|
|
109
|
+
// before the async work finishes.
|
|
110
|
+
server.middlewares.use((req, res, next) => {
|
|
111
|
+
if (req.method !== 'POST') return next();
|
|
112
|
+
const match = req.url?.match(/^\/api\/export\/([^/]+)$/);
|
|
113
|
+
if (!match) return next();
|
|
114
|
+
void handleExportRequest(server, match[1], res);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Post-middleware: catch-all HTML handler (after Vite internals)
|
|
15
118
|
return () => {
|
|
16
119
|
server.middlewares.use((req, res, next) => {
|
|
17
120
|
const url = req.url ?? '';
|
|
@@ -36,18 +139,35 @@ window.__vite_plugin_react_preamble_installed__ = true
|
|
|
36
139
|
<meta charset="UTF-8" />
|
|
37
140
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
38
141
|
<title>pdf-smith Preview</title>
|
|
142
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
143
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
144
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1&family=Inter:opsz,wght@14..32,100..900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
39
145
|
<style>
|
|
146
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
147
|
+
body {
|
|
148
|
+
margin: 0;
|
|
149
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
150
|
+
-webkit-font-smoothing: antialiased;
|
|
151
|
+
}
|
|
152
|
+
.pdf-smith-search::placeholder { color: #78716C; }
|
|
40
153
|
@media print {
|
|
41
154
|
[data-pdf-smith-nav] { display: none !important; }
|
|
155
|
+
[data-pdf-smith-toolbar] { display: none !important; }
|
|
156
|
+
[data-pdf-smith-minimap] { display: none !important; }
|
|
42
157
|
[data-pdf-smith-container] {
|
|
43
158
|
margin-left: 0 !important;
|
|
44
159
|
padding: 0 !important;
|
|
160
|
+
padding-top: 0 !important;
|
|
45
161
|
background: none !important;
|
|
46
162
|
}
|
|
47
|
-
[data-pdf-smith-container] >
|
|
163
|
+
[data-pdf-smith-container] > div {
|
|
164
|
+
padding: 0 !important;
|
|
165
|
+
gap: 0 !important;
|
|
166
|
+
}
|
|
167
|
+
[data-pdf-smith-document] > div {
|
|
48
168
|
margin-bottom: 0 !important;
|
|
49
169
|
}
|
|
50
|
-
[data-pdf-smith-
|
|
170
|
+
[data-pdf-smith-document] > div > div:first-child {
|
|
51
171
|
display: none !important;
|
|
52
172
|
}
|
|
53
173
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// src/preview/preview-plugin.ts
|
|
2
|
-
var VIRTUAL_DOCUMENTS_ID = "virtual:pdf-smith-documents";
|
|
3
|
-
var RESOLVED_VIRTUAL_DOCUMENTS_ID = `\0${VIRTUAL_DOCUMENTS_ID}`;
|
|
4
|
-
function pdfSmithPreviewPlugin({ pkgSrcDir }) {
|
|
5
|
-
return {
|
|
6
|
-
name: "pdf-smith-preview",
|
|
7
|
-
configureServer(server) {
|
|
8
|
-
return () => {
|
|
9
|
-
server.middlewares.use((req, res, next) => {
|
|
10
|
-
const url = req.url ?? "";
|
|
11
|
-
if (url.includes(".") || url.startsWith("/@") || url.startsWith("/node_modules/")) {
|
|
12
|
-
next();
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const html = `<!DOCTYPE html>
|
|
16
|
-
<html lang="en">
|
|
17
|
-
<head>
|
|
18
|
-
<script type="module">
|
|
19
|
-
import RefreshRuntime from "/@react-refresh"
|
|
20
|
-
RefreshRuntime.injectIntoGlobalHook(window)
|
|
21
|
-
window.$RefreshReg$ = () => {}
|
|
22
|
-
window.$RefreshSig$ = () => (type) => type
|
|
23
|
-
window.__vite_plugin_react_preamble_installed__ = true
|
|
24
|
-
</script>
|
|
25
|
-
<script type="module" src="/@vite/client"></script>
|
|
26
|
-
<meta charset="UTF-8" />
|
|
27
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
28
|
-
<title>pdf-smith Preview</title>
|
|
29
|
-
<style>
|
|
30
|
-
@media print {
|
|
31
|
-
[data-pdf-smith-nav] { display: none !important; }
|
|
32
|
-
[data-pdf-smith-container] {
|
|
33
|
-
margin-left: 0 !important;
|
|
34
|
-
padding: 0 !important;
|
|
35
|
-
background: none !important;
|
|
36
|
-
}
|
|
37
|
-
[data-pdf-smith-container] > [data-pdf-smith-document] > div {
|
|
38
|
-
margin-bottom: 0 !important;
|
|
39
|
-
}
|
|
40
|
-
[data-pdf-smith-container] > [data-pdf-smith-document] > div > div:first-child {
|
|
41
|
-
display: none !important;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
</style>
|
|
45
|
-
</head>
|
|
46
|
-
<body>
|
|
47
|
-
<div id="root"></div>
|
|
48
|
-
<script type="module" src="/@fs/${pkgSrcDir}/preview/entry.tsx"></script>
|
|
49
|
-
</body>
|
|
50
|
-
</html>`;
|
|
51
|
-
res.setHeader("Content-Type", "text/html");
|
|
52
|
-
res.statusCode = 200;
|
|
53
|
-
res.end(html);
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
resolveId(id) {
|
|
58
|
-
if (id === VIRTUAL_DOCUMENTS_ID) {
|
|
59
|
-
return RESOLVED_VIRTUAL_DOCUMENTS_ID;
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
load(id) {
|
|
63
|
-
if (id === RESOLVED_VIRTUAL_DOCUMENTS_ID) {
|
|
64
|
-
return `
|
|
65
|
-
const pageModules = import.meta.glob('/pdfs/*/pages/**/*.tsx', { eager: true });
|
|
66
|
-
const configModules = import.meta.glob('/pdfs/*/config.ts', { eager: true });
|
|
67
|
-
|
|
68
|
-
export function getDocuments() {
|
|
69
|
-
const documents = {};
|
|
70
|
-
|
|
71
|
-
for (const [path, mod] of Object.entries(pageModules)) {
|
|
72
|
-
const match = path.match(/^\\/pdfs\\/([^/]+)\\/pages\\/(.+)\\.[^.]+$/);
|
|
73
|
-
if (!match) continue;
|
|
74
|
-
const [, slug, pageName] = match;
|
|
75
|
-
if (!documents[slug]) {
|
|
76
|
-
documents[slug] = { slug, pages: {}, config: undefined };
|
|
77
|
-
}
|
|
78
|
-
documents[slug].pages[pageName] = mod.default;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const [path, mod] of Object.entries(configModules)) {
|
|
82
|
-
const match = path.match(/^\\/pdfs\\/([^/]+)\\/config\\.ts$/);
|
|
83
|
-
if (!match) continue;
|
|
84
|
-
const slug = match[1];
|
|
85
|
-
if (documents[slug]) {
|
|
86
|
-
documents[slug].config = mod.default;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return documents;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getDocumentSlugs() {
|
|
94
|
-
return Object.keys(getDocuments());
|
|
95
|
-
}
|
|
96
|
-
`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
export {
|
|
102
|
-
pdfSmithPreviewPlugin
|
|
103
|
-
};
|
|
104
|
-
//# sourceMappingURL=preview-plugin-QDLEMAEE.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preview/preview-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nconst VIRTUAL_DOCUMENTS_ID = 'virtual:pdf-smith-documents';\nconst RESOLVED_VIRTUAL_DOCUMENTS_ID = `\\0${VIRTUAL_DOCUMENTS_ID}`;\n\ninterface PreviewPluginOptions {\n pkgSrcDir: string;\n}\n\nexport function pdfSmithPreviewPlugin({ pkgSrcDir }: PreviewPluginOptions): Plugin {\n return {\n name: 'pdf-smith-preview',\n\n configureServer(server) {\n return () => {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '';\n\n // Skip file requests, Vite internals, and node_modules\n if (url.includes('.') || url.startsWith('/@') || url.startsWith('/node_modules/')) {\n next();\n return;\n }\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <script type=\"module\">\nimport RefreshRuntime from \"/@react-refresh\"\nRefreshRuntime.injectIntoGlobalHook(window)\nwindow.$RefreshReg$ = () => {}\nwindow.$RefreshSig$ = () => (type) => type\nwindow.__vite_plugin_react_preamble_installed__ = true\n</script>\n <script type=\"module\" src=\"/@vite/client\"></script>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>pdf-smith Preview</title>\n <style>\n @media print {\n [data-pdf-smith-nav] { display: none !important; }\n [data-pdf-smith-container] {\n margin-left: 0 !important;\n padding: 0 !important;\n background: none !important;\n }\n [data-pdf-smith-container] > [data-pdf-smith-document] > div {\n margin-bottom: 0 !important;\n }\n [data-pdf-smith-container] > [data-pdf-smith-document] > div > div:first-child {\n display: none !important;\n }\n }\n </style>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/@fs/${pkgSrcDir}/preview/entry.tsx\"></script>\n</body>\n</html>`;\n\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(html);\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_DOCUMENTS_ID) {\n return RESOLVED_VIRTUAL_DOCUMENTS_ID;\n }\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_DOCUMENTS_ID) {\n return `\nconst pageModules = import.meta.glob('/pdfs/*/pages/**/*.tsx', { eager: true });\nconst configModules = import.meta.glob('/pdfs/*/config.ts', { eager: true });\n\nexport function getDocuments() {\n const documents = {};\n\n for (const [path, mod] of Object.entries(pageModules)) {\n const match = path.match(/^\\\\/pdfs\\\\/([^/]+)\\\\/pages\\\\/(.+)\\\\.[^.]+$/);\n if (!match) continue;\n const [, slug, pageName] = match;\n if (!documents[slug]) {\n documents[slug] = { slug, pages: {}, config: undefined };\n }\n documents[slug].pages[pageName] = mod.default;\n }\n\n for (const [path, mod] of Object.entries(configModules)) {\n const match = path.match(/^\\\\/pdfs\\\\/([^/]+)\\\\/config\\\\.ts$/);\n if (!match) continue;\n const slug = match[1];\n if (documents[slug]) {\n documents[slug].config = mod.default;\n }\n }\n\n return documents;\n}\n\nexport function getDocumentSlugs() {\n return Object.keys(getDocuments());\n}\n`;\n }\n },\n };\n}\n"],"mappings":";AAEA,IAAM,uBAAuB;AAC7B,IAAM,gCAAgC,KAAK,oBAAoB;AAMxD,SAAS,sBAAsB,EAAE,UAAU,GAAiC;AACjF,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,gBAAgB,QAAQ;AACtB,aAAO,MAAM;AACX,eAAO,YAAY,IAAI,CAAC,KAAK,KAAK,SAAS;AACzC,gBAAM,MAAM,IAAI,OAAO;AAGvB,cAAI,IAAI,SAAS,GAAG,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW,gBAAgB,GAAG;AACjF,iBAAK;AACL;AAAA,UACF;AAEA,gBAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAiCa,SAAS;AAAA;AAAA;AAInC,cAAI,UAAU,gBAAgB,WAAW;AACzC,cAAI,aAAa;AACjB,cAAI,IAAI,IAAI;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,UAAU,IAAI;AACZ,UAAI,OAAO,sBAAsB;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,KAAK,IAAI;AACP,UAAI,OAAO,+BAA+B;AACxC,eAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiCT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|