mirror of
https://github.com/NohamR/pdfy.git
synced 2026-10-11 02:39:58 +00:00
Add configurable highlighting style
This commit is contained in:
@@ -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 |
|
||||
|
||||
|
||||
11
src/cli.js
11
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
11
src/index.js
11
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)
|
||||
|
||||
14
src/pdf.js
14
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'
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user