md-pdf-studio 1.1.0 → 2.1.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.
@@ -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 };
@@ -1,52 +1,58 @@
1
- const coverCss = require('./cover');
2
- const modernTheme = require('./modern');
3
- const darkTheme = require('./dark');
4
- const academicTheme = require('./academic');
5
- const githubTheme = require('./github');
6
- const ebookTheme = require('./ebook');
7
- const cyberpunkTheme = require('./cyberpunk');
8
- const minimalTheme = require('./minimal');
9
-
10
- const THEMES = {
11
- modern: modernTheme,
12
- dark: darkTheme,
13
- academic: academicTheme,
14
- github: githubTheme,
15
- ebook: ebookTheme,
16
- cyberpunk: cyberpunkTheme,
17
- minimal: minimalTheme
18
- };
19
-
20
- /**
21
- * Builds font override CSS string
22
- * @param {string} font
23
- * @returns {string} CSS snippet
24
- */
25
- function getFontOverrideCss(font) {
26
- if (!font) return '';
27
- if (font === 'Lora' || font === 'Merriweather') {
28
- return `body { font-family: '${font}', Georgia, serif !important; }\n`;
29
- }
30
- if (font === 'JetBrains Mono' || font === 'Fira Code') {
31
- return `body { font-family: '${font}', monospace !important; }\n`;
32
- }
33
- return `body { font-family: '${font}', sans-serif !important; }\n`;
34
- }
35
-
36
- /**
37
- * Returns complete CSS theme string with cover page and font override
38
- * @param {string} theme Name of theme
39
- * @param {string} [font] Font family override
40
- * @returns {string} Consolidated CSS string
41
- */
42
- function getThemeStyles(theme, font = '') {
43
- const themeCss = THEMES[theme] || THEMES.modern;
44
- const fontOverrideCss = getFontOverrideCss(font);
45
-
46
- return `${coverCss}\n${fontOverrideCss}${themeCss}`;
47
- }
48
-
49
- module.exports = {
50
- getThemeStyles,
51
- THEMES
52
- };
1
+ const coverCss = require('./cover');
2
+ const modernTheme = require('./modern');
3
+ const darkTheme = require('./dark');
4
+ const academicTheme = require('./academic');
5
+ const githubTheme = require('./github');
6
+ const ebookTheme = require('./ebook');
7
+ const cyberpunkTheme = require('./cyberpunk');
8
+ const minimalTheme = require('./minimal');
9
+ const highlightCss = require('./highlight');
10
+
11
+ const THEMES = {
12
+ modern: modernTheme,
13
+ dark: darkTheme,
14
+ academic: academicTheme,
15
+ github: githubTheme,
16
+ ebook: ebookTheme,
17
+ cyberpunk: cyberpunkTheme,
18
+ minimal: minimalTheme
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
+
24
+ /**
25
+ * Builds font override CSS string
26
+ * @param {string} font
27
+ * @returns {string} CSS snippet
28
+ */
29
+ function getFontOverrideCss(font) {
30
+ if (!font) return '';
31
+ if (font === 'Lora' || font === 'Merriweather') {
32
+ return `body { font-family: '${font}', Georgia, serif !important; }\n`;
33
+ }
34
+ if (font === 'JetBrains Mono' || font === 'Fira Code') {
35
+ return `body { font-family: '${font}', monospace !important; }\n`;
36
+ }
37
+ return `body { font-family: '${font}', sans-serif !important; }\n`;
38
+ }
39
+
40
+ /**
41
+ * Returns complete CSS theme string with cover page and font override
42
+ * @param {string} theme Name of theme
43
+ * @param {string} [font] Font family override
44
+ * @returns {string} Consolidated CSS string
45
+ */
46
+ function getThemeStyles(theme, font = '') {
47
+ const themeName = THEMES[theme] ? theme : 'modern';
48
+ const themeCss = THEMES[themeName];
49
+ const tokenCss = DARK_CODE_THEMES.has(themeName) ? highlightCss.dark : highlightCss.light;
50
+ const fontOverrideCss = getFontOverrideCss(font);
51
+
52
+ return `${coverCss}\n${fontOverrideCss}${themeCss}${tokenCss}`;
53
+ }
54
+
55
+ module.exports = {
56
+ getThemeStyles,
57
+ THEMES
58
+ };
@@ -1,20 +1,20 @@
1
- /**
2
- * Minimal Theme Styles
3
- */
4
- module.exports = `
5
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; line-height: 1.5; color: #000000; background-color: #ffffff; padding: 10px; }
6
- h1 { font-size: 20px; font-weight: 700; border-bottom: 2px solid #000; padding-bottom: 4px; break-after: avoid-page; }
7
- h2 { font-size: 15px; font-weight: 700; margin-top: 18px; break-after: avoid-page; }
8
- h3 { font-size: 13.5px; font-weight: 600; break-after: avoid-page; }
9
- blockquote { border-left: 3px solid #000; padding-left: 12px; margin: 10px 0; }
10
- table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; }
11
- tr { break-inside: avoid-page; }
12
- th, td { border: 1px solid #000; padding: 6px; }
13
- th { background: #f2f2f2; font-weight: 700; }
14
- code { font-family: monospace; font-size: 11px; background: #f2f2f2; padding: 2px 4px; }
15
- pre { font-family: monospace; font-size: 10.5px; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; break-inside: avoid-page; }
16
- .formula-box { border: 1px solid #000; padding: 8px; text-align: center; margin: 10px 0; }
17
- .mermaid-container { display: block; text-align: center; margin: 12px 0; border: 1px solid #ccc; padding: 8px; overflow-x: auto; break-inside: avoid-page; }
18
- .mermaid { display: block; text-align: center; width: 100%; }
19
- .mermaid svg { max-width: 100% !important; height: auto !important; margin: 0 auto; display: inline-block; }
20
- `;
1
+ /**
2
+ * Minimal Theme Styles
3
+ */
4
+ module.exports = `
5
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; line-height: 1.5; color: #000000; background-color: #ffffff; padding: 10px; }
6
+ h1 { font-size: 20px; font-weight: 700; border-bottom: 2px solid #000; padding-bottom: 4px; break-after: avoid-page; }
7
+ h2 { font-size: 15px; font-weight: 700; margin-top: 18px; break-after: avoid-page; }
8
+ h3 { font-size: 13.5px; font-weight: 600; break-after: avoid-page; }
9
+ blockquote { border-left: 3px solid #000; padding-left: 12px; margin: 10px 0; }
10
+ table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; }
11
+ tr { break-inside: avoid-page; }
12
+ th, td { border: 1px solid #000; padding: 6px; }
13
+ th { background: #f2f2f2; font-weight: 700; }
14
+ code { font-family: monospace; font-size: 11px; background: #f2f2f2; padding: 2px 4px; }
15
+ pre { font-family: monospace; font-size: 10.5px; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; break-inside: avoid-page; }
16
+ .formula-box { border: 1px solid #000; padding: 8px; text-align: center; margin: 10px 0; }
17
+ .mermaid-container { display: block; text-align: center; margin: 12px 0; border: 1px solid #ccc; padding: 8px; overflow-x: auto; break-inside: avoid-page; }
18
+ .mermaid { display: block; text-align: center; width: 100%; }
19
+ .mermaid svg { max-width: 100% !important; height: auto !important; margin: 0 auto; display: inline-block; }
20
+ `;
@@ -1,34 +1,34 @@
1
- /**
2
- * Modern Theme Styles
3
- */
4
- module.exports = `
5
- body { font-family: 'Inter', 'Noto Sans JP', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; line-height: 1.55; color: #1e293b; background-color: #ffffff; margin: 0; padding: 10px; }
6
- h1 { font-size: 21px; font-weight: 700; color: #0f172a; border-bottom: 3px solid #2563eb; padding-bottom: 8px; margin-top: 0; margin-bottom: 16px; text-transform: uppercase; letter-spacing: 0.3px; break-after: avoid-page; page-break-after: avoid; }
7
- h2 { font-size: 16px; font-weight: 700; color: #1e3a8a; background: #eff6ff; border-left: 5px solid #2563eb; padding: 6px 12px; margin-top: 22px; margin-bottom: 12px; border-radius: 0 4px 4px 0; break-after: avoid-page; page-break-after: avoid; }
8
- h3 { font-size: 14.5px; font-weight: 600; color: #1e40af; margin-top: 16px; margin-bottom: 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px; break-after: avoid-page; page-break-after: avoid; }
9
- h4 { font-size: 13.5px; font-weight: 600; color: #334155; margin-top: 12px; margin-bottom: 6px; break-after: avoid-page; page-break-after: avoid; }
10
- .section-group { break-inside: avoid-page; page-break-inside: avoid; }
11
- .formula-box { background: #f0f9ff; border: 1px solid #bae6fd; border-left: 4px solid #0284c7; padding: 10px 16px; border-radius: 4px; margin: 12px 0; font-size: 13.5px; color: #0369a1; text-align: center; }
12
- .formula-box span { font-weight: 500; background: #ffffff; padding: 2px 8px; border-radius: 3px; border: 1px solid #e0f2fe; }
13
- blockquote { margin: 12px 0; padding: 10px 16px; background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 4px; color: #334155; font-size: 12.5px; }
14
- blockquote p { margin: 4px 0; }
15
- table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; break-inside: auto; }
16
- tr { break-inside: avoid-page; page-break-inside: avoid; }
17
- th { background-color: #1e293b; color: #ffffff; font-weight: 600; text-align: left; padding: 7px 9px; border: 1px solid #334155; font-size: 11.5px; letter-spacing: 0.2px; }
18
- td { padding: 6px 9px; border: 1px solid #cbd5e1; vertical-align: top; }
19
- tbody tr:nth-child(even) { background-color: #f8fafc; }
20
- code { font-family: 'JetBrains Mono', Consolas, Monaco, monospace; font-size: 11.5px; background-color: #f1f5f9; color: #0f172a; padding: 1px 5px; border-radius: 3px; border: 1px solid #e2e8f0; }
21
- pre { font-family: 'JetBrains Mono', Consolas, Monaco, monospace; font-size: 10.5px; line-height: 1.4; background-color: #0f172a; color: #f8fafc; padding: 10px 12px; border-radius: 6px; overflow-x: auto; margin: 10px 0; white-space: pre-wrap; word-break: break-all; break-inside: avoid-page; page-break-inside: avoid; }
22
- pre code { background: none; color: inherit; padding: 0; border: none; font-size: inherit; }
23
- .ui-mockup-container { margin: 12px 0; break-inside: avoid-page; page-break-inside: avoid; }
24
- .ui-mockup { background-color: #1e1e2e; color: #a6adc8; border: 1px solid #313244; border-left: 4px solid #89b4fa; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
25
- .ui-mockup code { color: #cdd6f4; }
26
- .mermaid-container { display: block; text-align: center; margin: 14px 0; background-color: #ffffff; padding: 12px; border: 1px solid #e2e8f0; border-radius: 6px; overflow-x: auto; break-inside: avoid-page; page-break-inside: avoid; }
27
- .mermaid { display: block; text-align: center; width: 100%; }
28
- .mermaid svg { max-width: 100% !important; height: auto !important; margin: 0 auto; display: inline-block; }
29
- hr { border: 0; height: 1px; background: #e2e8f0; margin: 16px 0; }
30
- ul, ol { padding-left: 20px; margin: 6px 0; }
31
- li { margin-bottom: 3px; }
32
- strong { color: #0f172a; }
33
- a { color: #2563eb; text-decoration: none; }
34
- `;
1
+ /**
2
+ * Modern Theme Styles
3
+ */
4
+ module.exports = `
5
+ body { font-family: 'Inter', 'Noto Sans JP', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; line-height: 1.55; color: #1e293b; background-color: #ffffff; margin: 0; padding: 10px; }
6
+ h1 { font-size: 21px; font-weight: 700; color: #0f172a; border-bottom: 3px solid #2563eb; padding-bottom: 8px; margin-top: 0; margin-bottom: 16px; text-transform: uppercase; letter-spacing: 0.3px; break-after: avoid-page; page-break-after: avoid; }
7
+ h2 { font-size: 16px; font-weight: 700; color: #1e3a8a; background: #eff6ff; border-left: 5px solid #2563eb; padding: 6px 12px; margin-top: 22px; margin-bottom: 12px; border-radius: 0 4px 4px 0; break-after: avoid-page; page-break-after: avoid; }
8
+ h3 { font-size: 14.5px; font-weight: 600; color: #1e40af; margin-top: 16px; margin-bottom: 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px; break-after: avoid-page; page-break-after: avoid; }
9
+ h4 { font-size: 13.5px; font-weight: 600; color: #334155; margin-top: 12px; margin-bottom: 6px; break-after: avoid-page; page-break-after: avoid; }
10
+ .section-group { break-inside: avoid-page; page-break-inside: avoid; }
11
+ .formula-box { background: #f0f9ff; border: 1px solid #bae6fd; border-left: 4px solid #0284c7; padding: 10px 16px; border-radius: 4px; margin: 12px 0; font-size: 13.5px; color: #0369a1; text-align: center; }
12
+ .formula-box span { font-weight: 500; background: #ffffff; padding: 2px 8px; border-radius: 3px; border: 1px solid #e0f2fe; }
13
+ blockquote { margin: 12px 0; padding: 10px 16px; background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 4px; color: #334155; font-size: 12.5px; }
14
+ blockquote p { margin: 4px 0; }
15
+ table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; break-inside: auto; }
16
+ tr { break-inside: avoid-page; page-break-inside: avoid; }
17
+ th { background-color: #1e293b; color: #ffffff; font-weight: 600; text-align: left; padding: 7px 9px; border: 1px solid #334155; font-size: 11.5px; letter-spacing: 0.2px; }
18
+ td { padding: 6px 9px; border: 1px solid #cbd5e1; vertical-align: top; }
19
+ tbody tr:nth-child(even) { background-color: #f8fafc; }
20
+ code { font-family: 'JetBrains Mono', Consolas, Monaco, monospace; font-size: 11.5px; background-color: #f1f5f9; color: #0f172a; padding: 1px 5px; border-radius: 3px; border: 1px solid #e2e8f0; }
21
+ pre { font-family: 'JetBrains Mono', Consolas, Monaco, monospace; font-size: 10.5px; line-height: 1.4; background-color: #0f172a; color: #f8fafc; padding: 10px 12px; border-radius: 6px; overflow-x: auto; margin: 10px 0; white-space: pre-wrap; word-break: break-all; break-inside: avoid-page; page-break-inside: avoid; }
22
+ pre code { background: none; color: inherit; padding: 0; border: none; font-size: inherit; }
23
+ .ui-mockup-container { margin: 12px 0; break-inside: avoid-page; page-break-inside: avoid; }
24
+ .ui-mockup { background-color: #1e1e2e; color: #a6adc8; border: 1px solid #313244; border-left: 4px solid #89b4fa; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
25
+ .ui-mockup code { color: #cdd6f4; }
26
+ .mermaid-container { display: block; text-align: center; margin: 14px 0; background-color: #ffffff; padding: 12px; border: 1px solid #e2e8f0; border-radius: 6px; overflow-x: auto; break-inside: avoid-page; page-break-inside: avoid; }
27
+ .mermaid { display: block; text-align: center; width: 100%; }
28
+ .mermaid svg { max-width: 100% !important; height: auto !important; margin: 0 auto; display: inline-block; }
29
+ hr { border: 0; height: 1px; background: #e2e8f0; margin: 16px 0; }
30
+ ul, ol { padding-left: 20px; margin: 6px 0; }
31
+ li { margin-bottom: 3px; }
32
+ strong { color: #0f172a; }
33
+ a { color: #2563eb; text-decoration: none; }
34
+ `;
package/src/core/toc.js CHANGED
@@ -1,72 +1,72 @@
1
- /**
2
- * Table of Contents (TOC) Generator Utility
3
- */
4
- const { escapeHtml } = require('./sanitizer');
5
-
6
- /**
7
- * Generates a Table of Contents (TOC) HTML block from markdown headings with dotted leader lines
8
- * @param {string} mdContent
9
- * @returns {{ tocHtml: string, headingsMap: Map<string, string> }}
10
- */
11
- function generateToc(mdContent) {
12
- if (!mdContent) return { tocHtml: '', headingsMap: new Map() };
13
-
14
- const lines = mdContent.split('\n');
15
- const headings = [];
16
- const headingsMap = new Map();
17
- const slugCounts = {};
18
-
19
- lines.forEach(line => {
20
- const match = line.match(/^(#{1,3})\s+(.+)$/);
21
- if (match) {
22
- const level = match[1].length;
23
- let title = match[2].trim();
24
-
25
- // Clean inline markdown links/code from heading title
26
- title = title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
27
- .replace(/`([^`]+)`/g, '$1')
28
- .replace(/\*+/g, '');
29
-
30
- // Create unique slug
31
- let slug = title.toLowerCase()
32
- .replace(/[^\w\u00C0-\u024F\u1EA0-\u1EF9]+/g, '-')
33
- .replace(/^-+|-+$/g, '');
34
-
35
- if (!slug) slug = `heading-${headings.length + 1}`;
36
-
37
- if (slugCounts[slug]) {
38
- slugCounts[slug]++;
39
- slug = `${slug}-${slugCounts[slug]}`;
40
- } else {
41
- slugCounts[slug] = 1;
42
- }
43
-
44
- headings.push({ level, title, slug });
45
- headingsMap.set(match[2].trim(), slug);
46
- }
47
- });
48
-
49
- if (headings.length === 0) return { tocHtml: '', headingsMap };
50
-
51
- let tocHtml = `<div class="toc-container">
52
- <div class="toc-title">📋 Table of Contents</div>
53
- <ul class="toc-list">\n`;
54
-
55
- headings.forEach(h => {
56
- const indentClass = `toc-item-h${h.level}`;
57
- tocHtml += ` <li class="${indentClass}">
58
- <a href="#${h.slug}">
59
- <span class="toc-item-title">${escapeHtml(h.title)}</span>
60
- <span class="toc-item-dots"></span>
61
- </a>
62
- </li>\n`;
63
- });
64
-
65
- tocHtml += ` </ul>\n </div>\n`;
66
-
67
- return { tocHtml, headingsMap };
68
- }
69
-
70
- module.exports = {
71
- generateToc
72
- };
1
+ /**
2
+ * Table of Contents (TOC) Generator Utility
3
+ */
4
+ const { escapeHtml } = require('./sanitizer');
5
+
6
+ /**
7
+ * Generates a Table of Contents (TOC) HTML block from markdown headings with dotted leader lines
8
+ * @param {string} mdContent
9
+ * @returns {{ tocHtml: string, headingsMap: Map<string, string> }}
10
+ */
11
+ function generateToc(mdContent) {
12
+ if (!mdContent) return { tocHtml: '', headingsMap: new Map() };
13
+
14
+ const lines = mdContent.split('\n');
15
+ const headings = [];
16
+ const headingsMap = new Map();
17
+ const slugCounts = {};
18
+
19
+ lines.forEach(line => {
20
+ const match = line.match(/^(#{1,3})\s+(.+)$/);
21
+ if (match) {
22
+ const level = match[1].length;
23
+ let title = match[2].trim();
24
+
25
+ // Clean inline markdown links/code from heading title
26
+ title = title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
27
+ .replace(/`([^`]+)`/g, '$1')
28
+ .replace(/\*+/g, '');
29
+
30
+ // Create unique slug
31
+ let slug = title.toLowerCase()
32
+ .replace(/[^\w\u00C0-\u024F\u1EA0-\u1EF9]+/g, '-')
33
+ .replace(/^-+|-+$/g, '');
34
+
35
+ if (!slug) slug = `heading-${headings.length + 1}`;
36
+
37
+ if (slugCounts[slug]) {
38
+ slugCounts[slug]++;
39
+ slug = `${slug}-${slugCounts[slug]}`;
40
+ } else {
41
+ slugCounts[slug] = 1;
42
+ }
43
+
44
+ headings.push({ level, title, slug });
45
+ headingsMap.set(match[2].trim(), slug);
46
+ }
47
+ });
48
+
49
+ if (headings.length === 0) return { tocHtml: '', headingsMap };
50
+
51
+ let tocHtml = `<div class="toc-container">
52
+ <div class="toc-title">📋 Table of Contents</div>
53
+ <ul class="toc-list">\n`;
54
+
55
+ headings.forEach(h => {
56
+ const indentClass = `toc-item-h${h.level}`;
57
+ tocHtml += ` <li class="${indentClass}">
58
+ <a href="#${h.slug}">
59
+ <span class="toc-item-title">${escapeHtml(h.title)}</span>
60
+ <span class="toc-item-dots"></span>
61
+ </a>
62
+ </li>\n`;
63
+ });
64
+
65
+ tocHtml += ` </ul>\n </div>\n`;
66
+
67
+ return { tocHtml, headingsMap };
68
+ }
69
+
70
+ module.exports = {
71
+ generateToc
72
+ };
package/src/core/utils.js CHANGED
@@ -1,85 +1,85 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { parseFrontMatter } = require('./frontmatter');
4
- const { generateToc } = require('./toc');
5
- const { generateCoverPage } = require('./cover');
6
- const { sanitizeHtml, escapeHtml } = require('./sanitizer');
7
-
8
- /**
9
- * Preprocesses Markdown content before parsing:
10
- * - Transforms GitHub Callouts/Alerts (> [!NOTE], > [!TIP], etc.)
11
- * - Transforms pagebreak comments (<!-- pagebreak --> or \pagebreak)
12
- * - Converts simple LaTeX arrow/math symbols
13
- * @param {string} mdContent
14
- * @returns {string} Preprocessed markdown string
15
- */
16
- function preprocessMarkdown(mdContent = '') {
17
- if (!mdContent) return '';
18
-
19
- return mdContent
20
- // GitHub Callouts / Alerts
21
- .replace(/^>\s*\[!NOTE\]\s*\r?\n/gm, '> **ℹ️ NOTE:** ')
22
- .replace(/^>\s*\[!TIP\]\s*\r?\n/gm, '> **💡 TIP:** ')
23
- .replace(/^>\s*\[!WARNING\]\s*\r?\n/gm, '> **⚠️ WARNING:** ')
24
- .replace(/^>\s*\[!IMPORTANT\]\s*\r?\n/gm, '> **❗ IMPORTANT:** ')
25
- .replace(/^>\s*\[!CAUTION\]\s*\r?\n/gm, '> **🚫 CAUTION:** ')
26
- // Pagebreaks
27
- .replace(/<!--\s*page-?break\s*-->/gi, '<div class="page-break"></div>')
28
- .replace(/\\pagebreak/gi, '<div class="page-break"></div>')
29
- // LaTeX math symbols
30
- .replace(/\\longrightarrow/g, '⟶')
31
- .replace(/\\rightarrow/g, '→')
32
- .replace(/\$→\$/g, '→')
33
- .replace(/\$\\rightarrow\$/g, '→')
34
- .replace(/\$\\longrightarrow\$/g, '⟶')
35
- .replace(/\$\\ge\$/g, '≥')
36
- .replace(/\$\\le\$/g, '≤')
37
- .replace(/\$x\/y\$/g, 'x/y')
38
- .replace(/\$x < y\$/g, 'x < y')
39
- .replace(/\$x = y\$/g, 'x = y')
40
- // Formula box transformation
41
- .replace(/\$\$\s*\\text\{([^}]+)\}\s*=\s*\\text\{([^}]+)\}\s*-\s*\\text\{([^}]+)\}\s*\$\$/g,
42
- '<div class="formula-box"><strong>$1</strong> = <span>$2</span> &minus; <span>$3</span></div>')
43
- .replace(/\$\$\s*([\s\S]+?)\s*\$\$/g, '<div class="formula-box">$1</div>');
44
- }
45
-
46
- /**
47
- * Safely reads custom CSS file contents
48
- * @param {string} cssFilePath
49
- * @returns {string} CSS content
50
- */
51
- function readCustomCss(cssFilePath) {
52
- if (!cssFilePath) return '';
53
- const absPath = resolvePath(cssFilePath);
54
- if (fs.existsSync(absPath)) {
55
- try {
56
- return fs.readFileSync(absPath, 'utf8');
57
- } catch (err) {
58
- console.warn(`[Warning] Could not read custom CSS file at: ${absPath}`);
59
- }
60
- } else {
61
- console.warn(`[Warning] Custom CSS file not found at: ${absPath}`);
62
- }
63
- return '';
64
- }
65
-
66
- /**
67
- * Resolves absolute path safely
68
- * @param {string} filePath
69
- * @returns {string} Absolute file path
70
- */
71
- function resolvePath(filePath) {
72
- if (!filePath) return '';
73
- return path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
74
- }
75
-
76
- module.exports = {
77
- parseFrontMatter,
78
- generateCoverPage,
79
- preprocessMarkdown,
80
- generateToc,
81
- readCustomCss,
82
- sanitizeHtml,
83
- escapeHtml,
84
- resolvePath
85
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { parseFrontMatter } = require('./frontmatter');
4
+ const { generateToc } = require('./toc');
5
+ const { generateCoverPage } = require('./cover');
6
+ const { sanitizeHtml, escapeHtml } = require('./sanitizer');
7
+
8
+ /**
9
+ * Preprocesses Markdown content before parsing:
10
+ * - Transforms GitHub Callouts/Alerts (> [!NOTE], > [!TIP], etc.)
11
+ * - Transforms pagebreak comments (<!-- pagebreak --> or \pagebreak)
12
+ * - Converts simple LaTeX arrow/math symbols
13
+ * @param {string} mdContent
14
+ * @returns {string} Preprocessed markdown string
15
+ */
16
+ function preprocessMarkdown(mdContent = '') {
17
+ if (!mdContent) return '';
18
+
19
+ return mdContent
20
+ // GitHub Callouts / Alerts
21
+ .replace(/^>\s*\[!NOTE\]\s*\r?\n/gm, '> **ℹ️ NOTE:** ')
22
+ .replace(/^>\s*\[!TIP\]\s*\r?\n/gm, '> **💡 TIP:** ')
23
+ .replace(/^>\s*\[!WARNING\]\s*\r?\n/gm, '> **⚠️ WARNING:** ')
24
+ .replace(/^>\s*\[!IMPORTANT\]\s*\r?\n/gm, '> **❗ IMPORTANT:** ')
25
+ .replace(/^>\s*\[!CAUTION\]\s*\r?\n/gm, '> **🚫 CAUTION:** ')
26
+ // Pagebreaks
27
+ .replace(/<!--\s*page-?break\s*-->/gi, '<div class="page-break"></div>')
28
+ .replace(/\\pagebreak/gi, '<div class="page-break"></div>')
29
+ // LaTeX math symbols
30
+ .replace(/\\longrightarrow/g, '⟶')
31
+ .replace(/\\rightarrow/g, '→')
32
+ .replace(/\$→\$/g, '→')
33
+ .replace(/\$\\rightarrow\$/g, '→')
34
+ .replace(/\$\\longrightarrow\$/g, '⟶')
35
+ .replace(/\$\\ge\$/g, '≥')
36
+ .replace(/\$\\le\$/g, '≤')
37
+ .replace(/\$x\/y\$/g, 'x/y')
38
+ .replace(/\$x < y\$/g, 'x < y')
39
+ .replace(/\$x = y\$/g, 'x = y')
40
+ // Formula box transformation
41
+ .replace(/\$\$\s*\\text\{([^}]+)\}\s*=\s*\\text\{([^}]+)\}\s*-\s*\\text\{([^}]+)\}\s*\$\$/g,
42
+ '<div class="formula-box"><strong>$1</strong> = <span>$2</span> &minus; <span>$3</span></div>')
43
+ .replace(/\$\$\s*([\s\S]+?)\s*\$\$/g, '<div class="formula-box">$1</div>');
44
+ }
45
+
46
+ /**
47
+ * Safely reads custom CSS file contents
48
+ * @param {string} cssFilePath
49
+ * @returns {string} CSS content
50
+ */
51
+ function readCustomCss(cssFilePath) {
52
+ if (!cssFilePath) return '';
53
+ const absPath = resolvePath(cssFilePath);
54
+ if (fs.existsSync(absPath)) {
55
+ try {
56
+ return fs.readFileSync(absPath, 'utf8');
57
+ } catch (err) {
58
+ console.warn(`[Warning] Could not read custom CSS file at: ${absPath}`);
59
+ }
60
+ } else {
61
+ console.warn(`[Warning] Custom CSS file not found at: ${absPath}`);
62
+ }
63
+ return '';
64
+ }
65
+
66
+ /**
67
+ * Resolves absolute path safely
68
+ * @param {string} filePath
69
+ * @returns {string} Absolute file path
70
+ */
71
+ function resolvePath(filePath) {
72
+ if (!filePath) return '';
73
+ return path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
74
+ }
75
+
76
+ module.exports = {
77
+ parseFrontMatter,
78
+ generateCoverPage,
79
+ preprocessMarkdown,
80
+ generateToc,
81
+ readCustomCss,
82
+ sanitizeHtml,
83
+ escapeHtml,
84
+ resolvePath
85
+ };
package/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- // Re-export core API module
2
- module.exports = require('./core/index');
1
+ // Re-export core API module
2
+ module.exports = require('./core/index');