UNPKG

resumefy

Version:

A simple toolkit to bring your JSON Resume to life

128 lines (127 loc) 4.39 kB
import { strip } from 'ansicolor'; import puppeteer from 'puppeteer'; import ErrorHtmlRenderer from 'error-html'; import { existsSync } from 'fs'; import { mkdir } from 'fs/promises'; import { ResumePage } from './ResumePage.js'; /** * Represents a browser to render resume */ export class ResumeBrowser { #browser; #errorHtmlRenderer; #previewPage; constructor(browser) { this.#browser = browser; this.#errorHtmlRenderer = new ErrorHtmlRenderer({ appPath: process.cwd() }); } static async launch(options) { const puppeteerBrowser = await puppeteer.launch(options); return new ResumeBrowser(puppeteerBrowser); } isHeadless() { return !!this.#browser.process()?.spawnargs?.some((arg) => arg.includes('--headless')); } /** * Get a specific page of the browser. If the page does not exist, create it. * @returns Promise resolving with the requested page of the browser */ async getPage(number = 0) { const pages = await this.#browser.pages(); if (pages.length > number) { return new ResumePage(pages[number], this); } await this.#browser.newPage(); return this.getPage(number); } /** * Render given content to the first page of the browser * @param content Content to render * @returns Promise resolving with first page of the browser */ async render(content) { const page = await this.getPage(0); await page.setContent(content); return page; } /** * Render given error to the first page of the browser (if not in headless mode) * @param err Error to render * @returns Promise resolving with first page of the browser */ async error(err) { if (this.isHeadless()) { // Do not render error in headless mode return; } const error = err instanceof Error ? err : new Error(`An error occurred while rendering the resume: ${err}`); error.message = strip(error.message); error.stack = error.stack ? strip(error.stack) : error.stack; const htmlError = this.#errorHtmlRenderer.render(error); return this.render(htmlError); } /** * Close the browser * @returns Promise resolving when browser is closed */ async close() { await this.#browser.close(); } /** * Write HTML and PDF files to the given directory * @param dir Directory to write files to * @param name Name of the files * @returns Promise resolving when files are written */ async writeFiles(dir, name) { if (!existsSync(dir)) { await mkdir(dir); } const page = await this.getPage(0); return Promise.all([page.html(dir, name), page.pdf(dir, name)]); } /** * Add a preview button to the first page of the browser to open the given file * in a new tab (if not in headless mode) * @param fileUrl URL to the file to preview * @returns Promise resolving when button is added */ async addMenu(fileUrl) { if (this.isHeadless()) { // Do not add menu in headless mode return; } const resumePage = await this.getPage(0); const openFileInNewPage = async () => { if (this.#previewPage && !this.#previewPage.isClosed()) { // Bring preview page to front if it is already open await this.#previewPage.bringToFront(); } else { // Otherwise open new preview page and navigate to file const previewPage = await this.#browser.newPage(); await previewPage.goto(fileUrl); this.#previewPage = previewPage; } }; await resumePage.addMenu(openFileInNewPage); } /** * Reload the preview page (if not in headless mode) * @returns Promise resolving when page is reloaded */ async reloadPreview() { if (this.isHeadless()) { // Do not reload preview in headless mode return; } if (this.#previewPage && !this.#previewPage.isClosed()) { try { await this.#previewPage.reload(); } catch { // Page has been closed } } } }