Add configurable highlighting style

This commit is contained in:
√(noham)²
2026-06-20 14:50:39 +02:00
parent 158d6b8eab
commit 5ce76ffea6
7 changed files with 43 additions and 27 deletions

View File

@@ -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
@@ -34,6 +34,7 @@ Arguments:
Options:
-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
@@ -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 |

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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'
}

View File

@@ -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 })

View File

@@ -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