prettymdviewer 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yogesh swami
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # mdpretty
2
+
3
+ Beautiful markdown viewer — render `.md` files in a styled browser UI with TOC, dark/light themes, and scroll spy.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g mdpretty
9
+ ```
10
+
11
+ ## CLI Usage
12
+
13
+ ```bash
14
+ # Open in browser (default port 3333)
15
+ mdpretty README.md
16
+
17
+ # Output rendered HTML to stdout
18
+ mdpretty README.md --html
19
+
20
+ # Export to an HTML file
21
+ mdpretty README.md --out=output.html
22
+ ```
23
+
24
+ ## Programmatic Usage
25
+
26
+ ```js
27
+ const { renderHTML, serve } = require("mdpretty");
28
+
29
+ // Get a self-contained HTML string
30
+ const html = renderHTML("# Hello\n\nWorld", "example.md");
31
+
32
+ // Start a live-reload server
33
+ serve("./README.md", { port: 3333 });
34
+ ```
35
+
36
+ ## Features
37
+
38
+ - Sidebar table of contents generated from headings
39
+ - Dark / light theme toggle
40
+ - Scroll spy highlights current section
41
+ - Single self-contained HTML output (no external assets)
42
+ - Live reload on browser refresh in server mode
43
+
44
+ ## License
45
+
46
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const args = process.argv.slice(2);
7
+ const flags = {};
8
+ const positional = [];
9
+
10
+ for (const arg of args) {
11
+ if (arg === "--help" || arg === "-h") {
12
+ flags.help = true;
13
+ } else if (arg === "--version" || arg === "-v") {
14
+ flags.version = true;
15
+ } else if (arg === "--no-browser") {
16
+ flags.noBrowser = true;
17
+ } else if (arg.startsWith("--port=")) {
18
+ flags.port = parseInt(arg.split("=")[1], 10);
19
+ } else if (arg === "--html") {
20
+ flags.html = true;
21
+ } else if (arg.startsWith("--out=")) {
22
+ flags.out = arg.split("=")[1];
23
+ } else {
24
+ positional.push(arg);
25
+ }
26
+ }
27
+
28
+ if (flags.version) {
29
+ const pkg = require("../package.json");
30
+ console.log(pkg.version);
31
+ process.exit(0);
32
+ }
33
+
34
+ if (flags.help || positional.length === 0) {
35
+ console.log(`
36
+ mdpretty - Beautiful markdown viewer
37
+
38
+ Usage:
39
+ mdpretty <file.md> Open in browser
40
+ mdpretty <file.md> --html Output rendered HTML to stdout
41
+ mdpretty <file.md> --out=f.html Export to HTML file
42
+
43
+ Options:
44
+ --port=<n> Server port (default: 3333)
45
+ --no-browser Start server without opening browser
46
+ --html Print rendered HTML to stdout (no server)
47
+ --out=<file> Write rendered HTML to file (no server)
48
+ -v, --version Show version
49
+ -h, --help Show this help
50
+ `);
51
+ process.exit(0);
52
+ }
53
+
54
+ const filePath = path.resolve(positional[0]);
55
+
56
+ if (!fs.existsSync(filePath)) {
57
+ console.error(`File not found: ${filePath}`);
58
+ process.exit(1);
59
+ }
60
+
61
+ // --- HTML output mode (no server) ---
62
+ if (flags.html || flags.out) {
63
+ const { renderHTML } = require("../lib/renderer");
64
+ const md = fs.readFileSync(filePath, "utf-8");
65
+ const html = renderHTML(md, path.basename(filePath));
66
+
67
+ if (flags.out) {
68
+ fs.writeFileSync(flags.out, html, "utf-8");
69
+ console.log(`Written to ${flags.out}`);
70
+ } else {
71
+ process.stdout.write(html);
72
+ }
73
+ process.exit(0);
74
+ }
75
+
76
+ // --- Server mode (default) ---
77
+ const { serve } = require("../lib/server");
78
+ serve(filePath, {
79
+ port: flags.port,
80
+ noBrowser: flags.noBrowser,
81
+ });
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ const { renderHTML } = require("./lib/renderer");
2
+ const { serve } = require("./lib/server");
3
+
4
+ module.exports = { renderHTML, serve };
@@ -0,0 +1,526 @@
1
+ const { marked } = require("marked");
2
+
3
+ marked.setOptions({
4
+ gfm: true,
5
+ breaks: true,
6
+ });
7
+
8
+ function renderHTML(mdContent, fileName) {
9
+ const rendered = marked.parse(mdContent);
10
+
11
+ return `<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="UTF-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>${fileName}</title>
17
+ <link rel="preconnect" href="https://fonts.googleapis.com">
18
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
19
+ <style>
20
+ :root {
21
+ --bg: #0d1117;
22
+ --surface: #161b22;
23
+ --surface-2: #1c2129;
24
+ --border: #30363d;
25
+ --border-light: #3d444d;
26
+ --text: #e6edf3;
27
+ --text-muted: #8b949e;
28
+ --text-subtle: #6e7681;
29
+ --accent: #58a6ff;
30
+ --accent-soft: #1f6feb33;
31
+ --green: #3fb950;
32
+ --green-soft: #23863633;
33
+ --purple: #bc8cff;
34
+ --orange: #f0883e;
35
+ --red: #f85149;
36
+ --yellow: #d29922;
37
+ --code-bg: #0d1117;
38
+ --inline-code-bg: #262c36;
39
+ --scrollbar: #30363d;
40
+ --scrollbar-hover: #484f58;
41
+ --gradient-1: #58a6ff;
42
+ --gradient-2: #bc8cff;
43
+ --gradient-3: #3fb950;
44
+ }
45
+
46
+ * {
47
+ margin: 0;
48
+ padding: 0;
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ html {
53
+ scroll-behavior: smooth;
54
+ }
55
+
56
+ body {
57
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
58
+ background: var(--bg);
59
+ color: var(--text);
60
+ line-height: 1.7;
61
+ -webkit-font-smoothing: antialiased;
62
+ }
63
+
64
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
65
+ ::-webkit-scrollbar-track { background: transparent; }
66
+ ::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 4px; }
67
+ ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }
68
+
69
+ /* --- Top bar --- */
70
+ .topbar {
71
+ position: fixed;
72
+ top: 0;
73
+ left: 0;
74
+ right: 0;
75
+ height: 48px;
76
+ background: var(--surface);
77
+ border-bottom: 1px solid var(--border);
78
+ display: flex;
79
+ align-items: center;
80
+ padding: 0 24px;
81
+ z-index: 100;
82
+ backdrop-filter: blur(12px);
83
+ background: rgba(22, 27, 34, 0.85);
84
+ }
85
+
86
+ .topbar-dot {
87
+ width: 12px;
88
+ height: 12px;
89
+ border-radius: 50%;
90
+ margin-right: 8px;
91
+ }
92
+
93
+ .topbar-dot.red { background: #f85149; }
94
+ .topbar-dot.yellow { background: #d29922; }
95
+ .topbar-dot.green { background: #3fb950; }
96
+
97
+ .topbar-title {
98
+ margin-left: 16px;
99
+ font-size: 13px;
100
+ color: var(--text-muted);
101
+ font-weight: 500;
102
+ letter-spacing: 0.3px;
103
+ font-family: 'JetBrains Mono', monospace;
104
+ }
105
+
106
+ .topbar-actions {
107
+ margin-left: auto;
108
+ display: flex;
109
+ gap: 8px;
110
+ }
111
+
112
+ .topbar-btn {
113
+ background: var(--surface-2);
114
+ border: 1px solid var(--border);
115
+ color: var(--text-muted);
116
+ padding: 4px 12px;
117
+ border-radius: 6px;
118
+ font-size: 12px;
119
+ cursor: pointer;
120
+ font-family: 'Inter', sans-serif;
121
+ transition: all 0.15s ease;
122
+ }
123
+
124
+ .topbar-btn:hover {
125
+ background: var(--border);
126
+ color: var(--text);
127
+ }
128
+
129
+ /* --- Sidebar TOC --- */
130
+ .layout {
131
+ display: flex;
132
+ padding-top: 48px;
133
+ }
134
+
135
+ .sidebar {
136
+ position: fixed;
137
+ top: 48px;
138
+ left: 0;
139
+ width: 280px;
140
+ height: calc(100vh - 48px);
141
+ background: var(--surface);
142
+ border-right: 1px solid var(--border);
143
+ overflow-y: auto;
144
+ padding: 20px 0;
145
+ transition: transform 0.25s ease;
146
+ }
147
+
148
+ .sidebar-header {
149
+ padding: 0 20px 16px;
150
+ font-size: 11px;
151
+ font-weight: 600;
152
+ text-transform: uppercase;
153
+ letter-spacing: 1.2px;
154
+ color: var(--text-subtle);
155
+ }
156
+
157
+ .toc-item {
158
+ display: block;
159
+ padding: 6px 20px;
160
+ color: var(--text-muted);
161
+ text-decoration: none;
162
+ font-size: 13px;
163
+ transition: all 0.15s ease;
164
+ border-left: 2px solid transparent;
165
+ }
166
+
167
+ .toc-item:hover {
168
+ color: var(--text);
169
+ background: var(--surface-2);
170
+ }
171
+
172
+ .toc-item.active {
173
+ color: var(--accent);
174
+ border-left-color: var(--accent);
175
+ background: var(--accent-soft);
176
+ }
177
+
178
+ .toc-item.depth-2 { padding-left: 20px; }
179
+ .toc-item.depth-3 { padding-left: 36px; font-size: 12px; }
180
+
181
+ /* --- Main content --- */
182
+ .content {
183
+ margin-left: 280px;
184
+ max-width: 860px;
185
+ padding: 48px 56px 120px;
186
+ width: 100%;
187
+ }
188
+
189
+ /* --- Typography --- */
190
+ .markdown-body h1 {
191
+ font-size: 2em;
192
+ font-weight: 700;
193
+ margin: 48px 0 16px;
194
+ padding-bottom: 12px;
195
+ border-bottom: 1px solid var(--border);
196
+ background: linear-gradient(135deg, var(--gradient-1), var(--gradient-2));
197
+ -webkit-background-clip: text;
198
+ -webkit-text-fill-color: transparent;
199
+ background-clip: text;
200
+ letter-spacing: -0.5px;
201
+ }
202
+
203
+ .markdown-body h1:first-child {
204
+ margin-top: 0;
205
+ font-size: 2.4em;
206
+ }
207
+
208
+ .markdown-body h2 {
209
+ font-size: 1.5em;
210
+ font-weight: 600;
211
+ margin: 40px 0 16px;
212
+ padding-bottom: 8px;
213
+ border-bottom: 1px solid var(--border);
214
+ color: var(--text);
215
+ letter-spacing: -0.3px;
216
+ }
217
+
218
+ .markdown-body h3 {
219
+ font-size: 1.2em;
220
+ font-weight: 600;
221
+ margin: 32px 0 12px;
222
+ color: var(--accent);
223
+ }
224
+
225
+ .markdown-body h4 {
226
+ font-size: 1em;
227
+ font-weight: 600;
228
+ margin: 24px 0 8px;
229
+ color: var(--purple);
230
+ }
231
+
232
+ .markdown-body p {
233
+ margin: 0 0 16px;
234
+ color: var(--text-muted);
235
+ }
236
+
237
+ .markdown-body strong {
238
+ color: var(--text);
239
+ font-weight: 600;
240
+ }
241
+
242
+ .markdown-body a {
243
+ color: var(--accent);
244
+ text-decoration: none;
245
+ border-bottom: 1px solid transparent;
246
+ transition: border-color 0.15s ease;
247
+ }
248
+
249
+ .markdown-body a:hover {
250
+ border-bottom-color: var(--accent);
251
+ }
252
+
253
+ /* --- Lists --- */
254
+ .markdown-body ul,
255
+ .markdown-body ol {
256
+ padding-left: 24px;
257
+ margin: 0 0 16px;
258
+ }
259
+
260
+ .markdown-body li {
261
+ margin: 4px 0;
262
+ color: var(--text-muted);
263
+ }
264
+
265
+ .markdown-body li::marker {
266
+ color: var(--border-light);
267
+ }
268
+
269
+ /* --- Blockquotes --- */
270
+ .markdown-body blockquote {
271
+ margin: 0 0 16px;
272
+ padding: 12px 20px;
273
+ border-left: 3px solid var(--accent);
274
+ background: var(--accent-soft);
275
+ border-radius: 0 8px 8px 0;
276
+ }
277
+
278
+ .markdown-body blockquote p {
279
+ margin: 0;
280
+ color: var(--text);
281
+ font-size: 14px;
282
+ }
283
+
284
+ /* --- Code blocks --- */
285
+ .markdown-body pre {
286
+ background: var(--code-bg);
287
+ border: 1px solid var(--border);
288
+ border-radius: 8px;
289
+ padding: 16px 20px;
290
+ margin: 0 0 16px;
291
+ overflow-x: auto;
292
+ position: relative;
293
+ }
294
+
295
+ .markdown-body pre code {
296
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
297
+ font-size: 13px;
298
+ line-height: 1.6;
299
+ color: var(--text);
300
+ background: none;
301
+ padding: 0;
302
+ border: none;
303
+ border-radius: 0;
304
+ }
305
+
306
+ .markdown-body code {
307
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
308
+ font-size: 0.875em;
309
+ background: var(--inline-code-bg);
310
+ color: var(--orange);
311
+ padding: 2px 6px;
312
+ border-radius: 4px;
313
+ border: 1px solid var(--border);
314
+ }
315
+
316
+ /* --- Tables --- */
317
+ .markdown-body table {
318
+ width: 100%;
319
+ border-collapse: collapse;
320
+ margin: 0 0 16px;
321
+ font-size: 14px;
322
+ }
323
+
324
+ .markdown-body thead th {
325
+ background: var(--surface);
326
+ color: var(--text);
327
+ font-weight: 600;
328
+ text-align: left;
329
+ padding: 10px 16px;
330
+ border: 1px solid var(--border);
331
+ font-size: 13px;
332
+ text-transform: uppercase;
333
+ letter-spacing: 0.5px;
334
+ }
335
+
336
+ .markdown-body tbody td {
337
+ padding: 10px 16px;
338
+ border: 1px solid var(--border);
339
+ color: var(--text-muted);
340
+ }
341
+
342
+ .markdown-body tbody tr:nth-child(even) {
343
+ background: var(--surface);
344
+ }
345
+
346
+ .markdown-body tbody tr:hover {
347
+ background: var(--surface-2);
348
+ }
349
+
350
+ /* --- Horizontal rules --- */
351
+ .markdown-body hr {
352
+ border: none;
353
+ height: 1px;
354
+ background: var(--border);
355
+ margin: 32px 0;
356
+ }
357
+
358
+ /* --- Images --- */
359
+ .markdown-body img {
360
+ max-width: 100%;
361
+ border-radius: 8px;
362
+ border: 1px solid var(--border);
363
+ }
364
+
365
+ /* --- Scroll spy active heading --- */
366
+ .heading-anchor {
367
+ scroll-margin-top: 72px;
368
+ }
369
+
370
+ /* --- Responsive --- */
371
+ @media (max-width: 1024px) {
372
+ .sidebar { transform: translateX(-100%); }
373
+ .sidebar.open { transform: translateX(0); }
374
+ .content { margin-left: 0; padding: 32px 24px 80px; }
375
+ }
376
+
377
+ /* --- Print --- */
378
+ @media print {
379
+ .topbar, .sidebar { display: none; }
380
+ .content { margin-left: 0; max-width: 100%; }
381
+ body { background: white; color: #1a1a1a; }
382
+ }
383
+
384
+ /* --- Syntax-like coloring for code (basic) --- */
385
+ .markdown-body pre code .keyword { color: var(--purple); }
386
+ .markdown-body pre code .string { color: var(--green); }
387
+ .markdown-body pre code .comment { color: var(--text-subtle); }
388
+
389
+ /* --- Fade-in animation --- */
390
+ .markdown-body > * {
391
+ animation: fadeUp 0.4s ease both;
392
+ }
393
+
394
+ @keyframes fadeUp {
395
+ from { opacity: 0; transform: translateY(8px); }
396
+ to { opacity: 1; transform: translateY(0); }
397
+ }
398
+
399
+ .markdown-body > *:nth-child(1) { animation-delay: 0.02s; }
400
+ .markdown-body > *:nth-child(2) { animation-delay: 0.04s; }
401
+ .markdown-body > *:nth-child(3) { animation-delay: 0.06s; }
402
+ .markdown-body > *:nth-child(4) { animation-delay: 0.08s; }
403
+ .markdown-body > *:nth-child(5) { animation-delay: 0.10s; }
404
+ .markdown-body > *:nth-child(6) { animation-delay: 0.12s; }
405
+ .markdown-body > *:nth-child(7) { animation-delay: 0.14s; }
406
+ .markdown-body > *:nth-child(8) { animation-delay: 0.16s; }
407
+ .markdown-body > *:nth-child(9) { animation-delay: 0.18s; }
408
+ .markdown-body > *:nth-child(10) { animation-delay: 0.20s; }
409
+ </style>
410
+ </head>
411
+ <body>
412
+
413
+ <!-- Top Bar -->
414
+ <div class="topbar">
415
+ <span class="topbar-dot red"></span>
416
+ <span class="topbar-dot yellow"></span>
417
+ <span class="topbar-dot green"></span>
418
+ <span class="topbar-title">${fileName}</span>
419
+ <div class="topbar-actions">
420
+ <button class="topbar-btn" onclick="toggleSidebar()">TOC</button>
421
+ <button class="topbar-btn" onclick="toggleTheme()">Light</button>
422
+ <button class="topbar-btn" onclick="scrollToTop()">Top</button>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="layout">
427
+ <!-- Sidebar (generated by JS) -->
428
+ <nav class="sidebar" id="sidebar"></nav>
429
+
430
+ <!-- Content -->
431
+ <main class="content">
432
+ <article class="markdown-body" id="content">
433
+ ${rendered}
434
+ </article>
435
+ </main>
436
+ </div>
437
+
438
+ <script>
439
+ (function() {
440
+ // --- Build TOC from headings ---
441
+ const content = document.getElementById('content');
442
+ const sidebar = document.getElementById('sidebar');
443
+ const headings = content.querySelectorAll('h1, h2, h3');
444
+ let tocHTML = '<div class="sidebar-header">On this page</div>';
445
+
446
+ headings.forEach((h, i) => {
447
+ const id = 'heading-' + i;
448
+ h.id = id;
449
+ h.classList.add('heading-anchor');
450
+ const depth = parseInt(h.tagName[1]);
451
+ tocHTML += '<a class="toc-item depth-' + depth + '" href="#' + id + '">' + h.textContent + '</a>';
452
+ });
453
+
454
+ sidebar.innerHTML = tocHTML;
455
+
456
+ // --- Scroll spy ---
457
+ const tocLinks = sidebar.querySelectorAll('.toc-item');
458
+
459
+ function updateActive() {
460
+ let current = 0;
461
+ headings.forEach((h, i) => {
462
+ if (h.getBoundingClientRect().top <= 100) current = i;
463
+ });
464
+ tocLinks.forEach(l => l.classList.remove('active'));
465
+ if (tocLinks[current]) tocLinks[current].classList.add('active');
466
+ }
467
+
468
+ window.addEventListener('scroll', updateActive, { passive: true });
469
+ updateActive();
470
+
471
+ // --- Sidebar toggle (mobile) ---
472
+ window.toggleSidebar = function() {
473
+ sidebar.classList.toggle('open');
474
+ };
475
+
476
+ // --- Scroll to top ---
477
+ window.scrollToTop = function() {
478
+ window.scrollTo({ top: 0, behavior: 'smooth' });
479
+ };
480
+
481
+ // --- Light/Dark toggle ---
482
+ const lightVars = {
483
+ '--bg': '#ffffff',
484
+ '--surface': '#f6f8fa',
485
+ '--surface-2': '#eef1f5',
486
+ '--border': '#d0d7de',
487
+ '--border-light': '#c5ccd3',
488
+ '--text': '#1f2328',
489
+ '--text-muted': '#57606a',
490
+ '--text-subtle': '#8b949e',
491
+ '--accent': '#0969da',
492
+ '--accent-soft': '#0969da1a',
493
+ '--green': '#1a7f37',
494
+ '--green-soft': '#1a7f371a',
495
+ '--purple': '#8250df',
496
+ '--orange': '#bc4c00',
497
+ '--red': '#cf222e',
498
+ '--yellow': '#9a6700',
499
+ '--code-bg': '#f6f8fa',
500
+ '--inline-code-bg': '#eff1f3',
501
+ '--scrollbar': '#d0d7de',
502
+ '--scrollbar-hover': '#afb8c1',
503
+ };
504
+
505
+ const darkVars = {};
506
+ for (const key in lightVars) {
507
+ darkVars[key] = getComputedStyle(document.documentElement).getPropertyValue(key).trim();
508
+ }
509
+
510
+ let isDark = true;
511
+ window.toggleTheme = function() {
512
+ isDark = !isDark;
513
+ const vars = isDark ? darkVars : lightVars;
514
+ for (const [k, v] of Object.entries(vars)) {
515
+ document.documentElement.style.setProperty(k, v);
516
+ }
517
+ document.querySelector('.topbar-btn:nth-child(2)').textContent = isDark ? 'Light' : 'Dark';
518
+ };
519
+ })();
520
+ </script>
521
+
522
+ </body>
523
+ </html>`;
524
+ }
525
+
526
+ module.exports = { renderHTML };
package/lib/server.js ADDED
@@ -0,0 +1,49 @@
1
+ const http = require("http");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const { renderHTML } = require("./renderer");
5
+
6
+ function serve(filePath, options = {}) {
7
+ const port = options.port || 3333;
8
+ const absolutePath = path.resolve(filePath);
9
+
10
+ if (!fs.existsSync(absolutePath)) {
11
+ console.error(`File not found: ${absolutePath}`);
12
+ process.exit(1);
13
+ }
14
+
15
+ const server = http.createServer((req, res) => {
16
+ if (req.url === "/" || req.url === "/index.html" || req.url === "/reload") {
17
+ const md = fs.readFileSync(absolutePath, "utf-8");
18
+ const html = renderHTML(md, path.basename(absolutePath));
19
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
20
+ res.end(html);
21
+ } else {
22
+ res.writeHead(404);
23
+ res.end("Not found");
24
+ }
25
+ });
26
+
27
+ server.listen(port, async () => {
28
+ const url = `http://localhost:${port}`;
29
+ console.log(`\n mdpretty`);
30
+ console.log(` ────────`);
31
+ console.log(` File: ${absolutePath}`);
32
+ console.log(` Server: ${url}\n`);
33
+
34
+ if (!options.noBrowser) {
35
+ const { default: open } = await import("open");
36
+ await open(url);
37
+ }
38
+ });
39
+
40
+ process.on("SIGINT", () => {
41
+ console.log("\n Server stopped.");
42
+ server.close();
43
+ process.exit(0);
44
+ });
45
+
46
+ return server;
47
+ }
48
+
49
+ module.exports = { serve };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "prettymdviewer",
3
+ "version": "1.0.2",
4
+ "description": "Beautiful markdown viewer — render .md files in a styled browser UI with TOC, dark/light themes, and scroll spy",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "mdpretty": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/",
12
+ "index.js"
13
+ ],
14
+ "scripts": {
15
+ "start": "node bin/cli.js",
16
+ "test": "echo \"Error: no test specified\" && exit 1"
17
+ },
18
+ "keywords": [
19
+ "markdown",
20
+ "viewer",
21
+ "preview",
22
+ "renderer",
23
+ "cli",
24
+ "pretty",
25
+ "dark-theme"
26
+ ],
27
+ "author": "yogesh swami",
28
+ "license": "MIT",
29
+ "type": "commonjs",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/yogesh-79/mdpretty"
33
+ },
34
+ "dependencies": {
35
+ "marked": "^17.0.6",
36
+ "open": "^11.0.0"
37
+ }
38
+ }