memd-cli 3.3.0 → 3.4.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/.claude/settings.local.json +7 -0
- package/main.js +10 -5
- package/package.json +1 -1
- package/render-shared.js +13 -1
- package/test/memd.test.js +15 -1
package/main.js
CHANGED
|
@@ -622,10 +622,12 @@ async function main() {
|
|
|
622
622
|
|
|
623
623
|
if (options.html) {
|
|
624
624
|
// 3a. HTML path
|
|
625
|
-
const { renderToHTML, MERMAID_MODAL_SCRIPT } = await import('./render-shared.js');
|
|
625
|
+
const { renderToHTML, MERMAID_MODAL_SCRIPT, WIDTH_TOGGLE_SCRIPT } = await import('./render-shared.js');
|
|
626
626
|
const combined = markdownParts.join('\n\n');
|
|
627
627
|
let html = renderToHTML(combined, diagramColors);
|
|
628
|
-
|
|
628
|
+
let inlineScripts = WIDTH_TOGGLE_SCRIPT;
|
|
629
|
+
if (html.includes('mermaid-diagram')) inlineScripts += MERMAID_MODAL_SCRIPT;
|
|
630
|
+
html = html.replace('<!--memd:scripts-->', `<script>${inlineScripts}</script>`);
|
|
629
631
|
process.stdout.write(html);
|
|
630
632
|
} else {
|
|
631
633
|
// 3b. Terminal path
|
|
@@ -763,7 +765,7 @@ async function main() {
|
|
|
763
765
|
console.error('Invalid --workers: must be a positive integer');
|
|
764
766
|
process.exit(1);
|
|
765
767
|
}
|
|
766
|
-
const { MERMAID_MODAL_SCRIPT: mermaidModalScript } = await import('./render-shared.js');
|
|
768
|
+
const { MERMAID_MODAL_SCRIPT: mermaidModalScript, WIDTH_TOGGLE_SCRIPT: widthToggleScript } = await import('./render-shared.js');
|
|
767
769
|
const poolSize = options.workers ?? Math.min(Math.max(1, os.cpus().length - 1), 4);
|
|
768
770
|
const workerPath = new URL('./render-worker.js', import.meta.url);
|
|
769
771
|
const pool = createRenderPool(workerPath, poolSize, {
|
|
@@ -873,7 +875,10 @@ async function main() {
|
|
|
873
875
|
.memd-sidebar a { color: ${t.accent}; text-decoration: none; display: block; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.9rem; }
|
|
874
876
|
.memd-sidebar a:hover { background: color-mix(in srgb, ${t.fg} 5%, ${t.bg}); }
|
|
875
877
|
.memd-sidebar a[aria-current="page"] { background: color-mix(in srgb, ${t.accent} 12%, ${t.bg}); }
|
|
876
|
-
.memd-content { max-width:
|
|
878
|
+
.memd-content { max-width: 70%; padding: 2rem 1rem; margin: 0 auto; }
|
|
879
|
+
@media (max-width: 1024px) { .memd-content { max-width: 85%; } }
|
|
880
|
+
@media (max-width: 768px) { .memd-content { max-width: 100%; } }
|
|
881
|
+
body.memd-full-width .memd-content { max-width: none; margin: 0; }
|
|
877
882
|
body:has(.memd-layout) { max-width: none; margin: 0; padding: 0; }
|
|
878
883
|
.memd-hamburger { display: none; position: fixed; top: 0.5rem; left: 0.5rem; z-index: 10; background: color-mix(in srgb, ${t.fg} 8%, ${t.bg}); border: 1px solid ${t.line}; color: ${t.fg}; padding: 0.3rem 0.5rem; cursor: pointer; border-radius: 4px; font-size: 1.2rem; }
|
|
879
884
|
@media (max-width: 768px) {
|
|
@@ -896,7 +901,7 @@ body:has(.memd-layout) { max-width: none; margin: 0; padding: 0; }
|
|
|
896
901
|
res.end();
|
|
897
902
|
return;
|
|
898
903
|
}
|
|
899
|
-
let scripts =
|
|
904
|
+
let scripts = widthToggleScript;
|
|
900
905
|
if (options.watch) {
|
|
901
906
|
scripts += 'new EventSource("/_memd/events").onmessage=function(){location.reload()};';
|
|
902
907
|
}
|
package/package.json
CHANGED
package/render-shared.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Marked } from 'marked';
|
|
|
3
3
|
import { renderMermaidSVG } from '@ktrysmt/beautiful-mermaid';
|
|
4
4
|
import { escapeHtml, resolveThemeColors } from './render-utils.js';
|
|
5
5
|
|
|
6
|
+
export const WIDTH_TOGGLE_SCRIPT = "(function(){var b=document.body,g=document.querySelector('.memd-width-toggle');if(!g)return;var sb=document.querySelector('.memd-sidebar');if(sb){sb.insertBefore(g,sb.firstChild);g.style.position='static';g.style.margin='0 0 0.5rem 0';g.style.width='auto'}var btns=g.querySelectorAll('.memd-wt-btn');function u(){var f=b.classList.contains('memd-full-width');btns.forEach(function(n){n.classList.toggle('active',n.dataset.mode===(f?'full':'smart'))})}if(localStorage.getItem('memd-width')==='full')b.classList.add('memd-full-width');u();btns.forEach(function(n){n.onclick=function(){if(n.dataset.mode==='full'){b.classList.add('memd-full-width');localStorage.setItem('memd-width','full')}else{b.classList.remove('memd-full-width');localStorage.setItem('memd-width','smart')}u()}})})();";
|
|
7
|
+
|
|
6
8
|
export const MERMAID_MODAL_SCRIPT = [
|
|
7
9
|
"document.addEventListener('click',function(e){var d=e.target.closest('.mermaid-diagram');if(d){var o=document.createElement('div');o.className='mermaid-modal';o.innerHTML=d.querySelector('svg').outerHTML;o.onclick=function(){o.remove()};document.body.appendChild(o)}});",
|
|
8
10
|
"document.addEventListener('keydown',function(e){if(e.key==='Escape'){var m=document.querySelector('.mermaid-modal');if(m)m.remove()}});",
|
|
@@ -66,7 +68,16 @@ export function renderToHTML(markdown, diagramColors) {
|
|
|
66
68
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
67
69
|
${title ? `<title>${escapeHtml(title)}</title>` : ''}
|
|
68
70
|
<style>
|
|
69
|
-
body { background: ${t.bg}; color: ${t.fg}; font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; max-width:
|
|
71
|
+
body { background: ${t.bg}; color: ${t.fg}; font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; max-width: 70%; margin: 0 auto; padding: 2rem 1rem; }
|
|
72
|
+
@media (max-width: 1024px) { body { max-width: 85%; } }
|
|
73
|
+
@media (max-width: 768px) { body { max-width: 100%; } }
|
|
74
|
+
body.memd-full-width { max-width: none; margin: 0; }
|
|
75
|
+
.memd-width-toggle { position: fixed; top: 0.5rem; left: 0.5rem; z-index: 11; display: inline-flex; border-radius: 6px; overflow: hidden; border: 1px solid ${t.line}; font-size: 0; }
|
|
76
|
+
.memd-wt-btn { background: color-mix(in srgb, ${t.fg} 5%, ${t.bg}); border: none; color: ${t.muted}; padding: 0.25rem 0.5rem; cursor: pointer; font-size: 0.7rem; display: inline-flex; align-items: center; gap: 0.25rem; line-height: 1; }
|
|
77
|
+
.memd-wt-btn + .memd-wt-btn { border-left: 1px solid ${t.line}; }
|
|
78
|
+
.memd-wt-btn:hover { background: color-mix(in srgb, ${t.fg} 12%, ${t.bg}); }
|
|
79
|
+
.memd-wt-btn.active { background: color-mix(in srgb, ${t.accent} 18%, ${t.bg}); color: ${t.accent}; }
|
|
80
|
+
.memd-wt-btn svg { width: 12px; height: 12px; flex-shrink: 0; }
|
|
70
81
|
a { color: ${t.accent}; }
|
|
71
82
|
hr { border-color: ${t.line}; }
|
|
72
83
|
blockquote { border-left: 3px solid ${t.line}; color: ${t.muted}; padding-left: 1rem; }
|
|
@@ -85,6 +96,7 @@ th { background: color-mix(in srgb, ${t.fg} 5%, ${t.bg}); }
|
|
|
85
96
|
<!--memd:head-->
|
|
86
97
|
</head>
|
|
87
98
|
<body>
|
|
99
|
+
<div class="memd-width-toggle" role="group" aria-label="Width toggle"><button class="memd-wt-btn" data-mode="smart"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 8h5m4 0h5"/><path d="M4 5l3 3-3 3m8-6l-3 3 3 3"/></svg>Smart</button><button class="memd-wt-btn" data-mode="full"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 8h14"/><path d="M4 5L1 8l3 3m8-6l3 3-3 3"/></svg>Full</button></div>
|
|
88
100
|
<!--memd:content-->
|
|
89
101
|
${body.trimEnd()}
|
|
90
102
|
<!--/memd:content-->
|
package/test/memd.test.js
CHANGED
|
@@ -26,7 +26,7 @@ function runSync(args) {
|
|
|
26
26
|
describe('memd CLI', () => {
|
|
27
27
|
it.concurrent('--version', async () => {
|
|
28
28
|
const output = await run(['-v'])
|
|
29
|
-
expect(output).toContain('3.
|
|
29
|
+
expect(output).toContain('3.4.0')
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
it.concurrent('--help', async () => {
|
|
@@ -200,6 +200,20 @@ describe('memd CLI', () => {
|
|
|
200
200
|
expect(output).toMatch(/id="m1-/)
|
|
201
201
|
})
|
|
202
202
|
|
|
203
|
+
it('--html contains segmented width toggle with Smart/Full buttons', () => {
|
|
204
|
+
const output = runSync(['--html', 'test/test1.md'])
|
|
205
|
+
// Segmented toggle container
|
|
206
|
+
expect(output).toContain('class="memd-width-toggle"')
|
|
207
|
+
// Two buttons with data-mode attributes
|
|
208
|
+
expect(output).toContain('class="memd-wt-btn" data-mode="smart"')
|
|
209
|
+
expect(output).toContain('class="memd-wt-btn" data-mode="full"')
|
|
210
|
+
// SVG icons present in each button
|
|
211
|
+
const btnMatches = output.match(/memd-wt-btn/g)
|
|
212
|
+
expect(btnMatches.length).toBeGreaterThanOrEqual(4) // 2 in HTML + 2+ in CSS
|
|
213
|
+
// Toggle script sets active class
|
|
214
|
+
expect(output).toContain("classList.toggle('active'")
|
|
215
|
+
})
|
|
216
|
+
|
|
203
217
|
it('--html + multiple files -> combined single HTML', () => {
|
|
204
218
|
const output = runSync(['--html', 'test/test1.md', 'test/test2.md'])
|
|
205
219
|
expect(output).toContain('<!DOCTYPE html>')
|