@helping-ai-workflow/md2doc 1.0.3 → 1.1.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/lib/md2doc.js +188 -8
- package/package.json +11 -3
package/lib/md2doc.js
CHANGED
|
@@ -214,6 +214,65 @@ ${itemsHtml}
|
|
|
214
214
|
.join('');
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
function unbreakableRun(s) {
|
|
218
|
+
// Treat `_` as a break point — identifiers like `pmac_tx_*` are unbreakable in CSS but breakable for classification.
|
|
219
|
+
const matches = String(s || '').match(/[A-Za-z0-9\-./@:]+/g);
|
|
220
|
+
if (!matches) return 0;
|
|
221
|
+
let max = 0;
|
|
222
|
+
for (const m of matches) if (m.length > max) max = m.length;
|
|
223
|
+
return max;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function cellRawText(cell) {
|
|
227
|
+
if (!cell) return '';
|
|
228
|
+
if (Array.isArray(cell.tokens)) return flattenTokenText(cell.tokens);
|
|
229
|
+
return String(cell.text || '');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function classifyColumns(token) {
|
|
233
|
+
const colCount = (token.header || []).length;
|
|
234
|
+
const classes = [];
|
|
235
|
+
for (let i = 0; i < colCount; i++) {
|
|
236
|
+
const allCells = [];
|
|
237
|
+
if (token.header && token.header[i]) allCells.push(token.header[i]);
|
|
238
|
+
const dataCells = [];
|
|
239
|
+
if (Array.isArray(token.rows)) {
|
|
240
|
+
for (const row of token.rows) {
|
|
241
|
+
if (row && row[i]) {
|
|
242
|
+
allCells.push(row[i]);
|
|
243
|
+
dataCells.push(row[i]);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const allTexts = allCells.map(cellRawText);
|
|
248
|
+
const dataTexts = dataCells.map(cellRawText);
|
|
249
|
+
// Header labels (e.g. "Clock Domain") often contain whitespace not representative of cell content; use data rows for the heuristic and fall back to header only when the column has no data.
|
|
250
|
+
const heuristicTexts = dataTexts.length > 0 ? dataTexts : allTexts;
|
|
251
|
+
let maxTokenLen = 0;
|
|
252
|
+
let totalLen = 0;
|
|
253
|
+
let hasWhitespace = false;
|
|
254
|
+
let hasSentence = false;
|
|
255
|
+
for (const t of heuristicTexts) {
|
|
256
|
+
const r = unbreakableRun(t);
|
|
257
|
+
if (r > maxTokenLen) maxTokenLen = r;
|
|
258
|
+
totalLen += t.length;
|
|
259
|
+
if (/\s/.test(t)) hasWhitespace = true;
|
|
260
|
+
if (/。|\. /.test(t)) hasSentence = true;
|
|
261
|
+
}
|
|
262
|
+
const avgCellLen = heuristicTexts.length ? (totalLen / heuristicTexts.length) : 0;
|
|
263
|
+
|
|
264
|
+
// Narrow takes precedence over prose (tie-break: prefer narrow / conservative).
|
|
265
|
+
if (maxTokenLen <= 12 && !hasWhitespace) {
|
|
266
|
+
classes.push('col-narrow');
|
|
267
|
+
} else if (avgCellLen > 40 || hasSentence) {
|
|
268
|
+
classes.push('col-prose');
|
|
269
|
+
} else {
|
|
270
|
+
classes.push('col-default');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return classes;
|
|
274
|
+
}
|
|
275
|
+
|
|
217
276
|
function stripHtmlTags(value) {
|
|
218
277
|
return String(value || '').replace(/<[^>]*>/g, '');
|
|
219
278
|
}
|
|
@@ -256,13 +315,11 @@ ${itemsHtml}
|
|
|
256
315
|
const r = spawnSync('dot', ['-Tsvg'], { input: code, encoding: 'utf8', timeout: 10000 });
|
|
257
316
|
if (r.status === 0) {
|
|
258
317
|
// Strip XML declaration / DOCTYPE; keep only the <svg> element.
|
|
259
|
-
//
|
|
260
|
-
//
|
|
318
|
+
// Keep intrinsic width/height so small diagrams render at their natural
|
|
319
|
+
// size; max-width:100% in CSS still shrinks oversized ones to container.
|
|
261
320
|
const svg = r.stdout
|
|
262
321
|
.replace(/<\?xml[^>]*\?>/g, '')
|
|
263
322
|
.replace(/<!DOCTYPE[^>]*>/g, '')
|
|
264
|
-
.replace(/(<svg\b[^>]*?)\s+width="[^"]*"/i, '$1')
|
|
265
|
-
.replace(/(<svg\b[^>]*?)\s+height="[^"]*"/i, '$1')
|
|
266
323
|
.trim();
|
|
267
324
|
return `\n<div class="graphviz">${svg}</div>\n`;
|
|
268
325
|
}
|
|
@@ -308,7 +365,6 @@ ${itemsHtml}
|
|
|
308
365
|
return baseBlockquote(token);
|
|
309
366
|
};
|
|
310
367
|
|
|
311
|
-
const baseTable = renderer.table.bind(renderer);
|
|
312
368
|
renderer.table = function(token) {
|
|
313
369
|
collectCellText(token.header);
|
|
314
370
|
if (Array.isArray(token.rows)) {
|
|
@@ -316,7 +372,41 @@ ${itemsHtml}
|
|
|
316
372
|
collectCellText(row);
|
|
317
373
|
}
|
|
318
374
|
}
|
|
319
|
-
|
|
375
|
+
|
|
376
|
+
const classes = classifyColumns(token);
|
|
377
|
+
const colHtml = classes
|
|
378
|
+
.map((c) => (c === 'col-default' ? '<col>' : `<col class="${c}">`))
|
|
379
|
+
.join('');
|
|
380
|
+
const cellClassAttr = (i) => {
|
|
381
|
+
const c = classes[i];
|
|
382
|
+
if (c === 'col-narrow') return ' class="cell-narrow"';
|
|
383
|
+
if (c === 'col-prose') return ' class="cell-prose"';
|
|
384
|
+
return '';
|
|
385
|
+
};
|
|
386
|
+
const alignStyle = (cell) => (cell && cell.align)
|
|
387
|
+
? ` style="text-align:${cell.align}"`
|
|
388
|
+
: '';
|
|
389
|
+
|
|
390
|
+
const headerCells = (token.header || []).map((cell, i) => {
|
|
391
|
+
const inner = cell && Array.isArray(cell.tokens)
|
|
392
|
+
? this.parser.parseInline(cell.tokens)
|
|
393
|
+
: '';
|
|
394
|
+
return `<th${cellClassAttr(i)}${alignStyle(cell)}>${inner}</th>`;
|
|
395
|
+
}).join('');
|
|
396
|
+
const headerHtml = `<thead><tr>${headerCells}</tr></thead>`;
|
|
397
|
+
|
|
398
|
+
const bodyRows = Array.isArray(token.rows) ? token.rows.map((row) => {
|
|
399
|
+
const cells = (row || []).map((cell, i) => {
|
|
400
|
+
const inner = cell && Array.isArray(cell.tokens)
|
|
401
|
+
? this.parser.parseInline(cell.tokens)
|
|
402
|
+
: '';
|
|
403
|
+
return `<td${cellClassAttr(i)}${alignStyle(cell)}>${inner}</td>`;
|
|
404
|
+
}).join('');
|
|
405
|
+
return `<tr>${cells}</tr>`;
|
|
406
|
+
}).join('') : '';
|
|
407
|
+
const bodyHtmlPart = `<tbody>${bodyRows}</tbody>`;
|
|
408
|
+
|
|
409
|
+
return `<table>\n<colgroup>${colHtml}</colgroup>\n${headerHtml}\n${bodyHtmlPart}\n</table>\n`;
|
|
320
410
|
};
|
|
321
411
|
|
|
322
412
|
marked.setOptions({ gfm: true, breaks: false, renderer });
|
|
@@ -360,6 +450,7 @@ ${itemsHtml}
|
|
|
360
450
|
<span class="toc-title">Contents</span>
|
|
361
451
|
<button id="toc-expand-all" type="button" aria-label="Expand all">⊞</button>
|
|
362
452
|
<button id="toc-collapse-all" type="button" aria-label="Collapse all">⊟</button>
|
|
453
|
+
<button id="toc-collapse-toggle" type="button" aria-label="Collapse table of contents" title="Collapse / expand sidebar">◀</button>
|
|
363
454
|
</div>
|
|
364
455
|
${renderTocNodes(tocTree)}
|
|
365
456
|
</nav>
|
|
@@ -404,16 +495,52 @@ const html = `<!DOCTYPE html>
|
|
|
404
495
|
.reader-sidebar {
|
|
405
496
|
position: sticky;
|
|
406
497
|
top: 24px;
|
|
407
|
-
|
|
498
|
+
flex: 0 1 300px;
|
|
499
|
+
width: clamp(220px, 22vw, 300px);
|
|
500
|
+
min-width: 220px;
|
|
408
501
|
height: calc(100vh - 48px);
|
|
409
502
|
overflow: hidden;
|
|
410
|
-
flex: 0 0 320px;
|
|
411
503
|
padding-right: 8px;
|
|
412
504
|
box-sizing: border-box;
|
|
413
505
|
display: flex;
|
|
414
506
|
flex-direction: column;
|
|
415
507
|
gap: 12px;
|
|
416
508
|
}
|
|
509
|
+
body[data-toc-collapsed] .reader-sidebar {
|
|
510
|
+
flex-basis: 36px;
|
|
511
|
+
width: 36px;
|
|
512
|
+
min-width: 36px;
|
|
513
|
+
padding-right: 0;
|
|
514
|
+
}
|
|
515
|
+
body[data-toc-collapsed] .reader-tools,
|
|
516
|
+
body[data-toc-collapsed] .search-results,
|
|
517
|
+
body[data-toc-collapsed] .toc > .toc-list,
|
|
518
|
+
body[data-toc-collapsed] .toc-title {
|
|
519
|
+
display: none;
|
|
520
|
+
}
|
|
521
|
+
body[data-toc-collapsed] #toc-collapse-toggle {
|
|
522
|
+
transform: rotate(180deg);
|
|
523
|
+
}
|
|
524
|
+
#toc-collapse-toggle {
|
|
525
|
+
margin-left: 0;
|
|
526
|
+
padding: 2px 8px;
|
|
527
|
+
font: inherit;
|
|
528
|
+
font-size: 0.9em;
|
|
529
|
+
line-height: 1;
|
|
530
|
+
border: 1px solid #d0d7de;
|
|
531
|
+
border-radius: 6px;
|
|
532
|
+
background: #ffffff;
|
|
533
|
+
color: #57606a;
|
|
534
|
+
cursor: pointer;
|
|
535
|
+
transition: transform 0.15s ease;
|
|
536
|
+
}
|
|
537
|
+
#toc-collapse-toggle:hover {
|
|
538
|
+
background: #eef2f6;
|
|
539
|
+
color: #24292e;
|
|
540
|
+
}
|
|
541
|
+
@media (max-width: 1080px) {
|
|
542
|
+
#toc-collapse-toggle { display: none; }
|
|
543
|
+
}
|
|
417
544
|
.reader-tools { flex: 0 0 auto; }
|
|
418
545
|
.sidebar-toggle {
|
|
419
546
|
display: none;
|
|
@@ -698,6 +825,10 @@ const html = `<!DOCTYPE html>
|
|
|
698
825
|
.content {
|
|
699
826
|
min-width: 0;
|
|
700
827
|
flex: 1 1 auto;
|
|
828
|
+
}
|
|
829
|
+
.content p,
|
|
830
|
+
.content li,
|
|
831
|
+
.content blockquote {
|
|
701
832
|
overflow-wrap: anywhere;
|
|
702
833
|
word-break: break-word;
|
|
703
834
|
}
|
|
@@ -753,6 +884,36 @@ const html = `<!DOCTYPE html>
|
|
|
753
884
|
th, td { border: 1px solid #dfe2e5; padding: 7px 14px; text-align: left; }
|
|
754
885
|
th { background: #f6f8fa; font-weight: 600; }
|
|
755
886
|
tr:nth-child(even) { background: #fafbfc; }
|
|
887
|
+
.content table th,
|
|
888
|
+
.content table td {
|
|
889
|
+
overflow-wrap: normal;
|
|
890
|
+
word-break: normal;
|
|
891
|
+
}
|
|
892
|
+
.content table th {
|
|
893
|
+
white-space: nowrap;
|
|
894
|
+
}
|
|
895
|
+
.content table td code,
|
|
896
|
+
.content table th code {
|
|
897
|
+
white-space: nowrap;
|
|
898
|
+
}
|
|
899
|
+
.content table { table-layout: auto; }
|
|
900
|
+
.content table col.col-narrow { width: 1%; }
|
|
901
|
+
.content table col.col-prose { width: auto; }
|
|
902
|
+
.content table th.cell-narrow,
|
|
903
|
+
.content table td.cell-narrow { white-space: nowrap; }
|
|
904
|
+
.content table tbody td:first-child,
|
|
905
|
+
.content table thead th:first-child {
|
|
906
|
+
position: sticky;
|
|
907
|
+
left: 0;
|
|
908
|
+
z-index: 1;
|
|
909
|
+
background: #ffffff;
|
|
910
|
+
}
|
|
911
|
+
.content table thead th:first-child {
|
|
912
|
+
background: #f6f8fa;
|
|
913
|
+
}
|
|
914
|
+
.content table tbody tr:nth-child(even) td:first-child {
|
|
915
|
+
background: #fafbfc;
|
|
916
|
+
}
|
|
756
917
|
blockquote {
|
|
757
918
|
border-left: 4px solid #dfe2e5;
|
|
758
919
|
padding: 0 16px;
|
|
@@ -960,6 +1121,25 @@ ${mermaidInitTag}
|
|
|
960
1121
|
});
|
|
961
1122
|
}
|
|
962
1123
|
|
|
1124
|
+
function readTocCollapsed() {
|
|
1125
|
+
try { return localStorage.getItem('md2doc.toc.collapsed') === '1'; }
|
|
1126
|
+
catch (_) { return false; }
|
|
1127
|
+
}
|
|
1128
|
+
function writeTocCollapsed(v) {
|
|
1129
|
+
try { localStorage.setItem('md2doc.toc.collapsed', v ? '1' : '0'); }
|
|
1130
|
+
catch (_) { /* private mode / quota — ignore */ }
|
|
1131
|
+
}
|
|
1132
|
+
if (readTocCollapsed()) {
|
|
1133
|
+
document.body.setAttribute('data-toc-collapsed', '');
|
|
1134
|
+
}
|
|
1135
|
+
const tocCollapseBtn = document.getElementById('toc-collapse-toggle');
|
|
1136
|
+
if (tocCollapseBtn) {
|
|
1137
|
+
tocCollapseBtn.addEventListener('click', () => {
|
|
1138
|
+
const nowCollapsed = document.body.toggleAttribute('data-toc-collapsed');
|
|
1139
|
+
writeTocCollapsed(nowCollapsed);
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
963
1143
|
const SKIP_SELECTOR = 'svg, .mermaid, .graphviz, script, style';
|
|
964
1144
|
|
|
965
1145
|
function buildSnippet(section, query) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helping-ai-workflow/md2doc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"markdown",
|
|
7
|
+
"html",
|
|
8
|
+
"pdf",
|
|
9
|
+
"renderer",
|
|
10
|
+
"wavedrom",
|
|
11
|
+
"mermaid",
|
|
12
|
+
"graphviz"
|
|
13
|
+
],
|
|
6
14
|
"main": "lib/md2doc.js",
|
|
7
15
|
"bin": {
|
|
8
16
|
"md2html": "bin/md2html.js",
|
|
9
|
-
"md2pdf":
|
|
17
|
+
"md2pdf": "bin/md2pdf.js"
|
|
10
18
|
},
|
|
11
19
|
"files": [
|
|
12
20
|
"lib/",
|