From 5ce76ffea66c179e8d72dda777667c2881fa200c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=88=9A=28noham=29=C2=B2?= <100566912+NohamR@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:50:39 +0200 Subject: [PATCH] Add configurable highlighting style --- README.md | 20 ++++++++++++-------- src/cli.js | 11 ++++++++--- src/config.js | 2 ++ src/index.js | 11 ++++++----- src/pdf.js | 14 ++++++++------ src/pdfy.js | 5 +++-- src/server.js | 7 ++++--- 7 files changed, 43 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c8b4183..09bab62 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Convert web articles to PDF using the [Reader View](https://chromewebstore.googl - Renders using Reader View for clean typography - Supports all Reader View themes: `light`, `dark`, `sepia`, `groove-dark`, `solarized-light`, `solarized-dark`, `nord-light`, `nord-dark` - Custom CSS injection -- Syntax highlighting via highlight.js +- Syntax highlighting via highlight.js (configurable style) - Automatic reading time estimation - Configurable extension preferences @@ -33,13 +33,14 @@ Arguments: url URL of the article to convert Options: - -t, --theme Reader View theme (light, dark, sepia, groove-dark, solarized-light, solarized-dark, nord-light, nord-dark) - -c, --css path to custom CSS file - --config path to config file (.pdfyrc, .pdfyrc.json, pdfy.config.js) - -p, --prefs path to extension preferences JSON - -o, --output output file path (default: ./output/.pdf) - -l, --log-level <level> logging level (fatal, error, warn, info, debug) - -h, --help display help for command + -t, --theme <theme> Reader View theme (light, dark, sepia, groove-dark, solarized-light, solarized-dark, nord-light, nord-dark) + --highlight-style <style> highlight.js style (e.g. github, monokai, solarized-light, atom-one-dark) + -c, --css <path> path to custom CSS file + --config <path> path to config file (.pdfyrc, .pdfyrc.json, pdfy.config.js) + -p, --prefs <path> path to extension preferences JSON + -o, --output <path> output file path (default: ./output/<title>.pdf) + -l, --log-level <level> logging level (fatal, error, warn, info, debug) + -h, --help display help for command ``` ### Configuration @@ -78,6 +79,7 @@ CLI flags always override config file values. Use `--config <path>` to specify a node src/index.js https://example.com/article node src/index.js https://example.com/article --theme dark --css config/rules.css node src/index.js https://example.com/article --prefs config/reader-view-preferences.json +node src/index.js https://example.com/article --highlight-style monokai node src/index.js https://example.com/article --config .pdfyrc npm start -- https://example.com/article --theme dark --css config/rules.css ``` @@ -134,6 +136,7 @@ try { |--------|------|---------|-------------| | `theme` | string | `'light'` | Reader View theme | | `css` | string | `null` | Custom CSS string | +| `highlightStyle` | string | `'github'` | highlight.js style name | | `prefs` | object | `{}` | Extension preferences | | `output` | string | `null` | File path to write PDF (returns buffer when omitted) | | `browser` | Browser | `null` | Reuse a Puppeteer browser instance | @@ -178,6 +181,7 @@ curl -X POST http://localhost:8080/convert \ | `url` | string | Article URL (required unless `html` is provided) | | `theme` | string | Reader View theme | | `css` | string | Custom CSS string | +| `highlightStyle` | string | highlight.js style (default: `"github"`) | | `html` | string | Pre-fetched HTML (bypasses URL fetch) | | `prefs` | object | Extension preferences | diff --git a/src/cli.js b/src/cli.js index a588a85..d264256 100644 --- a/src/cli.js +++ b/src/cli.js @@ -21,6 +21,10 @@ program '-t, --theme <theme>', `Reader View theme (${VALID_THEMES.join(', ')})` ) + .option( + '--highlight-style <style>', + 'highlight.js style (e.g. github, monokai, solarized-light, atom-one-dark)' + ) .option('-c, --css <path>', 'path to custom CSS file') .option('--config <path>', 'path to config file (.pdfyrc, .pdfyrc.json, pdfy.config.js)') .option('-p, --prefs <path>', 'path to extension preferences JSON') @@ -37,8 +41,8 @@ program ` Examples: node src/index.js <URL> - node src/index.js <URL> --theme dark --css config/rules.css - npm start -- <URL> --theme dark --css config/rules.css + node src/index.js <URL> --theme dark --highlight-style monokai --css config/rules.css + npm start -- <URL> --theme dark --highlight-style monokai --css config/rules.css ` ) @@ -52,7 +56,8 @@ export function parseArgs () { css: opts.css || null, prefs: opts.prefs || null, output: opts.output || null, - config: opts.config || null + config: opts.config || null, + highlightStyle: opts.highlightStyle || null } } } diff --git a/src/config.js b/src/config.js index bb2dc74..3532a3a 100644 --- a/src/config.js +++ b/src/config.js @@ -11,6 +11,8 @@ function normalizeConfig (config) { if (config.css) result.css = config.css if (config.prefs) result.prefs = config.prefs if (config.output) result.output = config.output + if (config.highlightStyle) result.highlightStyle = config.highlightStyle + if (config['highlight-style']) result.highlightStyle = config['highlight-style'] if (config['log-level']) result.logLevel = config['log-level'] if (config.logLevel) result.logLevel = config.logLevel return result diff --git a/src/index.js b/src/index.js index 2b3f413..260f6fa 100644 --- a/src/index.js +++ b/src/index.js @@ -23,9 +23,9 @@ async function main () { const config = await loadConfigFile(opts.config) - const prefsPath = opts.prefs || null - const cssPath = opts.css || null - const outputPath = opts.output || null + const prefsPath = opts.prefs || config.prefs || null + const cssPath = opts.css || config.css || null + const outputPath = opts.output || config.output || null const preferences = prefsPath ? loadPreferences(path.resolve(prefsPath)) : {} const customCss = cssPath ? readCustomCss(path.resolve(cssPath)) : null @@ -37,10 +37,11 @@ async function main () { try { const result = await convert(currentUrl, { html, - theme: opts.theme || preferences.mode, + theme: opts.theme || config.theme || preferences.mode || 'light', css: customCss, prefs: preferences, - output: outputPath + output: outputPath, + highlightStyle: opts.highlightStyle || config.highlightStyle }) const sizeKb = (result.pdfBuffer.length / 1024).toFixed(0) diff --git a/src/pdf.js b/src/pdf.js index c9bb954..6c3d32c 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -2,11 +2,13 @@ import fs from 'node:fs' import path from 'node:path' import logger from './logger.js' -const HIGHLIGHT_CSS = - '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">' const HIGHLIGHT_JS = '<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"><\/script><script>hljs.highlightAll();<\/script>' +function highlightCss (style) { + return `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${style}.min.css">` +} + export function getOutputFilename (title, outputDir) { const safe = title .replace(/[<>:"/\\|?*]/g, '') @@ -27,10 +29,10 @@ export function resolveOutputPath (outputFlag) { return resolved.endsWith('.pdf') ? resolved : `${resolved}.pdf` } -export function enhanceHtml (html, customCss) { +export function enhanceHtml (html, customCss, highlightStyle = 'github') { let enhanced = html.replace( '</head>', - `${HIGHLIGHT_CSS}${HIGHLIGHT_JS}</head>` + `${highlightCss(highlightStyle)}${HIGHLIGHT_JS}</head>` ) if (customCss !== null) { enhanced = enhanced.replace( @@ -51,8 +53,8 @@ export async function generatePdf (page, html, outputPath, { timeout = 30000 } = format: 'A4', printBackground: true, margin: { - top: '0mm', - bottom: '0mm', + top: '10mm', + bottom: '10mm', left: '0mm', right: '0mm' } diff --git a/src/pdfy.js b/src/pdfy.js index 84f9831..0f915f3 100644 --- a/src/pdfy.js +++ b/src/pdfy.js @@ -37,7 +37,8 @@ export async function convert (url, options = {}) { css = null, prefs = {}, output = null, - browser: externalBrowser = null + browser: externalBrowser = null, + highlightStyle } = options if (!VALID_THEMES.includes(theme)) { @@ -112,7 +113,7 @@ export async function convert (url, options = {}) { contentHtml = contentHtml.replace(/ loading="lazy"/g, '') await readerPage.close() - const enhancedHtml = enhanceHtml(contentHtml, css) + const enhancedHtml = enhanceHtml(contentHtml, css, highlightStyle) const pdfPage = await browser.newPage() await pdfPage.setViewport({ width: 1280, height: 7200 }) diff --git a/src/server.js b/src/server.js index bfe26fc..331c43e 100644 --- a/src/server.js +++ b/src/server.js @@ -29,7 +29,7 @@ async function handleConvert (req, res) { return sendJson(res, 400, { error: 'Invalid JSON body' }) } - const { url, theme, css, prefs, html } = body + const { url, theme, css, prefs, html, highlightStyle } = body if (!url && !html) { return sendJson(res, 400, { error: 'Missing required field: url (or html for direct content)' }) @@ -47,7 +47,8 @@ async function handleConvert (req, res) { html: html || null, theme: theme || 'light', css: css || null, - prefs: prefs || {} + prefs: prefs || {}, + highlightStyle }) const sizeKb = (result.pdfBuffer.length / 1024).toFixed(0) @@ -92,7 +93,7 @@ const server = createServer((req, res) => { pdfy server POST /convert Convert a URL to PDF - Body: { "url": "...", "theme": "...", "css": "...", "html": "..." } + Body: { "url": "...", "theme": "...", "css": "...", "html": "...", "highlightStyle": "..." } GET /health Health check GET / This help