UNPKG

markugen

Version:

Markdown to HTML/PDF static site generation tool

185 lines (184 loc) 7.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const node_path_1 = __importDefault(require("node:path")); const fs_extra_1 = __importDefault(require("fs-extra")); const puppeteer_core_1 = __importDefault(require("puppeteer-core")); const url_1 = __importDefault(require("url")); const generator_1 = __importDefault(require("./generator")); __exportStar(require("./pdfoptions"), exports); /** * Generator for HTML to PDF file generation */ class PdfGenerator extends generator_1.default { /** * The options to use in generate */ options; /** * The list of files to convert */ files = []; /** * Constructs a new generator * @param mark the instance of {@link Markugen} */ constructor(mark, options) { super(mark, options); this.options = { input: options.input, remove: options.remove ?? false, extensions: options.extensions ?? ['html'], links: options.links ?? true, }; } /** * Generates the PDFs for the given {@link PdfOptions options} * @param options the {@link PdfOptions options} to use for generation * @returns a list of PDF files that were generated */ async generate() { this.validate(); this.start(); // prepare the browser this.log('Browser:', this.mark.options.browser); const generated = []; const promises = []; // loop over and write the pdf for each file for (const file of this.files) promises.push(this.writePdf(file)); // wait for all promises to be settled const results = await Promise.allSettled(promises); for (const result of results) { if (result.status === 'fulfilled') generated.push(result.value); else if (result.status === 'rejected') throw new Error(result.reason); } this.finish(); return generated; } /** * Creates the pdf version of the file * @param file the path to the html file or the html string */ async writePdf(file) { const parts = node_path_1.default.parse(file); const pdf = node_path_1.default.join(parts.dir, parts.name + '.pdf'); this.log('Generating PDF:', pdf); const browser = await puppeteer_core_1.default.launch({ executablePath: this.mark.options.browser, args: this.mark.options.sandbox === false ? ['--no-sandbox', '--disable-setuid-sandbox'] : undefined, }); const page = await browser.newPage(); await page.goto(url_1.default.pathToFileURL(file).toString(), { waitUntil: 'networkidle2' }); // replace all markdown relative links with the pdf equivalent if (this.options.links) await page.evaluate(() => { const links = document.querySelectorAll('.markugen-md-link'); for (const link of links) { //path.basename('foo/bar.txt'); // @ts-expect-error puppeteer types no work here const html = link.href.split('#')[0].split('/').pop(); const pdf = html.replace(/\.html$/ig, '.pdf'); // @ts-expect-error puppeteer types no work here link.href = link.href.replace(html, pdf); link.innerHTML = link.innerHTML.replace(html, pdf); } }); // get the content box const content = await page.$('#markugen-content'); const box = await content?.boxModel(); content?.dispose(); await page.pdf({ format: 'A4', path: pdf, margin: { left: box?.content[0].x ?? '25px', right: box?.content[3].x ?? '25px', bottom: box?.content[0].y ?? '25px', top: box?.content[0].y ?? '25px', }, }); // close the browser await browser.close(); // remove the html file if remove option given if (this.options.remove) fs_extra_1.default.removeSync(file); // return the path to the pdf return pdf; } /** * Validates the given options * @param options the {@link PdfOptions options} to validate * @returns an array of files to generate PDFs for */ validate() { // clear out the files this.files.length = 0; // always make it an array if (!Array.isArray(this.options.input)) this.options.input = [this.options.input]; // resolve the paths for (let i = 0; i < this.options.input.length; i++) { this.options.input[i] = node_path_1.default.resolve(this.options.input[i]); if (!fs_extra_1.default.existsSync(this.options.input[i])) throw new Error(`Input path does not exist [${this.options.input[i]}]`); } // check the browser if (!this.mark.options.browser || !fs_extra_1.default.existsSync(this.mark.options.browser)) throw new Error(`Unable to locate browser at [${this.mark.options.browser}], cannot generate PDFs`); // handle the extensions if (this.options.extensions.length === 0) this.options.extensions.push('html'); let pattern = '\\.'; for (let i = 0; i < this.options.extensions.length; i++) pattern += `${i !== 0 ? '|' : ''}(${this.options.extensions[i]})`; pattern += '$'; const regex = new RegExp(pattern, 'i'); // collect the files for (const file of this.options.input) { const stat = fs_extra_1.default.lstatSync(file); // directories need to be globbed if (stat.isDirectory()) this.collect(file, regex); // files are good to go else this.files.push(file); } // nothing to do if no files found if (this.files.length === 0) throw new Error('No files found for PDF generation'); } /** * Collects all html files found in the given directory * @param dir the directory to look in */ collect(dir, regex) { const paths = fs_extra_1.default.readdirSync(dir, { withFileTypes: true }); for (const p of paths) { const full = node_path_1.default.join(p.parentPath, p.name); if (p.isFile() && regex.test(p.name)) this.files.push(full); else if (p.isDirectory()) this.collect(full, regex); } } } exports.default = PdfGenerator;