@helping-ai-workflow/md2doc 1.0.3 → 1.1.0

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.
Files changed (2) hide show
  1. package/lib/md2doc.js +186 -4
  2. 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
  }
@@ -308,7 +367,6 @@ ${itemsHtml}
308
367
  return baseBlockquote(token);
309
368
  };
310
369
 
311
- const baseTable = renderer.table.bind(renderer);
312
370
  renderer.table = function(token) {
313
371
  collectCellText(token.header);
314
372
  if (Array.isArray(token.rows)) {
@@ -316,7 +374,41 @@ ${itemsHtml}
316
374
  collectCellText(row);
317
375
  }
318
376
  }
319
- return baseTable(token);
377
+
378
+ const classes = classifyColumns(token);
379
+ const colHtml = classes
380
+ .map((c) => (c === 'col-default' ? '<col>' : `<col class="${c}">`))
381
+ .join('');
382
+ const cellClassAttr = (i) => {
383
+ const c = classes[i];
384
+ if (c === 'col-narrow') return ' class="cell-narrow"';
385
+ if (c === 'col-prose') return ' class="cell-prose"';
386
+ return '';
387
+ };
388
+ const alignStyle = (cell) => (cell && cell.align)
389
+ ? ` style="text-align:${cell.align}"`
390
+ : '';
391
+
392
+ const headerCells = (token.header || []).map((cell, i) => {
393
+ const inner = cell && Array.isArray(cell.tokens)
394
+ ? this.parser.parseInline(cell.tokens)
395
+ : '';
396
+ return `<th${cellClassAttr(i)}${alignStyle(cell)}>${inner}</th>`;
397
+ }).join('');
398
+ const headerHtml = `<thead><tr>${headerCells}</tr></thead>`;
399
+
400
+ const bodyRows = Array.isArray(token.rows) ? token.rows.map((row) => {
401
+ const cells = (row || []).map((cell, i) => {
402
+ const inner = cell && Array.isArray(cell.tokens)
403
+ ? this.parser.parseInline(cell.tokens)
404
+ : '';
405
+ return `<td${cellClassAttr(i)}${alignStyle(cell)}>${inner}</td>`;
406
+ }).join('');
407
+ return `<tr>${cells}</tr>`;
408
+ }).join('') : '';
409
+ const bodyHtmlPart = `<tbody>${bodyRows}</tbody>`;
410
+
411
+ return `<table>\n<colgroup>${colHtml}</colgroup>\n${headerHtml}\n${bodyHtmlPart}\n</table>\n`;
320
412
  };
321
413
 
322
414
  marked.setOptions({ gfm: true, breaks: false, renderer });
@@ -360,6 +452,7 @@ ${itemsHtml}
360
452
  <span class="toc-title">Contents</span>
361
453
  <button id="toc-expand-all" type="button" aria-label="Expand all">⊞</button>
362
454
  <button id="toc-collapse-all" type="button" aria-label="Collapse all">⊟</button>
455
+ <button id="toc-collapse-toggle" type="button" aria-label="Collapse table of contents" title="Collapse / expand sidebar">◀</button>
363
456
  </div>
364
457
  ${renderTocNodes(tocTree)}
365
458
  </nav>
