diff --git a/LISCENCE b/LICENSE similarity index 100% rename from LISCENCE rename to LICENSE diff --git a/README.md b/README.md index bf00c4a..abe5271 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ npx docusaurus-pdf from-build-config - Optional: If you have a `baseUrl` configured in your `docusaurus.config.js` then pass this value as `baseUrl`. - Note: There is a optional parameter to set a custom filename. You can see further details using `npx docusaurus-pdf from-build --help`. +### Table of Contents +A table of contents will be generated wherever you place a `` tag. The items in the table of contents will be indented according to their header size. +The header will have class `toc-header`, the ul element will have class `toc-list`, and the individual li elements will have class `toc-item`. + ## Docker usage All dependencies needed to create a PDF from your docusaurus site are bundled in our Dockerfile. diff --git a/package-lock.json b/package-lock.json index 91887ad..c5d849e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2215,23 +2215,6 @@ "fastq": "^1.6.0" } }, - "@pdf-lib/standard-fonts": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-0.0.4.tgz", - "integrity": "sha512-2pg8hXnChVAF6aSFraXtwB0cx/AgE15FvuLJbdPJSq9LYp1xMp0lapH4+t1HsdD9cA05rnWYLqlEBwS4YK1jLg==", - "requires": { - "base64-arraybuffer": "^0.1.5", - "pako": "^1.0.6" - } - }, - "@pdf-lib/upng": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", - "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", - "requires": { - "pako": "^1.0.10" - } - }, "@sinonjs/commons": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", @@ -3733,11 +3716,6 @@ } } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -3783,6 +3761,16 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -6899,6 +6887,13 @@ "flat-cache": "^2.0.1" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "filesize": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", @@ -11274,6 +11269,13 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -11841,7 +11843,8 @@ "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true }, "parallel-transform": { "version": "1.2.0", @@ -12011,17 +12014,6 @@ "sha.js": "^2.4.8" } }, - "pdf-lib": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.7.0.tgz", - "integrity": "sha512-JauBSWwR5hCsdqxCsm1vJUJ4R1MSikzxWzi+E4zFFMU0zZHX71u18oyW0ZaELXJW13L11I/2LKrUJEhTISUzzg==", - "requires": { - "@pdf-lib/standard-fonts": "^0.0.4", - "@pdf-lib/upng": "^1.0.1", - "pako": "^1.0.11", - "tslib": "^1.11.1" - } - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -16521,7 +16513,8 @@ "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true }, "tsutils": { "version": "3.17.1", @@ -17249,7 +17242,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", @@ -17910,7 +17907,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", diff --git a/package.json b/package.json index 959d919..dfd5cc3 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "chalk": "^3.0.0", "commander": "^4.1.1", "express": "^4.17.1", - "pdf-lib": "^1.7.0", "puppeteer": "^2.1.1" }, "files": [ diff --git a/src/index.ts b/src/index.ts index c0d34cd..c81f730 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,25 +2,11 @@ import chalk = require("chalk"); import puppeteer = require("puppeteer"); import express = require("express"); import { AddressInfo } from "net"; -import { PDFDocument } from "pdf-lib"; import * as fs from "fs"; import * as path from "path"; -const generatedPdfBuffers: Array = []; - -async function mergePdfBuffers(pdfBuffers: Array) { - const outDoc = await PDFDocument.create(); - for (const pdfBuffer of pdfBuffers) { - const docToAdd = await PDFDocument.load(pdfBuffer); - const pages = await outDoc.copyPages(docToAdd, docToAdd.getPageIndices()); - for (const page of pages) { - outDoc.addPage(page); - } - } - - return outDoc.save(); -} +const htmlList: Array = []; const getURL = (origin: string, filePath: string) => { return origin + "/" + filePath.substring(filePath.startsWith("/") ? 1 : 0); @@ -112,10 +98,12 @@ export async function generatePdf( let scriptPath = ""; let nextPageUrl = initialDocsUrl; + const headers: Array<{ header: string; level: number; id: string }> = []; + let tocLocation = null; while (nextPageUrl) { console.log(); - console.log(chalk.cyan(`Generating PDF of ${nextPageUrl}`)); + console.log(chalk.cyan(`Retrieving html from ${nextPageUrl}`)); console.log(); await page @@ -126,8 +114,8 @@ export async function generatePdf( throw new Error( `Page could not be loaded! Did not get any HTML for ${nextPageUrl}` ); - stylePath = getStylesheetPathFromHTML(html, origin); - scriptPath = getScriptPathFromHTML(html, origin); + if (!stylePath) stylePath = getStylesheetPathFromHTML(html, origin); + if (!scriptPath) scriptPath = getScriptPathFromHTML(html, origin); }); try { @@ -141,28 +129,79 @@ export async function generatePdf( nextPageUrl = ""; } - const html = await page.$eval("article", (element) => { + let html = await page.$eval("article", (element) => { return element.outerHTML; }); - await page.setContent(html); - await page.addStyleTag({ url: stylePath }); - await page.addScriptTag({ url: scriptPath }); - const pdfBuffer = await page.pdf({ - path: "", - format: "A4", - printBackground: true, - margin: { top: 25, right: 35, left: 35, bottom: 25 }, - }); + const tocMatch = html.match(/(<\/toc>)|()/); + + if (tocMatch && tocLocation == null) { + htmlList.push(html.slice(0, tocMatch.index)); + tocLocation = htmlList.length; + html = html.slice(tocMatch.index); + } - generatedPdfBuffers.push(pdfBuffer); + if (tocLocation !== null) { + // parse headers in html for table of contents + html = html.replace(//g, (str) => { + // docusaurus inserts #s into headers for direct links to the header + const headerText = str + .replace(/]*>#<\/a( )*>/g, "") + .replace(/<[^>]*>/g, "") + .trim(); + const headerId = `${Math.random().toString(36).substr(2, 5)}-${ + headers.length + }`; + headers.push({ + header: headerText, + level: Number(str[str.indexOf("h") + 1]), + id: headerId, + }); + + const text = str.replace(//g, (header) => { + if (header.match(/id( )*=( )*"/g)) { + return header.replace(/id( )*=( )*"/g, `id="${headerId} `); + } else { + return ( + header.substring(0, header.length - 1) + ` id="${headerId}">` + ); + } + }); + return text; + }); + } + htmlList.push(html); console.log(chalk.green("Success")); } - await browser.close(); - const mergedPdfBuffer = await mergePdfBuffers(generatedPdfBuffers); - fs.writeFileSync(`${filename}`, mergedPdfBuffer); + if (tocLocation !== null) { + const toc = headers + .map( + (header) => + `
  • ${header.header}
  • ` + ) + .join("\n"); + htmlList.splice( + tocLocation, + 0, + `

    Table of contents:

      ${toc}
    ` + ); + } + + await page.setContent(htmlList.join("\n")); + await page.addStyleTag({ url: stylePath }); + await page.addScriptTag({ url: scriptPath }); + await page.pdf({ + path: filename, + format: "A4", + printBackground: true, + margin: { top: 25, right: 35, left: 35, bottom: 25 }, + }); + + await browser.close(); } interface LoadedConfig {