@udx/md2html 1.2.0 → 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/README.md +17 -54
- 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/README.md
CHANGED
|
@@ -6,9 +6,10 @@ A sophisticated tool for converting markdown files into a single, visually polis
|
|
|
6
6
|
|
|
7
7
|
- **Markdown Consolidation**: Intelligently combines and sorts multiple markdown files into a single cohesive document
|
|
8
8
|
- **Magazine-Quality Styling**: Applies high-end editorial styling with precise typography and spacing
|
|
9
|
-
- **Code Block Enhancements**: Syntax highlighting with file type indicators
|
|
9
|
+
- **Code Block Enhancements**: Syntax highlighting with file type indicators
|
|
10
10
|
- **Responsive Images**: Handles high-resolution images with proper sizing and caption support
|
|
11
11
|
- **Watch Mode**: Automatically rebuilds when source files change with intelligent debouncing
|
|
12
|
+
- **Browser Preview**: Live preview server with automatic browser opening
|
|
12
13
|
- **Print Optimization**: Carefully crafted print styling for document printing and PDF generation
|
|
13
14
|
- **Table of Contents**: Auto-generates navigable table of contents from document structure
|
|
14
15
|
- **Customizable**: External CSS, JavaScript, and HTML templates for easy customization
|
|
@@ -36,25 +37,33 @@ npm install --save-dev @udx/md2html
|
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
39
|
# Convert a single markdown file to HTML
|
|
39
|
-
md2html
|
|
40
|
+
md2html -s path/to/file.md -o output.html
|
|
40
41
|
|
|
41
|
-
# Convert all markdown files in a directory
|
|
42
|
-
md2html
|
|
42
|
+
# Convert all markdown files in a directory
|
|
43
|
+
md2html -s path/to/markdown/dir -o documentation.html
|
|
43
44
|
|
|
44
45
|
# Watch for changes and rebuild automatically
|
|
45
|
-
md2html
|
|
46
|
+
md2html -s path/to/markdown/dir -o documentation.html --watch
|
|
47
|
+
|
|
48
|
+
# Open in browser with live preview
|
|
49
|
+
md2html -s path/to/file.md --preview
|
|
50
|
+
|
|
51
|
+
# Custom port for preview server
|
|
52
|
+
md2html -s path/to/markdown/dir --preview --port 3000
|
|
46
53
|
|
|
47
54
|
# Enable debug output
|
|
48
|
-
md2html
|
|
55
|
+
md2html -s path/to/markdown/dir -o documentation.html --debug
|
|
49
56
|
```
|
|
50
57
|
|
|
51
58
|
## CLI Options
|
|
52
59
|
|
|
53
60
|
| Option | Description |
|
|
54
61
|
|--------|-------------|
|
|
55
|
-
| `-s, --src <
|
|
56
|
-
| `-o, --out <
|
|
62
|
+
| `-s, --src <file>` | Source markdown file or directory (required) |
|
|
63
|
+
| `-o, --out <file>` | Output HTML file path (optional - defaults to ./output.html) |
|
|
57
64
|
| `-w, --watch` | Watch for changes and rebuild automatically |
|
|
65
|
+
| `-p, --preview` | Open generated HTML in browser with live preview server |
|
|
66
|
+
| `--port <number>` | Port for preview server (default: random) |
|
|
58
67
|
| `-d, --debug` | Enable debug logging |
|
|
59
68
|
| `-h, --help` | Display help information |
|
|
60
69
|
| `-v, --version` | Output the version number |
|
|
@@ -101,28 +110,6 @@ Add captions to images with this syntax:
|
|
|
101
110
|
*This is the image caption*
|
|
102
111
|
```
|
|
103
112
|
|
|
104
|
-
### Mermaid Diagrams
|
|
105
|
-
|
|
106
|
-
Create professional diagrams using Mermaid syntax:
|
|
107
|
-
|
|
108
|
-
````markdown
|
|
109
|
-
```mermaid
|
|
110
|
-
flowchart LR
|
|
111
|
-
A[Client] --> B[Load Balancer]
|
|
112
|
-
B --> C[Server1]
|
|
113
|
-
B --> D[Server2]
|
|
114
|
-
```
|
|
115
|
-
````
|
|
116
|
-
|
|
117
|
-
Supported Mermaid diagram types:
|
|
118
|
-
- **Flowcharts**: Process flows and workflows
|
|
119
|
-
- **Sequence Diagrams**: System interactions over time
|
|
120
|
-
- **Gantt Charts**: Project timelines and schedules
|
|
121
|
-
- **Entity Relationship Diagrams**: Database schemas
|
|
122
|
-
- **User Journey**: User experience flows
|
|
123
|
-
- **Git Graphs**: Version control workflows
|
|
124
|
-
|
|
125
|
-
The diagrams are automatically rendered when the HTML document loads, with professional styling that matches the document theme.
|
|
126
113
|
|
|
127
114
|
## Customization
|
|
128
115
|
|
|
@@ -170,17 +157,6 @@ gh repo clone owner/repo
|
|
|
170
157
|
md2html --src ./repo/docs --out documentation.html
|
|
171
158
|
```
|
|
172
159
|
|
|
173
|
-
## Command Line Options
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
Options:
|
|
177
|
-
-V, --version output the version number
|
|
178
|
-
-s, --src <directory> Source directory containing markdown files
|
|
179
|
-
-o, --out <file> Output HTML file path
|
|
180
|
-
-w, --watch Watch for changes and rebuild automatically (default: false)
|
|
181
|
-
-d, --debug Enable debug logging (default: false)
|
|
182
|
-
-h, --help display help for command
|
|
183
|
-
```
|
|
184
160
|
|
|
185
161
|
## Output Example
|
|
186
162
|
|
|
@@ -193,19 +169,6 @@ The generated HTML document includes:
|
|
|
193
169
|
- Print-optimized layout with page breaks
|
|
194
170
|
- Responsive design for various screen sizes
|
|
195
171
|
|
|
196
|
-
## API Usage
|
|
197
|
-
|
|
198
|
-
md2html can also be used programmatically:
|
|
199
|
-
|
|
200
|
-
```javascript
|
|
201
|
-
import { buildHtml, watchMarkdown } from '@udx/md2html';
|
|
202
|
-
|
|
203
|
-
// Build HTML once
|
|
204
|
-
await buildHtml('/path/to/markdown/dir', 'output.html');
|
|
205
|
-
|
|
206
|
-
// Watch for changes
|
|
207
|
-
watchMarkdown('/path/to/markdown/dir', 'output.html');
|
|
208
|
-
```
|
|
209
172
|
|
|
210
173
|
## Best Practices
|
|
211
174
|
|
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
|
}
|