@@ -404,16 +497,52 @@ const html = `<!DOCTYPE html>
404
497
  .reader-sidebar {
405
498
  position: sticky;
406
499
  top: 24px;
407
- width: 320px;
500
+ flex: 0 1 300px;
501
+ width: clamp(220px, 22vw, 300px);
502
+ min-width: 220px;
408
503
  height: calc(100vh - 48px);
409
504
  overflow: hidden;
410
- flex: 0 0 320px;
411
505
  padding-right: 8px;
412
506
  box-sizing: border-box;
413
507
  display: flex;
414
508
  flex-direction: column;
415
509
  gap: 12px;
416
510
  }
511
+ body[data-toc-collapsed] .reader-sidebar {
512
+ flex-basis: 36px;
513
+ width: 36px;
514
+ min-width: 36px;
515
+ padding-right: 0;
516
+ }
517
+ body[data-toc-collapsed] .reader-tools,
518
+ body[data-toc-collapsed] .search-results,
519
+ body[data-toc-collapsed] .toc > .toc-list,
520
+ body[data-toc-collapsed] .toc-title {
521
+ display: none;
522
+ }
523
+ body[data-toc-collapsed] #toc-collapse-toggle {
524
+ transform: rotate(180deg);
525
+ }
526
+ #toc-collapse-toggle {
527
+ margin-left: 0;
528
+ padding: 2px 8px;
529
+ font: inherit;
530
+ font-size: 0.9em;
531
+ line-height: 1;
532
+ border: 1px solid #d0d7de;
533
+ border-radius: 6px;
534
+ background: #ffffff;
535
+ color: #57606a;
536
+ cursor: pointer;
537
+ transition: transform 0.15s ease;
538
+ }
539
+ #toc-collapse-toggle:hover {
540
+ background: #eef2f6;
541
+ color: #24292e;
542
+ }
543
+ @media (max-width: 1080px) {
544
+ #toc-collapse-toggle { display: none; }
545
+ }
417
546
  .reader-tools { flex: 0 0 auto; }
418
547
  .sidebar-toggle {
419
548
  display: none;
@@ -698,6 +827,10 @@ const html = `<!DOCTYPE html>
698
827
  .content {
699
828
  min-width: 0;
700
829
  flex: 1 1 auto;
830
+ }
831
+ .content p,
832
+ .content li,
833
+ .content blockquote {
701
834
  overflow-wrap: anywhere;
702
835
  word-break: break-word;
703
836
  }
@@ -753,6 +886,36 @@ const html = `<!DOCTYPE html>
753
886
  th, td { border: 1px solid #dfe2e5; padding: 7px 14px; text-align: left; }
754
887
  th { background: #f6f8fa; font-weight: 600; }
755
888
  tr:nth-child(even) { background: #fafbfc; }
889
+ .content table th,
890
+ .content table td {
891
+ overflow-wrap: normal;
892
+ word-break: normal;
893
+ }
894
+ .content table th {
895
+ white-space: nowrap;
896
+ }
897
+ .content table td code,
898
+ .content table th code {
899
+ white-space: nowrap;
900
+ }
901
+ .content table { table-layout: auto; }
902
+ .content table col.col-narrow { width: 1%; }
903
+ .content table col.col-prose { width: auto; }
904
+ .content table th.cell-narrow,
905
+ .content table td.cell-narrow { white-space: nowrap; }
906
+ .content table tbody td:first-child,
907
+ .content table thead th:first-child {
908
+ position: sticky;
909
+ left: 0;
910
+ z-index: 1;
911
+ background: #ffffff;
912
+ }
913
+ .content table thead th:first-child {
914
+ background: #f6f8fa;
915
+ }
916
+ .content table tbody tr:nth-child(even) td:first-child {
917
+ background: #fafbfc;
918
+ }
756
919
  blockquote {
757
920
  border-left: 4px solid #dfe2e5;
758
921
  padding: 0 16px;
@@ -960,6 +1123,25 @@ ${mermaidInitTag}
960
1123
  });
961
1124
  }
962
1125
 
1126
+ function readTocCollapsed() {
1127
+ try { return localStorage.getItem('md2doc.toc.collapsed') === '1'; }
1128
+ catch (_) { return false; }
1129
+ }
1130
+ function writeTocCollapsed(v) {
1131
+ try { localStorage.setItem('md2doc.toc.collapsed', v ? '1' : '0'); }
1132
+ catch (_) { /* private mode / quota — ignore */ }
1133
+ }
1134
+ if (readTocCollapsed()) {
1135
+ document.body.setAttribute('data-toc-collapsed', '');
1136
+ }
1137
+ const tocCollapseBtn = document.getElementById('toc-collapse-toggle');
1138
+ if (tocCollapseBtn) {
1139
+ tocCollapseBtn.addEventListener('click', () => {
1140
+ const nowCollapsed = document.body.toggleAttribute('data-toc-collapsed');
1141
+ writeTocCollapsed(nowCollapsed);
1142
+ });
1143
+ }
1144
+
963
1145
  const SKIP_SELECTOR = 'svg, .mermaid, .graphviz, script, style';
964
1146
 
965
1147
  function buildSnippet(section, query) {
package/package.json CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "@helping-ai-workflow/md2doc",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
5
- "keywords": ["markdown", "html", "pdf", "renderer", "wavedrom", "mermaid", "graphviz"],
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": "bin/md2pdf.js"
17
+ "md2pdf": "bin/md2pdf.js"
10
18
  },
11
19
  "files": [
12
20
  "lib/",