md-pdf-studio 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/bin/cli.js +2 -0
- package/package.json +2 -1
- package/src/core/footnotes.js +122 -0
- package/src/core/parser.js +15 -4
- package/src/core/sanitizer.js +23 -1
- package/src/core/templates.js +6 -0
- package/src/core/themes/highlight.js +38 -0
- package/src/core/themes/index.js +8 -2
package/README.md
CHANGED
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
- 🚀 **CLI & Batch Conversion**: Convert single files or batch process directories with wildcard patterns.
|
|
28
28
|
- 🎨 **Custom CSS Injection**: Inject custom branding CSS stylesheets (`-c, --css custom.css`).
|
|
29
29
|
- 📊 **Mermaid Diagrams**: Sequence diagrams (with auto note wrapping), flowcharts, class diagrams, and gantt charts.
|
|
30
|
+
- 🌈 **Syntax Highlighting**: Fenced code blocks that name a language (` ```js `, ` ```python `, ` ```diff `, …) are coloured at build time with highlight.js — no CDN, so it also works offline and in static HTML export. Colours follow the theme's code block background. Works in the CLI and in the extension preview.
|
|
30
31
|
- 📐 **LaTeX Math Support**: Mathematical equations rendered with KaTeX.
|
|
32
|
+
- 📝 **Footnotes**: `text[^1]` with `[^1]: the note` anywhere in the file. Notes are numbered in the order they are first referenced, gathered at the end of the document, and linked both ways. Works in the CLI and in the extension preview.
|
|
31
33
|
- 💡 **GitHub Callout Box Alerts**: Support for `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, `> [!IMPORTANT]`, and `> [!CAUTION]`.
|
|
32
34
|
- 💻 **ASCII UI Mockups**: Styled dark-theme boxes for terminal output & ASCII wireframe mockups.
|
|
33
35
|
|
|
@@ -103,6 +105,7 @@ npx md-pdf-studio ./docs/ -o ./dist/
|
|
|
103
105
|
| `-k, --keep-html` | | Keep temporary HTML file after conversion | `false` |
|
|
104
106
|
| `--no-mermaid` | | Disable Mermaid diagram rendering | `false` |
|
|
105
107
|
| `--no-katex` | | Disable KaTeX math formula rendering | `false` |
|
|
108
|
+
| `--no-highlight` | | Disable syntax highlighting of code blocks | `false` |
|
|
106
109
|
|
|
107
110
|
---
|
|
108
111
|
|
|
@@ -122,6 +125,7 @@ theme: github
|
|
|
122
125
|
font: Inter
|
|
123
126
|
pageSize: A4
|
|
124
127
|
landscape: false
|
|
128
|
+
highlight: true
|
|
125
129
|
---
|
|
126
130
|
# Document Executive Summary
|
|
127
131
|
...
|
|
@@ -134,15 +138,18 @@ landscape: false
|
|
|
134
138
|
This project follows standard **Git Flow** branching and automated release publishing:
|
|
135
139
|
|
|
136
140
|
### 🔀 Branching Strategy
|
|
137
|
-
- **`main`**: Production-ready branch. Stores stable releases.
|
|
141
|
+
- **`main`**: Production-ready branch. Stores stable releases. Pushing a version tag `v*` from `main` triggers a release.
|
|
138
142
|
- **`develop`**: Active development branch. Daily commits, feature additions, and refactoring take place here.
|
|
139
143
|
|
|
140
144
|
### 🚀 Automated Release Pipeline
|
|
141
|
-
Whenever
|
|
142
|
-
1.
|
|
145
|
+
Whenever a version tag `v*` is pushed, GitHub Actions ([`.github/workflows/release.yml`](.github/workflows/release.yml)) builds from that tag and automatically:
|
|
146
|
+
1. Checks that the tag matches the version in `package.json` and `extension/manifest.json`, then runs the full unit test suite (`npm test`).
|
|
143
147
|
2. Bundles cross-platform extension zip artifacts (`dist/extension-vX.Y.Z.zip`).
|
|
144
148
|
3. Auto-generates release notes from commit history.
|
|
145
149
|
4. Publishes a new official **[GitHub Release](https://github.com/vannt-dev/convert-md-to-pdf/releases)** with downloadable ZIP binaries attached!
|
|
150
|
+
5. Publishes [`md-pdf-studio`](https://www.npmjs.com/package/md-pdf-studio) to npm with provenance, through npm trusted publishing (no token secret). A version already on npm is skipped.
|
|
151
|
+
|
|
152
|
+
To release an existing tag again (for example after a failed run), run the workflow manually with `tag_name`.
|
|
146
153
|
|
|
147
154
|
### 💡 1-Click CLI Release Command
|
|
148
155
|
You can also bump version and trigger a release with a single CLI command:
|
package/bin/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ program
|
|
|
28
28
|
.option('-k, --keep-html', 'Keep temporary HTML file after conversion', false)
|
|
29
29
|
.option('--no-mermaid', 'Disable Mermaid diagram rendering')
|
|
30
30
|
.option('--no-katex', 'Disable KaTeX math formula rendering')
|
|
31
|
+
.option('--no-highlight', 'Disable syntax highlighting of code blocks')
|
|
31
32
|
.option('-b, --browser <path>', 'Custom Chrome/Edge executable path')
|
|
32
33
|
.action(async (inputArgs, options) => {
|
|
33
34
|
try {
|
|
@@ -89,6 +90,7 @@ program
|
|
|
89
90
|
keepHtml: options.keepHtml,
|
|
90
91
|
mermaid: options.mermaid,
|
|
91
92
|
katex: options.katex,
|
|
93
|
+
highlight: options.highlight,
|
|
92
94
|
executablePath: options.browser
|
|
93
95
|
});
|
|
94
96
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md-pdf-studio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A powerful CLI tool and Manifest V3 Browser Extension to convert Markdown files, text, or web pages into beautifully styled PDFs with Mermaid diagrams, LaTeX math, TOC, and themes.",
|
|
5
5
|
"main": "src/core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"commander": "^15.0.0",
|
|
50
|
+
"highlight.js": "^11.12.0",
|
|
50
51
|
"marked": "^18.0.14"
|
|
51
52
|
},
|
|
52
53
|
"engines": {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown footnotes: `text[^id]` in the body, `[^id]: note` anywhere on a line of its own.
|
|
3
|
+
*
|
|
4
|
+
* Works on the Markdown text, before it is parsed, and needs nothing from Node: the same file
|
|
5
|
+
* is shipped in the extension as extension/lib/footnotes.js (a test keeps the two identical).
|
|
6
|
+
*/
|
|
7
|
+
(function (scope) {
|
|
8
|
+
const DEFINITION = /^ {0,3}\[\^([^\]\s]+)\]:[ \t]*(.*)$/;
|
|
9
|
+
const REFERENCE = /\[\^([^\]\s]+)\]/g;
|
|
10
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
11
|
+
const CONTINUATION = /^(?: {2,}|\t)\S/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Splits the text into lines tagged as code or prose. Footnote syntax inside a fenced
|
|
15
|
+
* block is an example of the syntax, not a footnote.
|
|
16
|
+
* @param {string} markdown
|
|
17
|
+
* @returns {{ text: string, code: boolean }[]}
|
|
18
|
+
*/
|
|
19
|
+
function tagLines(markdown) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
let fence = null;
|
|
22
|
+
for (const text of markdown.split(/\r?\n/)) {
|
|
23
|
+
const match = FENCE.exec(text);
|
|
24
|
+
if (fence) {
|
|
25
|
+
lines.push({ text, code: true });
|
|
26
|
+
if (match && match[1][0] === fence[0] && match[1].length >= fence.length) fence = null;
|
|
27
|
+
} else if (match) {
|
|
28
|
+
fence = match[1];
|
|
29
|
+
lines.push({ text, code: true });
|
|
30
|
+
} else {
|
|
31
|
+
lines.push({ text, code: false });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return lines;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Applies `replace` to the parts of a line that are not inline code.
|
|
39
|
+
* @param {string} line
|
|
40
|
+
* @param {(prose: string) => string} replace
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function outsideInlineCode(line, replace) {
|
|
44
|
+
return line
|
|
45
|
+
.split(/(`+[^`]*`+)/)
|
|
46
|
+
.map((part, index) => (index % 2 === 1 ? part : replace(part)))
|
|
47
|
+
.join('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Numbers the footnotes of a Markdown document in the order they are first referenced,
|
|
52
|
+
* replaces each reference with a superscript link, and appends the notes as a list.
|
|
53
|
+
*
|
|
54
|
+
* A reference to a note that is never defined is left as the author typed it; a note that
|
|
55
|
+
* is never referenced is left out, as GitHub does.
|
|
56
|
+
* @param {string} markdown
|
|
57
|
+
* @returns {string} Markdown, with HTML for the references and the notes section
|
|
58
|
+
*/
|
|
59
|
+
function applyFootnotes(markdown = '') {
|
|
60
|
+
if (!markdown || markdown.indexOf('[^') === -1) return markdown;
|
|
61
|
+
|
|
62
|
+
const lines = tagLines(markdown);
|
|
63
|
+
const definitions = new Map();
|
|
64
|
+
const body = [];
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < lines.length; index++) {
|
|
67
|
+
const line = lines[index];
|
|
68
|
+
const match = line.code ? null : DEFINITION.exec(line.text);
|
|
69
|
+
if (!match) {
|
|
70
|
+
body.push(line);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const parts = [match[2].trim()];
|
|
74
|
+
// Indented lines that follow belong to the note.
|
|
75
|
+
while (
|
|
76
|
+
index + 1 < lines.length &&
|
|
77
|
+
!lines[index + 1].code &&
|
|
78
|
+
CONTINUATION.test(lines[index + 1].text)
|
|
79
|
+
) {
|
|
80
|
+
index += 1;
|
|
81
|
+
parts.push(lines[index].text.trim());
|
|
82
|
+
}
|
|
83
|
+
// The first definition of an id wins, as with link reference definitions.
|
|
84
|
+
if (!definitions.has(match[1])) definitions.set(match[1], parts.join(' ').trim());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (definitions.size === 0) return markdown;
|
|
88
|
+
|
|
89
|
+
const numbers = new Map();
|
|
90
|
+
const uses = new Map();
|
|
91
|
+
const text = body
|
|
92
|
+
.map((line) => {
|
|
93
|
+
if (line.code) return line.text;
|
|
94
|
+
return outsideInlineCode(line.text, (prose) =>
|
|
95
|
+
prose.replace(REFERENCE, (whole, id) => {
|
|
96
|
+
if (!definitions.has(id)) return whole;
|
|
97
|
+
if (!numbers.has(id)) numbers.set(id, numbers.size + 1);
|
|
98
|
+
const number = numbers.get(id);
|
|
99
|
+
const use = (uses.get(id) || 0) + 1;
|
|
100
|
+
uses.set(id, use);
|
|
101
|
+
const anchor = use === 1 ? `fnref-${number}` : `fnref-${number}-${use}`;
|
|
102
|
+
return `<sup class="footnote-ref"><a href="#fn-${number}" id="${anchor}">${number}</a></sup>`;
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
})
|
|
106
|
+
.join('\n');
|
|
107
|
+
|
|
108
|
+
if (numbers.size === 0) return text;
|
|
109
|
+
|
|
110
|
+
const notes = Array.from(numbers.entries()).map(([id, number]) => {
|
|
111
|
+
const note = definitions.get(id);
|
|
112
|
+
return `${number}. <span id="fn-${number}"></span>${note} <a href="#fnref-${number}" class="footnote-backref" aria-label="Back to the text">↩</a>`;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// The blank lines let Markdown inside the section be parsed as Markdown.
|
|
116
|
+
return `${text.replace(/\s+$/, '')}\n\n<section class="footnotes">\n\n${notes.join('\n')}\n\n</section>\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const api = { applyFootnotes };
|
|
120
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
121
|
+
else scope.MdPdfFootnotes = api;
|
|
122
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
package/src/core/parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { marked } = require('marked');
|
|
2
|
+
const hljs = require('highlight.js/lib/common');
|
|
2
3
|
const {
|
|
3
4
|
parseFrontMatter,
|
|
4
5
|
generateCoverPage,
|
|
@@ -9,13 +10,15 @@ const {
|
|
|
9
10
|
escapeHtml
|
|
10
11
|
} = require('./utils');
|
|
11
12
|
const { getHtmlTemplate } = require('./templates');
|
|
13
|
+
const { applyFootnotes } = require('./footnotes');
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Builds marked custom renderer for diagrams, UI mockups, and headings
|
|
15
17
|
* @param {Map<string, string>} headingsMap
|
|
18
|
+
* @param {boolean} highlight Colour fenced code blocks that name a known language
|
|
16
19
|
* @returns {marked.Renderer}
|
|
17
20
|
*/
|
|
18
|
-
function buildMarkedRenderer(headingsMap) {
|
|
21
|
+
function buildMarkedRenderer(headingsMap, highlight = true) {
|
|
19
22
|
const renderer = new marked.Renderer();
|
|
20
23
|
const originalCodeRenderer = renderer.code.bind(renderer);
|
|
21
24
|
|
|
@@ -31,6 +34,13 @@ function buildMarkedRenderer(headingsMap) {
|
|
|
31
34
|
return `<div class="ui-mockup-container"><pre class="ui-mockup"><code>${escapeHtml(text)}</code></pre></div>`;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
// Only fences that name a language are coloured: guessing one gets short snippets wrong.
|
|
38
|
+
const language = lang.trim().split(/\s+/)[0];
|
|
39
|
+
if (highlight && language && hljs.getLanguage(language)) {
|
|
40
|
+
const highlighted = hljs.highlight(text, { language, ignoreIllegals: true }).value;
|
|
41
|
+
return `<pre><code class="hljs language-${escapeHtml(language)}">${highlighted}</code></pre>\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
return originalCodeRenderer.call(this, token);
|
|
35
45
|
};
|
|
36
46
|
|
|
@@ -86,12 +96,13 @@ function parseMarkdownToHtml(rawMarkdownContent, options = {}) {
|
|
|
86
96
|
footer: options.footer !== undefined ? options.footer : (frontMatter.footer || true),
|
|
87
97
|
mermaid: options.mermaid !== undefined ? options.mermaid : (frontMatter.mermaid !== false),
|
|
88
98
|
katex: options.katex !== undefined ? options.katex : (frontMatter.katex !== false),
|
|
99
|
+
highlight: options.highlight !== undefined ? options.highlight : (frontMatter.highlight !== false),
|
|
89
100
|
customCss: options.customCss || '',
|
|
90
101
|
cssFile: options.cssFile || frontMatter.cssFile || frontMatter.css
|
|
91
102
|
};
|
|
92
103
|
|
|
93
|
-
// 3. Preprocess Markdown (LaTeX, pagebreaks, alerts)
|
|
94
|
-
const processedMd = preprocessMarkdown(markdownContent);
|
|
104
|
+
// 3. Preprocess Markdown (footnotes, LaTeX, pagebreaks, alerts)
|
|
105
|
+
const processedMd = preprocessMarkdown(applyFootnotes(markdownContent));
|
|
95
106
|
|
|
96
107
|
// 4. Generate TOC & Cover Page
|
|
97
108
|
let tocHtml = '';
|
|
@@ -118,7 +129,7 @@ function parseMarkdownToHtml(rawMarkdownContent, options = {}) {
|
|
|
118
129
|
|
|
119
130
|
// 6. Parse Markdown using custom renderer
|
|
120
131
|
marked.setOptions({ gfm: true, breaks: true });
|
|
121
|
-
const renderer = buildMarkedRenderer(headingsMap);
|
|
132
|
+
const renderer = buildMarkedRenderer(headingsMap, mergedOptions.highlight);
|
|
122
133
|
marked.use({ renderer });
|
|
123
134
|
|
|
124
135
|
let bodyHtml = marked.parse(processedMd);
|
package/src/core/sanitizer.js
CHANGED
|
@@ -11,7 +11,29 @@ function sanitizeHtml(html) {
|
|
|
11
11
|
if (!html) return '';
|
|
12
12
|
return html
|
|
13
13
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
14
|
-
.replace(
|
|
14
|
+
.replace(OPENING_TAG, stripEventHandlers);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// An opening tag, with quoted attribute values allowed to contain ">". A quote that is never
|
|
18
|
+
// closed still ends at the next ">", as the tag would in a lenient parser.
|
|
19
|
+
const OPENING_TAG = /<([a-zA-Z][^\s/>]*)((?:"[^"]*"|'[^']*'|[^>])*)>/g;
|
|
20
|
+
// One attribute: its name, then an optional value. Consuming the value keeps it from being
|
|
21
|
+
// read as another attribute.
|
|
22
|
+
const ATTRIBUTE = /[\s/]*([^\s"'<>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?/g;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Drops on* attributes from one opening tag. Text between tags is never touched, so code
|
|
26
|
+
* such as `el.onclick = fn` survives.
|
|
27
|
+
* @param {string} tag
|
|
28
|
+
* @param {string} name
|
|
29
|
+
* @param {string} attributes
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function stripEventHandlers(tag, name, attributes) {
|
|
33
|
+
const kept = attributes.replace(ATTRIBUTE, (attribute, attributeName) =>
|
|
34
|
+
/^on/i.test(attributeName) ? '' : attribute
|
|
35
|
+
);
|
|
36
|
+
return `<${name}${kept}>`;
|
|
15
37
|
}
|
|
16
38
|
|
|
17
39
|
/**
|
package/src/core/templates.js
CHANGED
|
@@ -99,6 +99,12 @@ function getHtmlTemplate({ title, author = '', bodyHtml, options = {} }) {
|
|
|
99
99
|
page-break-after: always;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
.footnote-ref { font-size: 0.75em; line-height: 0; }
|
|
103
|
+
.footnote-ref a, .footnote-backref { text-decoration: none; }
|
|
104
|
+
.footnotes { margin-top: 28px; padding-top: 10px; border-top: 1px solid currentColor; font-size: 0.85em; break-inside: avoid-page; }
|
|
105
|
+
.footnotes ol { margin: 0; padding-left: 1.6em; }
|
|
106
|
+
.footnotes li { margin: 3px 0; }
|
|
107
|
+
|
|
102
108
|
img {
|
|
103
109
|
max-width: 100% !important;
|
|
104
110
|
height: auto !important;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Highlighting Token Colors
|
|
3
|
+
* Two palettes, picked by the background each theme gives its code blocks.
|
|
4
|
+
*/
|
|
5
|
+
// Inline-code chrome must not repeat on every line of a block.
|
|
6
|
+
const base = `
|
|
7
|
+
pre code { background: none; padding: 0; border: none; }
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const light = `
|
|
11
|
+
.hljs-comment, .hljs-quote, .hljs-meta { color: #6e7781; font-style: italic; }
|
|
12
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-doctag, .hljs-template-tag { color: #cf222e; }
|
|
13
|
+
.hljs-string, .hljs-regexp, .hljs-char.escape_ { color: #0a3069; }
|
|
14
|
+
.hljs-number, .hljs-literal, .hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable, .hljs-symbol, .hljs-bullet { color: #0550ae; }
|
|
15
|
+
.hljs-title, .hljs-section, .hljs-selector-id, .hljs-selector-class { color: #8250df; }
|
|
16
|
+
.hljs-type, .hljs-built_in, .hljs-params { color: #953800; }
|
|
17
|
+
.hljs-name, .hljs-tag { color: #116329; }
|
|
18
|
+
.hljs-addition { color: #116329; background-color: #dafbe1; }
|
|
19
|
+
.hljs-deletion { color: #82071e; background-color: #ffebe9; }
|
|
20
|
+
.hljs-emphasis { font-style: italic; }
|
|
21
|
+
.hljs-strong { font-weight: 700; }
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const dark = `
|
|
25
|
+
.hljs-comment, .hljs-quote, .hljs-meta { color: #8b949e; font-style: italic; }
|
|
26
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-doctag, .hljs-template-tag { color: #ff7b72; }
|
|
27
|
+
.hljs-string, .hljs-regexp, .hljs-char.escape_ { color: #a5d6ff; }
|
|
28
|
+
.hljs-number, .hljs-literal, .hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable, .hljs-symbol, .hljs-bullet { color: #79c0ff; }
|
|
29
|
+
.hljs-title, .hljs-section, .hljs-selector-id, .hljs-selector-class { color: #d2a8ff; }
|
|
30
|
+
.hljs-type, .hljs-built_in, .hljs-params { color: #ffa657; }
|
|
31
|
+
.hljs-name, .hljs-tag { color: #7ee787; }
|
|
32
|
+
.hljs-addition { color: #aff5b4; background-color: #033a16; }
|
|
33
|
+
.hljs-deletion { color: #ffdcd7; background-color: #67060c; }
|
|
34
|
+
.hljs-emphasis { font-style: italic; }
|
|
35
|
+
.hljs-strong { font-weight: 700; }
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
module.exports = { light: base + light, dark: base + dark };
|
package/src/core/themes/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const githubTheme = require('./github');
|
|
|
6
6
|
const ebookTheme = require('./ebook');
|
|
7
7
|
const cyberpunkTheme = require('./cyberpunk');
|
|
8
8
|
const minimalTheme = require('./minimal');
|
|
9
|
+
const highlightCss = require('./highlight');
|
|
9
10
|
|
|
10
11
|
const THEMES = {
|
|
11
12
|
modern: modernTheme,
|
|
@@ -17,6 +18,9 @@ const THEMES = {
|
|
|
17
18
|
minimal: minimalTheme
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
// Themes whose code blocks sit on a dark background; the others use the light palette.
|
|
22
|
+
const DARK_CODE_THEMES = new Set(['modern', 'dark', 'ebook', 'cyberpunk']);
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* Builds font override CSS string
|
|
22
26
|
* @param {string} font
|
|
@@ -40,10 +44,12 @@ function getFontOverrideCss(font) {
|
|
|
40
44
|
* @returns {string} Consolidated CSS string
|
|
41
45
|
*/
|
|
42
46
|
function getThemeStyles(theme, font = '') {
|
|
43
|
-
const
|
|
47
|
+
const themeName = THEMES[theme] ? theme : 'modern';
|
|
48
|
+
const themeCss = THEMES[themeName];
|
|
49
|
+
const tokenCss = DARK_CODE_THEMES.has(themeName) ? highlightCss.dark : highlightCss.light;
|
|
44
50
|
const fontOverrideCss = getFontOverrideCss(font);
|
|
45
51
|
|
|
46
|
-
return `${coverCss}\n${fontOverrideCss}${themeCss}`;
|
|
52
|
+
return `${coverCss}\n${fontOverrideCss}${themeCss}${tokenCss}`;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
module.exports = {
|