Skip to content

Commit 4b80099

Browse files
feat: implement main process with window management, file system IPC, and PDF export functionality
1 parent 8c4444d commit 4b80099

2 files changed

Lines changed: 46 additions & 139 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api-documenter",
3-
"version": "1.0.19",
3+
"version": "1.0.20",
44
"description": "Self-hosted Postman alternative with folder-level RBAC and offline-first documentation",
55
"main": "./out/main/index.js",
66
"author": "Praneeth Kulukuri",

src/main/index.ts

Lines changed: 45 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import pg from 'pg'
88
import { spawn } from 'child_process'
99
import { autoUpdater } from 'electron-updater'
1010
import MarkdownIt from 'markdown-it'
11-
import puppeteer from 'puppeteer'
1211

1312
let mainWindow: BrowserWindow | null = null
1413

@@ -145,8 +144,8 @@ ipcMain.handle("export-pdf", async (_, html, fileName) => {
145144
win.close()
146145
})
147146

148-
// Helper for PDF generation
149-
async function generatePdfBuffer(markdownContent: string) {
147+
// Helper for PDF generation — uses Electron's built-in Chromium (no external Chrome needed)
148+
async function generatePdfBuffer(markdownContent: string): Promise<Buffer> {
150149
const md = new MarkdownIt({
151150
html: true,
152151
linkify: true,
@@ -162,7 +161,7 @@ async function generatePdfBuffer(markdownContent: string) {
162161
<style>
163162
@page {
164163
size: A4;
165-
margin: 20mm;
164+
margin: 25mm;
166165
}
167166
body {
168167
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -213,12 +212,11 @@ async function generatePdfBuffer(markdownContent: string) {
213212
h4 {
214213
font-size: 1.1rem;
215214
font-weight: 700;
216-
color: #374151;
215+
color: #6B7280;
217216
margin-top: 28px;
218217
margin-bottom: 12px;
219218
text-transform: uppercase;
220219
letter-spacing: 0.05em;
221-
color: #6B7280;
222220
}
223221
p {
224222
margin: 16px 0;
@@ -258,126 +256,34 @@ async function generatePdfBuffer(markdownContent: string) {
258256
.status-5xx { background: #FEE2E2; color: #991B1B; }
259257
260258
/* --- Table of Contents (Premium Modern) --- */
261-
.toc-container {
262-
margin: 80px 0;
263-
}
264-
.toc-title-bar {
265-
border-bottom: 3px solid #3B82F6;
266-
margin-bottom: 48px;
267-
padding-bottom: 24px;
268-
}
269-
.toc-title-bar h2 {
270-
margin: 0 !important;
271-
font-size: 3rem !important;
272-
border-bottom: none !important;
273-
color: #111827 !important;
274-
}
259+
.toc-container { margin: 80px 0; }
260+
.toc-title-bar { border-bottom: 3px solid #3B82F6; margin-bottom: 48px; padding-bottom: 24px; }
261+
.toc-title-bar h2 { margin: 0 !important; font-size: 3rem !important; border-bottom: none !important; color: #111827 !important; }
275262
.toc-list { display: flex; flex-direction: column; gap: 32px; }
276263
.toc-folder-group { position: relative; }
277264
.toc-folder-item { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; }
278-
.toc-folder-number {
279-
background: #F3F4F6;
280-
color: #3B82F6;
281-
font-weight: 800;
282-
padding: 2px 8px;
283-
border-radius: 6px;
284-
font-size: 0.9em;
285-
min-width: 32px;
286-
text-align: center;
287-
font-family: monospace;
288-
}
289-
.toc-folder-link {
290-
color: #111827;
291-
text-decoration: none;
292-
font-size: 1.4rem;
293-
font-weight: 800;
294-
}
295-
.toc-endpoints-container {
296-
border-left: 2px solid #F3F4F6;
297-
margin-left: 15px;
298-
padding-left: 32px;
299-
display: flex;
300-
flex-direction: column;
301-
gap: 12px;
302-
}
265+
.toc-folder-number { background: #F3F4F6; color: #3B82F6; font-weight: 800; padding: 2px 8px; border-radius: 6px; font-size: 0.9em; min-width: 32px; text-align: center; font-family: monospace; }
266+
.toc-folder-link { color: #111827; text-decoration: none; font-size: 1.4rem; font-weight: 800; }
267+
.toc-endpoints-container { border-left: 2px solid #F3F4F6; margin-left: 15px; padding-left: 32px; display: flex; flex-direction: column; gap: 12px; }
303268
.toc-endpoint-item { display: flex; align-items: center; gap: 12px; position: relative; }
304-
.toc-endpoint-item::before {
305-
content: "";
306-
position: absolute;
307-
left: -33px;
308-
top: 50%;
309-
width: 12px;
310-
height: 2px;
311-
background: #F3F4F6;
312-
}
269+
.toc-endpoint-item::before { content: ""; position: absolute; left: -33px; top: 50%; width: 12px; height: 2px; background: #F3F4F6; }
313270
.toc-endpoint-bullet { display: none; }
314271
.toc-endpoint-link { color: #4B5563; text-decoration: none; font-size: 1.1rem; font-weight: 500; }
315272
316273
/* --- Code & Pre --- */
317-
pre {
318-
background-color: #f8fafc;
319-
color: #1e293b;
320-
padding: 24px;
321-
border-radius: 12px;
322-
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
323-
font-size: 0.9rem;
324-
line-height: 1.7;
325-
overflow-x: auto;
326-
margin: 24px 0;
327-
border: 1px solid #e2e8f0;
328-
}
329-
code {
330-
font-family: inherit;
331-
background-color: #f1f5f9;
332-
color: #475569;
333-
padding: 0.2rem 0.4rem;
334-
border-radius: 4px;
335-
font-size: 0.9em;
336-
}
337-
pre code {
338-
background-color: transparent;
339-
color: inherit;
340-
padding: 0;
341-
}
274+
pre { background-color: #f8fafc; color: #1e293b; padding: 24px; border-radius: 12px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 0.9rem; line-height: 1.7; overflow-x: auto; margin: 24px 0; border: 1px solid #e2e8f0; }
275+
code { font-family: inherit; background-color: #f1f5f9; color: #475569; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
276+
pre code { background-color: transparent; color: inherit; padding: 0; }
342277
343278
/* --- Tables --- */
344-
table {
345-
width: 100%;
346-
border-collapse: collapse;
347-
margin: 32px 0;
348-
}
349-
table th, table td {
350-
padding: 14px 16px;
351-
border: 1px solid #E5E7EB;
352-
text-align: left;
353-
vertical-align: top;
354-
}
355-
table th {
356-
background-color: #F9FAFB;
357-
font-weight: 700;
358-
color: #374151;
359-
text-transform: uppercase;
360-
font-size: 0.75rem;
361-
letter-spacing: 0.05em;
362-
}
279+
table { width: 100%; border-collapse: collapse; margin: 32px 0; }
280+
table th, table td { padding: 14px 16px; border: 1px solid #E5E7EB; text-align: left; vertical-align: top; }
281+
table th { background-color: #F9FAFB; font-weight: 700; color: #374151; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; }
363282
364283
/* --- Helpers --- */
365-
.page-break {
366-
page-break-after: always;
367-
}
368-
blockquote {
369-
border-left: 4px solid #3B82F6;
370-
padding: 8px 16px;
371-
margin: 24px 0;
372-
background: #F9FAFB;
373-
color: #4B5563;
374-
font-style: italic;
375-
border-radius: 0 8px 8px 0;
376-
}
377-
img {
378-
max-width: 100%;
379-
border-radius: 12px;
380-
}
284+
.page-break { page-break-after: always; }
285+
blockquote { border-left: 4px solid #3B82F6; padding: 8px 16px; margin: 24px 0; background: #F9FAFB; color: #4B5563; font-style: italic; border-radius: 0 8px 8px 0; }
286+
img { max-width: 100%; border-radius: 12px; }
381287
</style>
382288
</head>
383289
<body>
@@ -388,32 +294,33 @@ async function generatePdfBuffer(markdownContent: string) {
388294
</html>
389295
`
390296

391-
const browser = await puppeteer.launch({
392-
headless: true,
393-
args: ['--no-sandbox', '--disable-setuid-sandbox']
394-
})
395-
const page = await browser.newPage()
396-
await page.setContent(fullHtml, { waitUntil: 'networkidle0' })
397-
398-
const buffer = await page.pdf({
399-
format: 'A4',
400-
margin: {
401-
top: '25mm',
402-
right: '25mm',
403-
bottom: '25mm',
404-
left: '25mm'
405-
},
406-
displayHeaderFooter: true,
407-
headerTemplate: '<span></span>',
408-
footerTemplate: `
409-
<div style="font-size: 10px; color: #aaa; width: 100%; text-align: center; font-family: 'Segoe UI', Arial, sans-serif;">
410-
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
411-
</div>`,
412-
printBackground: true
297+
// Write HTML to a temp file so BrowserWindow can load it as a local file
298+
const tempHtmlPath = path.join(os.tmpdir(), `api-doc-pdf-${Date.now()}.html`)
299+
fs.writeFileSync(tempHtmlPath, fullHtml, 'utf-8')
300+
301+
const win = new BrowserWindow({
302+
show: false,
303+
width: 1200,
304+
height: 1600,
305+
webPreferences: { javascript: false }
413306
})
414307

415-
await browser.close()
416-
return buffer
308+
try {
309+
await win.loadFile(tempHtmlPath)
310+
// Wait for fonts/images to settle
311+
await new Promise(resolve => setTimeout(resolve, 500))
312+
313+
const pdfBuffer = await win.webContents.printToPDF({
314+
printBackground: true,
315+
preferCSSPageSize: true,
316+
margins: { marginType: 'none' }
317+
})
318+
319+
return Buffer.from(pdfBuffer)
320+
} finally {
321+
win.close()
322+
try { fs.unlinkSync(tempHtmlPath) } catch (_) { /* ignore cleanup errors */ }
323+
}
417324
}
418325

419326
ipcMain.handle('preview-doc-pdf', async (_event, markdownContent: string) => {

0 commit comments

Comments
 (0)