@udx/md2html 1.2.1 → 1.3.1
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/index.js +83 -3
- package/package.json +1 -1
- package/static/chapter-navigation.css +22 -15
- package/static/scripts.js +65 -44
- package/static/styles.css +287 -6
- package/static/themes/legal.css +134 -0
- package/static/view.hbs +2 -0
package/index.js
CHANGED
|
@@ -89,7 +89,7 @@ const CHAPTER_NAV_STYLES_PATH = path.join(path.dirname(new URL(import.meta.url).
|
|
|
89
89
|
const SCRIPTS_PATH = path.join(path.dirname(new URL(import.meta.url).pathname), 'static/scripts.js');
|
|
90
90
|
|
|
91
91
|
program
|
|
92
|
-
.version('1.
|
|
92
|
+
.version('1.3.0')
|
|
93
93
|
.description('Convert markdown files to a single HTML document with Google Docs styling')
|
|
94
94
|
.option('-s, --src <file>', 'Source markdown file or directory')
|
|
95
95
|
.option('-o, --out <file>', 'Output HTML file path (optional - defaults to ./output.html)')
|
|
@@ -97,6 +97,8 @@ program
|
|
|
97
97
|
.option('-d, --debug', 'Enable debug logging', false)
|
|
98
98
|
.option('-p, --preview', 'Open generated HTML in browser with live preview server', false)
|
|
99
99
|
.option('--port <number>', 'Port for preview server (default: random)', parseInt)
|
|
100
|
+
.option('-c, --css <file>', 'Custom CSS file to include (or auto-detect custom.css in source directory)')
|
|
101
|
+
.option('--theme <name>', 'Built-in theme: default, legal', 'default')
|
|
100
102
|
.addHelpText('after', `
|
|
101
103
|
Examples:
|
|
102
104
|
Basic conversion:
|
|
@@ -111,7 +113,11 @@ Examples:
|
|
|
111
113
|
md2html -s content/docs --preview --port 3000
|
|
112
114
|
|
|
113
115
|
Single file:
|
|
114
|
-
md2html -s document.md --preview
|
|
116
|
+
md2html -s document.md --preview
|
|
117
|
+
|
|
118
|
+
Custom styling:
|
|
119
|
+
md2html -s content/docs --css=custom.css
|
|
120
|
+
md2html -s content/docs --theme=legal`)
|
|
115
121
|
.parse(process.argv);
|
|
116
122
|
|
|
117
123
|
const options = program.opts();
|
|
@@ -147,6 +153,40 @@ async function buildHtml(srcDir, outputFile) {
|
|
|
147
153
|
const chapterNavStyles = fs.readFileSync(CHAPTER_NAV_STYLES_PATH, 'utf8');
|
|
148
154
|
const jsScripts = fs.readFileSync(SCRIPTS_PATH, 'utf8');
|
|
149
155
|
|
|
156
|
+
// Load custom CSS if specified or auto-detect
|
|
157
|
+
let customStyles = '';
|
|
158
|
+
const srcDirResolved = fs.statSync(srcDir).isDirectory() ? srcDir : path.dirname(srcDir);
|
|
159
|
+
|
|
160
|
+
// Check for custom CSS in order of priority:
|
|
161
|
+
// 1. Explicit --css flag
|
|
162
|
+
// 2. custom.css in source directory
|
|
163
|
+
// 3. styles.css in source directory
|
|
164
|
+
const customCssPaths = [
|
|
165
|
+
options.css ? path.resolve(options.css) : null,
|
|
166
|
+
path.join(srcDirResolved, 'custom.css'),
|
|
167
|
+
path.join(srcDirResolved, 'styles.css')
|
|
168
|
+
].filter(Boolean);
|
|
169
|
+
|
|
170
|
+
for (const cssPath of customCssPaths) {
|
|
171
|
+
if (fs.existsSync(cssPath)) {
|
|
172
|
+
customStyles = fs.readFileSync(cssPath, 'utf8');
|
|
173
|
+
debug(`Loaded custom CSS from: ${cssPath}`);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply built-in theme styles from files
|
|
179
|
+
let themeStyles = '';
|
|
180
|
+
if (options.theme && options.theme !== 'default') {
|
|
181
|
+
const themePath = path.join(path.dirname(new URL(import.meta.url).pathname), `static/themes/${options.theme}.css`);
|
|
182
|
+
if (fs.existsSync(themePath)) {
|
|
183
|
+
themeStyles = fs.readFileSync(themePath, 'utf8');
|
|
184
|
+
debug(`Applied ${options.theme} theme from: ${themePath}`);
|
|
185
|
+
} else {
|
|
186
|
+
console.warn(`Warning: Theme '${options.theme}' not found at ${themePath}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
150
190
|
// Register custom Handlebars helpers
|
|
151
191
|
Handlebars.registerHelper('slugify', function(text) {
|
|
152
192
|
return text
|
|
@@ -552,6 +592,41 @@ async function buildHtml(srcDir, outputFile) {
|
|
|
552
592
|
|
|
553
593
|
processedContent = processedContent.replace(/<table>/g, '<table aria-hidden="true" role="presentation">');
|
|
554
594
|
|
|
595
|
+
// Convert checkbox syntax to FontAwesome icons
|
|
596
|
+
// Unchecked: - [ ] or * [ ] becomes FontAwesome square icon
|
|
597
|
+
// Checked: - [x] or * [x] becomes FontAwesome check-square icon
|
|
598
|
+
processedContent = processedContent.replace(
|
|
599
|
+
/<li>\s*\[\s*\]\s*/gi,
|
|
600
|
+
'<li class="checkbox-item"><i class="far fa-square checkbox-icon"></i> '
|
|
601
|
+
);
|
|
602
|
+
processedContent = processedContent.replace(
|
|
603
|
+
/<li>\s*\[x\]\s*/gi,
|
|
604
|
+
'<li class="checkbox-item checkbox-checked"><i class="fas fa-check-square checkbox-icon"></i> '
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
// Process inline SVG elements for proper sizing
|
|
608
|
+
// Add responsive wrapper and ensure viewBox is preserved
|
|
609
|
+
processedContent = processedContent.replace(
|
|
610
|
+
/<svg([^>]*)>/gi,
|
|
611
|
+
(match, attrs) => {
|
|
612
|
+
// Check if it already has a class
|
|
613
|
+
if (attrs.includes('class=')) {
|
|
614
|
+
return match.replace(/class="([^"]*)"/, 'class="$1 svg-responsive"');
|
|
615
|
+
}
|
|
616
|
+
return `<svg class="svg-responsive"${attrs}>`;
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
// Wrap standalone SVG elements in a figure for better layout control
|
|
621
|
+
processedContent = processedContent.replace(
|
|
622
|
+
/(<p>)?(<svg[^>]*>[\s\S]*?<\/svg>)(<\/p>)?/gi,
|
|
623
|
+
(match, openP, svg, closeP) => {
|
|
624
|
+
// If SVG is already in a figure, don't wrap again
|
|
625
|
+
if (match.includes('<figure')) return match;
|
|
626
|
+
return `<figure class="svg-container">${svg}</figure>`;
|
|
627
|
+
}
|
|
628
|
+
);
|
|
629
|
+
|
|
555
630
|
// If it's an imgix URL, add width parameter
|
|
556
631
|
processedContent = processedContent.replace(/src="(https:\/\/[^"]*imgix\.net\/[^"]+)(?:\?([^"]*))?"/g, (match, url, params) => {
|
|
557
632
|
if (params && params.includes('w=')) {
|
|
@@ -635,6 +710,11 @@ async function buildHtml(srcDir, outputFile) {
|
|
|
635
710
|
processedWithHeadingAttrs += `\n<script type="application/json" id="document-chapters-data">${chaptersJSON}</script>\n`;
|
|
636
711
|
|
|
637
712
|
// Prepare data for template
|
|
713
|
+
// Combine all styles: base + chapter nav + theme + custom (custom overrides all)
|
|
714
|
+
const allStyles = [cssStyles, chapterNavStyles, themeStyles, customStyles]
|
|
715
|
+
.filter(Boolean)
|
|
716
|
+
.join('\n\n');
|
|
717
|
+
|
|
638
718
|
const templateData = {
|
|
639
719
|
title,
|
|
640
720
|
description,
|
|
@@ -642,7 +722,7 @@ async function buildHtml(srcDir, outputFile) {
|
|
|
642
722
|
date,
|
|
643
723
|
version,
|
|
644
724
|
content: processedWithHeadingAttrs,
|
|
645
|
-
styles:
|
|
725
|
+
styles: allStyles,
|
|
646
726
|
scripts: jsScripts,
|
|
647
727
|
chapters: chaptersInfo
|
|
648
728
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/* Chapter Navigation Styles */
|
|
2
2
|
:root {
|
|
3
|
-
--chapter-nav-width:
|
|
4
|
-
--chapter-nav-text-color: #
|
|
5
|
-
--chapter-nav-text-faded: rgba(51, 51, 51, 0.
|
|
6
|
-
--chapter-
|
|
7
|
-
--chapter-text-
|
|
8
|
-
--chapter-text-inactive-size: calc(var(--chapter-text-normal-size) * 0.8);
|
|
3
|
+
--chapter-nav-width: 220px;
|
|
4
|
+
--chapter-nav-text-color: #1a1a1a;
|
|
5
|
+
--chapter-nav-text-faded: rgba(51, 51, 51, 0.65);
|
|
6
|
+
--chapter-nav-active-color: #991b1b;
|
|
7
|
+
--chapter-text-size: 0.85rem;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
/* Container for the floating navigation */
|
|
@@ -47,20 +46,24 @@
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
.chapter-nav-item {
|
|
50
|
-
margin-bottom:
|
|
49
|
+
margin-bottom: 12px;
|
|
51
50
|
transition: all 0.3s ease;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
.chapter-nav-link {
|
|
55
54
|
display: block;
|
|
56
|
-
padding:
|
|
57
|
-
font-size: var(--chapter-text-
|
|
55
|
+
padding: 6px 0;
|
|
56
|
+
font-size: var(--chapter-text-size);
|
|
58
57
|
color: var(--chapter-nav-text-faded);
|
|
59
58
|
text-decoration: none;
|
|
60
|
-
transition:
|
|
59
|
+
transition: color 0.2s ease, text-decoration 0.2s ease;
|
|
61
60
|
background-color: transparent;
|
|
62
61
|
border-radius: 0;
|
|
63
|
-
line-height: 1.
|
|
62
|
+
line-height: 1.4;
|
|
63
|
+
/* Better text handling for long titles */
|
|
64
|
+
word-wrap: break-word;
|
|
65
|
+
overflow-wrap: break-word;
|
|
66
|
+
hyphens: auto;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
.chapter-nav-link:hover {
|
|
@@ -68,9 +71,12 @@
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
.chapter-nav-item.active .chapter-nav-link {
|
|
71
|
-
font-size
|
|
72
|
-
|
|
74
|
+
/* Use color and underline instead of font-size to prevent jumping */
|
|
75
|
+
font-size: var(--chapter-text-size);
|
|
76
|
+
color: var(--chapter-nav-active-color);
|
|
73
77
|
font-weight: 500;
|
|
78
|
+
text-decoration: underline;
|
|
79
|
+
text-underline-offset: 3px;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
/* Mobile dropdown styles - native-looking and minimal */
|
|
@@ -169,8 +175,9 @@
|
|
|
169
175
|
|
|
170
176
|
.chapter-dropdown-menu li.active a {
|
|
171
177
|
font-weight: 500;
|
|
172
|
-
color: var(--chapter-nav-
|
|
173
|
-
|
|
178
|
+
color: var(--chapter-nav-active-color);
|
|
179
|
+
text-decoration: underline;
|
|
180
|
+
text-underline-offset: 3px;
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
/* Add padding to the content to prevent floating nav from overlapping */
|
package/static/scripts.js
CHANGED
|
@@ -44,51 +44,90 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// Smart table sizing - auto-fit columns based on content
|
|
48
48
|
document.querySelectorAll('table').forEach(table => {
|
|
49
|
-
//
|
|
50
|
-
table.style.
|
|
51
|
-
table.style.
|
|
52
|
-
table.
|
|
53
|
-
table.classList.add('
|
|
54
|
-
table.classList.add('table-fixed');
|
|
49
|
+
// Use auto layout for smart column sizing
|
|
50
|
+
table.style.tableLayout = 'auto';
|
|
51
|
+
table.style.width = 'auto';
|
|
52
|
+
table.style.maxWidth = '100%';
|
|
53
|
+
table.classList.add('auto-size');
|
|
55
54
|
|
|
56
55
|
// Add responsive wrapper if not already wrapped
|
|
57
56
|
if (table.parentElement.tagName !== 'DIV' || !table.parentElement.classList.contains('table-responsive')) {
|
|
58
57
|
const wrapper = document.createElement('div');
|
|
59
|
-
wrapper.className = 'table-responsive
|
|
60
|
-
wrapper.style.width = '100%';
|
|
58
|
+
wrapper.className = 'table-responsive overflow-x-auto my-8';
|
|
61
59
|
wrapper.style.maxWidth = '100%';
|
|
62
60
|
table.parentNode.insertBefore(wrapper, table);
|
|
63
61
|
wrapper.appendChild(table);
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
// Analyze columns and apply smart sizing
|
|
65
|
+
const rows = table.querySelectorAll('tr');
|
|
66
|
+
if (rows.length > 0) {
|
|
67
|
+
const headerCells = table.querySelectorAll('th');
|
|
68
|
+
const numCols = headerCells.length || (rows[0] ? rows[0].querySelectorAll('td').length : 0);
|
|
69
|
+
|
|
70
|
+
// Analyze each column's content
|
|
71
|
+
for (let colIdx = 0; colIdx < numCols; colIdx++) {
|
|
72
|
+
let isNumeric = true;
|
|
73
|
+
let isCurrency = true;
|
|
74
|
+
let isCompact = true;
|
|
75
|
+
let maxLength = 0;
|
|
76
|
+
|
|
77
|
+
rows.forEach(row => {
|
|
78
|
+
const cells = row.querySelectorAll('th, td');
|
|
79
|
+
if (cells[colIdx]) {
|
|
80
|
+
const text = cells[colIdx].textContent.trim();
|
|
81
|
+
maxLength = Math.max(maxLength, text.length);
|
|
82
|
+
|
|
83
|
+
// Check if numeric (including decimals and commas)
|
|
84
|
+
if (!/^[\d,.\-\s]+$/.test(text) && text !== '') {
|
|
85
|
+
isNumeric = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if currency (starts with $ or ends with .00)
|
|
89
|
+
if (!/^\$?[\d,]+\.?\d*$/.test(text) && text !== '') {
|
|
90
|
+
isCurrency = false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Compact if short content (less than 15 chars)
|
|
94
|
+
if (text.length > 15) {
|
|
95
|
+
isCompact = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Apply column classes based on analysis
|
|
101
|
+
rows.forEach(row => {
|
|
102
|
+
const cells = row.querySelectorAll('th, td');
|
|
103
|
+
if (cells[colIdx]) {
|
|
104
|
+
if (isCurrency && maxLength <= 15) {
|
|
105
|
+
cells[colIdx].classList.add('col-currency');
|
|
106
|
+
} else if (isNumeric && maxLength <= 10) {
|
|
107
|
+
cells[colIdx].classList.add('col-numeric');
|
|
108
|
+
} else if (isCompact && maxLength <= 10) {
|
|
109
|
+
cells[colIdx].classList.add('col-compact');
|
|
110
|
+
} else {
|
|
111
|
+
cells[colIdx].classList.add('col-text');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
66
118
|
// Add proper classes and styles to table headers
|
|
67
119
|
table.querySelectorAll('th').forEach(th => {
|
|
68
120
|
th.classList.add('bg-gray-50');
|
|
69
121
|
th.classList.add('font-semibold');
|
|
70
|
-
th.
|
|
71
|
-
th.
|
|
72
|
-
th.classList.add('border');
|
|
73
|
-
th.classList.add('border-gray-200');
|
|
74
|
-
th.style.padding = '0.75rem 1rem';
|
|
122
|
+
th.style.padding = '0.5rem 0.75rem';
|
|
123
|
+
th.style.borderBottom = '2px solid #e5e7eb';
|
|
75
124
|
});
|
|
76
125
|
|
|
77
126
|
// Add proper classes and styles to table cells
|
|
78
127
|
table.querySelectorAll('td').forEach(td => {
|
|
79
|
-
td.
|
|
80
|
-
td.
|
|
81
|
-
td.classList.add('border');
|
|
82
|
-
td.classList.add('border-gray-200');
|
|
83
|
-
td.style.padding = '0.75rem 1rem';
|
|
84
|
-
td.style.borderColor = 'rgba(0, 0, 0, 0.1)';
|
|
128
|
+
td.style.padding = '0.5rem 0.75rem';
|
|
129
|
+
td.style.borderBottom = '1px solid #e5e7eb';
|
|
85
130
|
});
|
|
86
|
-
|
|
87
|
-
// Force table to use entire container width
|
|
88
|
-
const parentWidth = table.parentElement.offsetWidth;
|
|
89
|
-
if (parentWidth > 0) {
|
|
90
|
-
table.style.minWidth = parentWidth + 'px';
|
|
91
|
-
}
|
|
92
131
|
});
|
|
93
132
|
|
|
94
133
|
// Remove any blue highlights
|
|
@@ -122,24 +161,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
|
|
122
161
|
}
|
|
123
162
|
});
|
|
124
163
|
|
|
125
|
-
// Improve table layout and ensure proper alignment
|
|
126
|
-
document.querySelectorAll('table').forEach(table => {
|
|
127
|
-
// Add a container for horizontal scrolling if needed
|
|
128
|
-
const wrapper = document.createElement('div');
|
|
129
|
-
wrapper.style.width = '100%';
|
|
130
|
-
wrapper.style.overflowX = 'auto';
|
|
131
|
-
table.parentNode.insertBefore(wrapper, table);
|
|
132
|
-
wrapper.appendChild(table);
|
|
133
|
-
|
|
134
|
-
// Ensure consistent styling
|
|
135
|
-
table.querySelectorAll('th, td').forEach(cell => {
|
|
136
|
-
cell.style.padding = '0.75em 1em';
|
|
137
|
-
cell.style.verticalAlign = 'top';
|
|
138
|
-
cell.style.textAlign = 'left';
|
|
139
|
-
cell.style.fontSize = '18px';
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
164
|
// Process quote attributions with em dash
|
|
144
165
|
document.querySelectorAll('blockquote + p').forEach(p => {
|
|
145
166
|
const text = p.textContent || '';
|
package/static/styles.css
CHANGED
|
@@ -338,26 +338,60 @@ pre:has(.file-type-badge) .copy-button {
|
|
|
338
338
|
display: none;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
/*
|
|
341
|
+
/* ============================================
|
|
342
|
+
VISUAL ELEMENT SPACING
|
|
343
|
+
Consistent spacing for images, SVG, mermaid, figures
|
|
344
|
+
============================================ */
|
|
345
|
+
|
|
346
|
+
/* Base visual element spacing - consistent 1.5em above and below */
|
|
347
|
+
:root {
|
|
348
|
+
--visual-spacing-top: 1.5em;
|
|
349
|
+
--visual-spacing-bottom: 1.5em;
|
|
350
|
+
--visual-spacing-print-top: 1em;
|
|
351
|
+
--visual-spacing-print-bottom: 1em;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Figure element - wrapper for images with captions */
|
|
342
355
|
figure {
|
|
343
|
-
margin-top:
|
|
344
|
-
margin-bottom:
|
|
356
|
+
margin-top: var(--visual-spacing-top);
|
|
357
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
345
358
|
text-align: center;
|
|
359
|
+
display: block;
|
|
346
360
|
}
|
|
347
361
|
|
|
362
|
+
/* Images - standalone or within figures */
|
|
348
363
|
img {
|
|
349
364
|
max-width: 100%;
|
|
350
365
|
height: auto;
|
|
351
366
|
border-radius: 0.25em;
|
|
352
|
-
|
|
353
|
-
margin-
|
|
367
|
+
display: block;
|
|
368
|
+
margin-left: auto;
|
|
369
|
+
margin-right: auto;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Standalone images (not in figure) get vertical spacing */
|
|
373
|
+
p > img,
|
|
374
|
+
article > img,
|
|
375
|
+
section > img,
|
|
376
|
+
main > img,
|
|
377
|
+
.prose > img {
|
|
378
|
+
margin-top: var(--visual-spacing-top);
|
|
379
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
354
380
|
}
|
|
355
381
|
|
|
382
|
+
/* Images inside figures don't need extra spacing */
|
|
383
|
+
figure img {
|
|
384
|
+
margin-top: 0;
|
|
385
|
+
margin-bottom: 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Figure captions */
|
|
356
389
|
figcaption {
|
|
357
|
-
margin-top: 0.
|
|
390
|
+
margin-top: 0.75em;
|
|
358
391
|
font-size: 0.9em;
|
|
359
392
|
color: #64748b;
|
|
360
393
|
font-style: italic;
|
|
394
|
+
text-align: center;
|
|
361
395
|
}
|
|
362
396
|
|
|
363
397
|
/* Alt text is visually hidden but accessible for screen readers */
|
|
@@ -482,3 +516,250 @@ a:hover {
|
|
|
482
516
|
.MathJax {
|
|
483
517
|
font-size: 1.1em !important;
|
|
484
518
|
}
|
|
519
|
+
|
|
520
|
+
/* Checkbox styling with FontAwesome icons */
|
|
521
|
+
.checkbox-item {
|
|
522
|
+
list-style: none;
|
|
523
|
+
margin-left: -1.5em;
|
|
524
|
+
padding-left: 0;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.checkbox-icon {
|
|
528
|
+
color: #6b7280;
|
|
529
|
+
font-size: 1.1em;
|
|
530
|
+
margin-right: 0.5em;
|
|
531
|
+
vertical-align: middle;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.checkbox-checked .checkbox-icon {
|
|
535
|
+
color: #10b981;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/* SVG responsive styling */
|
|
539
|
+
.svg-responsive {
|
|
540
|
+
max-width: 100%;
|
|
541
|
+
height: auto;
|
|
542
|
+
display: block;
|
|
543
|
+
margin-left: auto;
|
|
544
|
+
margin-right: auto;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* SVG container with consistent spacing */
|
|
548
|
+
.svg-container {
|
|
549
|
+
margin-top: var(--visual-spacing-top);
|
|
550
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
551
|
+
margin-left: auto;
|
|
552
|
+
margin-right: auto;
|
|
553
|
+
text-align: center;
|
|
554
|
+
max-width: 100%;
|
|
555
|
+
overflow-x: auto;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.svg-container svg {
|
|
559
|
+
max-width: 100%;
|
|
560
|
+
height: auto;
|
|
561
|
+
display: block;
|
|
562
|
+
margin-left: auto;
|
|
563
|
+
margin-right: auto;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* Standalone SVG elements (not in container) */
|
|
567
|
+
article > svg,
|
|
568
|
+
section > svg,
|
|
569
|
+
main > svg,
|
|
570
|
+
.prose > svg {
|
|
571
|
+
margin-top: var(--visual-spacing-top);
|
|
572
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
573
|
+
display: block;
|
|
574
|
+
margin-left: auto;
|
|
575
|
+
margin-right: auto;
|
|
576
|
+
max-width: 100%;
|
|
577
|
+
height: auto;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* Mermaid diagram containers - consistent spacing */
|
|
581
|
+
.mermaid {
|
|
582
|
+
margin-top: var(--visual-spacing-top);
|
|
583
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
584
|
+
text-align: center;
|
|
585
|
+
overflow-x: auto;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.mermaid svg {
|
|
589
|
+
max-width: 100%;
|
|
590
|
+
height: auto;
|
|
591
|
+
display: block;
|
|
592
|
+
margin-left: auto;
|
|
593
|
+
margin-right: auto;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Signature block styling for SVG signatures */
|
|
597
|
+
.signature-block {
|
|
598
|
+
margin-top: var(--visual-spacing-top);
|
|
599
|
+
margin-bottom: var(--visual-spacing-bottom);
|
|
600
|
+
padding: 1em;
|
|
601
|
+
border-top: 1px solid #e5e7eb;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.signature-block svg {
|
|
605
|
+
max-width: 300px;
|
|
606
|
+
height: auto;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Print styling for visual elements */
|
|
610
|
+
@media print {
|
|
611
|
+
/* Consistent print spacing for all visual elements */
|
|
612
|
+
figure,
|
|
613
|
+
.svg-container,
|
|
614
|
+
.mermaid,
|
|
615
|
+
article > img,
|
|
616
|
+
section > img,
|
|
617
|
+
main > img,
|
|
618
|
+
.prose > img {
|
|
619
|
+
margin-top: var(--visual-spacing-print-top);
|
|
620
|
+
margin-bottom: var(--visual-spacing-print-bottom);
|
|
621
|
+
page-break-inside: avoid;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.svg-responsive {
|
|
625
|
+
max-width: 6in;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Ensure images don't overflow in print */
|
|
629
|
+
img, svg {
|
|
630
|
+
max-width: 100% !important;
|
|
631
|
+
height: auto !important;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* ============================================
|
|
636
|
+
PRINT PAGE BREAK SUPPORT
|
|
637
|
+
============================================ */
|
|
638
|
+
|
|
639
|
+
/* Manual page break - use <!-- pagebreak --> in markdown */
|
|
640
|
+
.page-break {
|
|
641
|
+
page-break-before: always;
|
|
642
|
+
break-before: page;
|
|
643
|
+
height: 0;
|
|
644
|
+
margin: 0;
|
|
645
|
+
padding: 0;
|
|
646
|
+
border: none;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/* Print-specific page break rules */
|
|
650
|
+
@media print {
|
|
651
|
+
/* Force page break before h1 (new chapters/volumes) */
|
|
652
|
+
h1 {
|
|
653
|
+
page-break-before: always;
|
|
654
|
+
break-before: page;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* First h1 should not have page break before it */
|
|
658
|
+
section:first-of-type h1:first-child,
|
|
659
|
+
article:first-of-type h1:first-child,
|
|
660
|
+
main > h1:first-child {
|
|
661
|
+
page-break-before: auto;
|
|
662
|
+
break-before: auto;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* Keep headings with their following content */
|
|
666
|
+
h1, h2, h3, h4, h5, h6 {
|
|
667
|
+
page-break-after: avoid;
|
|
668
|
+
break-after: avoid;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* Prevent page breaks inside these elements */
|
|
672
|
+
table, figure, pre, blockquote, ul, ol {
|
|
673
|
+
page-break-inside: avoid;
|
|
674
|
+
break-inside: avoid;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* For very long tables, allow breaks but keep header visible */
|
|
678
|
+
table.allow-break {
|
|
679
|
+
page-break-inside: auto;
|
|
680
|
+
break-inside: auto;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
table.allow-break thead {
|
|
684
|
+
display: table-header-group;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
table.allow-break tr {
|
|
688
|
+
page-break-inside: avoid;
|
|
689
|
+
break-inside: avoid;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* Orphan and widow control for paragraphs */
|
|
693
|
+
p {
|
|
694
|
+
orphans: 3;
|
|
695
|
+
widows: 3;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/* Keep images with their captions */
|
|
699
|
+
figure {
|
|
700
|
+
page-break-inside: avoid;
|
|
701
|
+
break-inside: avoid;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/* Keep list items together */
|
|
705
|
+
li {
|
|
706
|
+
page-break-inside: avoid;
|
|
707
|
+
break-inside: avoid;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* ============================================
|
|
712
|
+
SMART TABLE SIZING
|
|
713
|
+
============================================ */
|
|
714
|
+
|
|
715
|
+
/* Auto-sized tables - columns fit content */
|
|
716
|
+
table.auto-size {
|
|
717
|
+
table-layout: auto;
|
|
718
|
+
width: auto;
|
|
719
|
+
max-width: 100%;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* Compact columns for short content (numbers, codes) */
|
|
723
|
+
table .col-compact {
|
|
724
|
+
width: 1%;
|
|
725
|
+
white-space: nowrap;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/* Numeric columns - right aligned, compact */
|
|
729
|
+
table .col-numeric {
|
|
730
|
+
width: 1%;
|
|
731
|
+
white-space: nowrap;
|
|
732
|
+
text-align: right;
|
|
733
|
+
font-variant-numeric: tabular-nums;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/* Currency columns */
|
|
737
|
+
table .col-currency {
|
|
738
|
+
width: 1%;
|
|
739
|
+
white-space: nowrap;
|
|
740
|
+
text-align: right;
|
|
741
|
+
font-variant-numeric: tabular-nums;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* Description/text columns - expand to fill */
|
|
745
|
+
table .col-text {
|
|
746
|
+
width: auto;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/* Shrink-to-fit table wrapper */
|
|
750
|
+
.table-auto-wrapper {
|
|
751
|
+
display: inline-block;
|
|
752
|
+
max-width: 100%;
|
|
753
|
+
overflow-x: auto;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* Print table sizing */
|
|
757
|
+
@media print {
|
|
758
|
+
table {
|
|
759
|
+
font-size: 10pt;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
table th, table td {
|
|
763
|
+
padding: 4pt 8pt;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* Legal Theme - Professional government proposal and legal document styling */
|
|
2
|
+
/* Inspired by NSF GOALI proposal formatting and federal document standards */
|
|
3
|
+
|
|
4
|
+
body {
|
|
5
|
+
font-family: "Times New Roman", Times, Georgia, serif;
|
|
6
|
+
font-size: 12pt;
|
|
7
|
+
line-height: 1.5;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.content-container {
|
|
11
|
+
max-width: 8.5in;
|
|
12
|
+
padding: 1in;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Formal heading styles */
|
|
16
|
+
.prose h1, h1 {
|
|
17
|
+
font-size: 18pt !important;
|
|
18
|
+
font-weight: bold !important;
|
|
19
|
+
text-align: center !important;
|
|
20
|
+
margin-bottom: 24pt !important;
|
|
21
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.prose h2, h2 {
|
|
25
|
+
font-size: 14pt !important;
|
|
26
|
+
font-weight: bold !important;
|
|
27
|
+
margin-top: 18pt !important;
|
|
28
|
+
margin-bottom: 12pt !important;
|
|
29
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.prose h3, h3 {
|
|
33
|
+
font-size: 12pt !important;
|
|
34
|
+
font-weight: bold !important;
|
|
35
|
+
margin-top: 12pt !important;
|
|
36
|
+
margin-bottom: 6pt !important;
|
|
37
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.prose h4, h4, .prose h5, h5, .prose h6, h6 {
|
|
41
|
+
font-size: 12pt !important;
|
|
42
|
+
font-weight: bold !important;
|
|
43
|
+
font-style: italic !important;
|
|
44
|
+
margin-top: 12pt !important;
|
|
45
|
+
margin-bottom: 6pt !important;
|
|
46
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.prose p, p {
|
|
50
|
+
font-size: 12pt !important;
|
|
51
|
+
text-align: justify !important;
|
|
52
|
+
margin-bottom: 12pt !important;
|
|
53
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.prose li {
|
|
57
|
+
font-size: 12pt !important;
|
|
58
|
+
font-family: "Times New Roman", Times, Georgia, serif !important;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Table styling for government documents */
|
|
62
|
+
.prose table {
|
|
63
|
+
font-size: 10pt !important;
|
|
64
|
+
border-collapse: collapse !important;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.prose table th {
|
|
68
|
+
background-color: #f0f0f0 !important;
|
|
69
|
+
font-weight: bold !important;
|
|
70
|
+
border: 1px solid #333 !important;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.prose table td {
|
|
74
|
+
border: 1px solid #333 !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Sidebar adjustments for formal documents */
|
|
78
|
+
.chapter-navigation {
|
|
79
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Visual element spacing for legal documents - tighter than default */
|
|
83
|
+
:root {
|
|
84
|
+
--visual-spacing-top: 12pt;
|
|
85
|
+
--visual-spacing-bottom: 12pt;
|
|
86
|
+
--visual-spacing-print-top: 10pt;
|
|
87
|
+
--visual-spacing-print-bottom: 10pt;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Figure and image styling for legal documents */
|
|
91
|
+
figure {
|
|
92
|
+
margin-top: 12pt;
|
|
93
|
+
margin-bottom: 12pt;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
figcaption {
|
|
97
|
+
font-size: 10pt !important;
|
|
98
|
+
font-style: italic;
|
|
99
|
+
text-align: center;
|
|
100
|
+
margin-top: 6pt;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* SVG and diagram styling */
|
|
104
|
+
.svg-container,
|
|
105
|
+
.mermaid {
|
|
106
|
+
margin-top: 12pt;
|
|
107
|
+
margin-bottom: 12pt;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Images in legal documents */
|
|
111
|
+
img {
|
|
112
|
+
margin-top: 12pt;
|
|
113
|
+
margin-bottom: 12pt;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
figure img {
|
|
117
|
+
margin-top: 0;
|
|
118
|
+
margin-bottom: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Print-optimized styling */
|
|
122
|
+
@media print {
|
|
123
|
+
body {
|
|
124
|
+
font-size: 12pt !important;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.chapter-navigation, .mobile-chapter-nav {
|
|
128
|
+
display: none !important;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.chapter-content {
|
|
132
|
+
padding-left: 0 !important;
|
|
133
|
+
}
|
|
134
|
+
}
|
package/static/view.hbs
CHANGED
|
@@ -317,6 +317,8 @@
|
|
|
317
317
|
if (code) {
|
|
318
318
|
const mermaidDiv = document.createElement('div');
|
|
319
319
|
mermaidDiv.className = 'mermaid';
|
|
320
|
+
mermaidDiv.setAttribute('aria-hidden', 'true');
|
|
321
|
+
mermaidDiv.setAttribute('role', 'presentation');
|
|
320
322
|
mermaidDiv.textContent = code.textContent;
|
|
321
323
|
pre.parentNode.replaceChild(mermaidDiv, pre);
|
|
322
324
|
}
|